Add API 31 sources

Test: None
Change-Id: Ie45894f7a232b2a15e2439b2527ca1813f334cc5
diff --git a/android/view/AbsSavedState.java b/android/view/AbsSavedState.java
new file mode 100644
index 0000000..009898e
--- /dev/null
+++ b/android/view/AbsSavedState.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A {@link Parcelable} implementation that should be used by inheritance
+ * hierarchies to ensure the state of all classes along the chain is saved.
+ */
+public abstract class AbsSavedState implements Parcelable {
+    public static final AbsSavedState EMPTY_STATE = new AbsSavedState() {};
+
+    private final Parcelable mSuperState;
+
+    /**
+     * Constructor used to make the EMPTY_STATE singleton
+     */
+    private AbsSavedState() {
+        mSuperState = null;
+    }
+
+    /**
+     * Constructor called by derived classes when creating their SavedState objects
+     *
+     * @param superState The state of the superclass of this view
+     */
+    protected AbsSavedState(Parcelable superState) {
+        if (superState == null) {
+            throw new IllegalArgumentException("superState must not be null");
+        }
+        mSuperState = superState != EMPTY_STATE ? superState : null;
+    }
+
+    /**
+     * Constructor used when reading from a parcel. Reads the state of the superclass.
+     *
+     * @param source parcel to read from
+     */
+    protected AbsSavedState(Parcel source) {
+        this(source, null);
+    }
+
+    /**
+     * Constructor used when reading from a parcel using a given class loader.
+     * Reads the state of the superclass.
+     *
+     * @param source parcel to read from
+     * @param loader ClassLoader to use for reading
+     */
+    protected AbsSavedState(Parcel source, ClassLoader loader) {
+        Parcelable superState = source.readParcelable(loader);
+        mSuperState = superState != null ? superState : EMPTY_STATE;
+    }
+
+    final public Parcelable getSuperState() {
+        return mSuperState;
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(mSuperState, flags);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<AbsSavedState> CREATOR
+            = new Parcelable.ClassLoaderCreator<AbsSavedState>() {
+
+        @Override
+        public AbsSavedState createFromParcel(Parcel in) {
+            return createFromParcel(in, null);
+        }
+
+        @Override
+        public AbsSavedState createFromParcel(Parcel in, ClassLoader loader) {
+            Parcelable superState = in.readParcelable(loader);
+            if (superState != null) {
+                throw new IllegalStateException("superState must be null");
+            }
+            return EMPTY_STATE;
+        }
+
+        @Override
+        public AbsSavedState[] newArray(int size) {
+            return new AbsSavedState[size];
+        }
+    };
+}
diff --git a/android/view/AccessibilityEmbeddedConnection.java b/android/view/AccessibilityEmbeddedConnection.java
new file mode 100644
index 0000000..5d34669
--- /dev/null
+++ b/android/view/AccessibilityEmbeddedConnection.java
@@ -0,0 +1,84 @@
+/*
+ * 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.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Matrix;
+import android.os.IBinder;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IAccessibilityEmbeddedConnection;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * This class is an interface this ViewRootImpl provides to the host view to the latter
+ * can interact with the view hierarchy in SurfaceControlViewHost.
+ *
+ * @hide
+ */
+final class AccessibilityEmbeddedConnection extends IAccessibilityEmbeddedConnection.Stub {
+    private final WeakReference<ViewRootImpl> mViewRootImpl;
+    private final Matrix mTmpScreenMatrix = new Matrix();
+
+    AccessibilityEmbeddedConnection(ViewRootImpl viewRootImpl) {
+        mViewRootImpl = new WeakReference<>(viewRootImpl);
+    }
+
+    @Override
+    public @Nullable IBinder associateEmbeddedHierarchy(@NonNull IBinder host, int hostViewId) {
+        final ViewRootImpl viewRootImpl = mViewRootImpl.get();
+        if (viewRootImpl != null) {
+            final AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(
+                    viewRootImpl.mContext);
+            viewRootImpl.mAttachInfo.mLeashedParentToken = host;
+            viewRootImpl.mAttachInfo.mLeashedParentAccessibilityViewId = hostViewId;
+            if (accessibilityManager.isEnabled()) {
+                accessibilityManager.associateEmbeddedHierarchy(host, viewRootImpl.mLeashToken);
+            }
+            return viewRootImpl.mLeashToken;
+        }
+        return null;
+    }
+
+    @Override
+    public void disassociateEmbeddedHierarchy() {
+        final ViewRootImpl viewRootImpl = mViewRootImpl.get();
+        if (viewRootImpl != null) {
+            final AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(
+                    viewRootImpl.mContext);
+            viewRootImpl.mAttachInfo.mLeashedParentToken = null;
+            viewRootImpl.mAttachInfo.mLeashedParentAccessibilityViewId = View.NO_ID;
+            viewRootImpl.mAttachInfo.mLocationInParentDisplay.set(0, 0);
+            if (accessibilityManager.isEnabled()) {
+                accessibilityManager.disassociateEmbeddedHierarchy(viewRootImpl.mLeashToken);
+            }
+        }
+    }
+
+    @Override
+    public void setScreenMatrix(float[] matrixValues) {
+        final ViewRootImpl viewRootImpl = mViewRootImpl.get();
+        if (viewRootImpl != null) {
+            mTmpScreenMatrix.setValues(matrixValues);
+            if (viewRootImpl.mAttachInfo.mScreenMatrixInEmbeddedHierarchy == null) {
+                viewRootImpl.mAttachInfo.mScreenMatrixInEmbeddedHierarchy = new Matrix();
+            }
+            viewRootImpl.mAttachInfo.mScreenMatrixInEmbeddedHierarchy.set(mTmpScreenMatrix);
+        }
+    }
+}
diff --git a/android/view/AccessibilityInteractionController.java b/android/view/AccessibilityInteractionController.java
new file mode 100644
index 0000000..199ecb3
--- /dev/null
+++ b/android/view/AccessibilityInteractionController.java
@@ -0,0 +1,1642 @@
+/*
+ * 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.view;
+
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN;
+import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_REQUESTED_KEY;
+import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
+
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.text.style.AccessibilityClickableSpan;
+import android.text.style.ClickableSpan;
+import android.util.LongSparseArray;
+import android.util.Slog;
+import android.view.accessibility.AccessibilityInteractionClient;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeIdManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import android.view.accessibility.AccessibilityNodeProvider;
+import android.view.accessibility.AccessibilityRequestPreparer;
+import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.SomeArgs;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.function.Predicate;
+
+/**
+ * Class for managing accessibility interactions initiated from the system
+ * and targeting the view hierarchy. A *ClientThread method is to be
+ * called from the interaction connection ViewAncestor gives the system to
+ * talk to it and a corresponding *UiThread method that is executed on the
+ * UI thread.
+ *
+ * @hide
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public final class AccessibilityInteractionController {
+
+    private static final String LOG_TAG = "AccessibilityInteractionController";
+
+    // Debugging flag
+    private static final boolean ENFORCE_NODE_TREE_CONSISTENT = false;
+
+    // Constants for readability
+    private static final boolean IGNORE_REQUEST_PREPARERS = true;
+    private static final boolean CONSIDER_REQUEST_PREPARERS = false;
+
+    // If an app holds off accessibility for longer than this, the hold-off is canceled to prevent
+    // accessibility from hanging
+    private static final long REQUEST_PREPARER_TIMEOUT_MS = 500;
+
+    // Callbacks should have the same configuration of the flags below to allow satisfying a pending
+    // node request on prefetch
+    private static final int FLAGS_AFFECTING_REPORTED_DATA =
+            AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+            | AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
+
+    private final ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
+        new ArrayList<AccessibilityNodeInfo>();
+
+    private final Object mLock = new Object();
+
+    private final PrivateHandler mHandler;
+
+    private final ViewRootImpl mViewRootImpl;
+
+    private final AccessibilityNodePrefetcher mPrefetcher;
+
+    private final long mMyLooperThreadId;
+
+    private final int mMyProcessId;
+
+    private final AccessibilityManager mA11yManager;
+
+    private final ArrayList<View> mTempArrayList = new ArrayList<View>();
+
+    private final Point mTempPoint = new Point();
+    private final Rect mTempRect = new Rect();
+    private final Rect mTempRect1 = new Rect();
+    private final Rect mTempRect2 = new Rect();
+    private final RectF mTempRectF = new RectF();
+
+    private AddNodeInfosForViewId mAddNodeInfosForViewId;
+
+    @GuardedBy("mLock")
+    private ArrayList<Message> mPendingFindNodeByIdMessages;
+
+    @GuardedBy("mLock")
+    private int mNumActiveRequestPreparers;
+    @GuardedBy("mLock")
+    private List<MessageHolder> mMessagesWaitingForRequestPreparer;
+    @GuardedBy("mLock")
+    private int mActiveRequestPreparerId;
+
+    public AccessibilityInteractionController(ViewRootImpl viewRootImpl) {
+        Looper looper =  viewRootImpl.mHandler.getLooper();
+        mMyLooperThreadId = looper.getThread().getId();
+        mMyProcessId = Process.myPid();
+        mHandler = new PrivateHandler(looper);
+        mViewRootImpl = viewRootImpl;
+        mPrefetcher = new AccessibilityNodePrefetcher();
+        mA11yManager = mViewRootImpl.mContext.getSystemService(AccessibilityManager.class);
+        mPendingFindNodeByIdMessages = new ArrayList<>();
+    }
+
+    private void scheduleMessage(Message message, int interrogatingPid, long interrogatingTid,
+            boolean ignoreRequestPreparers) {
+        if (ignoreRequestPreparers
+                || !holdOffMessageIfNeeded(message, interrogatingPid, interrogatingTid)) {
+            // If the interrogation is performed by the same thread as the main UI
+            // thread in this process, set the message as a static reference so
+            // after this call completes the same thread but in the interrogating
+            // client can handle the message to generate the result.
+            if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId
+                    && mHandler.hasAccessibilityCallback(message)) {
+                AccessibilityInteractionClient.getInstanceForThread(
+                        interrogatingTid).setSameThreadMessage(message);
+            } else {
+                // For messages without callback of interrogating client, just handle the
+                // message immediately if this is UI thread.
+                if (!mHandler.hasAccessibilityCallback(message)
+                        && Thread.currentThread().getId() == mMyLooperThreadId) {
+                    mHandler.handleMessage(message);
+                } else {
+                    mHandler.sendMessage(message);
+                }
+            }
+        }
+    }
+
+    private boolean isShown(View view) {
+        return (view != null) && (view.getWindowVisibility() == View.VISIBLE && view.isShown());
+    }
+
+    public void findAccessibilityNodeInfoByAccessibilityIdClientThread(
+            long accessibilityNodeId, Region interactiveRegion, int interactionId,
+            IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
+            long interrogatingTid, MagnificationSpec spec, Bundle arguments) {
+        final Message message = mHandler.obtainMessage();
+        message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID;
+        message.arg1 = flags;
+
+        final SomeArgs args = SomeArgs.obtain();
+        args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
+        args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
+        args.argi3 = interactionId;
+        args.arg1 = callback;
+        args.arg2 = spec;
+        args.arg3 = interactiveRegion;
+        args.arg4 = arguments;
+        message.obj = args;
+
+        synchronized (mLock) {
+            mPendingFindNodeByIdMessages.add(message);
+            scheduleMessage(message, interrogatingPid, interrogatingTid,
+                    CONSIDER_REQUEST_PREPARERS);
+        }
+    }
+
+    /**
+     * Check if this message needs to be held off while the app prepares to meet either this
+     * request, or a request ahead of it.
+     *
+     * @param originalMessage The message to be processed
+     * @param callingPid The calling process id
+     * @param callingTid The calling thread id
+     *
+     * @return {@code true} if the message is held off and will be processed later, {@code false} if
+     *         the message should be posted.
+     */
+    private boolean holdOffMessageIfNeeded(
+            Message originalMessage, int callingPid, long callingTid) {
+        synchronized (mLock) {
+            // If a request is already pending, queue this request for when it's finished
+            if (mNumActiveRequestPreparers != 0) {
+                queueMessageToHandleOncePrepared(originalMessage, callingPid, callingTid);
+                return true;
+            }
+
+            // Currently the only message that can hold things off is findByA11yId with extra data.
+            if (originalMessage.what
+                    != PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID) {
+                return false;
+            }
+            SomeArgs originalMessageArgs = (SomeArgs) originalMessage.obj;
+            Bundle requestArguments = (Bundle) originalMessageArgs.arg4;
+            if (requestArguments == null) {
+                return false;
+            }
+
+            // If nothing it registered for this view, nothing to do
+            int accessibilityViewId = originalMessageArgs.argi1;
+            final List<AccessibilityRequestPreparer> preparers =
+                    mA11yManager.getRequestPreparersForAccessibilityId(accessibilityViewId);
+            if (preparers == null) {
+                return false;
+            }
+
+            // If the bundle doesn't request the extra data, nothing to do
+            final String extraDataKey = requestArguments.getString(EXTRA_DATA_REQUESTED_KEY);
+            if (extraDataKey == null) {
+                return false;
+            }
+
+            // Send the request to the AccessibilityRequestPreparers on the UI thread
+            mNumActiveRequestPreparers = preparers.size();
+            for (int i = 0; i < preparers.size(); i++) {
+                final Message requestPreparerMessage = mHandler.obtainMessage(
+                        PrivateHandler.MSG_PREPARE_FOR_EXTRA_DATA_REQUEST);
+                final SomeArgs requestPreparerArgs = SomeArgs.obtain();
+                // virtualDescendentId
+                requestPreparerArgs.argi1 =
+                        (originalMessageArgs.argi2 == AccessibilityNodeInfo.UNDEFINED_ITEM_ID)
+                        ? AccessibilityNodeProvider.HOST_VIEW_ID : originalMessageArgs.argi2;
+                requestPreparerArgs.arg1 = preparers.get(i);
+                requestPreparerArgs.arg2 = extraDataKey;
+                requestPreparerArgs.arg3 = requestArguments;
+                Message preparationFinishedMessage = mHandler.obtainMessage(
+                        PrivateHandler.MSG_APP_PREPARATION_FINISHED);
+                preparationFinishedMessage.arg1 = ++mActiveRequestPreparerId;
+                requestPreparerArgs.arg4 = preparationFinishedMessage;
+
+                requestPreparerMessage.obj = requestPreparerArgs;
+                scheduleMessage(requestPreparerMessage, callingPid, callingTid,
+                        IGNORE_REQUEST_PREPARERS);
+                mHandler.obtainMessage(PrivateHandler.MSG_APP_PREPARATION_TIMEOUT);
+                mHandler.sendEmptyMessageDelayed(PrivateHandler.MSG_APP_PREPARATION_TIMEOUT,
+                        REQUEST_PREPARER_TIMEOUT_MS);
+            }
+
+            // Set the initial request aside
+            queueMessageToHandleOncePrepared(originalMessage, callingPid, callingTid);
+            return true;
+        }
+    }
+
+    private void prepareForExtraDataRequestUiThread(Message message) {
+        SomeArgs args = (SomeArgs) message.obj;
+        final int virtualDescendantId = args.argi1;
+        final AccessibilityRequestPreparer preparer = (AccessibilityRequestPreparer) args.arg1;
+        final String extraDataKey = (String) args.arg2;
+        final Bundle requestArguments = (Bundle) args.arg3;
+        final Message preparationFinishedMessage = (Message) args.arg4;
+
+        preparer.onPrepareExtraData(virtualDescendantId, extraDataKey,
+                requestArguments, preparationFinishedMessage);
+    }
+
+    private void queueMessageToHandleOncePrepared(Message message, int interrogatingPid,
+            long interrogatingTid) {
+        if (mMessagesWaitingForRequestPreparer == null) {
+            mMessagesWaitingForRequestPreparer = new ArrayList<>(1);
+        }
+        MessageHolder messageHolder =
+                new MessageHolder(message, interrogatingPid, interrogatingTid);
+        mMessagesWaitingForRequestPreparer.add(messageHolder);
+    }
+
+    private void requestPreparerDoneUiThread(Message message) {
+        synchronized (mLock) {
+            if (message.arg1 != mActiveRequestPreparerId) {
+                Slog.e(LOG_TAG, "Surprising AccessibilityRequestPreparer callback (likely late)");
+                return;
+            }
+            mNumActiveRequestPreparers--;
+            if (mNumActiveRequestPreparers <= 0) {
+                mHandler.removeMessages(PrivateHandler.MSG_APP_PREPARATION_TIMEOUT);
+                scheduleAllMessagesWaitingForRequestPreparerLocked();
+            }
+        }
+    }
+
+    private void requestPreparerTimeoutUiThread() {
+        synchronized (mLock) {
+            Slog.e(LOG_TAG, "AccessibilityRequestPreparer timed out");
+            scheduleAllMessagesWaitingForRequestPreparerLocked();
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void scheduleAllMessagesWaitingForRequestPreparerLocked() {
+        int numMessages = mMessagesWaitingForRequestPreparer.size();
+        for (int i = 0; i < numMessages; i++) {
+            MessageHolder request = mMessagesWaitingForRequestPreparer.get(i);
+            scheduleMessage(request.mMessage, request.mInterrogatingPid,
+                    request.mInterrogatingTid,
+                    (i == 0) /* the app is ready for the first request */);
+        }
+        mMessagesWaitingForRequestPreparer.clear();
+        mNumActiveRequestPreparers = 0; // Just to be safe - should be unnecessary
+        mActiveRequestPreparerId = -1;
+    }
+
+    private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
+        synchronized (mLock) {
+            mPendingFindNodeByIdMessages.remove(message);
+        }
+        final int flags = message.arg1;
+
+        SomeArgs args = (SomeArgs) message.obj;
+        final int accessibilityViewId = args.argi1;
+        final int virtualDescendantId = args.argi2;
+        final int interactionId = args.argi3;
+        final IAccessibilityInteractionConnectionCallback callback =
+            (IAccessibilityInteractionConnectionCallback) args.arg1;
+        final MagnificationSpec spec = (MagnificationSpec) args.arg2;
+        final Region interactiveRegion = (Region) args.arg3;
+        final Bundle arguments = (Bundle) args.arg4;
+
+        args.recycle();
+
+        View requestedView = null;
+        AccessibilityNodeInfo requestedNode = null;
+        try {
+            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
+                return;
+            }
+            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
+            requestedView = findViewByAccessibilityId(accessibilityViewId);
+            if (requestedView != null && isShown(requestedView)) {
+                requestedNode = populateAccessibilityNodeInfoForView(
+                        requestedView, arguments, virtualDescendantId);
+            }
+        } finally {
+            updateInfoForViewportAndReturnFindNodeResult(
+                    requestedNode == null ? null : AccessibilityNodeInfo.obtain(requestedNode),
+                    callback, interactionId, spec, interactiveRegion);
+        }
+        ArrayList<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
+        infos.clear();
+        mPrefetcher.prefetchAccessibilityNodeInfos(requestedView,
+                requestedNode == null ? null : AccessibilityNodeInfo.obtain(requestedNode),
+                virtualDescendantId, flags, infos);
+        mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+        updateInfosForViewPort(infos, spec, interactiveRegion);
+        final SatisfiedFindAccessibilityNodeByAccessibilityIdRequest satisfiedRequest =
+                getSatisfiedRequestInPrefetch(
+                        requestedNode == null ? null : requestedNode, infos, flags);
+
+        if (satisfiedRequest != null && satisfiedRequest.mSatisfiedRequestNode != requestedNode) {
+            infos.remove(satisfiedRequest.mSatisfiedRequestNode);
+        }
+
+        returnPrefetchResult(interactionId, infos, callback);
+
+        if (satisfiedRequest != null) {
+            returnFindNodeResult(satisfiedRequest);
+        }
+    }
+
+    private AccessibilityNodeInfo populateAccessibilityNodeInfoForView(
+            View view, Bundle arguments, int virtualViewId) {
+        AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
+        // Determine if we'll be populating extra data
+        final String extraDataRequested = (arguments == null) ? null
+                : arguments.getString(EXTRA_DATA_REQUESTED_KEY);
+        AccessibilityNodeInfo root = null;
+        if (provider == null) {
+            root = view.createAccessibilityNodeInfo();
+            if (root != null) {
+                if (extraDataRequested != null) {
+                    view.addExtraDataToAccessibilityNodeInfo(root, extraDataRequested, arguments);
+                }
+            }
+        } else {
+            root = provider.createAccessibilityNodeInfo(virtualViewId);
+            if (root != null) {
+                if (extraDataRequested != null) {
+                    provider.addExtraDataToAccessibilityNodeInfo(
+                            virtualViewId, root, extraDataRequested, arguments);
+                }
+            }
+        }
+        return root;
+    }
+
+    public void findAccessibilityNodeInfosByViewIdClientThread(long accessibilityNodeId,
+            String viewId, Region interactiveRegion, int interactionId,
+            IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
+            long interrogatingTid, MagnificationSpec spec) {
+        Message message = mHandler.obtainMessage();
+        message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID;
+        message.arg1 = flags;
+        message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
+
+        SomeArgs args = SomeArgs.obtain();
+        args.argi1 = interactionId;
+        args.arg1 = callback;
+        args.arg2 = spec;
+        args.arg3 = viewId;
+        args.arg4 = interactiveRegion;
+        message.obj = args;
+
+        scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
+    }
+
+    private void findAccessibilityNodeInfosByViewIdUiThread(Message message) {
+        final int flags = message.arg1;
+        final int accessibilityViewId = message.arg2;
+
+        SomeArgs args = (SomeArgs) message.obj;
+        final int interactionId = args.argi1;
+        final IAccessibilityInteractionConnectionCallback callback =
+            (IAccessibilityInteractionConnectionCallback) args.arg1;
+        final MagnificationSpec spec = (MagnificationSpec) args.arg2;
+        final String viewId = (String) args.arg3;
+        final Region interactiveRegion = (Region) args.arg4;
+        args.recycle();
+
+        final List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
+        infos.clear();
+        try {
+            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null
+                    || viewId == null) {
+                return;
+            }
+            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
+            final View root = findViewByAccessibilityId(accessibilityViewId);
+            if (root != null) {
+                final int resolvedViewId = root.getContext().getResources()
+                        .getIdentifier(viewId, null, null);
+                if (resolvedViewId <= 0) {
+                    return;
+                }
+                if (mAddNodeInfosForViewId == null) {
+                    mAddNodeInfosForViewId = new AddNodeInfosForViewId();
+                }
+                mAddNodeInfosForViewId.init(resolvedViewId, infos);
+                root.findViewByPredicate(mAddNodeInfosForViewId);
+                mAddNodeInfosForViewId.reset();
+            }
+        } finally {
+            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+            updateInfosForViewportAndReturnFindNodeResult(
+                    infos, callback, interactionId, spec, interactiveRegion);
+        }
+    }
+
+    public void findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId,
+            String text, Region interactiveRegion, int interactionId,
+            IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
+            long interrogatingTid, MagnificationSpec spec) {
+        Message message = mHandler.obtainMessage();
+        message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT;
+        message.arg1 = flags;
+
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = text;
+        args.arg2 = callback;
+        args.arg3 = spec;
+        args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
+        args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
+        args.argi3 = interactionId;
+        args.arg4 = interactiveRegion;
+        message.obj = args;
+
+        scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
+    }
+
+    private void findAccessibilityNodeInfosByTextUiThread(Message message) {
+        final int flags = message.arg1;
+
+        SomeArgs args = (SomeArgs) message.obj;
+        final String text = (String) args.arg1;
+        final IAccessibilityInteractionConnectionCallback callback =
+            (IAccessibilityInteractionConnectionCallback) args.arg2;
+        final MagnificationSpec spec = (MagnificationSpec) args.arg3;
+        final int accessibilityViewId = args.argi1;
+        final int virtualDescendantId = args.argi2;
+        final int interactionId = args.argi3;
+        final Region interactiveRegion = (Region) args.arg4;
+        args.recycle();
+
+        List<AccessibilityNodeInfo> infos = null;
+        try {
+            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
+                return;
+            }
+            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
+            final View root = findViewByAccessibilityId(accessibilityViewId);
+            if (root != null && isShown(root)) {
+                AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
+                if (provider != null) {
+                    infos = provider.findAccessibilityNodeInfosByText(text,
+                            virtualDescendantId);
+                } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
+                    ArrayList<View> foundViews = mTempArrayList;
+                    foundViews.clear();
+                    root.findViewsWithText(foundViews, text, View.FIND_VIEWS_WITH_TEXT
+                            | View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION
+                            | View.FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS);
+                    if (!foundViews.isEmpty()) {
+                        infos = mTempAccessibilityNodeInfoList;
+                        infos.clear();
+                        final int viewCount = foundViews.size();
+                        for (int i = 0; i < viewCount; i++) {
+                            View foundView = foundViews.get(i);
+                            if (isShown(foundView)) {
+                                provider = foundView.getAccessibilityNodeProvider();
+                                if (provider != null) {
+                                    List<AccessibilityNodeInfo> infosFromProvider =
+                                        provider.findAccessibilityNodeInfosByText(text,
+                                                AccessibilityNodeProvider.HOST_VIEW_ID);
+                                    if (infosFromProvider != null) {
+                                        infos.addAll(infosFromProvider);
+                                    }
+                                } else  {
+                                    infos.add(foundView.createAccessibilityNodeInfo());
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        } finally {
+            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+            updateInfosForViewportAndReturnFindNodeResult(
+                    infos, callback, interactionId, spec, interactiveRegion);
+        }
+    }
+
+    public void findFocusClientThread(long accessibilityNodeId, int focusType,
+            Region interactiveRegion, int interactionId,
+            IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
+            long interrogatingTid, MagnificationSpec spec) {
+        Message message = mHandler.obtainMessage();
+        message.what = PrivateHandler.MSG_FIND_FOCUS;
+        message.arg1 = flags;
+        message.arg2 = focusType;
+
+        SomeArgs args = SomeArgs.obtain();
+        args.argi1 = interactionId;
+        args.argi2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
+        args.argi3 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
+        args.arg1 = callback;
+        args.arg2 = spec;
+        args.arg3 = interactiveRegion;
+
+        message.obj = args;
+
+        scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
+    }
+
+    private void findFocusUiThread(Message message) {
+        final int flags = message.arg1;
+        final int focusType = message.arg2;
+
+        SomeArgs args = (SomeArgs) message.obj;
+        final int interactionId = args.argi1;
+        final int accessibilityViewId = args.argi2;
+        final int virtualDescendantId = args.argi3;
+        final IAccessibilityInteractionConnectionCallback callback =
+            (IAccessibilityInteractionConnectionCallback) args.arg1;
+        final MagnificationSpec spec = (MagnificationSpec) args.arg2;
+        final Region interactiveRegion = (Region) args.arg3;
+        args.recycle();
+
+        AccessibilityNodeInfo focused = null;
+        try {
+            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
+                return;
+            }
+            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
+            final View root = findViewByAccessibilityId(accessibilityViewId);
+            if (root != null && isShown(root)) {
+                switch (focusType) {
+                    case AccessibilityNodeInfo.FOCUS_ACCESSIBILITY: {
+                        View host = mViewRootImpl.mAccessibilityFocusedHost;
+                        // If there is no accessibility focus host or it is not a descendant
+                        // of the root from which to start the search, then the search failed.
+                        if (host == null || !ViewRootImpl.isViewDescendantOf(host, root)) {
+                            break;
+                        }
+                        // The focused view not shown, we failed.
+                        if (!isShown(host)) {
+                            break;
+                        }
+                        // If the host has a provider ask this provider to search for the
+                        // focus instead fetching all provider nodes to do the search here.
+                        AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
+                        if (provider != null) {
+                            if (mViewRootImpl.mAccessibilityFocusedVirtualView != null) {
+                                focused = AccessibilityNodeInfo.obtain(
+                                        mViewRootImpl.mAccessibilityFocusedVirtualView);
+                            }
+                        } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
+                            focused = host.createAccessibilityNodeInfo();
+                        }
+                    } break;
+                    case AccessibilityNodeInfo.FOCUS_INPUT: {
+                        View target = root.findFocus();
+                        if (!isShown(target)) {
+                            break;
+                        }
+                        AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
+                        if (provider != null) {
+                            focused = provider.findFocus(focusType);
+                        }
+                        if (focused == null) {
+                            focused = target.createAccessibilityNodeInfo();
+                        }
+                    } break;
+                    default:
+                        throw new IllegalArgumentException("Unknown focus type: " + focusType);
+                }
+            }
+        } finally {
+            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+            updateInfoForViewportAndReturnFindNodeResult(
+                    focused, callback, interactionId, spec, interactiveRegion);
+        }
+    }
+
+    public void focusSearchClientThread(long accessibilityNodeId, int direction,
+            Region interactiveRegion, int interactionId,
+            IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
+            long interrogatingTid, MagnificationSpec spec) {
+        Message message = mHandler.obtainMessage();
+        message.what = PrivateHandler.MSG_FOCUS_SEARCH;
+        message.arg1 = flags;
+        message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
+
+        SomeArgs args = SomeArgs.obtain();
+        args.argi2 = direction;
+        args.argi3 = interactionId;
+        args.arg1 = callback;
+        args.arg2 = spec;
+        args.arg3 = interactiveRegion;
+
+        message.obj = args;
+
+        scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
+    }
+
+    private void focusSearchUiThread(Message message) {
+        final int flags = message.arg1;
+        final int accessibilityViewId = message.arg2;
+
+        SomeArgs args = (SomeArgs) message.obj;
+        final int direction = args.argi2;
+        final int interactionId = args.argi3;
+        final IAccessibilityInteractionConnectionCallback callback =
+            (IAccessibilityInteractionConnectionCallback) args.arg1;
+        final MagnificationSpec spec = (MagnificationSpec) args.arg2;
+        final Region interactiveRegion = (Region) args.arg3;
+
+        args.recycle();
+
+        AccessibilityNodeInfo next = null;
+        try {
+            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
+                return;
+            }
+            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
+            final View root = findViewByAccessibilityId(accessibilityViewId);
+            if (root != null && isShown(root)) {
+                View nextView = root.focusSearch(direction);
+                if (nextView != null) {
+                    next = nextView.createAccessibilityNodeInfo();
+                }
+            }
+        } finally {
+            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+            updateInfoForViewportAndReturnFindNodeResult(
+                    next, callback, interactionId, spec, interactiveRegion);
+        }
+    }
+
+    public void performAccessibilityActionClientThread(long accessibilityNodeId, int action,
+            Bundle arguments, int interactionId,
+            IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
+            long interrogatingTid) {
+        Message message = mHandler.obtainMessage();
+        message.what = PrivateHandler.MSG_PERFORM_ACCESSIBILITY_ACTION;
+        message.arg1 = flags;
+        message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
+
+        SomeArgs args = SomeArgs.obtain();
+        args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
+        args.argi2 = action;
+        args.argi3 = interactionId;
+        args.arg1 = callback;
+        args.arg2 = arguments;
+
+        message.obj = args;
+
+        scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
+    }
+
+    private void performAccessibilityActionUiThread(Message message) {
+        final int flags = message.arg1;
+        final int accessibilityViewId = message.arg2;
+
+        SomeArgs args = (SomeArgs) message.obj;
+        final int virtualDescendantId = args.argi1;
+        final int action = args.argi2;
+        final int interactionId = args.argi3;
+        final IAccessibilityInteractionConnectionCallback callback =
+            (IAccessibilityInteractionConnectionCallback) args.arg1;
+        Bundle arguments = (Bundle) args.arg2;
+
+        args.recycle();
+
+        boolean succeeded = false;
+        try {
+            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null ||
+                    mViewRootImpl.mStopped || mViewRootImpl.mPausedForTransition) {
+                return;
+            }
+            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
+            final View target = findViewByAccessibilityId(accessibilityViewId);
+            if (target != null && isShown(target)) {
+                mA11yManager.notifyPerformingAction(action);
+                if (action == R.id.accessibilityActionClickOnClickableSpan) {
+                    // Handle this hidden action separately
+                    succeeded = handleClickableSpanActionUiThread(
+                            target, virtualDescendantId, arguments);
+                } else {
+                    AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
+                    if (provider != null) {
+                        succeeded = provider.performAction(virtualDescendantId, action,
+                                arguments);
+                    } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
+                        succeeded = target.performAccessibilityAction(action, arguments);
+                    }
+                }
+                mA11yManager.notifyPerformingAction(0);
+            }
+        } finally {
+            try {
+                mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+                callback.setPerformAccessibilityActionResult(succeeded, interactionId);
+            } catch (RemoteException re) {
+                /* ignore - the other side will time out */
+            }
+        }
+    }
+
+    /**
+     * Finds the accessibility focused node in the root, and clears the accessibility focus.
+     */
+    public void clearAccessibilityFocusClientThread() {
+        final Message message = mHandler.obtainMessage();
+        message.what = PrivateHandler.MSG_CLEAR_ACCESSIBILITY_FOCUS;
+
+        // Don't care about pid and tid because there's no interrogating client for this message.
+        scheduleMessage(message, 0, 0, CONSIDER_REQUEST_PREPARERS);
+    }
+
+    private void clearAccessibilityFocusUiThread() {
+        if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
+            return;
+        }
+        try {
+            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags =
+                    AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
+            final View root = mViewRootImpl.mView;
+            if (root != null && isShown(root)) {
+                final View host = mViewRootImpl.mAccessibilityFocusedHost;
+                // If there is no accessibility focus host or it is not a descendant
+                // of the root from which to start the search, then the search failed.
+                if (host == null || !ViewRootImpl.isViewDescendantOf(host, root)) {
+                    return;
+                }
+                final AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
+                final AccessibilityNodeInfo focusNode =
+                        mViewRootImpl.mAccessibilityFocusedVirtualView;
+                if (provider != null && focusNode != null) {
+                    final int virtualNodeId = AccessibilityNodeInfo.getVirtualDescendantId(
+                            focusNode.getSourceNodeId());
+                    provider.performAction(virtualNodeId,
+                            AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS.getId(),
+                            null);
+                } else {
+                    host.performAccessibilityAction(
+                            AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS.getId(),
+                            null);
+                }
+            }
+        } finally {
+            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+        }
+    }
+
+    /**
+     * Notify outside touch event to the target window.
+     */
+    public void notifyOutsideTouchClientThread() {
+        final Message message = mHandler.obtainMessage();
+        message.what = PrivateHandler.MSG_NOTIFY_OUTSIDE_TOUCH;
+
+        // Don't care about pid and tid because there's no interrogating client for this message.
+        scheduleMessage(message, 0, 0, CONSIDER_REQUEST_PREPARERS);
+    }
+
+    private void notifyOutsideTouchUiThread() {
+        if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null
+                || mViewRootImpl.mStopped || mViewRootImpl.mPausedForTransition) {
+            return;
+        }
+        final View root = mViewRootImpl.mView;
+        if (root != null && isShown(root)) {
+            // trigger ACTION_OUTSIDE to notify windows
+            final long now = SystemClock.uptimeMillis();
+            final MotionEvent event = MotionEvent.obtain(now, now, MotionEvent.ACTION_OUTSIDE,
+                    0, 0, 0);
+            event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+            mViewRootImpl.dispatchInputEvent(event);
+        }
+    }
+
+    private View findViewByAccessibilityId(int accessibilityId) {
+        if (accessibilityId == AccessibilityNodeInfo.ROOT_ITEM_ID) {
+            return mViewRootImpl.mView;
+        } else {
+            return AccessibilityNodeIdManager.getInstance().findView(accessibilityId);
+        }
+    }
+
+    private void adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info,
+            Region interactiveRegion) {
+        if (interactiveRegion == null || info == null) {
+            return;
+        }
+        Rect boundsInScreen = mTempRect;
+        info.getBoundsInScreen(boundsInScreen);
+        if (interactiveRegion.quickReject(boundsInScreen) && !shouldBypassAdjustIsVisible()) {
+            info.setVisibleToUser(false);
+        }
+    }
+
+    private boolean shouldBypassAdjustIsVisible() {
+        final int windowType = mViewRootImpl.mOrigWindowType;
+        if (windowType == TYPE_INPUT_METHOD) {
+            return true;
+        }
+        return false;
+    }
+
+    private void adjustBoundsInScreenIfNeeded(AccessibilityNodeInfo info) {
+        if (info == null || shouldBypassAdjustBoundsInScreen()) {
+            return;
+        }
+        final Rect boundsInScreen = mTempRect;
+        info.getBoundsInScreen(boundsInScreen);
+        boundsInScreen.offset(mViewRootImpl.mAttachInfo.mLocationInParentDisplay.x,
+                mViewRootImpl.mAttachInfo.mLocationInParentDisplay.y);
+        info.setBoundsInScreen(boundsInScreen);
+    }
+
+    private boolean shouldBypassAdjustBoundsInScreen() {
+        return mViewRootImpl.mAttachInfo.mLocationInParentDisplay.equals(0, 0);
+    }
+
+    private void applyScreenMatrixIfNeeded(List<AccessibilityNodeInfo> infos) {
+        if (infos == null || shouldBypassApplyScreenMatrix()) {
+            return;
+        }
+        final int infoCount = infos.size();
+        for (int i = 0; i < infoCount; i++) {
+            final AccessibilityNodeInfo info = infos.get(i);
+            applyScreenMatrixIfNeeded(info);
+        }
+    }
+
+    private void applyScreenMatrixIfNeeded(AccessibilityNodeInfo info) {
+        if (info == null || shouldBypassApplyScreenMatrix()) {
+            return;
+        }
+        final Rect boundsInScreen = mTempRect;
+        final RectF transformedBounds = mTempRectF;
+        final Matrix screenMatrix = mViewRootImpl.mAttachInfo.mScreenMatrixInEmbeddedHierarchy;
+
+        info.getBoundsInScreen(boundsInScreen);
+        transformedBounds.set(boundsInScreen);
+        screenMatrix.mapRect(transformedBounds);
+        boundsInScreen.set((int) transformedBounds.left, (int) transformedBounds.top,
+                (int) transformedBounds.right, (int) transformedBounds.bottom);
+        info.setBoundsInScreen(boundsInScreen);
+    }
+
+    private boolean shouldBypassApplyScreenMatrix() {
+        final Matrix screenMatrix = mViewRootImpl.mAttachInfo.mScreenMatrixInEmbeddedHierarchy;
+        return screenMatrix == null || screenMatrix.isIdentity();
+    }
+
+    private void associateLeashedParentIfNeeded(AccessibilityNodeInfo info) {
+        if (info == null || shouldBypassAssociateLeashedParent()) {
+            return;
+        }
+        // The node id of root node in embedded maybe not be ROOT_NODE_ID so we compare the id
+        // with root view.
+        if (mViewRootImpl.mView.getAccessibilityViewId()
+                != AccessibilityNodeInfo.getAccessibilityViewId(info.getSourceNodeId())) {
+            return;
+        }
+        info.setLeashedParent(mViewRootImpl.mAttachInfo.mLeashedParentToken,
+                mViewRootImpl.mAttachInfo.mLeashedParentAccessibilityViewId);
+    }
+
+    private boolean shouldBypassAssociateLeashedParent() {
+        return (mViewRootImpl.mAttachInfo.mLeashedParentToken == null
+                && mViewRootImpl.mAttachInfo.mLeashedParentAccessibilityViewId == View.NO_ID);
+    }
+
+    private void applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info,
+            MagnificationSpec spec) {
+        if (info == null) {
+            return;
+        }
+
+        final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
+        if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
+            return;
+        }
+
+        Rect boundsInParent = mTempRect;
+        Rect boundsInScreen = mTempRect1;
+
+        info.getBoundsInParent(boundsInParent);
+        info.getBoundsInScreen(boundsInScreen);
+        if (applicationScale != 1.0f) {
+            boundsInParent.scale(applicationScale);
+            boundsInScreen.scale(applicationScale);
+        }
+        if (spec != null) {
+            boundsInParent.scale(spec.scale);
+            // boundsInParent must not be offset.
+            boundsInScreen.scale(spec.scale);
+            boundsInScreen.offset((int) spec.offsetX, (int) spec.offsetY);
+        }
+        info.setBoundsInParent(boundsInParent);
+        info.setBoundsInScreen(boundsInScreen);
+
+        // Scale text locations if they are present
+        if (info.hasExtras()) {
+            Bundle extras = info.getExtras();
+            Parcelable[] textLocations =
+                    extras.getParcelableArray(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY);
+            if (textLocations != null) {
+                for (int i = 0; i < textLocations.length; i++) {
+                    // Unchecked cast - an app that puts other objects in this bundle with this
+                    // key will crash.
+                    RectF textLocation = ((RectF) textLocations[i]);
+                    textLocation.scale(applicationScale);
+                    if (spec != null) {
+                        textLocation.scale(spec.scale);
+                        textLocation.offset(spec.offsetX, spec.offsetY);
+                    }
+                }
+            }
+        }
+    }
+
+    private boolean shouldApplyAppScaleAndMagnificationSpec(float appScale,
+            MagnificationSpec spec) {
+        return (appScale != 1.0f || (spec != null && !spec.isNop()));
+    }
+
+    private void updateInfosForViewPort(List<AccessibilityNodeInfo> infos, MagnificationSpec spec,
+                                        Region interactiveRegion) {
+        for (int i = 0; i < infos.size(); i++) {
+            updateInfoForViewPort(infos.get(i), spec, interactiveRegion);
+        }
+    }
+
+    private void updateInfoForViewPort(AccessibilityNodeInfo info, MagnificationSpec spec,
+                                       Region interactiveRegion) {
+        associateLeashedParentIfNeeded(info);
+        applyScreenMatrixIfNeeded(info);
+        adjustBoundsInScreenIfNeeded(info);
+        // To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node,
+        // then impact the visibility result, we need to adjust visibility before apply scale.
+        adjustIsVisibleToUserIfNeeded(info, interactiveRegion);
+        applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
+    }
+
+    private void updateInfosForViewportAndReturnFindNodeResult(List<AccessibilityNodeInfo> infos,
+            IAccessibilityInteractionConnectionCallback callback, int interactionId,
+            MagnificationSpec spec, Region interactiveRegion) {
+        if (infos != null) {
+            updateInfosForViewPort(infos, spec, interactiveRegion);
+        }
+        returnFindNodesResult(infos, callback, interactionId);
+    }
+
+    private void returnFindNodeResult(AccessibilityNodeInfo info,
+                                      IAccessibilityInteractionConnectionCallback callback,
+                                      int interactionId) {
+        try {
+            callback.setFindAccessibilityNodeInfoResult(info, interactionId);
+        } catch (RemoteException re) {
+            /* ignore - the other side will time out */
+        }
+    }
+
+    private void returnFindNodeResult(SatisfiedFindAccessibilityNodeByAccessibilityIdRequest
+            satisfiedRequest) {
+        try {
+            final AccessibilityNodeInfo info = satisfiedRequest.mSatisfiedRequestNode;
+            final IAccessibilityInteractionConnectionCallback callback =
+                    satisfiedRequest.mSatisfiedRequestCallback;
+            final int interactionId = satisfiedRequest.mSatisfiedRequestInteractionId;
+            callback.setFindAccessibilityNodeInfoResult(info, interactionId);
+        } catch (RemoteException re) {
+            /* ignore - the other side will time out */
+        }
+    }
+
+    private void returnFindNodesResult(List<AccessibilityNodeInfo> infos,
+            IAccessibilityInteractionConnectionCallback callback, int interactionId) {
+        try {
+            callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
+            if (infos != null) {
+                infos.clear();
+            }
+        } catch (RemoteException re) {
+            /* ignore - the other side will time out */
+        }
+    }
+
+    private SatisfiedFindAccessibilityNodeByAccessibilityIdRequest getSatisfiedRequestInPrefetch(
+            AccessibilityNodeInfo requestedNode, List<AccessibilityNodeInfo> infos, int flags) {
+        SatisfiedFindAccessibilityNodeByAccessibilityIdRequest satisfiedRequest = null;
+        synchronized (mLock) {
+            for (int i = 0; i < mPendingFindNodeByIdMessages.size(); i++) {
+                final Message pendingMessage = mPendingFindNodeByIdMessages.get(i);
+                final int pendingFlags = pendingMessage.arg1;
+                if ((pendingFlags & FLAGS_AFFECTING_REPORTED_DATA)
+                        != (flags & FLAGS_AFFECTING_REPORTED_DATA)) {
+                    continue;
+                }
+                SomeArgs args = (SomeArgs) pendingMessage.obj;
+                final int accessibilityViewId = args.argi1;
+                final int virtualDescendantId = args.argi2;
+
+                final AccessibilityNodeInfo satisfiedRequestNode = nodeWithIdFromList(requestedNode,
+                        infos, AccessibilityNodeInfo.makeNodeId(
+                                accessibilityViewId, virtualDescendantId));
+
+                if (satisfiedRequestNode != null) {
+                    mHandler.removeMessages(
+                            PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID,
+                            pendingMessage.obj);
+                    final IAccessibilityInteractionConnectionCallback satisfiedRequestCallback =
+                            (IAccessibilityInteractionConnectionCallback) args.arg1;
+                    final int satisfiedRequestInteractionId = args.argi3;
+                    satisfiedRequest = new SatisfiedFindAccessibilityNodeByAccessibilityIdRequest(
+                                    satisfiedRequestNode, satisfiedRequestCallback,
+                                    satisfiedRequestInteractionId);
+                    args.recycle();
+                    break;
+                }
+            }
+            mPendingFindNodeByIdMessages.clear();
+            return satisfiedRequest;
+        }
+    }
+
+    private AccessibilityNodeInfo nodeWithIdFromList(AccessibilityNodeInfo requestedNode,
+            List<AccessibilityNodeInfo> infos, long nodeId) {
+        if (requestedNode != null && requestedNode.getSourceNodeId() == nodeId) {
+            return requestedNode;
+        }
+        for (int j = 0; j < infos.size(); j++) {
+            AccessibilityNodeInfo info = infos.get(j);
+            if (info.getSourceNodeId() == nodeId) {
+                return info;
+            }
+        }
+        return null;
+    }
+
+    private void returnPrefetchResult(int interactionId, List<AccessibilityNodeInfo> infos,
+                                      IAccessibilityInteractionConnectionCallback callback) {
+        if (infos.size() > 0) {
+            try {
+                callback.setPrefetchAccessibilityNodeInfoResult(infos, interactionId);
+            } catch (RemoteException re) {
+                /* ignore - other side isn't too bothered if this doesn't arrive */
+            }
+        }
+    }
+
+    private void updateInfoForViewportAndReturnFindNodeResult(AccessibilityNodeInfo info,
+            IAccessibilityInteractionConnectionCallback callback, int interactionId,
+            MagnificationSpec spec, Region interactiveRegion) {
+        updateInfoForViewPort(info, spec, interactiveRegion);
+        returnFindNodeResult(info, callback, interactionId);
+    }
+
+    private boolean handleClickableSpanActionUiThread(
+            View view, int virtualDescendantId, Bundle arguments) {
+        Parcelable span = arguments.getParcelable(ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN);
+        if (!(span instanceof AccessibilityClickableSpan)) {
+            return false;
+        }
+
+        // Find the original ClickableSpan if it's still on the screen
+        AccessibilityNodeInfo infoWithSpan = null;
+        AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
+        if (provider != null) {
+            infoWithSpan = provider.createAccessibilityNodeInfo(virtualDescendantId);
+        } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
+            infoWithSpan = view.createAccessibilityNodeInfo();
+        }
+        if (infoWithSpan == null) {
+            return false;
+        }
+
+        // Click on the corresponding span
+        ClickableSpan clickableSpan = ((AccessibilityClickableSpan) span).findClickableSpan(
+                infoWithSpan.getOriginalText());
+        if (clickableSpan != null) {
+            clickableSpan.onClick(view);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * This class encapsulates a prefetching strategy for the accessibility APIs for
+     * querying window content. It is responsible to prefetch a batch of
+     * AccessibilityNodeInfos in addition to the one for a requested node.
+     */
+    private class AccessibilityNodePrefetcher {
+
+        private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50;
+
+        private final ArrayList<View> mTempViewList = new ArrayList<View>();
+
+        public void prefetchAccessibilityNodeInfos(View view, AccessibilityNodeInfo root,
+                int virtualViewId, int fetchFlags, List<AccessibilityNodeInfo> outInfos) {
+            if (root == null) {
+                return;
+            }
+            AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
+            if (provider == null) {
+                if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
+                    prefetchPredecessorsOfRealNode(view, outInfos);
+                }
+                if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
+                    prefetchSiblingsOfRealNode(view, outInfos);
+                }
+                if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
+                    prefetchDescendantsOfRealNode(view, outInfos);
+                }
+            } else {
+                if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
+                    prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
+                }
+                if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
+                    prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
+                }
+                if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
+                    prefetchDescendantsOfVirtualNode(root, provider, outInfos);
+                }
+            }
+            if (ENFORCE_NODE_TREE_CONSISTENT) {
+                enforceNodeTreeConsistent(root, outInfos);
+            }
+        }
+
+        private boolean shouldStopPrefetching(List prefetchededInfos) {
+            return mHandler.hasUserInteractiveMessagesWaiting()
+                    || prefetchededInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE;
+        }
+
+        private void enforceNodeTreeConsistent(
+                AccessibilityNodeInfo root, List<AccessibilityNodeInfo> nodes) {
+            LongSparseArray<AccessibilityNodeInfo> nodeMap =
+                    new LongSparseArray<AccessibilityNodeInfo>();
+            final int nodeCount = nodes.size();
+            for (int i = 0; i < nodeCount; i++) {
+                AccessibilityNodeInfo node = nodes.get(i);
+                nodeMap.put(node.getSourceNodeId(), node);
+            }
+
+            // If the nodes are a tree it does not matter from
+            // which node we start to search for the root.
+            AccessibilityNodeInfo parent = root;
+            while (parent != null) {
+                root = parent;
+                parent = nodeMap.get(parent.getParentNodeId());
+            }
+
+            // Traverse the tree and do some checks.
+            AccessibilityNodeInfo accessFocus = null;
+            AccessibilityNodeInfo inputFocus = null;
+            HashSet<AccessibilityNodeInfo> seen = new HashSet<AccessibilityNodeInfo>();
+            Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>();
+            fringe.add(root);
+
+            while (!fringe.isEmpty()) {
+                AccessibilityNodeInfo current = fringe.poll();
+
+                // Check for duplicates
+                if (!seen.add(current)) {
+                    throw new IllegalStateException("Duplicate node: "
+                            + current + " in window:"
+                            + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
+                }
+
+                // Check for one accessibility focus.
+                if (current.isAccessibilityFocused()) {
+                    if (accessFocus != null) {
+                        throw new IllegalStateException("Duplicate accessibility focus:"
+                                + current
+                                + " in window:" + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
+                    } else {
+                        accessFocus = current;
+                    }
+                }
+
+                // Check for one input focus.
+                if (current.isFocused()) {
+                    if (inputFocus != null) {
+                        throw new IllegalStateException("Duplicate input focus: "
+                            + current + " in window:"
+                            + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
+                    } else {
+                        inputFocus = current;
+                    }
+                }
+
+                final int childCount = current.getChildCount();
+                for (int j = 0; j < childCount; j++) {
+                    final long childId = current.getChildId(j);
+                    final AccessibilityNodeInfo child = nodeMap.get(childId);
+                    if (child != null) {
+                        fringe.add(child);
+                    }
+                }
+            }
+
+            // Check for disconnected nodes.
+            for (int j = nodeMap.size() - 1; j >= 0; j--) {
+                AccessibilityNodeInfo info = nodeMap.valueAt(j);
+                if (!seen.contains(info)) {
+                    throw new IllegalStateException("Disconnected node: " + info);
+                }
+            }
+        }
+
+        private void prefetchPredecessorsOfRealNode(View view,
+                List<AccessibilityNodeInfo> outInfos) {
+            if (shouldStopPrefetching(outInfos)) {
+                return;
+            }
+            ViewParent parent = view.getParentForAccessibility();
+            while (parent instanceof View && !shouldStopPrefetching(outInfos)) {
+                View parentView = (View) parent;
+                AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo();
+                if (info != null) {
+                    outInfos.add(info);
+                }
+                parent = parent.getParentForAccessibility();
+            }
+        }
+
+        private void prefetchSiblingsOfRealNode(View current,
+                List<AccessibilityNodeInfo> outInfos) {
+            if (shouldStopPrefetching(outInfos)) {
+                return;
+            }
+            ViewParent parent = current.getParentForAccessibility();
+            if (parent instanceof ViewGroup) {
+                ViewGroup parentGroup = (ViewGroup) parent;
+                ArrayList<View> children = mTempViewList;
+                children.clear();
+                try {
+                    parentGroup.addChildrenForAccessibility(children);
+                    final int childCount = children.size();
+                    for (int i = 0; i < childCount; i++) {
+                        if (shouldStopPrefetching(outInfos)) {
+                            return;
+                        }
+                        View child = children.get(i);
+                        if (child.getAccessibilityViewId() != current.getAccessibilityViewId()
+                                && isShown(child)) {
+                            AccessibilityNodeInfo info = null;
+                            AccessibilityNodeProvider provider =
+                                child.getAccessibilityNodeProvider();
+                            if (provider == null) {
+                                info = child.createAccessibilityNodeInfo();
+                            } else {
+                                info = provider.createAccessibilityNodeInfo(
+                                        AccessibilityNodeProvider.HOST_VIEW_ID);
+                            }
+                            if (info != null) {
+                                outInfos.add(info);
+                            }
+                        }
+                    }
+                } finally {
+                    children.clear();
+                }
+            }
+        }
+
+        private void prefetchDescendantsOfRealNode(View root,
+                List<AccessibilityNodeInfo> outInfos) {
+            if (shouldStopPrefetching(outInfos) || !(root instanceof ViewGroup)) {
+                return;
+            }
+            HashMap<View, AccessibilityNodeInfo> addedChildren =
+                new HashMap<View, AccessibilityNodeInfo>();
+            ArrayList<View> children = mTempViewList;
+            children.clear();
+            try {
+                root.addChildrenForAccessibility(children);
+                final int childCount = children.size();
+                for (int i = 0; i < childCount; i++) {
+                    if (shouldStopPrefetching(outInfos)) {
+                        return;
+                    }
+                    View child = children.get(i);
+                    if (isShown(child)) {
+                        AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
+                        if (provider == null) {
+                            AccessibilityNodeInfo info = child.createAccessibilityNodeInfo();
+                            if (info != null) {
+                                outInfos.add(info);
+                                addedChildren.put(child, null);
+                            }
+                        } else {
+                            AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo(
+                                   AccessibilityNodeProvider.HOST_VIEW_ID);
+                            if (info != null) {
+                                outInfos.add(info);
+                                addedChildren.put(child, info);
+                            }
+                        }
+                    }
+                }
+            } finally {
+                children.clear();
+            }
+            if (!shouldStopPrefetching(outInfos)) {
+                for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) {
+                    View addedChild = entry.getKey();
+                    AccessibilityNodeInfo virtualRoot = entry.getValue();
+                    if (virtualRoot == null) {
+                        prefetchDescendantsOfRealNode(addedChild, outInfos);
+                    } else {
+                        AccessibilityNodeProvider provider =
+                            addedChild.getAccessibilityNodeProvider();
+                        prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos);
+                    }
+                }
+            }
+        }
+
+        private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root,
+                View providerHost, AccessibilityNodeProvider provider,
+                List<AccessibilityNodeInfo> outInfos) {
+            final int initialResultSize = outInfos.size();
+            long parentNodeId = root.getParentNodeId();
+            int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
+            while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
+                if (shouldStopPrefetching(outInfos)) {
+                    return;
+                }
+                final int virtualDescendantId =
+                    AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
+                if (virtualDescendantId != AccessibilityNodeProvider.HOST_VIEW_ID
+                        || accessibilityViewId == providerHost.getAccessibilityViewId()) {
+                    final AccessibilityNodeInfo parent;
+                    parent = provider.createAccessibilityNodeInfo(virtualDescendantId);
+                    if (parent == null) {
+                        // Going up the parent relation we found a null predecessor,
+                        // so remove these disconnected nodes form the result.
+                        final int currentResultSize = outInfos.size();
+                        for (int i = currentResultSize - 1; i >= initialResultSize; i--) {
+                            outInfos.remove(i);
+                        }
+                        // Couldn't obtain the parent, which means we have a
+                        // disconnected sub-tree. Abort prefetch immediately.
+                        return;
+                    }
+                    outInfos.add(parent);
+                    parentNodeId = parent.getParentNodeId();
+                    accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
+                            parentNodeId);
+                } else {
+                    prefetchPredecessorsOfRealNode(providerHost, outInfos);
+                    return;
+                }
+            }
+        }
+
+        private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost,
+                AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
+            final long parentNodeId = current.getParentNodeId();
+            final int parentAccessibilityViewId =
+                AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
+            final int parentVirtualDescendantId =
+                AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
+            if (parentVirtualDescendantId != AccessibilityNodeProvider.HOST_VIEW_ID
+                    || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) {
+                final AccessibilityNodeInfo parent =
+                        provider.createAccessibilityNodeInfo(parentVirtualDescendantId);
+                if (parent != null) {
+                    final int childCount = parent.getChildCount();
+                    for (int i = 0; i < childCount; i++) {
+                        if (shouldStopPrefetching(outInfos)) {
+                            return;
+                        }
+                        final long childNodeId = parent.getChildId(i);
+                        if (childNodeId != current.getSourceNodeId()) {
+                            final int childVirtualDescendantId =
+                                AccessibilityNodeInfo.getVirtualDescendantId(childNodeId);
+                            AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
+                                    childVirtualDescendantId);
+                            if (child != null) {
+                                outInfos.add(child);
+                            }
+                        }
+                    }
+                }
+            } else {
+                prefetchSiblingsOfRealNode(providerHost, outInfos);
+            }
+        }
+
+        private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root,
+                AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
+            final int initialOutInfosSize = outInfos.size();
+            final int childCount = root.getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                if (shouldStopPrefetching(outInfos)) {
+                    return;
+                }
+                final long childNodeId = root.getChildId(i);
+                AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
+                        AccessibilityNodeInfo.getVirtualDescendantId(childNodeId));
+                if (child != null) {
+                    outInfos.add(child);
+                }
+            }
+            if (!shouldStopPrefetching(outInfos)) {
+                final int addedChildCount = outInfos.size() - initialOutInfosSize;
+                for (int i = 0; i < addedChildCount; i++) {
+                    AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i);
+                    prefetchDescendantsOfVirtualNode(child, provider, outInfos);
+                }
+            }
+        }
+    }
+
+    private class PrivateHandler extends Handler {
+        private static final int MSG_PERFORM_ACCESSIBILITY_ACTION = 1;
+        private static final int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2;
+        private static final int MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID = 3;
+        private static final int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT = 4;
+        private static final int MSG_FIND_FOCUS = 5;
+        private static final int MSG_FOCUS_SEARCH = 6;
+        private static final int MSG_PREPARE_FOR_EXTRA_DATA_REQUEST = 7;
+        private static final int MSG_APP_PREPARATION_FINISHED = 8;
+        private static final int MSG_APP_PREPARATION_TIMEOUT = 9;
+
+        // Uses FIRST_NO_ACCESSIBILITY_CALLBACK_MSG for messages that don't need to call back
+        // results to interrogating client.
+        private static final int FIRST_NO_ACCESSIBILITY_CALLBACK_MSG = 100;
+        private static final int MSG_CLEAR_ACCESSIBILITY_FOCUS =
+                FIRST_NO_ACCESSIBILITY_CALLBACK_MSG + 1;
+        private static final int MSG_NOTIFY_OUTSIDE_TOUCH =
+                FIRST_NO_ACCESSIBILITY_CALLBACK_MSG + 2;
+
+        public PrivateHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public String getMessageName(Message message) {
+            final int type = message.what;
+            switch (type) {
+                case MSG_PERFORM_ACCESSIBILITY_ACTION:
+                    return "MSG_PERFORM_ACCESSIBILITY_ACTION";
+                case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID:
+                    return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID";
+                case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID:
+                    return "MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID";
+                case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT:
+                    return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT";
+                case MSG_FIND_FOCUS:
+                    return "MSG_FIND_FOCUS";
+                case MSG_FOCUS_SEARCH:
+                    return "MSG_FOCUS_SEARCH";
+                case MSG_PREPARE_FOR_EXTRA_DATA_REQUEST:
+                    return "MSG_PREPARE_FOR_EXTRA_DATA_REQUEST";
+                case MSG_APP_PREPARATION_FINISHED:
+                    return "MSG_APP_PREPARATION_FINISHED";
+                case MSG_APP_PREPARATION_TIMEOUT:
+                    return "MSG_APP_PREPARATION_TIMEOUT";
+                case MSG_CLEAR_ACCESSIBILITY_FOCUS:
+                    return "MSG_CLEAR_ACCESSIBILITY_FOCUS";
+                case MSG_NOTIFY_OUTSIDE_TOUCH:
+                    return "MSG_NOTIFY_OUTSIDE_TOUCH";
+                default:
+                    throw new IllegalArgumentException("Unknown message type: " + type);
+            }
+        }
+
+        @Override
+        public void handleMessage(Message message) {
+            final int type = message.what;
+            switch (type) {
+                case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID: {
+                    findAccessibilityNodeInfoByAccessibilityIdUiThread(message);
+                } break;
+                case MSG_PERFORM_ACCESSIBILITY_ACTION: {
+                    performAccessibilityActionUiThread(message);
+                } break;
+                case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID: {
+                    findAccessibilityNodeInfosByViewIdUiThread(message);
+                } break;
+                case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT: {
+                    findAccessibilityNodeInfosByTextUiThread(message);
+                } break;
+                case MSG_FIND_FOCUS: {
+                    findFocusUiThread(message);
+                } break;
+                case MSG_FOCUS_SEARCH: {
+                    focusSearchUiThread(message);
+                } break;
+                case MSG_PREPARE_FOR_EXTRA_DATA_REQUEST: {
+                    prepareForExtraDataRequestUiThread(message);
+                } break;
+                case MSG_APP_PREPARATION_FINISHED: {
+                    requestPreparerDoneUiThread(message);
+                } break;
+                case MSG_APP_PREPARATION_TIMEOUT: {
+                    requestPreparerTimeoutUiThread();
+                } break;
+                case MSG_CLEAR_ACCESSIBILITY_FOCUS: {
+                    clearAccessibilityFocusUiThread();
+                } break;
+                case MSG_NOTIFY_OUTSIDE_TOUCH: {
+                    notifyOutsideTouchUiThread();
+                } break;
+                default:
+                    throw new IllegalArgumentException("Unknown message type: " + type);
+            }
+        }
+
+        boolean hasAccessibilityCallback(Message message) {
+            return message.what < FIRST_NO_ACCESSIBILITY_CALLBACK_MSG ? true : false;
+        }
+
+        boolean hasUserInteractiveMessagesWaiting() {
+            return hasMessagesOrCallbacks();
+        }
+    }
+
+    private final class AddNodeInfosForViewId implements Predicate<View> {
+        private int mViewId = View.NO_ID;
+        private List<AccessibilityNodeInfo> mInfos;
+
+        public void init(int viewId, List<AccessibilityNodeInfo> infos) {
+            mViewId = viewId;
+            mInfos = infos;
+        }
+
+        public void reset() {
+            mViewId = View.NO_ID;
+            mInfos = null;
+        }
+
+        @Override
+        public boolean test(View view) {
+            if (view.getId() == mViewId && isShown(view)) {
+                mInfos.add(view.createAccessibilityNodeInfo());
+            }
+            return false;
+        }
+    }
+
+    private static final class MessageHolder {
+        final Message mMessage;
+        final int mInterrogatingPid;
+        final long mInterrogatingTid;
+
+        MessageHolder(Message message, int interrogatingPid, long interrogatingTid) {
+            mMessage = message;
+            mInterrogatingPid = interrogatingPid;
+            mInterrogatingTid = interrogatingTid;
+        }
+    }
+
+    private static class SatisfiedFindAccessibilityNodeByAccessibilityIdRequest {
+        final AccessibilityNodeInfo mSatisfiedRequestNode;
+        final IAccessibilityInteractionConnectionCallback mSatisfiedRequestCallback;
+        final int mSatisfiedRequestInteractionId;
+
+        SatisfiedFindAccessibilityNodeByAccessibilityIdRequest(
+                AccessibilityNodeInfo satisfiedRequestNode,
+                IAccessibilityInteractionConnectionCallback satisfiedRequestCallback,
+                int satisfiedRequestInteractionId) {
+            mSatisfiedRequestNode = satisfiedRequestNode;
+            mSatisfiedRequestCallback = satisfiedRequestCallback;
+            mSatisfiedRequestInteractionId = satisfiedRequestInteractionId;
+        }
+    }
+}
diff --git a/android/view/AccessibilityIterators.java b/android/view/AccessibilityIterators.java
new file mode 100644
index 0000000..c41b3cf
--- /dev/null
+++ b/android/view/AccessibilityIterators.java
@@ -0,0 +1,332 @@
+/*
+ * 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.view;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.Configuration;
+import android.os.Build;
+
+import java.text.BreakIterator;
+import java.util.Locale;
+
+/**
+ * This class contains the implementation of text segment iterators
+ * for accessibility support.
+ *
+ * Note: Such iterators are needed in the view package since we want
+ * to be able to iterator over content description of any view.
+ *
+ * @hide
+ */
+public final class AccessibilityIterators {
+
+    /**
+     * @hide
+     */
+    public static interface TextSegmentIterator {
+        public int[] following(int current);
+        public int[] preceding(int current);
+    }
+
+    /**
+     * @hide
+     */
+    public static abstract class AbstractTextSegmentIterator implements TextSegmentIterator {
+
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        public AbstractTextSegmentIterator() {
+        }
+
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        protected String mText;
+
+        private final int[] mSegment = new int[2];
+
+        public void initialize(String text) {
+            mText = text;
+        }
+
+        protected int[] getRange(int start, int end) {
+            if (start < 0 || end < 0 || start ==  end) {
+                return null;
+            }
+            mSegment[0] = start;
+            mSegment[1] = end;
+            return mSegment;
+        }
+    }
+
+    static class CharacterTextSegmentIterator extends AbstractTextSegmentIterator
+            implements ViewRootImpl.ConfigChangedCallback {
+        private static CharacterTextSegmentIterator sInstance;
+
+        private Locale mLocale;
+
+        protected BreakIterator mImpl;
+
+        public static CharacterTextSegmentIterator getInstance(Locale locale) {
+            if (sInstance == null) {
+                sInstance = new CharacterTextSegmentIterator(locale);
+            }
+            return sInstance;
+        }
+
+        private CharacterTextSegmentIterator(Locale locale) {
+            mLocale = locale;
+            onLocaleChanged(locale);
+            ViewRootImpl.addConfigCallback(this);
+        }
+
+        @Override
+        public void initialize(String text) {
+            super.initialize(text);
+            mImpl.setText(text);
+        }
+
+        @Override
+        public int[] following(int offset) {
+            final int textLegth = mText.length();
+            if (textLegth <= 0) {
+                return null;
+            }
+            if (offset >= textLegth) {
+                return null;
+            }
+            int start = offset;
+            if (start < 0) {
+                start = 0;
+            }
+            while (!mImpl.isBoundary(start)) {
+                start = mImpl.following(start);
+                if (start == BreakIterator.DONE) {
+                    return null;
+                }
+            }
+            final int end = mImpl.following(start);
+            if (end == BreakIterator.DONE) {
+                return null;
+            }
+            return getRange(start, end);
+        }
+
+        @Override
+        public int[] preceding(int offset) {
+            final int textLegth = mText.length();
+            if (textLegth <= 0) {
+                return null;
+            }
+            if (offset <= 0) {
+                return null;
+            }
+            int end = offset;
+            if (end > textLegth) {
+                end = textLegth;
+            }
+            while (!mImpl.isBoundary(end)) {
+                end = mImpl.preceding(end);
+                if (end == BreakIterator.DONE) {
+                    return null;
+                }
+            }
+            final int start = mImpl.preceding(end);
+            if (start == BreakIterator.DONE) {
+                return null;
+            }
+            return getRange(start, end);
+        }
+
+        @Override
+        public void onConfigurationChanged(Configuration globalConfig) {
+            final Locale locale = globalConfig.getLocales().get(0);
+            if (locale == null) {
+                return;
+            }
+            if (!mLocale.equals(locale)) {
+                mLocale = locale;
+                onLocaleChanged(locale);
+            }
+        }
+
+        protected void onLocaleChanged(Locale locale) {
+            mImpl = BreakIterator.getCharacterInstance(locale);
+        }
+    }
+
+    static class WordTextSegmentIterator extends CharacterTextSegmentIterator {
+        private static WordTextSegmentIterator sInstance;
+
+        public static WordTextSegmentIterator getInstance(Locale locale) {
+            if (sInstance == null) {
+                sInstance = new WordTextSegmentIterator(locale);
+            }
+            return sInstance;
+        }
+
+        private WordTextSegmentIterator(Locale locale) {
+           super(locale);
+        }
+
+        @Override
+        protected void onLocaleChanged(Locale locale) {
+            mImpl = BreakIterator.getWordInstance(locale);
+        }
+
+        @Override
+        public int[] following(int offset) {
+            final int textLegth = mText.length();
+            if (textLegth <= 0) {
+                return null;
+            }
+            if (offset >= mText.length()) {
+                return null;
+            }
+            int start = offset;
+            if (start < 0) {
+                start = 0;
+            }
+            while (!isLetterOrDigit(start) && !isStartBoundary(start)) {
+                start = mImpl.following(start);
+                if (start == BreakIterator.DONE) {
+                    return null;
+                }
+            }
+            final int end = mImpl.following(start);
+            if (end == BreakIterator.DONE || !isEndBoundary(end)) {
+                return null;
+            }
+            return getRange(start, end);
+        }
+
+        @Override
+        public int[] preceding(int offset) {
+            final int textLegth = mText.length();
+            if (textLegth <= 0) {
+                return null;
+            }
+            if (offset <= 0) {
+                return null;
+            }
+            int end = offset;
+            if (end > textLegth) {
+                end = textLegth;
+            }
+            while (end > 0 && !isLetterOrDigit(end - 1) && !isEndBoundary(end)) {
+                end = mImpl.preceding(end);
+                if (end == BreakIterator.DONE) {
+                    return null;
+                }
+            }
+            final int start = mImpl.preceding(end);
+            if (start == BreakIterator.DONE || !isStartBoundary(start)) {
+                return null;
+            }
+            return getRange(start, end);
+        }
+
+        private boolean isStartBoundary(int index) {
+            return isLetterOrDigit(index)
+                && (index == 0 || !isLetterOrDigit(index - 1));
+        }
+
+        private boolean isEndBoundary(int index) {
+            return (index > 0 && isLetterOrDigit(index - 1))
+                && (index == mText.length() || !isLetterOrDigit(index));
+        }
+
+        private boolean isLetterOrDigit(int index) {
+            if (index >= 0 && index < mText.length()) {
+                final int codePoint = mText.codePointAt(index);
+                return Character.isLetterOrDigit(codePoint);
+            }
+            return false;
+        }
+    }
+
+    static class ParagraphTextSegmentIterator extends AbstractTextSegmentIterator {
+        private static ParagraphTextSegmentIterator sInstance;
+
+        public static ParagraphTextSegmentIterator getInstance() {
+            if (sInstance == null) {
+                sInstance = new ParagraphTextSegmentIterator();
+            }
+            return sInstance;
+        }
+
+        @Override
+        public int[] following(int offset) {
+            final int textLength = mText.length();
+            if (textLength <= 0) {
+                return null;
+            }
+            if (offset >= textLength) {
+                return null;
+            }
+            int start = offset;
+            if (start < 0) {
+                start = 0;
+            }
+            while (start < textLength && mText.charAt(start) == '\n'
+                    && !isStartBoundary(start)) {
+                start++;
+            }
+            if (start >= textLength) {
+                return null;
+            }
+            int end = start + 1;
+            while (end < textLength && !isEndBoundary(end)) {
+                end++;
+            }
+            return getRange(start, end);
+        }
+
+        @Override
+        public int[] preceding(int offset) {
+            final int textLength = mText.length();
+            if (textLength <= 0) {
+                return null;
+            }
+            if (offset <= 0) {
+                return null;
+            }
+            int end = offset;
+            if (end > textLength) {
+                end = textLength;
+            }
+            while(end > 0 && mText.charAt(end - 1) == '\n' && !isEndBoundary(end)) {
+                end--;
+            }
+            if (end <= 0) {
+                return null;
+            }
+            int start = end - 1;
+            while (start > 0 && !isStartBoundary(start)) {
+                start--;
+            }
+            return getRange(start, end);
+        }
+
+        private boolean isStartBoundary(int index) {
+            return (mText.charAt(index) != '\n'
+                && (index == 0 || mText.charAt(index - 1) == '\n'));
+        }
+
+        private boolean isEndBoundary(int index) {
+            return (index > 0 && mText.charAt(index - 1) != '\n'
+                && (index == mText.length() || mText.charAt(index) == '\n'));
+        }
+    }
+}
diff --git a/android/view/ActionMode.java b/android/view/ActionMode.java
new file mode 100644
index 0000000..d9f9d03
--- /dev/null
+++ b/android/view/ActionMode.java
@@ -0,0 +1,373 @@
+/*
+ * 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.view;
+
+
+import android.annotation.StringRes;
+import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.Rect;
+
+/**
+ * Represents a contextual mode of the user interface. Action modes can be used to provide
+ * alternative interaction modes and replace parts of the normal UI until finished.
+ * Examples of good action modes include text selection and contextual actions.
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For information about how to provide contextual actions with {@code ActionMode},
+ * read the <a href="{@docRoot}guide/topics/ui/menus.html#context-menu">Menus</a>
+ * developer guide.</p>
+ * </div>
+ */
+public abstract class ActionMode {
+
+    /**
+     * The action mode is treated as a Primary mode. This is the default.
+     * Use with {@link #setType}.
+     */
+    public static final int TYPE_PRIMARY = 0;
+    /**
+     * The action mode is treated as a Floating Toolbar.
+     * Use with {@link #setType}.
+     */
+    public static final int TYPE_FLOATING = 1;
+
+    /**
+     * Default value to hide the action mode for
+     * {@link ViewConfiguration#getDefaultActionModeHideDuration()}.
+     */
+    public static final int DEFAULT_HIDE_DURATION = -1;
+
+    private Object mTag;
+    private boolean mTitleOptionalHint;
+    private int mType = TYPE_PRIMARY;
+
+    /**
+     * Set a tag object associated with this ActionMode.
+     *
+     * <p>Like the tag available to views, this allows applications to associate arbitrary
+     * data with an ActionMode for later reference.
+     *
+     * @param tag Tag to associate with this ActionMode
+     *
+     * @see #getTag()
+     */
+    public void setTag(Object tag) {
+        mTag = tag;
+    }
+
+    /**
+     * Retrieve the tag object associated with this ActionMode.
+     *
+     * <p>Like the tag available to views, this allows applications to associate arbitrary
+     * data with an ActionMode for later reference.
+     *
+     * @return Tag associated with this ActionMode
+     *
+     * @see #setTag(Object)
+     */
+    public Object getTag() {
+        return mTag;
+    }
+
+    /**
+     * Set the title of the action mode. This method will have no visible effect if
+     * a custom view has been set.
+     *
+     * @param title Title string to set
+     *
+     * @see #setTitle(int)
+     * @see #setCustomView(View)
+     */
+    public abstract void setTitle(CharSequence title);
+
+    /**
+     * Set the title of the action mode. This method will have no visible effect if
+     * a custom view has been set.
+     *
+     * @param resId Resource ID of a string to set as the title
+     *
+     * @see #setTitle(CharSequence)
+     * @see #setCustomView(View)
+     */
+    public abstract void setTitle(@StringRes int resId);
+
+    /**
+     * Set the subtitle of the action mode. This method will have no visible effect if
+     * a custom view has been set.
+     *
+     * @param subtitle Subtitle string to set
+     *
+     * @see #setSubtitle(int)
+     * @see #setCustomView(View)
+     */
+    public abstract void setSubtitle(CharSequence subtitle);
+
+    /**
+     * Set the subtitle of the action mode. This method will have no visible effect if
+     * a custom view has been set.
+     *
+     * @param resId Resource ID of a string to set as the subtitle
+     *
+     * @see #setSubtitle(CharSequence)
+     * @see #setCustomView(View)
+     */
+    public abstract void setSubtitle(@StringRes int resId);
+
+    /**
+     * Set whether or not the title/subtitle display for this action mode
+     * is optional.
+     *
+     * <p>In many cases the supplied title for an action mode is merely
+     * meant to add context and is not strictly required for the action
+     * mode to be useful. If the title is optional, the system may choose
+     * to hide the title entirely rather than truncate it due to a lack
+     * of available space.</p>
+     *
+     * <p>Note that this is merely a hint; the underlying implementation
+     * may choose to ignore this setting under some circumstances.</p>
+     *
+     * @param titleOptional true if the title only presents optional information.
+     */
+    public void setTitleOptionalHint(boolean titleOptional) {
+        mTitleOptionalHint = titleOptional;
+    }
+
+    /**
+     * @return true if this action mode has been given a hint to consider the
+     *         title/subtitle display to be optional.
+     *
+     * @see #setTitleOptionalHint(boolean)
+     * @see #isTitleOptional()
+     */
+    public boolean getTitleOptionalHint() {
+        return mTitleOptionalHint;
+    }
+
+    /**
+     * @return true if this action mode considers the title and subtitle fields
+     *         as optional. Optional titles may not be displayed to the user.
+     */
+    public boolean isTitleOptional() {
+        return false;
+    }
+
+    /**
+     * Set a custom view for this action mode. The custom view will take the place of
+     * the title and subtitle. Useful for things like search boxes.
+     *
+     * @param view Custom view to use in place of the title/subtitle.
+     *
+     * @see #setTitle(CharSequence)
+     * @see #setSubtitle(CharSequence)
+     */
+    public abstract void setCustomView(View view);
+
+    /**
+     * Set a type for this action mode. This will affect how the system renders the action mode if
+     * it has to.
+     *
+     * @param type One of {@link #TYPE_PRIMARY} or {@link #TYPE_FLOATING}.
+     */
+    public void setType(int type) {
+        mType = type;
+    }
+
+    /**
+     * Returns the type for this action mode.
+     *
+     * @return One of {@link #TYPE_PRIMARY} or {@link #TYPE_FLOATING}.
+     */
+    public int getType() {
+        return mType;
+    }
+
+    /**
+     * Invalidate the action mode and refresh menu content. The mode's
+     * {@link ActionMode.Callback} will have its
+     * {@link Callback#onPrepareActionMode(ActionMode, Menu)} method called.
+     * If it returns true the menu will be scanned for updated content and any relevant changes
+     * will be reflected to the user.
+     */
+    public abstract void invalidate();
+
+    /**
+     * Invalidate the content rect associated to this ActionMode. This only makes sense for
+     * action modes that support dynamic positioning on the screen, and provides a more efficient
+     * way to reposition it without invalidating the whole action mode.
+     *
+     * @see Callback2#onGetContentRect(ActionMode, View, Rect) .
+     */
+    public void invalidateContentRect() {}
+
+    /**
+     * Hide the action mode view from obstructing the content below for a short duration.
+     * This only makes sense for action modes that support dynamic positioning on the screen.
+     * If this method is called again before the hide duration expires, the later hide call will
+     * cancel the former and then take effect.
+     * NOTE that there is an internal limit to how long the mode can be hidden for. It's typically
+     * about a few seconds.
+     *
+     * @param duration The number of milliseconds to hide for.
+     * @see #DEFAULT_HIDE_DURATION
+     */
+    public void hide(long duration) {}
+
+    /**
+     * Finish and close this action mode. The action mode's {@link ActionMode.Callback} will
+     * have its {@link Callback#onDestroyActionMode(ActionMode)} method called.
+     */
+    public abstract void finish();
+
+    /**
+     * Returns the menu of actions that this action mode presents.
+     * @return The action mode's menu.
+     */
+    public abstract Menu getMenu();
+
+    /**
+     * Returns the current title of this action mode.
+     * @return Title text
+     */
+    public abstract CharSequence getTitle();
+
+    /**
+     * Returns the current subtitle of this action mode.
+     * @return Subtitle text
+     */
+    public abstract CharSequence getSubtitle();
+
+    /**
+     * Returns the current custom view for this action mode.
+     * @return The current custom view
+     */
+    public abstract View getCustomView();
+
+    /**
+     * Returns a {@link MenuInflater} with the ActionMode's context.
+     */
+    public abstract MenuInflater getMenuInflater();
+
+    /**
+     * Called when the window containing the view that started this action mode gains or loses
+     * focus.
+     *
+     * @param hasWindowFocus True if the window containing the view that started this action mode
+     *        now has focus, false otherwise.
+     *
+     */
+    public void onWindowFocusChanged(boolean hasWindowFocus) {}
+
+    /**
+     * Returns whether the UI presenting this action mode can take focus or not.
+     * This is used by internal components within the framework that would otherwise
+     * present an action mode UI that requires focus, such as an EditText as a custom view.
+     *
+     * @return true if the UI used to show this action mode can take focus
+     * @hide Internal use only
+     */
+    @UnsupportedAppUsage
+    @TestApi
+    public boolean isUiFocusable() {
+        return true;
+    }
+
+    /**
+     * Callback interface for action modes. Supplied to
+     * {@link View#startActionMode(Callback)}, a Callback
+     * configures and handles events raised by a user's interaction with an action mode.
+     *
+     * <p>An action mode's lifecycle is as follows:
+     * <ul>
+     * <li>{@link Callback#onCreateActionMode(ActionMode, Menu)} once on initial
+     * creation</li>
+     * <li>{@link Callback#onPrepareActionMode(ActionMode, Menu)} after creation
+     * and any time the {@link ActionMode} is invalidated</li>
+     * <li>{@link Callback#onActionItemClicked(ActionMode, MenuItem)} any time a
+     * contextual action button is clicked</li>
+     * <li>{@link Callback#onDestroyActionMode(ActionMode)} when the action mode
+     * is closed</li>
+     * </ul>
+     */
+    public interface Callback {
+        /**
+         * Called when action mode is first created. The menu supplied will be used to
+         * generate action buttons for the action mode.
+         *
+         * @param mode ActionMode being created
+         * @param menu Menu used to populate action buttons
+         * @return true if the action mode should be created, false if entering this
+         *              mode should be aborted.
+         */
+        public boolean onCreateActionMode(ActionMode mode, Menu menu);
+
+        /**
+         * Called to refresh an action mode's action menu whenever it is invalidated.
+         *
+         * @param mode ActionMode being prepared
+         * @param menu Menu used to populate action buttons
+         * @return true if the menu or action mode was updated, false otherwise.
+         */
+        public boolean onPrepareActionMode(ActionMode mode, Menu menu);
+
+        /**
+         * Called to report a user click on an action button.
+         *
+         * @param mode The current ActionMode
+         * @param item The item that was clicked
+         * @return true if this callback handled the event, false if the standard MenuItem
+         *          invocation should continue.
+         */
+        public boolean onActionItemClicked(ActionMode mode, MenuItem item);
+
+        /**
+         * Called when an action mode is about to be exited and destroyed.
+         *
+         * @param mode The current ActionMode being destroyed
+         */
+        public void onDestroyActionMode(ActionMode mode);
+    }
+
+    /**
+     * Extension of {@link ActionMode.Callback} to provide content rect information. This is
+     * required for ActionModes with dynamic positioning such as the ones with type
+     * {@link ActionMode#TYPE_FLOATING} to ensure the positioning doesn't obscure app content. If
+     * an app fails to provide a subclass of this class, a default implementation will be used.
+     */
+    public static abstract class Callback2 implements ActionMode.Callback {
+
+        /**
+         * Called when an ActionMode needs to be positioned on screen, potentially occluding view
+         * content. Note this may be called on a per-frame basis.
+         *
+         * @param mode The ActionMode that requires positioning.
+         * @param view The View that originated the ActionMode, in whose coordinates the Rect should
+         *          be provided.
+         * @param outRect The Rect to be populated with the content position. Use this to specify
+         *          where the content in your app lives within the given view. This will be used
+         *          to avoid occluding the given content Rect with the created ActionMode.
+         */
+        public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
+            if (view != null) {
+                outRect.set(0, 0, view.getWidth(), view.getHeight());
+            } else {
+                outRect.set(0, 0, 0, 0);
+            }
+        }
+
+    }
+}
diff --git a/android/view/ActionProvider.java b/android/view/ActionProvider.java
new file mode 100644
index 0000000..e1be0fe
--- /dev/null
+++ b/android/view/ActionProvider.java
@@ -0,0 +1,267 @@
+/*
+ * 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.view;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.util.Log;
+
+/**
+ * An ActionProvider defines rich menu interaction in a single component.
+ * ActionProvider can generate action views for use in the action bar,
+ * dynamically populate submenus of a MenuItem, and handle default menu
+ * item invocations.
+ *
+ * <p>An ActionProvider can be optionally specified for a {@link MenuItem} and will be
+ * responsible for creating the action view that appears in the {@link android.app.ActionBar}
+ * in place of a simple button in the bar. When the menu item is presented in a way that
+ * does not allow custom action views, (e.g. in an overflow menu,) the ActionProvider
+ * can perform a default action.</p>
+ *
+ * <p>There are two ways to use an action provider:
+ * <ul>
+ * <li>
+ * Set the action provider on a {@link MenuItem} directly by calling
+ * {@link MenuItem#setActionProvider(ActionProvider)}.
+ * </li>
+ * <li>
+ * Declare the action provider in an XML menu resource. For example:
+ * <pre>
+ * <code>
+ *   &lt;item android:id="@+id/my_menu_item"
+ *     android:title="Title"
+ *     android:icon="@drawable/my_menu_item_icon"
+ *     android:showAsAction="ifRoom"
+ *     android:actionProviderClass="foo.bar.SomeActionProvider" /&gt;
+ * </code>
+ * </pre>
+ * </li>
+ * </ul>
+ * </p>
+ *
+ * @see MenuItem#setActionProvider(ActionProvider)
+ * @see MenuItem#getActionProvider()
+ */
+public abstract class ActionProvider {
+    private static final String TAG = "ActionProvider";
+    private SubUiVisibilityListener mSubUiVisibilityListener;
+    private VisibilityListener mVisibilityListener;
+
+    /**
+     * Creates a new instance. ActionProvider classes should always implement a
+     * constructor that takes a single Context parameter for inflating from menu XML.
+     *
+     * @param context Context for accessing resources.
+     */
+    public ActionProvider(Context context) {
+    }
+
+    /**
+     * Factory method called by the Android framework to create new action views.
+     *
+     * <p>This method has been deprecated in favor of {@link #onCreateActionView(MenuItem)}.
+     * Newer apps that wish to support platform versions prior to API 16 should also
+     * implement this method to return a valid action view.</p>
+     *
+     * @return A new action view.
+     *
+     * @deprecated use {@link #onCreateActionView(MenuItem)}
+     */
+    @Deprecated
+    public abstract View onCreateActionView();
+
+    /**
+     * Factory method called by the Android framework to create new action views.
+     * This method returns a new action view for the given MenuItem.
+     *
+     * <p>If your ActionProvider implementation overrides the deprecated no-argument overload
+     * {@link #onCreateActionView()}, overriding this method for devices running API 16 or later
+     * is recommended but optional. The default implementation calls {@link #onCreateActionView()}
+     * for compatibility with applications written for older platform versions.</p>
+     *
+     * @param forItem MenuItem to create the action view for
+     * @return the new action view
+     */
+    public View onCreateActionView(MenuItem forItem) {
+        return onCreateActionView();
+    }
+
+    /**
+     * The result of this method determines whether or not {@link #isVisible()} will be used
+     * by the {@link MenuItem} this ActionProvider is bound to help determine its visibility.
+     *
+     * @return true if this ActionProvider overrides the visibility of the MenuItem
+     *         it is bound to, false otherwise. The default implementation returns false.
+     * @see #isVisible()
+     */
+    public boolean overridesItemVisibility() {
+        return false;
+    }
+
+    /**
+     * If {@link #overridesItemVisibility()} returns true, the return value of this method
+     * will help determine the visibility of the {@link MenuItem} this ActionProvider is bound to.
+     *
+     * <p>If the MenuItem's visibility is explicitly set to false by the application,
+     * the MenuItem will not be shown, even if this method returns true.</p>
+     *
+     * @return true if the MenuItem this ActionProvider is bound to is visible, false if
+     *         it is invisible. The default implementation returns true.
+     */
+    public boolean isVisible() {
+        return true;
+    }
+
+    /**
+     * If this ActionProvider is associated with an item in a menu,
+     * refresh the visibility of the item based on {@link #overridesItemVisibility()} and
+     * {@link #isVisible()}. If {@link #overridesItemVisibility()} returns false, this call
+     * will have no effect.
+     */
+    public void refreshVisibility() {
+        if (mVisibilityListener != null && overridesItemVisibility()) {
+            mVisibilityListener.onActionProviderVisibilityChanged(isVisible());
+        }
+    }
+
+    /**
+     * Performs an optional default action.
+     * <p>
+     * For the case of an action provider placed in a menu item not shown as an action this
+     * method is invoked if previous callbacks for processing menu selection has handled
+     * the event.
+     * </p>
+     * <p>
+     * A menu item selection is processed in the following order:
+     * <ul>
+     * <li>
+     * Receiving a call to {@link MenuItem.OnMenuItemClickListener#onMenuItemClick
+     *  MenuItem.OnMenuItemClickListener.onMenuItemClick}.
+     * </li>
+     * <li>
+     * Receiving a call to {@link android.app.Activity#onOptionsItemSelected(MenuItem)
+     *  Activity.onOptionsItemSelected(MenuItem)}
+     * </li>
+     * <li>
+     * Receiving a call to {@link android.app.Fragment#onOptionsItemSelected(MenuItem)
+     *  Fragment.onOptionsItemSelected(MenuItem)}
+     * </li>
+     * <li>
+     * Launching the {@link android.content.Intent} set via
+     * {@link MenuItem#setIntent(android.content.Intent) MenuItem.setIntent(android.content.Intent)}
+     * </li>
+     * <li>
+     * Invoking this method.
+     * </li>
+     * </ul>
+     * </p>
+     * <p>
+     * The default implementation does not perform any action and returns false.
+     * </p>
+     */
+    public boolean onPerformDefaultAction() {
+        return false;
+    }
+
+    /**
+     * Determines if this ActionProvider has a submenu associated with it.
+     *
+     * <p>Associated submenus will be shown when an action view is not. This
+     * provider instance will receive a call to {@link #onPrepareSubMenu(SubMenu)}
+     * after the call to {@link #onPerformDefaultAction()} and before a submenu is
+     * displayed to the user.
+     *
+     * @return true if the item backed by this provider should have an associated submenu
+     */
+    public boolean hasSubMenu() {
+        return false;
+    }
+
+    /**
+     * Called to prepare an associated submenu for the menu item backed by this ActionProvider.
+     *
+     * <p>if {@link #hasSubMenu()} returns true, this method will be called when the
+     * menu item is selected to prepare the submenu for presentation to the user. Apps
+     * may use this to create or alter submenu content right before display.
+     *
+     * @param subMenu Submenu that will be displayed
+     */
+    public void onPrepareSubMenu(SubMenu subMenu) {
+    }
+
+    /**
+     * Notify the system that the visibility of an action view's sub-UI such as
+     * an anchored popup has changed. This will affect how other system
+     * visibility notifications occur.
+     *
+     * @hide Pending future API approval
+     */
+    public void subUiVisibilityChanged(boolean isVisible) {
+        if (mSubUiVisibilityListener != null) {
+            mSubUiVisibilityListener.onSubUiVisibilityChanged(isVisible);
+        }
+    }
+
+    /**
+     * @hide Internal use only
+     */
+    @UnsupportedAppUsage
+    public void setSubUiVisibilityListener(SubUiVisibilityListener listener) {
+        mSubUiVisibilityListener = listener;
+    }
+
+    /**
+     * Set a listener to be notified when this ActionProvider's overridden visibility changes.
+     * This should only be used by MenuItem implementations.
+     *
+     * @param listener listener to set
+     */
+    public void setVisibilityListener(VisibilityListener listener) {
+        if (mVisibilityListener != null) {
+            Log.w(TAG, "setVisibilityListener: Setting a new ActionProvider.VisibilityListener " +
+                    "when one is already set. Are you reusing this " + getClass().getSimpleName() +
+                    " instance while it is still in use somewhere else?");
+        }
+        mVisibilityListener = listener;
+    }
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void reset() {
+        mVisibilityListener = null;
+        mSubUiVisibilityListener = null;
+    }
+
+    /**
+     * @hide Internal use only
+     */
+    public interface SubUiVisibilityListener {
+        public void onSubUiVisibilityChanged(boolean isVisible);
+    }
+
+    /**
+     * Listens to changes in visibility as reported by {@link ActionProvider#refreshVisibility()}.
+     *
+     * @see ActionProvider#overridesItemVisibility()
+     * @see ActionProvider#isVisible()
+     */
+    public interface VisibilityListener {
+        public void onActionProviderVisibilityChanged(boolean isVisible);
+    }
+}
diff --git a/android/view/AppTransitionAnimationSpec.java b/android/view/AppTransitionAnimationSpec.java
new file mode 100644
index 0000000..3215f2b
--- /dev/null
+++ b/android/view/AppTransitionAnimationSpec.java
@@ -0,0 +1,62 @@
+package android.view;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Holds information about how the next app transition animation should be executed.
+ *
+ * This class is intended to be used with IWindowManager.overridePendingAppTransition* methods when
+ * simple arguments are not enough to describe the animation.
+ *
+ * @hide
+ */
+public class AppTransitionAnimationSpec implements Parcelable {
+    public final int taskId;
+    public final HardwareBuffer buffer;
+    public final Rect rect;
+
+    @UnsupportedAppUsage
+    public AppTransitionAnimationSpec(int taskId, HardwareBuffer buffer, Rect rect) {
+        this.taskId = taskId;
+        this.rect = rect;
+        this.buffer = buffer;
+    }
+
+    public AppTransitionAnimationSpec(Parcel in) {
+        taskId = in.readInt();
+        rect = in.readTypedObject(Rect.CREATOR);
+        buffer = in.readTypedObject(HardwareBuffer.CREATOR);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(taskId);
+        dest.writeTypedObject(rect, 0 /* flags */);
+        dest.writeTypedObject(buffer, 0 /* flags */);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<AppTransitionAnimationSpec> CREATOR
+            = new Parcelable.Creator<AppTransitionAnimationSpec>() {
+        public AppTransitionAnimationSpec createFromParcel(Parcel in) {
+            return new AppTransitionAnimationSpec(in);
+        }
+
+        public AppTransitionAnimationSpec[] newArray(int size) {
+            return new AppTransitionAnimationSpec[size];
+        }
+    };
+
+    @Override
+    public String toString() {
+        return "{taskId: " + taskId + ", buffer: " + buffer + ", rect: " + rect + "}";
+    }
+}
diff --git a/android/view/AttachInfo_Accessor.java b/android/view/AttachInfo_Accessor.java
new file mode 100644
index 0000000..60c13c0
--- /dev/null
+++ b/android/view/AttachInfo_Accessor.java
@@ -0,0 +1,58 @@
+/*
+ * 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.view;
+
+import android.content.Context;
+import android.os.Handler;
+import android.view.View.AttachInfo;
+
+import com.android.layoutlib.bridge.util.ReflectionUtils;
+
+/**
+ * Class allowing access to package-protected methods/fields.
+ */
+public class AttachInfo_Accessor {
+
+    public static void setAttachInfo(View view) {
+        Context context = view.getContext();
+        WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+        Display display = wm.getDefaultDisplay();
+        ViewRootImpl root = new ViewRootImpl(context, display);
+        AttachInfo info = new AttachInfo(ReflectionUtils.createProxy(IWindowSession.class),
+                ReflectionUtils.createProxy(IWindow.class), display, root, new Handler(), null,
+                context);
+        info.mHasWindowFocus = true;
+        info.mWindowVisibility = View.VISIBLE;
+        info.mInTouchMode = false; // this is so that we can display selections.
+        info.mHardwareAccelerated = false;
+        view.dispatchAttachedToWindow(info, 0);
+    }
+
+    public static void dispatchOnPreDraw(View view) {
+        view.mAttachInfo.mTreeObserver.dispatchOnPreDraw();
+    }
+
+    public static void detachFromWindow(View view) {
+        if (view != null) {
+            view.dispatchDetachedFromWindow();
+        }
+    }
+
+    public static ViewRootImpl getRootView(View view) {
+        return view.mAttachInfo != null ? view.mAttachInfo.mViewRootImpl : null;
+    }
+}
diff --git a/android/view/AttachedSurfaceControl.java b/android/view/AttachedSurfaceControl.java
new file mode 100644
index 0000000..bcc5b56
--- /dev/null
+++ b/android/view/AttachedSurfaceControl.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UiThread;
+
+/**
+ * Provides an interface to the root-Surface of a View Hierarchy or Window. This
+ * is used in combination with the {@link android.view.SurfaceControl} API to enable
+ * attaching app created SurfaceControl to the SurfaceControl hierarchy used
+ * by the app, and enable SurfaceTransactions to be performed in sync with the
+ * View hierarchy drawing.
+ *
+ * This object is obtained from {@link android.view.View#getRootSurfaceControl} and
+ * {@link android.view.Window#getRootSurfaceControl}. It must be used from the UI thread of
+ * the object it was obtained from.
+ */
+@UiThread
+public interface AttachedSurfaceControl {
+    /**
+     * Create a transaction which will reparent {@param child} to the View hierarchy
+     * root SurfaceControl. See
+     * {@link SurfaceControl.Transaction#reparent}. This transacton must be applied
+     * or merged in to another transaction by the caller, otherwise it will have
+     * no effect.
+     *
+     * @param child The SurfaceControl to reparent.
+     * @return A new transaction which performs the reparent operation when applied.
+     */
+    @Nullable SurfaceControl.Transaction buildReparentTransaction(@NonNull SurfaceControl child);
+
+    /**
+     * Consume the passed in transaction, and request the View hierarchy to apply it atomically
+     * with the next draw. This transaction will be merged with the buffer transaction from the
+     * ViewRoot and they will show up on-screen atomically synced.
+     *
+     * This will not cause a draw to be scheduled, and if there are no other changes
+     * to the View hierarchy you may need to call {@link android.view.View#invalidate}
+     */
+    boolean applyTransactionOnDraw(@NonNull SurfaceControl.Transaction t);
+}
diff --git a/android/view/BatchedInputEventReceiver.java b/android/view/BatchedInputEventReceiver.java
new file mode 100644
index 0000000..7023e4b
--- /dev/null
+++ b/android/view/BatchedInputEventReceiver.java
@@ -0,0 +1,103 @@
+/*
+ * 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.view;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Looper;
+
+/**
+ * Similar to {@link InputEventReceiver}, but batches events to vsync boundaries when possible.
+ * @hide
+ */
+public class BatchedInputEventReceiver extends InputEventReceiver {
+    private Choreographer mChoreographer;
+    private boolean mBatchingEnabled;
+    private boolean mBatchedInputScheduled;
+
+    @UnsupportedAppUsage
+    public BatchedInputEventReceiver(
+            InputChannel inputChannel, Looper looper, Choreographer choreographer) {
+        super(inputChannel, looper);
+        mChoreographer = choreographer;
+        mBatchingEnabled = true;
+    }
+
+    @Override
+    public void onBatchedInputEventPending(int source) {
+        if (mBatchingEnabled) {
+            scheduleBatchedInput();
+        } else {
+            consumeBatchedInputEvents(-1);
+        }
+    }
+
+    @Override
+    public void dispose() {
+        unscheduleBatchedInput();
+        consumeBatchedInputEvents(-1);
+        super.dispose();
+    }
+
+    /**
+     * Sets whether to enable batching on this input event receiver.
+     * @hide
+     */
+    public void setBatchingEnabled(boolean batchingEnabled) {
+        mBatchingEnabled = batchingEnabled;
+        if (!batchingEnabled) {
+            unscheduleBatchedInput();
+            consumeBatchedInputEvents(-1);
+        }
+    }
+
+    void doConsumeBatchedInput(long frameTimeNanos) {
+        if (mBatchedInputScheduled) {
+            mBatchedInputScheduled = false;
+            if (consumeBatchedInputEvents(frameTimeNanos) && frameTimeNanos != -1) {
+                // If we consumed a batch here, we want to go ahead and schedule the
+                // consumption of batched input events on the next frame. Otherwise, we would
+                // wait until we have more input events pending and might get starved by other
+                // things occurring in the process. If the frame time is -1, however, then
+                // we're in a non-batching mode, so there's no need to schedule this.
+                scheduleBatchedInput();
+            }
+        }
+    }
+
+    private void scheduleBatchedInput() {
+        if (!mBatchedInputScheduled) {
+            mBatchedInputScheduled = true;
+            mChoreographer.postCallback(Choreographer.CALLBACK_INPUT, mBatchedInputRunnable, null);
+        }
+    }
+
+    private void unscheduleBatchedInput() {
+        if (mBatchedInputScheduled) {
+            mBatchedInputScheduled = false;
+            mChoreographer.removeCallbacks(
+                    Choreographer.CALLBACK_INPUT, mBatchedInputRunnable, null);
+        }
+    }
+
+    private final class BatchedInputRunnable implements Runnable {
+        @Override
+        public void run() {
+            doConsumeBatchedInput(mChoreographer.getFrameTimeNanos());
+        }
+    }
+    private final BatchedInputRunnable mBatchedInputRunnable = new BatchedInputRunnable();
+}
diff --git a/android/view/BridgeInflater.java b/android/view/BridgeInflater.java
new file mode 100644
index 0000000..dfb46f2
--- /dev/null
+++ b/android/view/BridgeInflater.java
@@ -0,0 +1,596 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import com.android.SdkConstants;
+import com.android.ide.common.rendering.api.ILayoutLog;
+import com.android.ide.common.rendering.api.LayoutlibCallback;
+import com.android.ide.common.rendering.api.MergeCookie;
+import com.android.ide.common.rendering.api.ResourceNamespace;
+import com.android.ide.common.rendering.api.ResourceReference;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.BridgeConstants;
+import com.android.layoutlib.bridge.MockView;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import com.android.layoutlib.bridge.android.support.DrawerLayoutUtil;
+import com.android.layoutlib.bridge.android.support.RecyclerViewUtil;
+import com.android.layoutlib.bridge.impl.ParserFactory;
+import com.android.layoutlib.bridge.util.ReflectionUtils;
+import com.android.tools.layoutlib.annotations.NotNull;
+import com.android.tools.layoutlib.annotations.Nullable;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.ResolvingAttributeSet;
+import android.view.View.OnAttachStateChangeListener;
+import android.widget.ImageView;
+import android.widget.NumberPicker;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.BiFunction;
+
+import static com.android.layoutlib.bridge.android.BridgeContext.getBaseContext;
+
+/**
+ * Custom implementation of {@link LayoutInflater} to handle custom views.
+ */
+public final class BridgeInflater extends LayoutInflater {
+    private static final String INFLATER_CLASS_ATTR_NAME = "viewInflaterClass";
+    private static final ResourceReference RES_AUTO_INFLATER_CLASS_ATTR =
+            ResourceReference.attr(ResourceNamespace.RES_AUTO, INFLATER_CLASS_ATTR_NAME);
+    private static final ResourceReference LEGACY_APPCOMPAT_INFLATER_CLASS_ATTR =
+            ResourceReference.attr(ResourceNamespace.APPCOMPAT_LEGACY, INFLATER_CLASS_ATTR_NAME);
+    private static final ResourceReference ANDROIDX_APPCOMPAT_INFLATER_CLASS_ATTR =
+            ResourceReference.attr(ResourceNamespace.APPCOMPAT, INFLATER_CLASS_ATTR_NAME);
+    private static final String LEGACY_DEFAULT_APPCOMPAT_INFLATER_NAME =
+            "android.support.v7.app.AppCompatViewInflater";
+    private static final String ANDROIDX_DEFAULT_APPCOMPAT_INFLATER_NAME =
+            "androidx.appcompat.app.AppCompatViewInflater";
+    private final LayoutlibCallback mLayoutlibCallback;
+
+    private boolean mIsInMerge = false;
+    private ResourceReference mResourceReference;
+    private Map<View, String> mOpenDrawerLayouts;
+
+    // Keep in sync with the same value in LayoutInflater.
+    private static final int[] ATTRS_THEME = new int[] {com.android.internal.R.attr.theme };
+
+    /**
+     * List of class prefixes which are tried first by default.
+     * <p/>
+     * This should match the list in com.android.internal.policy.impl.PhoneLayoutInflater.
+     */
+    private static final String[] sClassPrefixList = {
+        "android.widget.",
+        "android.webkit.",
+        "android.app."
+    };
+    private BiFunction<String, AttributeSet, View> mCustomInflater;
+
+    public static String[] getClassPrefixList() {
+        return sClassPrefixList;
+    }
+
+    private BridgeInflater(LayoutInflater original, Context newContext) {
+        super(original, newContext);
+        newContext = getBaseContext(newContext);
+        mLayoutlibCallback = (newContext instanceof BridgeContext) ?
+                ((BridgeContext) newContext).getLayoutlibCallback() :
+                null;
+    }
+
+    /**
+     * Instantiate a new BridgeInflater with an {@link LayoutlibCallback} object.
+     *
+     * @param context The Android application context.
+     * @param layoutlibCallback the {@link LayoutlibCallback} object.
+     */
+    public BridgeInflater(BridgeContext context, LayoutlibCallback layoutlibCallback) {
+        super(context);
+        mLayoutlibCallback = layoutlibCallback;
+        mConstructorArgs[0] = context;
+    }
+
+    @Override
+    public View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
+        View view = createViewFromCustomInflater(name, attrs);
+
+        if (view == null) {
+            try {
+                // First try to find a class using the default Android prefixes
+                for (String prefix : sClassPrefixList) {
+                    try {
+                        view = createView(name, prefix, attrs);
+                        if (view != null) {
+                            break;
+                        }
+                    } catch (ClassNotFoundException e) {
+                        // Ignore. We'll try again using the base class below.
+                    }
+                }
+
+                // Next try using the parent loader. This will most likely only work for
+                // fully-qualified class names.
+                try {
+                    if (view == null) {
+                        view = super.onCreateView(name, attrs);
+                    }
+                } catch (ClassNotFoundException e) {
+                    // Ignore. We'll try again using the custom view loader below.
+                }
+
+                // Finally try again using the custom view loader
+                if (view == null) {
+                    view = loadCustomView(name, attrs);
+                }
+            } catch (InflateException e) {
+                // Don't catch the InflateException below as that results in hiding the real cause.
+                throw e;
+            } catch (Exception e) {
+                // Wrap the real exception in a ClassNotFoundException, so that the calling method
+                // can deal with it.
+                throw new ClassNotFoundException("onCreateView", e);
+            }
+        }
+
+        setupViewInContext(view, attrs);
+
+        return view;
+    }
+
+    /**
+     * Finds the createView method in the given customInflaterClass. Since createView is
+     * currently package protected, it will show in the declared class so we iterate up the
+     * hierarchy and return the first instance we find.
+     * The returned method will be accessible.
+     */
+    @NotNull
+    private static Method getCreateViewMethod(Class<?> customInflaterClass) throws NoSuchMethodException {
+        Class<?> current = customInflaterClass;
+        do {
+            try {
+                Method method = current.getDeclaredMethod("createView", View.class, String.class,
+                                Context.class, AttributeSet.class, boolean.class, boolean.class,
+                                boolean.class, boolean.class);
+                method.setAccessible(true);
+                return method;
+            } catch (NoSuchMethodException ignore) {
+            }
+            current = current.getSuperclass();
+        } while (current != null && current != Object.class);
+
+        throw new NoSuchMethodException();
+    }
+
+    /**
+     * Finds the custom inflater class. If it's defined in the theme, we'll use that one (if the
+     * class does not exist, null is returned).
+     * If {@code viewInflaterClass} is not defined in the theme, we'll try to instantiate
+     * {@code android.support.v7.app.AppCompatViewInflater}
+     */
+    @Nullable
+    private static Class<?> findCustomInflater(@NotNull BridgeContext bc,
+            @NotNull LayoutlibCallback layoutlibCallback) {
+        ResourceReference attrRef;
+        if (layoutlibCallback.isResourceNamespacingRequired()) {
+            if (layoutlibCallback.hasLegacyAppCompat()) {
+                attrRef = LEGACY_APPCOMPAT_INFLATER_CLASS_ATTR;
+            } else if (layoutlibCallback.hasAndroidXAppCompat()) {
+                attrRef = ANDROIDX_APPCOMPAT_INFLATER_CLASS_ATTR;
+            } else {
+                return null;
+            }
+        } else {
+            attrRef = RES_AUTO_INFLATER_CLASS_ATTR;
+        }
+        ResourceValue value = bc.getRenderResources().findItemInTheme(attrRef);
+        String inflaterName = value != null ? value.getValue() : null;
+
+        if (inflaterName != null) {
+            try {
+                return layoutlibCallback.findClass(inflaterName);
+            } catch (ClassNotFoundException ignore) {
+            }
+
+            // viewInflaterClass was defined but we couldn't find the class.
+        } else if (bc.isAppCompatTheme()) {
+            // Older versions of AppCompat do not define the viewInflaterClass so try to get it
+            // manually.
+            try {
+                if (layoutlibCallback.hasLegacyAppCompat()) {
+                    return layoutlibCallback.findClass(LEGACY_DEFAULT_APPCOMPAT_INFLATER_NAME);
+                } else if (layoutlibCallback.hasAndroidXAppCompat()) {
+                    return layoutlibCallback.findClass(ANDROIDX_DEFAULT_APPCOMPAT_INFLATER_NAME);
+                }
+            } catch (ClassNotFoundException ignore) {
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Checks if there is a custom inflater and, when present, tries to instantiate the view
+     * using it.
+     */
+    @Nullable
+    private View createViewFromCustomInflater(@NotNull String name, @NotNull AttributeSet attrs) {
+        if (mCustomInflater == null) {
+            Context context = getContext();
+            context = getBaseContext(context);
+            if (context instanceof BridgeContext) {
+                BridgeContext bc = (BridgeContext) context;
+                Class<?> inflaterClass = findCustomInflater(bc, mLayoutlibCallback);
+
+                if (inflaterClass != null) {
+                    try {
+                        Constructor<?> constructor =  inflaterClass.getDeclaredConstructor();
+                        constructor.setAccessible(true);
+                        Object inflater = constructor.newInstance();
+                        Method method = getCreateViewMethod(inflaterClass);
+                        mCustomInflater = (viewName, attributeSet) -> {
+                            try {
+                                return (View) method.invoke(inflater, null, viewName,
+                                        mConstructorArgs[0],
+                                        attributeSet,
+                                        false,
+                                        false /*readAndroidTheme*/, // No need after L
+                                        true /*readAppTheme*/,
+                                        true /*wrapContext*/);
+                            } catch (IllegalAccessException | InvocationTargetException e) {
+                                Bridge.getLog().error(ILayoutLog.TAG_BROKEN, e.getMessage(), e,
+                                        null, null);
+                            }
+                            return null;
+                        };
+                    } catch (InvocationTargetException | IllegalAccessException |
+                            NoSuchMethodException | InstantiationException ignore) {
+                    }
+                }
+            }
+
+            if (mCustomInflater == null) {
+                // There is no custom inflater. We'll create a nop custom inflater to avoid the
+                // penalty of trying to instantiate again
+                mCustomInflater = (s, attributeSet) -> null;
+            }
+        }
+
+        return mCustomInflater.apply(name, attrs);
+    }
+
+    @Override
+    public View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
+            boolean ignoreThemeAttr) {
+        View view = null;
+        if (name.equals("view")) {
+            // This is usually done by the superclass but this allows us catching the error and
+            // reporting something useful.
+            name = attrs.getAttributeValue(null, "class");
+
+            if (name == null) {
+                Bridge.getLog().error(ILayoutLog.TAG_BROKEN, "Unable to inflate view tag without " +
+                  "class attribute", null, null);
+                // We weren't able to resolve the view so we just pass a mock View to be able to
+                // continue rendering.
+                view = new MockView(context, attrs);
+                ((MockView) view).setText("view");
+            }
+        }
+
+        try {
+            if (view == null) {
+                view = super.createViewFromTag(parent, name, context, attrs, ignoreThemeAttr);
+            }
+        } catch (InflateException e) {
+            // Creation of ContextThemeWrapper code is same as in the super method.
+            // Apply a theme wrapper, if allowed and one is specified.
+            if (!ignoreThemeAttr) {
+                final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
+                final int themeResId = ta.getResourceId(0, 0);
+                if (themeResId != 0) {
+                    context = new ContextThemeWrapper(context, themeResId);
+                }
+                ta.recycle();
+            }
+            if (!(e.getCause() instanceof ClassNotFoundException)) {
+                // There is some unknown inflation exception in inflating a View that was found.
+                view = new MockView(context, attrs);
+                ((MockView) view).setText(name);
+                Bridge.getLog().error(ILayoutLog.TAG_BROKEN, e.getMessage(), e, null, null);
+            } else {
+                final Object lastContext = mConstructorArgs[0];
+                mConstructorArgs[0] = context;
+                // try to load the class from using the custom view loader
+                try {
+                    view = loadCustomView(name, attrs);
+                } catch (Exception e2) {
+                    // Wrap the real exception in an InflateException so that the calling
+                    // method can deal with it.
+                    InflateException exception = new InflateException();
+                    if (!e2.getClass().equals(ClassNotFoundException.class)) {
+                        exception.initCause(e2);
+                    } else {
+                        exception.initCause(e);
+                    }
+                    throw exception;
+                } finally {
+                    mConstructorArgs[0] = lastContext;
+                }
+            }
+        }
+
+        setupViewInContext(view, attrs);
+
+        return view;
+    }
+
+    @Override
+    public View inflate(int resource, ViewGroup root) {
+        Context context = getContext();
+        context = getBaseContext(context);
+        if (context instanceof BridgeContext) {
+            BridgeContext bridgeContext = (BridgeContext)context;
+
+            ResourceValue value = null;
+
+            ResourceReference layoutInfo = Bridge.resolveResourceId(resource);
+            if (layoutInfo == null) {
+                layoutInfo = mLayoutlibCallback.resolveResourceId(resource);
+
+            }
+            if (layoutInfo != null) {
+                value = bridgeContext.getRenderResources().getResolvedResource(layoutInfo);
+            }
+
+            if (value != null) {
+                String path = value.getValue();
+                try {
+                    XmlPullParser parser = ParserFactory.create(path, true);
+                    if (parser == null) {
+                        return null;
+                    }
+
+                    BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser(
+                            parser, bridgeContext, value.getNamespace());
+
+                    return inflate(bridgeParser, root);
+                } catch (Exception e) {
+                    Bridge.getLog().error(ILayoutLog.TAG_RESOURCES_READ,
+                            "Failed to parse file " + path, e, null, null);
+
+                    return null;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Instantiates the given view name and returns the instance. If the view doesn't exist, a
+     * MockView or null might be returned.
+     * @param name the custom view name
+     * @param attrs the {@link AttributeSet} to be passed to the view constructor
+     * @param silent if true, errors while loading the view won't be reported and, if the view
+     * doesn't exist, null will be returned.
+     */
+    private View loadCustomView(String name, AttributeSet attrs, boolean silent) throws Exception {
+        if (mLayoutlibCallback != null) {
+            // first get the classname in case it's not the node name
+            if (name.equals("view")) {
+                name = attrs.getAttributeValue(null, "class");
+                if (name == null) {
+                    return null;
+                }
+            }
+
+            mConstructorArgs[1] = attrs;
+
+            Object customView = silent ?
+                    mLayoutlibCallback.loadClass(name, mConstructorSignature, mConstructorArgs)
+                    : mLayoutlibCallback.loadView(name, mConstructorSignature, mConstructorArgs);
+
+            if (customView instanceof View) {
+                return (View)customView;
+            }
+        }
+
+        return null;
+    }
+
+    private View loadCustomView(String name, AttributeSet attrs) throws Exception {
+        return loadCustomView(name, attrs, false);
+    }
+
+    private void setupViewInContext(View view, AttributeSet attrs) {
+        Context context = getContext();
+        context = getBaseContext(context);
+        if (!(context instanceof BridgeContext)) {
+            return;
+        }
+
+        BridgeContext bc = (BridgeContext) context;
+        // get the view key
+        Object viewKey = getViewKeyFromParser(attrs, bc, mResourceReference, mIsInMerge);
+        if (viewKey != null) {
+            bc.addViewKey(view, viewKey);
+        }
+        String scrollPosX = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, "scrollX");
+        if (scrollPosX != null && scrollPosX.endsWith("px")) {
+            int value = Integer.parseInt(scrollPosX.substring(0, scrollPosX.length() - 2));
+            bc.setScrollXPos(view, value);
+        }
+        String scrollPosY = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, "scrollY");
+        if (scrollPosY != null && scrollPosY.endsWith("px")) {
+            int value = Integer.parseInt(scrollPosY.substring(0, scrollPosY.length() - 2));
+            bc.setScrollYPos(view, value);
+        }
+        if (ReflectionUtils.isInstanceOf(view, RecyclerViewUtil.CN_RECYCLER_VIEW)) {
+            int resourceId = 0;
+            int attrItemCountValue = attrs.getAttributeIntValue(BridgeConstants.NS_TOOLS_URI,
+                    BridgeConstants.ATTR_ITEM_COUNT, -1);
+            if (attrs instanceof ResolvingAttributeSet) {
+                ResourceValue attrListItemValue =
+                        ((ResolvingAttributeSet) attrs).getResolvedAttributeValue(
+                                BridgeConstants.NS_TOOLS_URI, BridgeConstants.ATTR_LIST_ITEM);
+                if (attrListItemValue != null) {
+                    resourceId = bc.getResourceId(attrListItemValue.asReference(), 0);
+                }
+            }
+            RecyclerViewUtil.setAdapter(view, bc, mLayoutlibCallback, resourceId, attrItemCountValue);
+        } else if (ReflectionUtils.isInstanceOf(view, DrawerLayoutUtil.CN_DRAWER_LAYOUT)) {
+            String attrVal = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI,
+                    BridgeConstants.ATTR_OPEN_DRAWER);
+            if (attrVal != null) {
+                getDrawerLayoutMap().put(view, attrVal);
+            }
+        }
+        else if (view instanceof NumberPicker) {
+            NumberPicker numberPicker = (NumberPicker) view;
+            String minValue = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI, "minValue");
+            if (minValue != null) {
+                numberPicker.setMinValue(Integer.parseInt(minValue));
+            }
+            String maxValue = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI, "maxValue");
+            if (maxValue != null) {
+                numberPicker.setMaxValue(Integer.parseInt(maxValue));
+            }
+        }
+        else if (view instanceof ImageView) {
+            ImageView img = (ImageView) view;
+            Drawable drawable = img.getDrawable();
+            if (drawable instanceof Animatable) {
+                if (!((Animatable) drawable).isRunning()) {
+                    ((Animatable) drawable).start();
+                }
+            }
+        }
+        else if (view instanceof ViewStub) {
+            // By default, ViewStub will be set to GONE and won't be inflate. If the XML has the
+            // tools:visibility attribute we'll workaround that behavior.
+            String visibility = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI,
+                    SdkConstants.ATTR_VISIBILITY);
+
+            boolean isVisible = "visible".equals(visibility);
+            if (isVisible || "invisible".equals(visibility)) {
+                // We can not inflate the view until is attached to its parent so we need to delay
+                // the setVisible call until after that happens.
+                final int visibilityValue = isVisible ? View.VISIBLE : View.INVISIBLE;
+                view.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
+                    @Override
+                    public void onViewAttachedToWindow(View v) {
+                        v.removeOnAttachStateChangeListener(this);
+                        view.setVisibility(visibilityValue);
+                    }
+
+                    @Override
+                    public void onViewDetachedFromWindow(View v) {}
+                });
+            }
+        }
+
+    }
+
+    public void setIsInMerge(boolean isInMerge) {
+        mIsInMerge = isInMerge;
+    }
+
+    public void setResourceReference(ResourceReference reference) {
+        mResourceReference = reference;
+    }
+
+    @Override
+    public LayoutInflater cloneInContext(Context newContext) {
+        return new BridgeInflater(this, newContext);
+    }
+
+    /*package*/ static Object getViewKeyFromParser(AttributeSet attrs, BridgeContext bc,
+            ResourceReference resourceReference, boolean isInMerge) {
+
+        if (!(attrs instanceof BridgeXmlBlockParser)) {
+            return null;
+        }
+        BridgeXmlBlockParser parser = ((BridgeXmlBlockParser) attrs);
+
+        // get the view key
+        Object viewKey = parser.getViewCookie();
+
+        if (viewKey == null) {
+            int currentDepth = parser.getDepth();
+
+            // test whether we are in an included file or in a adapter binding view.
+            BridgeXmlBlockParser previousParser = bc.getPreviousParser();
+            if (previousParser != null) {
+                // looks like we are inside an embedded layout.
+                // only apply the cookie of the calling node (<include>) if we are at the
+                // top level of the embedded layout. If there is a merge tag, then
+                // skip it and look for the 2nd level
+                int testDepth = isInMerge ? 2 : 1;
+                if (currentDepth == testDepth) {
+                    viewKey = previousParser.getViewCookie();
+                    // if we are in a merge, wrap the cookie in a MergeCookie.
+                    if (viewKey != null && isInMerge) {
+                        viewKey = new MergeCookie(viewKey);
+                    }
+                }
+            } else if (resourceReference != null && currentDepth == 1) {
+                // else if there's a resource reference, this means we are in an adapter
+                // binding case. Set the resource ref as the view cookie only for the top
+                // level view.
+                viewKey = resourceReference;
+            }
+        }
+
+        return viewKey;
+    }
+
+    public void postInflateProcess(View view) {
+        if (mOpenDrawerLayouts != null) {
+            String gravity = mOpenDrawerLayouts.get(view);
+            if (gravity != null) {
+                DrawerLayoutUtil.openDrawer(view, gravity);
+            }
+            mOpenDrawerLayouts.remove(view);
+        }
+    }
+
+    @NonNull
+    private Map<View, String> getDrawerLayoutMap() {
+        if (mOpenDrawerLayouts == null) {
+            mOpenDrawerLayouts = new HashMap<>(4);
+        }
+        return mOpenDrawerLayouts;
+    }
+
+    public void onDoneInflation() {
+        if (mOpenDrawerLayouts != null) {
+            mOpenDrawerLayouts.clear();
+        }
+    }
+}
diff --git a/android/view/Choreographer.java b/android/view/Choreographer.java
new file mode 100644
index 0000000..be172f7
--- /dev/null
+++ b/android/view/Choreographer.java
@@ -0,0 +1,1111 @@
+/*
+ * 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.view;
+
+import static android.view.DisplayEventReceiver.VSYNC_SOURCE_APP;
+import static android.view.DisplayEventReceiver.VSYNC_SOURCE_SURFACE_FLINGER;
+
+import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.FrameInfo;
+import android.graphics.Insets;
+import android.hardware.display.DisplayManagerGlobal;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.util.Log;
+import android.util.TimeUtils;
+import android.view.animation.AnimationUtils;
+
+import java.io.PrintWriter;
+
+/**
+ * Coordinates the timing of animations, input and drawing.
+ * <p>
+ * The choreographer receives timing pulses (such as vertical synchronization)
+ * from the display subsystem then schedules work to occur as part of rendering
+ * the next display frame.
+ * </p><p>
+ * Applications typically interact with the choreographer indirectly using
+ * higher level abstractions in the animation framework or the view hierarchy.
+ * Here are some examples of things you can do using the higher-level APIs.
+ * </p>
+ * <ul>
+ * <li>To post an animation to be processed on a regular time basis synchronized with
+ * display frame rendering, use {@link android.animation.ValueAnimator#start}.</li>
+ * <li>To post a {@link Runnable} to be invoked once at the beginning of the next display
+ * frame, use {@link View#postOnAnimation}.</li>
+ * <li>To post a {@link Runnable} to be invoked once at the beginning of the next display
+ * frame after a delay, use {@link View#postOnAnimationDelayed}.</li>
+ * <li>To post a call to {@link View#invalidate()} to occur once at the beginning of the
+ * next display frame, use {@link View#postInvalidateOnAnimation()} or
+ * {@link View#postInvalidateOnAnimation(int, int, int, int)}.</li>
+ * <li>To ensure that the contents of a {@link View} scroll smoothly and are drawn in
+ * sync with display frame rendering, do nothing.  This already happens automatically.
+ * {@link View#onDraw} will be called at the appropriate time.</li>
+ * </ul>
+ * <p>
+ * However, there are a few cases where you might want to use the functions of the
+ * choreographer directly in your application.  Here are some examples.
+ * </p>
+ * <ul>
+ * <li>If your application does its rendering in a different thread, possibly using GL,
+ * or does not use the animation framework or view hierarchy at all
+ * and you want to ensure that it is appropriately synchronized with the display, then use
+ * {@link Choreographer#postFrameCallback}.</li>
+ * <li>... and that's about it.</li>
+ * </ul>
+ * <p>
+ * Each {@link Looper} thread has its own choreographer.  Other threads can
+ * post callbacks to run on the choreographer but they will run on the {@link Looper}
+ * to which the choreographer belongs.
+ * </p>
+ */
+public final class Choreographer {
+    private static final String TAG = "Choreographer";
+
+    // Prints debug messages about jank which was detected (low volume).
+    private static final boolean DEBUG_JANK = false;
+
+    // Prints debug messages about every frame and callback registered (high volume).
+    private static final boolean DEBUG_FRAMES = false;
+
+    // The default amount of time in ms between animation frames.
+    // When vsync is not enabled, we want to have some idea of how long we should
+    // wait before posting the next animation message.  It is important that the
+    // default value be less than the true inter-frame delay on all devices to avoid
+    // situations where we might skip frames by waiting too long (we must compensate
+    // for jitter and hardware variations).  Regardless of this value, the animation
+    // and display loop is ultimately rate-limited by how fast new graphics buffers can
+    // be dequeued.
+    private static final long DEFAULT_FRAME_DELAY = 10;
+
+    // The number of milliseconds between animation frames.
+    private static volatile long sFrameDelay = DEFAULT_FRAME_DELAY;
+
+    // Thread local storage for the choreographer.
+    private static final ThreadLocal<Choreographer> sThreadInstance =
+            new ThreadLocal<Choreographer>() {
+        @Override
+        protected Choreographer initialValue() {
+            Looper looper = Looper.myLooper();
+            if (looper == null) {
+                throw new IllegalStateException("The current thread must have a looper!");
+            }
+            Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
+            if (looper == Looper.getMainLooper()) {
+                mMainInstance = choreographer;
+            }
+            return choreographer;
+        }
+    };
+
+    private static volatile Choreographer mMainInstance;
+
+    // Thread local storage for the SF choreographer.
+    private static final ThreadLocal<Choreographer> sSfThreadInstance =
+            new ThreadLocal<Choreographer>() {
+                @Override
+                protected Choreographer initialValue() {
+                    Looper looper = Looper.myLooper();
+                    if (looper == null) {
+                        throw new IllegalStateException("The current thread must have a looper!");
+                    }
+                    return new Choreographer(looper, VSYNC_SOURCE_SURFACE_FLINGER);
+                }
+            };
+
+    // Enable/disable vsync for animations and drawing.
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769497)
+    private static final boolean USE_VSYNC = SystemProperties.getBoolean(
+            "debug.choreographer.vsync", true);
+
+    // Enable/disable using the frame time instead of returning now.
+    private static final boolean USE_FRAME_TIME = SystemProperties.getBoolean(
+            "debug.choreographer.frametime", true);
+
+    // Set a limit to warn about skipped frames.
+    // Skipped frames imply jank.
+    private static final int SKIPPED_FRAME_WARNING_LIMIT = SystemProperties.getInt(
+            "debug.choreographer.skipwarning", 30);
+
+    private static final int MSG_DO_FRAME = 0;
+    private static final int MSG_DO_SCHEDULE_VSYNC = 1;
+    private static final int MSG_DO_SCHEDULE_CALLBACK = 2;
+
+    // All frame callbacks posted by applications have this token.
+    private static final Object FRAME_CALLBACK_TOKEN = new Object() {
+        public String toString() { return "FRAME_CALLBACK_TOKEN"; }
+    };
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    private final Object mLock = new Object();
+
+    private final Looper mLooper;
+    private final FrameHandler mHandler;
+
+    // The display event receiver can only be accessed by the looper thread to which
+    // it is attached.  We take care to ensure that we post message to the looper
+    // if appropriate when interacting with the display event receiver.
+    @UnsupportedAppUsage
+    private final FrameDisplayEventReceiver mDisplayEventReceiver;
+
+    private CallbackRecord mCallbackPool;
+
+    @UnsupportedAppUsage
+    private final CallbackQueue[] mCallbackQueues;
+
+    private boolean mFrameScheduled;
+    private boolean mCallbacksRunning;
+    @UnsupportedAppUsage
+    private long mLastFrameTimeNanos;
+
+    /** DO NOT USE since this will not updated when screen refresh changes. */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R,
+            publicAlternatives = "Use {@link android.view.Display#getRefreshRate} instead")
+    @Deprecated
+    private long mFrameIntervalNanos;
+    private long mLastFrameIntervalNanos;
+
+    private boolean mDebugPrintNextFrameTimeDelta;
+    private int mFPSDivisor = 1;
+    private DisplayEventReceiver.VsyncEventData mLastVsyncEventData =
+            new DisplayEventReceiver.VsyncEventData();
+
+    /**
+     * Contains information about the current frame for jank-tracking,
+     * mainly timings of key events along with a bit of metadata about
+     * view tree state
+     *
+     * TODO: Is there a better home for this? Currently Choreographer
+     * is the only one with CALLBACK_ANIMATION start time, hence why this
+     * resides here.
+     *
+     * @hide
+     */
+    FrameInfo mFrameInfo = new FrameInfo();
+
+    /**
+     * Must be kept in sync with CALLBACK_* ints below, used to index into this array.
+     * @hide
+     */
+    private static final String[] CALLBACK_TRACE_TITLES = {
+            "input", "animation", "insets_animation", "traversal", "commit"
+    };
+
+    /**
+     * Callback type: Input callback.  Runs first.
+     * @hide
+     */
+    public static final int CALLBACK_INPUT = 0;
+
+    /**
+     * Callback type: Animation callback.  Runs before {@link #CALLBACK_INSETS_ANIMATION}.
+     * @hide
+     */
+    @TestApi
+    public static final int CALLBACK_ANIMATION = 1;
+
+    /**
+     * Callback type: Animation callback to handle inset updates. This is separate from
+     * {@link #CALLBACK_ANIMATION} as we need to "gather" all inset animation updates via
+     * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)} for multiple
+     * ongoing animations but then update the whole view system with a single callback to
+     * {@link View#dispatchWindowInsetsAnimationProgress} that contains all the combined updated
+     * insets.
+     * <p>
+     * Both input and animation may change insets, so we need to run this after these callbacks, but
+     * before traversals.
+     * <p>
+     * Runs before traversals.
+     * @hide
+     */
+    public static final int CALLBACK_INSETS_ANIMATION = 2;
+
+    /**
+     * Callback type: Traversal callback.  Handles layout and draw.  Runs
+     * after all other asynchronous messages have been handled.
+     * @hide
+     */
+    public static final int CALLBACK_TRAVERSAL = 3;
+
+    /**
+     * Callback type: Commit callback.  Handles post-draw operations for the frame.
+     * Runs after traversal completes.  The {@link #getFrameTime() frame time} reported
+     * during this callback may be updated to reflect delays that occurred while
+     * traversals were in progress in case heavy layout operations caused some frames
+     * to be skipped.  The frame time reported during this callback provides a better
+     * estimate of the start time of the frame in which animations (and other updates
+     * to the view hierarchy state) actually took effect.
+     * @hide
+     */
+    public static final int CALLBACK_COMMIT = 4;
+
+    private static final int CALLBACK_LAST = CALLBACK_COMMIT;
+
+    private Choreographer(Looper looper, int vsyncSource) {
+        mLooper = looper;
+        mHandler = new FrameHandler(looper);
+        mDisplayEventReceiver = USE_VSYNC
+                ? new FrameDisplayEventReceiver(looper, vsyncSource)
+                : null;
+        mLastFrameTimeNanos = Long.MIN_VALUE;
+
+        mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
+
+        mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
+        for (int i = 0; i <= CALLBACK_LAST; i++) {
+            mCallbackQueues[i] = new CallbackQueue();
+        }
+        // b/68769804: For low FPS experiments.
+        setFPSDivisor(SystemProperties.getInt(ThreadedRenderer.DEBUG_FPS_DIVISOR, 1));
+    }
+
+    private static float getRefreshRate() {
+        DisplayInfo di = DisplayManagerGlobal.getInstance().getDisplayInfo(
+                Display.DEFAULT_DISPLAY);
+        return di.getRefreshRate();
+    }
+
+    /**
+     * Gets the choreographer for the calling thread.  Must be called from
+     * a thread that already has a {@link android.os.Looper} associated with it.
+     *
+     * @return The choreographer for this thread.
+     * @throws IllegalStateException if the thread does not have a looper.
+     */
+    public static Choreographer getInstance() {
+        return sThreadInstance.get();
+    }
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static Choreographer getSfInstance() {
+        return sSfThreadInstance.get();
+    }
+
+    /**
+     * @return The Choreographer of the main thread, if it exists, or {@code null} otherwise.
+     * @hide
+     */
+    public static Choreographer getMainThreadInstance() {
+        return mMainInstance;
+    }
+
+    /** Destroys the calling thread's choreographer
+     * @hide
+     */
+    public static void releaseInstance() {
+        Choreographer old = sThreadInstance.get();
+        sThreadInstance.remove();
+        old.dispose();
+    }
+
+    private void dispose() {
+        mDisplayEventReceiver.dispose();
+    }
+
+    /**
+     * The amount of time, in milliseconds, between each frame of the animation.
+     * <p>
+     * This is a requested time that the animation will attempt to honor, but the actual delay
+     * between frames may be different, depending on system load and capabilities. This is a static
+     * function because the same delay will be applied to all animations, since they are all
+     * run off of a single timing loop.
+     * </p><p>
+     * The frame delay may be ignored when the animation system uses an external timing
+     * source, such as the display refresh rate (vsync), to govern animations.
+     * </p>
+     *
+     * @return the requested time between frames, in milliseconds
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @TestApi
+    public static long getFrameDelay() {
+        return sFrameDelay;
+    }
+
+    /**
+     * The amount of time, in milliseconds, between each frame of the animation.
+     * <p>
+     * This is a requested time that the animation will attempt to honor, but the actual delay
+     * between frames may be different, depending on system load and capabilities. This is a static
+     * function because the same delay will be applied to all animations, since they are all
+     * run off of a single timing loop.
+     * </p><p>
+     * The frame delay may be ignored when the animation system uses an external timing
+     * source, such as the display refresh rate (vsync), to govern animations.
+     * </p>
+     *
+     * @param frameDelay the requested time between frames, in milliseconds
+     * @hide
+     */
+    @TestApi
+    public static void setFrameDelay(long frameDelay) {
+        sFrameDelay = frameDelay;
+    }
+
+    /**
+     * Subtracts typical frame delay time from a delay interval in milliseconds.
+     * <p>
+     * This method can be used to compensate for animation delay times that have baked
+     * in assumptions about the frame delay.  For example, it's quite common for code to
+     * assume a 60Hz frame time and bake in a 16ms delay.  When we call
+     * {@link #postAnimationCallbackDelayed} we want to know how long to wait before
+     * posting the animation callback but let the animation timer take care of the remaining
+     * frame delay time.
+     * </p><p>
+     * This method is somewhat conservative about how much of the frame delay it
+     * subtracts.  It uses the same value returned by {@link #getFrameDelay} which by
+     * default is 10ms even though many parts of the system assume 16ms.  Consequently,
+     * we might still wait 6ms before posting an animation callback that we want to run
+     * on the next frame, but this is much better than waiting a whole 16ms and likely
+     * missing the deadline.
+     * </p>
+     *
+     * @param delayMillis The original delay time including an assumed frame delay.
+     * @return The adjusted delay time with the assumed frame delay subtracted out.
+     * @hide
+     */
+    public static long subtractFrameDelay(long delayMillis) {
+        final long frameDelay = sFrameDelay;
+        return delayMillis <= frameDelay ? 0 : delayMillis - frameDelay;
+    }
+
+    /**
+     * @return The refresh rate as the nanoseconds between frames
+     * @hide
+     */
+    public long getFrameIntervalNanos() {
+        synchronized (mLock) {
+            return mLastFrameIntervalNanos;
+        }
+    }
+
+    void dump(String prefix, PrintWriter writer) {
+        String innerPrefix = prefix + "  ";
+        writer.print(prefix); writer.println("Choreographer:");
+        writer.print(innerPrefix); writer.print("mFrameScheduled=");
+                writer.println(mFrameScheduled);
+        writer.print(innerPrefix); writer.print("mLastFrameTime=");
+                writer.println(TimeUtils.formatUptime(mLastFrameTimeNanos / 1000000));
+    }
+
+    /**
+     * Posts a callback to run on the next frame.
+     * <p>
+     * The callback runs once then is automatically removed.
+     * </p>
+     *
+     * @param callbackType The callback type.
+     * @param action The callback action to run during the next frame.
+     * @param token The callback token, or null if none.
+     *
+     * @see #removeCallbacks
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @TestApi
+    public void postCallback(int callbackType, Runnable action, Object token) {
+        postCallbackDelayed(callbackType, action, token, 0);
+    }
+
+    /**
+     * Posts a callback to run on the next frame after the specified delay.
+     * <p>
+     * The callback runs once then is automatically removed.
+     * </p>
+     *
+     * @param callbackType The callback type.
+     * @param action The callback action to run during the next frame after the specified delay.
+     * @param token The callback token, or null if none.
+     * @param delayMillis The delay time in milliseconds.
+     *
+     * @see #removeCallback
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @TestApi
+    public void postCallbackDelayed(int callbackType,
+            Runnable action, Object token, long delayMillis) {
+        if (action == null) {
+            throw new IllegalArgumentException("action must not be null");
+        }
+        if (callbackType < 0 || callbackType > CALLBACK_LAST) {
+            throw new IllegalArgumentException("callbackType is invalid");
+        }
+
+        postCallbackDelayedInternal(callbackType, action, token, delayMillis);
+    }
+
+    private void postCallbackDelayedInternal(int callbackType,
+            Object action, Object token, long delayMillis) {
+        if (DEBUG_FRAMES) {
+            Log.d(TAG, "PostCallback: type=" + callbackType
+                    + ", action=" + action + ", token=" + token
+                    + ", delayMillis=" + delayMillis);
+        }
+
+        synchronized (mLock) {
+            final long now = SystemClock.uptimeMillis();
+            final long dueTime = now + delayMillis;
+            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
+
+            if (dueTime <= now) {
+                scheduleFrameLocked(now);
+            } else {
+                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
+                msg.arg1 = callbackType;
+                msg.setAsynchronous(true);
+                mHandler.sendMessageAtTime(msg, dueTime);
+            }
+        }
+    }
+
+    /**
+     * Removes callbacks that have the specified action and token.
+     *
+     * @param callbackType The callback type.
+     * @param action The action property of the callbacks to remove, or null to remove
+     * callbacks with any action.
+     * @param token The token property of the callbacks to remove, or null to remove
+     * callbacks with any token.
+     *
+     * @see #postCallback
+     * @see #postCallbackDelayed
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @TestApi
+    public void removeCallbacks(int callbackType, Runnable action, Object token) {
+        if (callbackType < 0 || callbackType > CALLBACK_LAST) {
+            throw new IllegalArgumentException("callbackType is invalid");
+        }
+
+        removeCallbacksInternal(callbackType, action, token);
+    }
+
+    private void removeCallbacksInternal(int callbackType, Object action, Object token) {
+        if (DEBUG_FRAMES) {
+            Log.d(TAG, "RemoveCallbacks: type=" + callbackType
+                    + ", action=" + action + ", token=" + token);
+        }
+
+        synchronized (mLock) {
+            mCallbackQueues[callbackType].removeCallbacksLocked(action, token);
+            if (action != null && token == null) {
+                mHandler.removeMessages(MSG_DO_SCHEDULE_CALLBACK, action);
+            }
+        }
+    }
+
+    /**
+     * Posts a frame callback to run on the next frame.
+     * <p>
+     * The callback runs once then is automatically removed.
+     * </p>
+     *
+     * @param callback The frame callback to run during the next frame.
+     *
+     * @see #postFrameCallbackDelayed
+     * @see #removeFrameCallback
+     */
+    public void postFrameCallback(FrameCallback callback) {
+        postFrameCallbackDelayed(callback, 0);
+    }
+
+    /**
+     * Posts a frame callback to run on the next frame after the specified delay.
+     * <p>
+     * The callback runs once then is automatically removed.
+     * </p>
+     *
+     * @param callback The frame callback to run during the next frame.
+     * @param delayMillis The delay time in milliseconds.
+     *
+     * @see #postFrameCallback
+     * @see #removeFrameCallback
+     */
+    public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
+        if (callback == null) {
+            throw new IllegalArgumentException("callback must not be null");
+        }
+
+        postCallbackDelayedInternal(CALLBACK_ANIMATION,
+                callback, FRAME_CALLBACK_TOKEN, delayMillis);
+    }
+
+    /**
+     * Removes a previously posted frame callback.
+     *
+     * @param callback The frame callback to remove.
+     *
+     * @see #postFrameCallback
+     * @see #postFrameCallbackDelayed
+     */
+    public void removeFrameCallback(FrameCallback callback) {
+        if (callback == null) {
+            throw new IllegalArgumentException("callback must not be null");
+        }
+
+        removeCallbacksInternal(CALLBACK_ANIMATION, callback, FRAME_CALLBACK_TOKEN);
+    }
+
+    /**
+     * Gets the time when the current frame started.
+     * <p>
+     * This method provides the time in milliseconds when the frame started being rendered.
+     * The frame time provides a stable time base for synchronizing animations
+     * and drawing.  It should be used instead of {@link SystemClock#uptimeMillis()}
+     * or {@link System#nanoTime()} for animations and drawing in the UI.  Using the frame
+     * time helps to reduce inter-frame jitter because the frame time is fixed at the time
+     * the frame was scheduled to start, regardless of when the animations or drawing
+     * callback actually runs.  All callbacks that run as part of rendering a frame will
+     * observe the same frame time so using the frame time also helps to synchronize effects
+     * that are performed by different callbacks.
+     * </p><p>
+     * Please note that the framework already takes care to process animations and
+     * drawing using the frame time as a stable time base.  Most applications should
+     * not need to use the frame time information directly.
+     * </p><p>
+     * This method should only be called from within a callback.
+     * </p>
+     *
+     * @return The frame start time, in the {@link SystemClock#uptimeMillis()} time base.
+     *
+     * @throws IllegalStateException if no frame is in progress.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public long getFrameTime() {
+        return getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
+    }
+
+    /**
+     * Same as {@link #getFrameTime()} but with nanosecond precision.
+     *
+     * @return The frame start time, in the {@link System#nanoTime()} time base.
+     *
+     * @throws IllegalStateException if no frame is in progress.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public long getFrameTimeNanos() {
+        synchronized (mLock) {
+            if (!mCallbacksRunning) {
+                throw new IllegalStateException("This method must only be called as "
+                        + "part of a callback while a frame is in progress.");
+            }
+            return USE_FRAME_TIME ? mLastFrameTimeNanos : System.nanoTime();
+        }
+    }
+
+    /**
+     * Like {@link #getLastFrameTimeNanos}, but always returns the last frame time, not matter
+     * whether callbacks are currently running.
+     * @return The frame start time of the last frame, in the {@link System#nanoTime()} time base.
+     * @hide
+     */
+    public long getLastFrameTimeNanos() {
+        synchronized (mLock) {
+            return USE_FRAME_TIME ? mLastFrameTimeNanos : System.nanoTime();
+        }
+    }
+
+    private void scheduleFrameLocked(long now) {
+        if (!mFrameScheduled) {
+            mFrameScheduled = true;
+            if (USE_VSYNC) {
+                if (DEBUG_FRAMES) {
+                    Log.d(TAG, "Scheduling next frame on vsync.");
+                }
+
+                // If running on the Looper thread, then schedule the vsync immediately,
+                // otherwise post a message to schedule the vsync from the UI thread
+                // as soon as possible.
+                if (isRunningOnLooperThreadLocked()) {
+                    scheduleVsyncLocked();
+                } else {
+                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
+                    msg.setAsynchronous(true);
+                    mHandler.sendMessageAtFrontOfQueue(msg);
+                }
+            } else {
+                final long nextFrameTime = Math.max(
+                        mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
+                if (DEBUG_FRAMES) {
+                    Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
+                }
+                Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
+                msg.setAsynchronous(true);
+                mHandler.sendMessageAtTime(msg, nextFrameTime);
+            }
+        }
+    }
+
+    /**
+     * Returns the vsync id of the last frame callback. Client are expected to call
+     * this function from their frame callback function to get the vsyncId and pass
+     * it together with a buffer or transaction to the Surface Composer. Calling
+     * this function from anywhere else will return an undefined value.
+     *
+     * @hide
+     */
+    public long getVsyncId() {
+        return mLastVsyncEventData.id;
+    }
+
+    /**
+     * Returns the frame deadline in {@link System#nanoTime()} timebase that it is allotted for the
+     * frame to be completed. Client are expected to call this function from their frame callback
+     * function. Calling this function from anywhere else will return an undefined value.
+     *
+     * @hide
+     */
+    public long getFrameDeadline() {
+        return mLastVsyncEventData.frameDeadline;
+    }
+
+    void setFPSDivisor(int divisor) {
+        if (divisor <= 0) divisor = 1;
+        mFPSDivisor = divisor;
+        ThreadedRenderer.setFPSDivisor(divisor);
+    }
+
+    private void traceMessage(String msg) {
+        Trace.traceBegin(Trace.TRACE_TAG_VIEW, msg);
+        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+    }
+
+    void doFrame(long frameTimeNanos, int frame,
+            DisplayEventReceiver.VsyncEventData vsyncEventData) {
+        final long startNanos;
+        final long frameIntervalNanos = vsyncEventData.frameInterval;
+        try {
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+                        "Choreographer#doFrame " + vsyncEventData.id);
+            }
+            synchronized (mLock) {
+                if (!mFrameScheduled) {
+                    traceMessage("Frame not scheduled");
+                    return; // no work to do
+                }
+
+                if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {
+                    mDebugPrintNextFrameTimeDelta = false;
+                    Log.d(TAG, "Frame time delta: "
+                            + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
+                }
+
+                long intendedFrameTimeNanos = frameTimeNanos;
+                startNanos = System.nanoTime();
+                final long jitterNanos = startNanos - frameTimeNanos;
+                if (jitterNanos >= frameIntervalNanos) {
+                    final long skippedFrames = jitterNanos / frameIntervalNanos;
+                    if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
+                        Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
+                                + "The application may be doing too much work on its main thread.");
+                    }
+                    final long lastFrameOffset = jitterNanos % frameIntervalNanos;
+                    if (DEBUG_JANK) {
+                        Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
+                                + "which is more than the frame interval of "
+                                + (frameIntervalNanos * 0.000001f) + " ms!  "
+                                + "Skipping " + skippedFrames + " frames and setting frame "
+                                + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
+                    }
+                    frameTimeNanos = startNanos - lastFrameOffset;
+                }
+
+                if (frameTimeNanos < mLastFrameTimeNanos) {
+                    if (DEBUG_JANK) {
+                        Log.d(TAG, "Frame time appears to be going backwards.  May be due to a "
+                                + "previously skipped frame.  Waiting for next vsync.");
+                    }
+                    traceMessage("Frame time goes backward");
+                    scheduleVsyncLocked();
+                    return;
+                }
+
+                if (mFPSDivisor > 1) {
+                    long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
+                    if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
+                        traceMessage("Frame skipped due to FPSDivisor");
+                        scheduleVsyncLocked();
+                        return;
+                    }
+                }
+
+                mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
+                        vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval);
+                mFrameScheduled = false;
+                mLastFrameTimeNanos = frameTimeNanos;
+                mLastFrameIntervalNanos = frameIntervalNanos;
+                mLastVsyncEventData = vsyncEventData;
+            }
+
+            AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
+
+            mFrameInfo.markInputHandlingStart();
+            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos, frameIntervalNanos);
+
+            mFrameInfo.markAnimationsStart();
+            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos, frameIntervalNanos);
+            doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos,
+                    frameIntervalNanos);
+
+            mFrameInfo.markPerformTraversalsStart();
+            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos, frameIntervalNanos);
+
+            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos, frameIntervalNanos);
+        } finally {
+            AnimationUtils.unlockAnimationClock();
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        }
+
+        if (DEBUG_FRAMES) {
+            final long endNanos = System.nanoTime();
+            Log.d(TAG, "Frame " + frame + ": Finished, took "
+                    + (endNanos - startNanos) * 0.000001f + " ms, latency "
+                    + (startNanos - frameTimeNanos) * 0.000001f + " ms.");
+        }
+    }
+
+    void doCallbacks(int callbackType, long frameTimeNanos, long frameIntervalNanos) {
+        CallbackRecord callbacks;
+        synchronized (mLock) {
+            // We use "now" to determine when callbacks become due because it's possible
+            // for earlier processing phases in a frame to post callbacks that should run
+            // in a following phase, such as an input event that causes an animation to start.
+            final long now = System.nanoTime();
+            callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
+                    now / TimeUtils.NANOS_PER_MS);
+            if (callbacks == null) {
+                return;
+            }
+            mCallbacksRunning = true;
+
+            // Update the frame time if necessary when committing the frame.
+            // We only update the frame time if we are more than 2 frames late reaching
+            // the commit phase.  This ensures that the frame time which is observed by the
+            // callbacks will always increase from one frame to the next and never repeat.
+            // We never want the next frame's starting frame time to end up being less than
+            // or equal to the previous frame's commit frame time.  Keep in mind that the
+            // next frame has most likely already been scheduled by now so we play it
+            // safe by ensuring the commit time is always at least one frame behind.
+            if (callbackType == Choreographer.CALLBACK_COMMIT) {
+                final long jitterNanos = now - frameTimeNanos;
+                Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos);
+                if (jitterNanos >= 2 * frameIntervalNanos) {
+                    final long lastFrameOffset = jitterNanos % frameIntervalNanos
+                            + frameIntervalNanos;
+                    if (DEBUG_JANK) {
+                        Log.d(TAG, "Commit callback delayed by " + (jitterNanos * 0.000001f)
+                                + " ms which is more than twice the frame interval of "
+                                + (frameIntervalNanos * 0.000001f) + " ms!  "
+                                + "Setting frame time to " + (lastFrameOffset * 0.000001f)
+                                + " ms in the past.");
+                        mDebugPrintNextFrameTimeDelta = true;
+                    }
+                    frameTimeNanos = now - lastFrameOffset;
+                    mLastFrameTimeNanos = frameTimeNanos;
+                }
+            }
+        }
+        try {
+            Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
+            for (CallbackRecord c = callbacks; c != null; c = c.next) {
+                if (DEBUG_FRAMES) {
+                    Log.d(TAG, "RunCallback: type=" + callbackType
+                            + ", action=" + c.action + ", token=" + c.token
+                            + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
+                }
+                c.run(frameTimeNanos);
+            }
+        } finally {
+            synchronized (mLock) {
+                mCallbacksRunning = false;
+                do {
+                    final CallbackRecord next = callbacks.next;
+                    recycleCallbackLocked(callbacks);
+                    callbacks = next;
+                } while (callbacks != null);
+            }
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        }
+    }
+
+    void doScheduleVsync() {
+        synchronized (mLock) {
+            if (mFrameScheduled) {
+                scheduleVsyncLocked();
+            }
+        }
+    }
+
+    void doScheduleCallback(int callbackType) {
+        synchronized (mLock) {
+            if (!mFrameScheduled) {
+                final long now = SystemClock.uptimeMillis();
+                if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
+                    scheduleFrameLocked(now);
+                }
+            }
+        }
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private void scheduleVsyncLocked() {
+        try {
+            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#scheduleVsyncLocked");
+            mDisplayEventReceiver.scheduleVsync();
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        }
+    }
+
+    private boolean isRunningOnLooperThreadLocked() {
+        return Looper.myLooper() == mLooper;
+    }
+
+    private CallbackRecord obtainCallbackLocked(long dueTime, Object action, Object token) {
+        CallbackRecord callback = mCallbackPool;
+        if (callback == null) {
+            callback = new CallbackRecord();
+        } else {
+            mCallbackPool = callback.next;
+            callback.next = null;
+        }
+        callback.dueTime = dueTime;
+        callback.action = action;
+        callback.token = token;
+        return callback;
+    }
+
+    private void recycleCallbackLocked(CallbackRecord callback) {
+        callback.action = null;
+        callback.token = null;
+        callback.next = mCallbackPool;
+        mCallbackPool = callback;
+    }
+
+    /**
+     * Implement this interface to receive a callback when a new display frame is
+     * being rendered.  The callback is invoked on the {@link Looper} thread to
+     * which the {@link Choreographer} is attached.
+     */
+    public interface FrameCallback {
+        /**
+         * Called when a new display frame is being rendered.
+         * <p>
+         * This method provides the time in nanoseconds when the frame started being rendered.
+         * The frame time provides a stable time base for synchronizing animations
+         * and drawing.  It should be used instead of {@link SystemClock#uptimeMillis()}
+         * or {@link System#nanoTime()} for animations and drawing in the UI.  Using the frame
+         * time helps to reduce inter-frame jitter because the frame time is fixed at the time
+         * the frame was scheduled to start, regardless of when the animations or drawing
+         * callback actually runs.  All callbacks that run as part of rendering a frame will
+         * observe the same frame time so using the frame time also helps to synchronize effects
+         * that are performed by different callbacks.
+         * </p><p>
+         * Please note that the framework already takes care to process animations and
+         * drawing using the frame time as a stable time base.  Most applications should
+         * not need to use the frame time information directly.
+         * </p>
+         *
+         * @param frameTimeNanos The time in nanoseconds when the frame started being rendered,
+         * in the {@link System#nanoTime()} timebase.  Divide this value by {@code 1000000}
+         * to convert it to the {@link SystemClock#uptimeMillis()} time base.
+         */
+        public void doFrame(long frameTimeNanos);
+    }
+
+    private final class FrameHandler extends Handler {
+        public FrameHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_DO_FRAME:
+                    doFrame(System.nanoTime(), 0, new DisplayEventReceiver.VsyncEventData());
+                    break;
+                case MSG_DO_SCHEDULE_VSYNC:
+                    doScheduleVsync();
+                    break;
+                case MSG_DO_SCHEDULE_CALLBACK:
+                    doScheduleCallback(msg.arg1);
+                    break;
+            }
+        }
+    }
+
+    private final class FrameDisplayEventReceiver extends DisplayEventReceiver
+            implements Runnable {
+        private boolean mHavePendingVsync;
+        private long mTimestampNanos;
+        private int mFrame;
+        private VsyncEventData mLastVsyncEventData = new VsyncEventData();
+
+        public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
+            super(looper, vsyncSource, 0);
+        }
+
+        // TODO(b/116025192): physicalDisplayId is ignored because SF only emits VSYNC events for
+        // the internal display and DisplayEventReceiver#scheduleVsync only allows requesting VSYNC
+        // for the internal display implicitly.
+        @Override
+        public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
+                VsyncEventData vsyncEventData) {
+            try {
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                    Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+                            "Choreographer#onVsync " + vsyncEventData.id);
+                }
+                // Post the vsync event to the Handler.
+                // The idea is to prevent incoming vsync events from completely starving
+                // the message queue.  If there are no messages in the queue with timestamps
+                // earlier than the frame time, then the vsync event will be processed immediately.
+                // Otherwise, messages that predate the vsync event will be handled first.
+                long now = System.nanoTime();
+                if (timestampNanos > now) {
+                    Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
+                            + " ms in the future!  Check that graphics HAL is generating vsync "
+                            + "timestamps using the correct timebase.");
+                    timestampNanos = now;
+                }
+
+                if (mHavePendingVsync) {
+                    Log.w(TAG, "Already have a pending vsync event.  There should only be "
+                            + "one at a time.");
+                } else {
+                    mHavePendingVsync = true;
+                }
+
+                mTimestampNanos = timestampNanos;
+                mFrame = frame;
+                mLastVsyncEventData = vsyncEventData;
+                Message msg = Message.obtain(mHandler, this);
+                msg.setAsynchronous(true);
+                mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            }
+        }
+
+        @Override
+        public void run() {
+            mHavePendingVsync = false;
+            doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
+        }
+    }
+
+    private static final class CallbackRecord {
+        public CallbackRecord next;
+        public long dueTime;
+        public Object action; // Runnable or FrameCallback
+        public Object token;
+
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        public void run(long frameTimeNanos) {
+            if (token == FRAME_CALLBACK_TOKEN) {
+                ((FrameCallback)action).doFrame(frameTimeNanos);
+            } else {
+                ((Runnable)action).run();
+            }
+        }
+    }
+
+    private final class CallbackQueue {
+        private CallbackRecord mHead;
+
+        public boolean hasDueCallbacksLocked(long now) {
+            return mHead != null && mHead.dueTime <= now;
+        }
+
+        public CallbackRecord extractDueCallbacksLocked(long now) {
+            CallbackRecord callbacks = mHead;
+            if (callbacks == null || callbacks.dueTime > now) {
+                return null;
+            }
+
+            CallbackRecord last = callbacks;
+            CallbackRecord next = last.next;
+            while (next != null) {
+                if (next.dueTime > now) {
+                    last.next = null;
+                    break;
+                }
+                last = next;
+                next = next.next;
+            }
+            mHead = next;
+            return callbacks;
+        }
+
+        @UnsupportedAppUsage
+        public void addCallbackLocked(long dueTime, Object action, Object token) {
+            CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
+            CallbackRecord entry = mHead;
+            if (entry == null) {
+                mHead = callback;
+                return;
+            }
+            if (dueTime < entry.dueTime) {
+                callback.next = entry;
+                mHead = callback;
+                return;
+            }
+            while (entry.next != null) {
+                if (dueTime < entry.next.dueTime) {
+                    callback.next = entry.next;
+                    break;
+                }
+                entry = entry.next;
+            }
+            entry.next = callback;
+        }
+
+        public void removeCallbacksLocked(Object action, Object token) {
+            CallbackRecord predecessor = null;
+            for (CallbackRecord callback = mHead; callback != null;) {
+                final CallbackRecord next = callback.next;
+                if ((action == null || callback.action == action)
+                        && (token == null || callback.token == token)) {
+                    if (predecessor != null) {
+                        predecessor.next = next;
+                    } else {
+                        mHead = next;
+                    }
+                    recycleCallbackLocked(callback);
+                } else {
+                    predecessor = callback;
+                }
+                callback = next;
+            }
+        }
+    }
+}
diff --git a/android/view/Choreographer_Delegate.java b/android/view/Choreographer_Delegate.java
new file mode 100644
index 0000000..3a8839f
--- /dev/null
+++ b/android/view/Choreographer_Delegate.java
@@ -0,0 +1,95 @@
+/*
+ * 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.view;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.animation.AnimationHandler;
+import android.util.TimeUtils;
+import android.view.animation.AnimationUtils;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link Choreographer}
+ *
+ * Through the layoutlib_create tool, the original  methods of Choreographer have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ */
+public class Choreographer_Delegate {
+    private static final AtomicReference<Choreographer> mInstance = new AtomicReference<Choreographer>();
+
+    private static final int MS_16 = 16000000;
+
+    @LayoutlibDelegate
+    public static Choreographer getInstance() {
+        if (mInstance.get() == null) {
+            mInstance.compareAndSet(null, Choreographer.getInstance_Original());
+        }
+
+        return mInstance.get();
+    }
+
+    @LayoutlibDelegate
+    public static float getRefreshRate() {
+        return 60.f;
+    }
+
+    @LayoutlibDelegate
+    static void scheduleVsyncLocked(Choreographer thisChoreographer) {
+        // do nothing
+    }
+
+    public static void doFrame(long frameTimeNanos) {
+        Choreographer thisChoreographer = Choreographer.getInstance();
+
+        AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
+
+        try {
+            thisChoreographer.mLastFrameTimeNanos = frameTimeNanos - thisChoreographer.getFrameIntervalNanos();
+            thisChoreographer.mFrameInfo.markInputHandlingStart();
+            thisChoreographer.doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos, MS_16);
+
+            thisChoreographer.mFrameInfo.markAnimationsStart();
+            thisChoreographer.doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos, MS_16);
+
+            thisChoreographer.mFrameInfo.markPerformTraversalsStart();
+            thisChoreographer.doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos, MS_16);
+
+            thisChoreographer.doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos, MS_16);
+        } finally {
+            AnimationUtils.unlockAnimationClock();
+        }
+    }
+
+    public static void clearFrames() {
+        Choreographer thisChoreographer = Choreographer.getInstance();
+
+        thisChoreographer.removeCallbacks(Choreographer.CALLBACK_INPUT, null, null);
+        thisChoreographer.removeCallbacks(Choreographer.CALLBACK_ANIMATION, null, null);
+        thisChoreographer.removeCallbacks(Choreographer.CALLBACK_TRAVERSAL, null, null);
+        thisChoreographer.removeCallbacks(Choreographer.CALLBACK_COMMIT, null, null);
+
+        // Release animation handler instance since it holds references to the callbacks
+        AnimationHandler.sAnimatorHandler.set(null);
+    }
+
+    public static void dispose() {
+        clearFrames();
+        Choreographer.releaseInstance();
+    }
+}
diff --git a/android/view/CollapsibleActionView.java b/android/view/CollapsibleActionView.java
new file mode 100644
index 0000000..ab2365e
--- /dev/null
+++ b/android/view/CollapsibleActionView.java
@@ -0,0 +1,41 @@
+/*
+ * 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.view;
+
+import android.view.MenuItem.OnActionExpandListener;
+
+/**
+ * When a {@link View} implements this interface it will receive callbacks
+ * when expanded or collapsed as an action view alongside the optional,
+ * app-specified callbacks to {@link OnActionExpandListener}.
+ *
+ * <p>See {@link MenuItem} for more information about action views.
+ * See {@link android.app.ActionBar} for more information about the action bar.
+ */
+public interface CollapsibleActionView {
+    /**
+     * Called when this view is expanded as an action view.
+     * See {@link MenuItem#expandActionView()}.
+     */
+    public void onActionViewExpanded();
+
+    /**
+     * Called when this view is collapsed as an action view.
+     * See {@link MenuItem#collapseActionView()}.
+     */
+    public void onActionViewCollapsed();
+}
diff --git a/android/view/CompositionSamplingListener.java b/android/view/CompositionSamplingListener.java
new file mode 100644
index 0000000..677a559
--- /dev/null
+++ b/android/view/CompositionSamplingListener.java
@@ -0,0 +1,102 @@
+/*
+ * 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.view;
+
+import android.graphics.Rect;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Listener for sampling the result of the screen composition.
+ * {@hide}
+ */
+public abstract class CompositionSamplingListener {
+
+    private long mNativeListener;
+    private final Executor mExecutor;
+
+    public CompositionSamplingListener(Executor executor) {
+        mExecutor = executor;
+        mNativeListener = nativeCreate(this);
+    }
+
+    public void destroy() {
+        if (mNativeListener == 0) {
+            return;
+        }
+        unregister(this);
+        nativeDestroy(mNativeListener);
+        mNativeListener = 0;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            destroy();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Reports a luma sample from the registered region.
+     */
+    public abstract void onSampleCollected(float medianLuma);
+
+    /**
+     * Registers a sampling listener.
+     */
+    public static void register(CompositionSamplingListener listener,
+            int displayId, SurfaceControl stopLayer, Rect samplingArea) {
+        if (listener.mNativeListener == 0) {
+            return;
+        }
+        Preconditions.checkArgument(displayId == Display.DEFAULT_DISPLAY,
+                "default display only for now");
+        long nativeStopLayerObject = stopLayer != null ? stopLayer.mNativeObject : 0;
+        nativeRegister(listener.mNativeListener, nativeStopLayerObject, samplingArea.left,
+                samplingArea.top, samplingArea.right, samplingArea.bottom);
+    }
+
+    /**
+     * Unregisters a sampling listener.
+     */
+    public static void unregister(CompositionSamplingListener listener) {
+        if (listener.mNativeListener == 0) {
+            return;
+        }
+        nativeUnregister(listener.mNativeListener);
+    }
+
+    /**
+     * Dispatch the collected sample.
+     *
+     * Called from native code on a binder thread.
+     */
+    private static void dispatchOnSampleCollected(CompositionSamplingListener listener,
+            float medianLuma) {
+        listener.mExecutor.execute(() -> listener.onSampleCollected(medianLuma));
+    }
+
+    private static native long nativeCreate(CompositionSamplingListener thiz);
+    private static native void nativeDestroy(long ptr);
+    private static native void nativeRegister(long ptr, long stopLayerObject,
+            int samplingAreaLeft, int top, int right, int bottom);
+    private static native void nativeUnregister(long ptr);
+}
diff --git a/android/view/ContentInfo.java b/android/view/ContentInfo.java
new file mode 100644
index 0000000..b1b0e6b
--- /dev/null
+++ b/android/view/ContentInfo.java
@@ -0,0 +1,496 @@
+/*
+ * 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.view;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Pair;
+import android.view.inputmethod.InputContentInfo;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Objects;
+import java.util.function.Predicate;
+
+/**
+ * Holds all the relevant data for a request to {@link View#performReceiveContent}.
+ */
+public final class ContentInfo implements Parcelable {
+
+    /**
+     * Specifies the UI through which content is being inserted. Future versions of Android may
+     * support additional values.
+     *
+     * @hide
+     */
+    @IntDef(prefix = {"SOURCE_"}, value = {SOURCE_APP, SOURCE_CLIPBOARD, SOURCE_INPUT_METHOD,
+            SOURCE_DRAG_AND_DROP, SOURCE_AUTOFILL, SOURCE_PROCESS_TEXT})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Source {}
+
+    /**
+     * Specifies that the operation was triggered by the app that contains the target view.
+     */
+    public static final int SOURCE_APP = 0;
+
+    /**
+     * Specifies that the operation was triggered by a paste from the clipboard (e.g. "Paste" or
+     * "Paste as plain text" action in the insertion/selection menu).
+     */
+    public static final int SOURCE_CLIPBOARD = 1;
+
+    /**
+     * Specifies that the operation was triggered from the soft keyboard (also known as input
+     * method editor or IME). See https://developer.android.com/guide/topics/text/image-keyboard
+     * for more info.
+     */
+    public static final int SOURCE_INPUT_METHOD = 2;
+
+    /**
+     * Specifies that the operation was triggered by the drag/drop framework. See
+     * https://developer.android.com/guide/topics/ui/drag-drop for more info.
+     */
+    public static final int SOURCE_DRAG_AND_DROP = 3;
+
+    /**
+     * Specifies that the operation was triggered by the autofill framework. See
+     * https://developer.android.com/guide/topics/text/autofill for more info.
+     */
+    public static final int SOURCE_AUTOFILL = 4;
+
+    /**
+     * Specifies that the operation was triggered by a result from a
+     * {@link android.content.Intent#ACTION_PROCESS_TEXT PROCESS_TEXT} action in the selection
+     * menu.
+     */
+    public static final int SOURCE_PROCESS_TEXT = 5;
+
+    /**
+     * Returns the symbolic name of the given source.
+     *
+     * @hide
+     */
+    static String sourceToString(@Source int source) {
+        switch (source) {
+            case SOURCE_APP: return "SOURCE_APP";
+            case SOURCE_CLIPBOARD: return "SOURCE_CLIPBOARD";
+            case SOURCE_INPUT_METHOD: return "SOURCE_INPUT_METHOD";
+            case SOURCE_DRAG_AND_DROP: return "SOURCE_DRAG_AND_DROP";
+            case SOURCE_AUTOFILL: return "SOURCE_AUTOFILL";
+            case SOURCE_PROCESS_TEXT: return "SOURCE_PROCESS_TEXT";
+        }
+        return String.valueOf(source);
+    }
+
+    /**
+     * Flags to configure the insertion behavior.
+     *
+     * @hide
+     */
+    @IntDef(flag = true, prefix = {"FLAG_"}, value = {FLAG_CONVERT_TO_PLAIN_TEXT})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Flags {}
+
+    /**
+     * Flag requesting that the content should be converted to plain text prior to inserting.
+     */
+    public static final int FLAG_CONVERT_TO_PLAIN_TEXT = 1 << 0;
+
+    /**
+     * Returns the symbolic names of the set flags or {@code "0"} if no flags are set.
+     *
+     * @hide
+     */
+    static String flagsToString(@Flags int flags) {
+        if ((flags & FLAG_CONVERT_TO_PLAIN_TEXT) != 0) {
+            return "FLAG_CONVERT_TO_PLAIN_TEXT";
+        }
+        return String.valueOf(flags);
+    }
+
+    @NonNull
+    private final ClipData mClip;
+    @Source
+    private final int mSource;
+    @Flags
+    private final int mFlags;
+    @Nullable
+    private final Uri mLinkUri;
+    @Nullable
+    private final Bundle mExtras;
+    @Nullable
+    private final InputContentInfo mInputContentInfo;
+    @Nullable
+    private final DragAndDropPermissions mDragAndDropPermissions;
+
+    private ContentInfo(Builder b) {
+        this.mClip = Objects.requireNonNull(b.mClip);
+        this.mSource = Preconditions.checkArgumentInRange(b.mSource, 0, SOURCE_PROCESS_TEXT,
+                "source");
+        this.mFlags = Preconditions.checkFlagsArgument(b.mFlags, FLAG_CONVERT_TO_PLAIN_TEXT);
+        this.mLinkUri = b.mLinkUri;
+        this.mExtras = b.mExtras;
+        this.mInputContentInfo = b.mInputContentInfo;
+        this.mDragAndDropPermissions = b.mDragAndDropPermissions;
+    }
+
+    /**
+     * If the content came from a source that supports proactive release of URI permissions
+     * (e.g. IME), releases permissions; otherwise a no-op.
+     *
+     * @hide
+     */
+    @TestApi
+    public void releasePermissions() {
+        if (mInputContentInfo != null) {
+            mInputContentInfo.releasePermission();
+        }
+        if (mDragAndDropPermissions != null) {
+            mDragAndDropPermissions.release();
+        }
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "ContentInfo{"
+                + "clip=" + mClip
+                + ", source=" + sourceToString(mSource)
+                + ", flags=" + flagsToString(mFlags)
+                + ", linkUri=" + mLinkUri
+                + ", extras=" + mExtras
+                + "}";
+    }
+
+    /**
+     * The data to be inserted.
+     */
+    @NonNull
+    public ClipData getClip() {
+        return mClip;
+    }
+
+    /**
+     * The source of the operation. See {@code SOURCE_} constants. Future versions of Android
+     * may pass additional values.
+     */
+    @Source
+    public int getSource() {
+        return mSource;
+    }
+
+    /**
+     * Optional flags that control the insertion behavior. See {@code FLAG_} constants.
+     */
+    @Flags
+    public int getFlags() {
+        return mFlags;
+    }
+
+    /**
+     * Optional http/https URI for the content that may be provided by the IME. This is only
+     * populated if the source is {@link #SOURCE_INPUT_METHOD} and if a non-empty
+     * {@link android.view.inputmethod.InputContentInfo#getLinkUri linkUri} was passed by the
+     * IME.
+     */
+    @Nullable
+    public Uri getLinkUri() {
+        return mLinkUri;
+    }
+
+    /**
+     * Optional additional metadata. If the source is {@link #SOURCE_INPUT_METHOD}, this will
+     * include the {@link android.view.inputmethod.InputConnection#commitContent opts} passed by
+     * the IME.
+     */
+    @Nullable
+    @SuppressLint("NullableCollection")
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    /**
+     * Partitions this content based on the given predicate.
+     *
+     * <p>This function classifies the content and organizes it into a pair, grouping the items
+     * that matched vs didn't match the predicate.
+     *
+     * <p>Except for the {@link ClipData} items, the returned objects will contain all the same
+     * metadata as this {@link ContentInfo}.
+     *
+     * @param itemPredicate The predicate to test each {@link ClipData.Item} to determine which
+     *                      partition to place it into.
+     * @return A pair containing the partitioned content. The pair's first object will have the
+     * content that matched the predicate, or null if none of the items matched. The pair's
+     * second object will have the content that didn't match the predicate, or null if all of
+     * the items matched.
+     *
+     * @hide
+     */
+    @TestApi
+    @NonNull
+    public Pair<ContentInfo, ContentInfo> partition(
+            @NonNull Predicate<ClipData.Item> itemPredicate) {
+        if (mClip.getItemCount() == 1) {
+            boolean matched = itemPredicate.test(mClip.getItemAt(0));
+            return Pair.create(matched ? this : null, matched ? null : this);
+        }
+        ArrayList<ClipData.Item> acceptedItems = new ArrayList<>();
+        ArrayList<ClipData.Item> remainingItems = new ArrayList<>();
+        for (int i = 0; i < mClip.getItemCount(); i++) {
+            ClipData.Item item = mClip.getItemAt(i);
+            if (itemPredicate.test(item)) {
+                acceptedItems.add(item);
+            } else {
+                remainingItems.add(item);
+            }
+        }
+        if (acceptedItems.isEmpty()) {
+            return Pair.create(null, this);
+        }
+        if (remainingItems.isEmpty()) {
+            return Pair.create(this, null);
+        }
+        ContentInfo accepted = new Builder(this)
+                .setClip(new ClipData(new ClipDescription(mClip.getDescription()), acceptedItems))
+                .build();
+        ContentInfo remaining = new Builder(this)
+                .setClip(new ClipData(new ClipDescription(mClip.getDescription()), remainingItems))
+                .build();
+        return Pair.create(accepted, remaining);
+    }
+
+    /**
+     * Builder for {@link ContentInfo}.
+     */
+    public static final class Builder {
+        @NonNull
+        private ClipData mClip;
+        @Source
+        private int mSource;
+        @Flags
+        private  int mFlags;
+        @Nullable
+        private Uri mLinkUri;
+        @Nullable
+        private Bundle mExtras;
+        @Nullable
+        private InputContentInfo mInputContentInfo;
+        @Nullable
+        private DragAndDropPermissions mDragAndDropPermissions;
+
+        /**
+         * Creates a new builder initialized with the data from the given builder.
+         */
+        public Builder(@NonNull ContentInfo other) {
+            mClip = other.mClip;
+            mSource = other.mSource;
+            mFlags = other.mFlags;
+            mLinkUri = other.mLinkUri;
+            mExtras = other.mExtras;
+            mInputContentInfo = other.mInputContentInfo;
+            mDragAndDropPermissions = other.mDragAndDropPermissions;
+        }
+
+        /**
+         * Creates a new builder.
+         * @param clip   The data to insert.
+         * @param source The source of the operation. See {@code SOURCE_} constants.
+         */
+        public Builder(@NonNull ClipData clip, @Source int source) {
+            mClip = clip;
+            mSource = source;
+        }
+
+        /**
+         * Sets the data to be inserted.
+         * @param clip The data to insert.
+         * @return this builder
+         */
+        @NonNull
+        public Builder setClip(@NonNull ClipData clip) {
+            mClip = clip;
+            return this;
+        }
+
+        /**
+         * Sets the source of the operation.
+         * @param source The source of the operation. See {@code SOURCE_} constants.
+         * @return this builder
+         */
+        @NonNull
+        public Builder setSource(@Source int source) {
+            mSource = source;
+            return this;
+        }
+
+        /**
+         * Sets flags that control content insertion behavior.
+         * @param flags Optional flags to configure the insertion behavior. Use 0 for default
+         *              behavior. See {@code FLAG_} constants.
+         * @return this builder
+         */
+        @NonNull
+        public Builder setFlags(@Flags int flags) {
+            mFlags = flags;
+            return this;
+        }
+
+        /**
+         * Sets the http/https URI for the content. See
+         * {@link android.view.inputmethod.InputContentInfo#getLinkUri} for more info.
+         * @param linkUri Optional http/https URI for the content.
+         * @return this builder
+         */
+        @NonNull
+        public Builder setLinkUri(@Nullable Uri linkUri) {
+            mLinkUri = linkUri;
+            return this;
+        }
+
+        /**
+         * Sets additional metadata.
+         * @param extras Optional bundle with additional metadata.
+         * @return this builder
+         */
+        @NonNull
+        public Builder setExtras(@SuppressLint("NullableCollection") @Nullable Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
+         * Set the {@link InputContentInfo} object if the content is coming from the IME. This can
+         * be used for proactive cleanup of permissions.
+         *
+         * @hide
+         */
+        @TestApi
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public Builder setInputContentInfo(@Nullable InputContentInfo inputContentInfo) {
+            mInputContentInfo = inputContentInfo;
+            return this;
+        }
+
+        /**
+         * Set the {@link DragAndDropPermissions} object if the content is coming via drag-and-drop.
+         * This can be used for proactive cleanup of permissions.
+         *
+         * @hide
+         */
+        @TestApi
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public Builder setDragAndDropPermissions(@Nullable DragAndDropPermissions permissions) {
+            mDragAndDropPermissions = permissions;
+            return this;
+        }
+
+
+        /**
+         * @return A new {@link ContentInfo} instance with the data from this builder.
+         */
+        @NonNull
+        public ContentInfo build() {
+            return new ContentInfo(this);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Writes this object into the given parcel.
+     *
+     * @param dest  The parcel to write into.
+     * @param flags The flags to use for parceling.
+     */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        mClip.writeToParcel(dest, flags);
+        dest.writeInt(mSource);
+        dest.writeInt(mFlags);
+        Uri.writeToParcel(dest, mLinkUri);
+        dest.writeBundle(mExtras);
+        if (mInputContentInfo == null) {
+            dest.writeInt(0);
+        } else {
+            dest.writeInt(1);
+            mInputContentInfo.writeToParcel(dest, flags);
+        }
+        if (mDragAndDropPermissions == null) {
+            dest.writeInt(0);
+        } else {
+            dest.writeInt(1);
+            mDragAndDropPermissions.writeToParcel(dest, flags);
+        }
+    }
+
+    /**
+     * Creates {@link ContentInfo} instances from parcels.
+     */
+    @NonNull
+    public static final Parcelable.Creator<ContentInfo> CREATOR =
+            new Parcelable.Creator<ContentInfo>() {
+        @Override
+        public ContentInfo createFromParcel(Parcel parcel) {
+            ClipData clip = ClipData.CREATOR.createFromParcel(parcel);
+            int source = parcel.readInt();
+            int flags = parcel.readInt();
+            Uri linkUri = Uri.CREATOR.createFromParcel(parcel);
+            Bundle extras = parcel.readBundle();
+            InputContentInfo inputContentInfo = null;
+            if (parcel.readInt() != 0) {
+                inputContentInfo = InputContentInfo.CREATOR.createFromParcel(parcel);
+            }
+            DragAndDropPermissions dragAndDropPermissions = null;
+            if (parcel.readInt() != 0) {
+                dragAndDropPermissions = DragAndDropPermissions.CREATOR.createFromParcel(parcel);
+            }
+            return new ContentInfo.Builder(clip, source)
+                    .setFlags(flags)
+                    .setLinkUri(linkUri)
+                    .setExtras(extras)
+                    .setInputContentInfo(inputContentInfo)
+                    .setDragAndDropPermissions(dragAndDropPermissions)
+                    .build();
+        }
+
+        @Override
+        public ContentInfo[] newArray(int size) {
+            return new ContentInfo[size];
+        }
+    };
+}
diff --git a/android/view/ContextMenu.java b/android/view/ContextMenu.java
new file mode 100644
index 0000000..85fe421
--- /dev/null
+++ b/android/view/ContextMenu.java
@@ -0,0 +1,99 @@
+/*
+ * 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.view;
+
+import android.annotation.DrawableRes;
+import android.annotation.StringRes;
+import android.app.Activity;
+import android.graphics.drawable.Drawable;
+import android.widget.AdapterView;
+
+/**
+ * Extension of {@link Menu} for context menus providing functionality to modify
+ * the header of the context menu.
+ * <p>
+ * Context menus do not support item shortcuts and item icons.
+ * <p>
+ * To show a context menu on long click, most clients will want to call
+ * {@link Activity#registerForContextMenu} and override
+ * {@link Activity#onCreateContextMenu}.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For information about creating menus, read the
+ * <a href="{@docRoot}guide/topics/ui/menus.html">Menus</a> developer guide.</p>
+ * </div>
+ */
+public interface ContextMenu extends Menu {
+    /**
+     * Sets the context menu header's title to the title given in <var>titleRes</var>
+     * resource identifier.
+     * 
+     * @param titleRes The string resource identifier used for the title.
+     * @return This ContextMenu so additional setters can be called.
+     */
+    public ContextMenu setHeaderTitle(@StringRes int titleRes);
+
+    /**
+     * Sets the context menu header's title to the title given in <var>title</var>.
+     * 
+     * @param title The character sequence used for the title.
+     * @return This ContextMenu so additional setters can be called.
+     */
+    public ContextMenu setHeaderTitle(CharSequence title);
+    
+    /**
+     * Sets the context menu header's icon to the icon given in <var>iconRes</var>
+     * resource id.
+     * 
+     * @param iconRes The resource identifier used for the icon.
+     * @return This ContextMenu so additional setters can be called.
+     */
+    public ContextMenu setHeaderIcon(@DrawableRes int iconRes);
+
+    /**
+     * Sets the context menu header's icon to the icon given in <var>icon</var>
+     * {@link Drawable}.
+     * 
+     * @param icon The {@link Drawable} used for the icon.
+     * @return This ContextMenu so additional setters can be called.
+     */
+    public ContextMenu setHeaderIcon(Drawable icon);
+    
+    /**
+     * Sets the header of the context menu to the {@link View} given in
+     * <var>view</var>. This replaces the header title and icon (and those
+     * replace this).
+     * 
+     * @param view The {@link View} used for the header.
+     * @return This ContextMenu so additional setters can be called.
+     */
+    public ContextMenu setHeaderView(View view);
+    
+    /**
+     * Clears the header of the context menu.
+     */
+    public void clearHeader();
+    
+    /**
+     * Additional information regarding the creation of the context menu.  For example,
+     * {@link AdapterView}s use this to pass the exact item position within the adapter
+     * that initiated the context menu.
+     */
+    public interface ContextMenuInfo {
+    }
+}
diff --git a/android/view/ContextThemeWrapper.java b/android/view/ContextThemeWrapper.java
new file mode 100644
index 0000000..876331b
--- /dev/null
+++ b/android/view/ContextThemeWrapper.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.AssetManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Build;
+
+/**
+ * A context wrapper that allows you to modify or replace the theme of the
+ * wrapped context.
+ */
+public class ContextThemeWrapper extends ContextWrapper {
+    @UnsupportedAppUsage
+    private int mThemeResource;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768723)
+    private Resources.Theme mTheme;
+    @UnsupportedAppUsage
+    private LayoutInflater mInflater;
+    private Configuration mOverrideConfiguration;
+    @UnsupportedAppUsage
+    private Resources mResources;
+
+    /**
+     * Creates a new context wrapper with no theme and no base context.
+     * <p class="note">
+     * <strong>Note:</strong> A base context <strong>must</strong> be attached
+     * using {@link #attachBaseContext(Context)} before calling any other
+     * method on the newly constructed context wrapper.
+     */
+    public ContextThemeWrapper() {
+        super(null);
+    }
+
+    /**
+     * Creates a new context wrapper with the specified theme.
+     * <p>
+     * The specified theme will be applied on top of the base context's theme.
+     * Any attributes not explicitly defined in the theme identified by
+     * <var>themeResId</var> will retain their original values.
+     *
+     * @param base the base context
+     * @param themeResId the resource ID of the theme to be applied on top of
+     *                   the base context's theme
+     */
+    public ContextThemeWrapper(Context base, @StyleRes int themeResId) {
+        super(base);
+        mThemeResource = themeResId;
+    }
+
+    /**
+     * Creates a new context wrapper with the specified theme.
+     * <p>
+     * Unlike {@link #ContextThemeWrapper(Context, int)}, the theme passed to
+     * this constructor will completely replace the base context's theme.
+     *
+     * @param base the base context
+     * @param theme the theme against which resources should be inflated
+     */
+    public ContextThemeWrapper(Context base, Resources.Theme theme) {
+        super(base);
+        mTheme = theme;
+    }
+
+    @Override
+    protected void attachBaseContext(Context newBase) {
+        super.attachBaseContext(newBase);
+    }
+
+    /**
+     * Call to set an "override configuration" on this context -- this is
+     * a configuration that replies one or more values of the standard
+     * configuration that is applied to the context.  See
+     * {@link Context#createConfigurationContext(Configuration)} for more
+     * information.
+     *
+     * <p>This method can only be called once, and must be called before any
+     * calls to {@link #getResources()} or {@link #getAssets()} are made.
+     */
+    public void applyOverrideConfiguration(Configuration overrideConfiguration) {
+        if (mResources != null) {
+            throw new IllegalStateException(
+                    "getResources() or getAssets() has already been called");
+        }
+        if (mOverrideConfiguration != null) {
+            throw new IllegalStateException("Override configuration has already been set");
+        }
+        mOverrideConfiguration = new Configuration(overrideConfiguration);
+    }
+
+    /**
+     * Used by ActivityThread to apply the overridden configuration to onConfigurationChange
+     * callbacks.
+     * @hide
+     */
+    public Configuration getOverrideConfiguration() {
+        return mOverrideConfiguration;
+    }
+
+    @Override
+    public AssetManager getAssets() {
+        // Ensure we're returning assets with the correct configuration.
+        return getResourcesInternal().getAssets();
+    }
+
+    @Override
+    public Resources getResources() {
+        return getResourcesInternal();
+    }
+
+    private Resources getResourcesInternal() {
+        if (mResources == null) {
+            if (mOverrideConfiguration == null) {
+                mResources = super.getResources();
+            } else {
+                final Context resContext = createConfigurationContext(mOverrideConfiguration);
+                mResources = resContext.getResources();
+            }
+        }
+        return mResources;
+    }
+
+    @Override
+    public void setTheme(int resid) {
+        if (mThemeResource != resid) {
+            mThemeResource = resid;
+            initializeTheme();
+        }
+    }
+
+    /**
+     * Set the configure the current theme. If null is provided then the default Theme is returned
+     * on the next call to {@link #getTheme()}
+     * @param theme Theme to consume in the wrapper, a value of null resets the theme to the default
+     */
+    public void setTheme(@Nullable Resources.Theme theme) {
+        mTheme = theme;
+    }
+
+    /** @hide */
+    @Override
+    @UnsupportedAppUsage
+    public int getThemeResId() {
+        return mThemeResource;
+    }
+
+    @Override
+    public Resources.Theme getTheme() {
+        if (mTheme != null) {
+            return mTheme;
+        }
+
+        mThemeResource = Resources.selectDefaultTheme(mThemeResource,
+                getApplicationInfo().targetSdkVersion);
+        initializeTheme();
+
+        return mTheme;
+    }
+
+    @Override
+    public Object getSystemService(String name) {
+        if (LAYOUT_INFLATER_SERVICE.equals(name)) {
+            if (mInflater == null) {
+                mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
+            }
+            return mInflater;
+        }
+        return getBaseContext().getSystemService(name);
+    }
+
+    /**
+     * Called by {@link #setTheme} and {@link #getTheme} to apply a theme
+     * resource to the current Theme object. May be overridden to change the
+     * default (simple) behavior. This method will not be called in multiple
+     * threads simultaneously.
+     *
+     * @param theme the theme being modified
+     * @param resId the style resource being applied to <var>theme</var>
+     * @param first {@code true} if this is the first time a style is being
+     *              applied to <var>theme</var>
+     */
+    protected void onApplyThemeResource(Resources.Theme theme, int resId, boolean first) {
+        theme.applyStyle(resId, true);
+    }
+
+    @UnsupportedAppUsage
+    private void initializeTheme() {
+        final boolean first = mTheme == null;
+        if (first) {
+            mTheme = getResources().newTheme();
+            final Resources.Theme theme = getBaseContext().getTheme();
+            if (theme != null) {
+                mTheme.setTo(theme);
+            }
+        }
+        onApplyThemeResource(mTheme, mThemeResource, first);
+    }
+}
+
diff --git a/android/view/CrossWindowBlurListeners.java b/android/view/CrossWindowBlurListeners.java
new file mode 100644
index 0000000..761a2b8
--- /dev/null
+++ b/android/view/CrossWindowBlurListeners.java
@@ -0,0 +1,148 @@
+/**
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Class that holds all registered {@link CrossWindowBlurEnabledListener}s. It listens
+ * for updates from the WindowManagerService and updates all registered listeners.
+ * @hide
+ */
+public final class CrossWindowBlurListeners {
+    private static final String TAG = "CrossWindowBlurListeners";
+
+    // property for background blur support in surface flinger
+    private static final String BLUR_PROPERTY = "ro.surface_flinger.supports_background_blur";
+    public static final boolean CROSS_WINDOW_BLUR_SUPPORTED =
+            SystemProperties.getBoolean(BLUR_PROPERTY, false);
+
+    private static volatile CrossWindowBlurListeners sInstance;
+    private static final Object sLock = new Object();
+
+    private final BlurEnabledListenerInternal mListenerInternal = new BlurEnabledListenerInternal();
+    private final ArrayMap<Consumer<Boolean>, Executor> mListeners = new ArrayMap();
+    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
+    private boolean mInternalListenerAttached = false;
+    private boolean mCrossWindowBlurEnabled;
+
+    private CrossWindowBlurListeners() {}
+
+    /**
+     * Returns a CrossWindowBlurListeners instance
+     */
+    public static CrossWindowBlurListeners getInstance() {
+        CrossWindowBlurListeners instance = sInstance;
+        if (instance == null) {
+
+            synchronized (sLock) {
+                instance = sInstance;
+                if (instance == null) {
+                    instance = new CrossWindowBlurListeners();
+                    sInstance = instance;
+                }
+            }
+        }
+        return instance;
+    }
+
+    public boolean isCrossWindowBlurEnabled() {
+        synchronized (sLock) {
+            attachInternalListenerIfNeededLocked();
+            return mCrossWindowBlurEnabled;
+        }
+    }
+
+    public void addListener(@NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<Boolean> listener) {
+        Preconditions.checkNotNull(listener, "listener cannot be null");
+        Preconditions.checkNotNull(executor, "executor cannot be null");
+
+        synchronized (sLock) {
+            attachInternalListenerIfNeededLocked();
+
+            mListeners.put(listener, executor);
+            notifyListener(listener, executor, mCrossWindowBlurEnabled);
+        }
+    }
+
+
+    public void removeListener(Consumer<Boolean> listener) {
+        Preconditions.checkNotNull(listener, "listener cannot be null");
+
+        synchronized (sLock) {
+            mListeners.remove(listener);
+
+            if (mInternalListenerAttached && mListeners.size() == 0) {
+                try {
+                    WindowManagerGlobal.getWindowManagerService()
+                            .unregisterCrossWindowBlurEnabledListener(mListenerInternal);
+                    mInternalListenerAttached = false;
+                } catch (RemoteException e) {
+                    Log.d(TAG, "Could not unregister ICrossWindowBlurEnabledListener");
+                }
+            }
+        }
+    }
+
+    private void attachInternalListenerIfNeededLocked() {
+        if (!mInternalListenerAttached) {
+            try {
+                mCrossWindowBlurEnabled = WindowManagerGlobal.getWindowManagerService()
+                        .registerCrossWindowBlurEnabledListener(mListenerInternal);
+                mInternalListenerAttached = true;
+            } catch (RemoteException e) {
+                Log.d(TAG, "Could not register ICrossWindowBlurEnabledListener");
+            }
+        }
+    }
+
+    private void notifyListener(Consumer<Boolean> listener, Executor executor, boolean enabled) {
+        executor.execute(() -> listener.accept(enabled));
+    }
+
+    private final class BlurEnabledListenerInternal extends ICrossWindowBlurEnabledListener.Stub {
+        @Override
+        public void onCrossWindowBlurEnabledChanged(boolean enabled) {
+            synchronized (sLock) {
+                mCrossWindowBlurEnabled = enabled;
+
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    for (int i = 0; i < mListeners.size(); i++) {
+                        notifyListener(mListeners.keyAt(i), mListeners.valueAt(i), enabled);
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+        }
+    }
+}
diff --git a/android/view/CutoutSpecification.java b/android/view/CutoutSpecification.java
new file mode 100644
index 0000000..850e9fc
--- /dev/null
+++ b/android/view/CutoutSpecification.java
@@ -0,0 +1,484 @@
+/*
+ * 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.view;
+
+import static android.view.Gravity.BOTTOM;
+import static android.view.Gravity.LEFT;
+import static android.view.Gravity.RIGHT;
+import static android.view.Gravity.TOP;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Insets;
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.PathParser;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * In order to accept the cutout specification for all of edges in devices, the specification
+ * parsing method is extracted from
+ * {@link android.view.DisplayCutout#fromResourcesRectApproximation(Resources, int, int)} to be
+ * the specified class for parsing the specification.
+ * BNF definition:
+ * <ul>
+ *      <li>Cutouts Specification = ([Cutout Delimiter],Cutout Specification) {...}, [Dp] ; </li>
+ *      <li>Cutout Specification  = [Vertical Position], (SVG Path Element), [Horizontal Position]
+ *                                  [Bind Cutout] ;</li>
+ *      <li>Vertical Position     = "@bottom" | "@center_vertical" ;</li>
+ *      <li>Horizontal Position   = "@left" | "@right" ;</li>
+ *      <li>Bind Cutout           = "@bind_left_cutout" | "@bind_right_cutout" ;</li>
+ *      <li>Cutout Delimiter      = "@cutout" ;</li>
+ *      <li>Dp                    = "@dp"</li>
+ * </ul>
+ *
+ * <ul>
+ *     <li>Vertical position is top by default if there is neither "@bottom" nor "@center_vertical"
+ *     </li>
+ *     <li>Horizontal position is center horizontal by default if there is neither "@left" nor
+ *     "@right".</li>
+ *     <li>@bottom make the cutout piece bind to bottom edge.</li>
+ *     <li>both of @bind_left_cutout and @bind_right_cutout are use to claim the cutout belong to
+ *     left or right edge cutout.</li>
+ * </ul>
+ *
+ * @hide
+ */
+@VisibleForTesting(visibility = PACKAGE)
+public class CutoutSpecification {
+    private static final String TAG = "CutoutSpecification";
+    private static final boolean DEBUG = false;
+
+    private static final int MINIMAL_ACCEPTABLE_PATH_LENGTH = "H1V1Z".length();
+
+    private static final char MARKER_START_CHAR = '@';
+    private static final String DP_MARKER = MARKER_START_CHAR + "dp";
+
+    private static final String BOTTOM_MARKER = MARKER_START_CHAR + "bottom";
+    private static final String RIGHT_MARKER = MARKER_START_CHAR + "right";
+    private static final String LEFT_MARKER = MARKER_START_CHAR + "left";
+    private static final String CUTOUT_MARKER = MARKER_START_CHAR + "cutout";
+    private static final String CENTER_VERTICAL_MARKER = MARKER_START_CHAR + "center_vertical";
+
+    /* By default, it's top bound cutout. That's why TOP_BOUND_CUTOUT_MARKER is not defined */
+    private static final String BIND_RIGHT_CUTOUT_MARKER = MARKER_START_CHAR + "bind_right_cutout";
+    private static final String BIND_LEFT_CUTOUT_MARKER = MARKER_START_CHAR + "bind_left_cutout";
+
+    private final Path mPath;
+    private final Rect mLeftBound;
+    private final Rect mTopBound;
+    private final Rect mRightBound;
+    private final Rect mBottomBound;
+    private final Insets mInsets;
+
+    private CutoutSpecification(@NonNull Parser parser) {
+        mPath = parser.mPath;
+        mLeftBound = parser.mLeftBound;
+        mTopBound = parser.mTopBound;
+        mRightBound = parser.mRightBound;
+        mBottomBound = parser.mBottomBound;
+        mInsets = parser.mInsets;
+
+        if (DEBUG) {
+            Log.d(TAG, String.format(Locale.ENGLISH,
+                    "left cutout = %s, top cutout = %s, right cutout = %s, bottom cutout = %s",
+                    mLeftBound != null ? mLeftBound.toString() : "",
+                    mTopBound != null ? mTopBound.toString() : "",
+                    mRightBound != null ? mRightBound.toString() : "",
+                    mBottomBound != null ? mBottomBound.toString() : ""));
+        }
+    }
+
+    @VisibleForTesting(visibility = PACKAGE)
+    @Nullable
+    public Path getPath() {
+        return mPath;
+    }
+
+    @VisibleForTesting(visibility = PACKAGE)
+    @Nullable
+    public Rect getLeftBound() {
+        return mLeftBound;
+    }
+
+    @VisibleForTesting(visibility = PACKAGE)
+    @Nullable
+    public Rect getTopBound() {
+        return mTopBound;
+    }
+
+    @VisibleForTesting(visibility = PACKAGE)
+    @Nullable
+    public Rect getRightBound() {
+        return mRightBound;
+    }
+
+    @VisibleForTesting(visibility = PACKAGE)
+    @Nullable
+    public Rect getBottomBound() {
+        return mBottomBound;
+    }
+
+    /**
+     * To count the safe inset according to the cutout bounds and waterfall inset.
+     *
+     * @return the safe inset.
+     */
+    @VisibleForTesting(visibility = PACKAGE)
+    @NonNull
+    public Rect getSafeInset() {
+        return mInsets.toRect();
+    }
+
+    private static int decideWhichEdge(boolean isTopEdgeShortEdge,
+            boolean isShortEdge, boolean isStart) {
+        return (isTopEdgeShortEdge)
+                ? ((isShortEdge) ? (isStart ? TOP : BOTTOM) : (isStart ? LEFT : RIGHT))
+                : ((isShortEdge) ? (isStart ? LEFT : RIGHT) : (isStart ? TOP : BOTTOM));
+    }
+
+    /**
+     * The CutoutSpecification Parser.
+     */
+    @VisibleForTesting(visibility = PACKAGE)
+    public static class Parser {
+        private final boolean mIsShortEdgeOnTop;
+        private final float mDensity;
+        private final int mDisplayWidth;
+        private final int mDisplayHeight;
+        private final Matrix mMatrix;
+        private Insets mInsets;
+        private int mSafeInsetLeft;
+        private int mSafeInsetTop;
+        private int mSafeInsetRight;
+        private int mSafeInsetBottom;
+
+        private final Rect mTmpRect = new Rect();
+        private final RectF mTmpRectF = new RectF();
+
+        private boolean mInDp;
+
+        private Path mPath;
+        private Rect mLeftBound;
+        private Rect mTopBound;
+        private Rect mRightBound;
+        private Rect mBottomBound;
+
+        private boolean mPositionFromLeft = false;
+        private boolean mPositionFromRight = false;
+        private boolean mPositionFromBottom = false;
+        private boolean mPositionFromCenterVertical = false;
+
+        private boolean mBindLeftCutout = false;
+        private boolean mBindRightCutout = false;
+        private boolean mBindBottomCutout = false;
+
+        private boolean mIsTouchShortEdgeStart;
+        private boolean mIsTouchShortEdgeEnd;
+        private boolean mIsCloserToStartSide;
+
+        /**
+         * The constructor of the CutoutSpecification parser to parse the specification of cutout.
+         * @param density the display density.
+         * @param displayWidth the display width.
+         * @param displayHeight the display height.
+         */
+        @VisibleForTesting(visibility = PACKAGE)
+        public Parser(float density, int displayWidth, int displayHeight) {
+            mDensity = density;
+            mDisplayWidth = displayWidth;
+            mDisplayHeight = displayHeight;
+            mMatrix = new Matrix();
+            mIsShortEdgeOnTop = mDisplayWidth < mDisplayHeight;
+        }
+
+        private void computeBoundsRectAndAddToRegion(Path p, Region inoutRegion, Rect inoutRect) {
+            mTmpRectF.setEmpty();
+            p.computeBounds(mTmpRectF, false /* unused */);
+            mTmpRectF.round(inoutRect);
+            inoutRegion.op(inoutRect, Region.Op.UNION);
+        }
+
+        private void resetStatus(StringBuilder sb) {
+            sb.setLength(0);
+            mPositionFromBottom = false;
+            mPositionFromLeft = false;
+            mPositionFromRight = false;
+            mPositionFromCenterVertical = false;
+
+            mBindLeftCutout = false;
+            mBindRightCutout = false;
+            mBindBottomCutout = false;
+        }
+
+        private void translateMatrix() {
+            final float offsetX;
+            if (mPositionFromRight) {
+                offsetX = mDisplayWidth;
+            } else if (mPositionFromLeft) {
+                offsetX = 0;
+            } else {
+                offsetX = mDisplayWidth / 2f;
+            }
+
+            final float offsetY;
+            if (mPositionFromBottom) {
+                offsetY = mDisplayHeight;
+            } else if (mPositionFromCenterVertical) {
+                offsetY = mDisplayHeight / 2f;
+            } else {
+                offsetY = 0;
+            }
+
+            mMatrix.reset();
+            if (mInDp) {
+                mMatrix.postScale(mDensity, mDensity);
+            }
+            mMatrix.postTranslate(offsetX, offsetY);
+        }
+
+        private int computeSafeInsets(int gravity, Rect rect) {
+            if (gravity == LEFT && rect.right > 0 && rect.right < mDisplayWidth) {
+                return rect.right;
+            } else if (gravity == TOP && rect.bottom > 0 && rect.bottom < mDisplayHeight) {
+                return rect.bottom;
+            } else if (gravity == RIGHT && rect.left > 0 && rect.left < mDisplayWidth) {
+                return mDisplayWidth - rect.left;
+            } else if (gravity == BOTTOM && rect.top > 0 && rect.top < mDisplayHeight) {
+                return mDisplayHeight - rect.top;
+            }
+            return 0;
+        }
+
+        private void setSafeInset(int gravity, int inset) {
+            if (gravity == LEFT) {
+                mSafeInsetLeft = inset;
+            } else if (gravity == TOP) {
+                mSafeInsetTop = inset;
+            } else if (gravity == RIGHT) {
+                mSafeInsetRight = inset;
+            } else if (gravity == BOTTOM) {
+                mSafeInsetBottom = inset;
+            }
+        }
+
+        private int getSafeInset(int gravity) {
+            if (gravity == LEFT) {
+                return mSafeInsetLeft;
+            } else if (gravity == TOP) {
+                return mSafeInsetTop;
+            } else if (gravity == RIGHT) {
+                return mSafeInsetRight;
+            } else if (gravity == BOTTOM) {
+                return mSafeInsetBottom;
+            }
+            return 0;
+        }
+
+        @NonNull
+        private Rect onSetEdgeCutout(boolean isStart, boolean isShortEdge, @NonNull Rect rect) {
+            final int gravity;
+            if (isShortEdge) {
+                gravity = decideWhichEdge(mIsShortEdgeOnTop, true, isStart);
+            } else {
+                if (mIsTouchShortEdgeStart && mIsTouchShortEdgeEnd) {
+                    gravity = decideWhichEdge(mIsShortEdgeOnTop, false, isStart);
+                } else if (mIsTouchShortEdgeStart || mIsTouchShortEdgeEnd) {
+                    gravity = decideWhichEdge(mIsShortEdgeOnTop, true,
+                            mIsCloserToStartSide);
+                } else {
+                    gravity = decideWhichEdge(mIsShortEdgeOnTop, isShortEdge, isStart);
+                }
+            }
+
+            int oldSafeInset = getSafeInset(gravity);
+            int newSafeInset = computeSafeInsets(gravity, rect);
+            if (oldSafeInset < newSafeInset) {
+                setSafeInset(gravity, newSafeInset);
+            }
+
+            return new Rect(rect);
+        }
+
+        private void setEdgeCutout(@NonNull Path newPath) {
+            if (mBindRightCutout && mRightBound == null) {
+                mRightBound = onSetEdgeCutout(false, !mIsShortEdgeOnTop, mTmpRect);
+            } else if (mBindLeftCutout && mLeftBound == null) {
+                mLeftBound = onSetEdgeCutout(true, !mIsShortEdgeOnTop, mTmpRect);
+            } else if (mBindBottomCutout && mBottomBound == null) {
+                mBottomBound = onSetEdgeCutout(false, mIsShortEdgeOnTop, mTmpRect);
+            } else if (!(mBindBottomCutout || mBindLeftCutout || mBindRightCutout)
+                    && mTopBound == null) {
+                mTopBound = onSetEdgeCutout(true, mIsShortEdgeOnTop, mTmpRect);
+            } else {
+                return;
+            }
+
+            if (mPath != null) {
+                mPath.addPath(newPath);
+            } else {
+                mPath = newPath;
+            }
+        }
+
+        private void parseSvgPathSpec(Region region, String spec) {
+            if (TextUtils.length(spec) < MINIMAL_ACCEPTABLE_PATH_LENGTH) {
+                Log.e(TAG, "According to SVG definition, it shouldn't happen");
+                return;
+            }
+            spec.trim();
+            translateMatrix();
+
+            final Path newPath = PathParser.createPathFromPathData(spec);
+            newPath.transform(mMatrix);
+            computeBoundsRectAndAddToRegion(newPath, region, mTmpRect);
+
+            if (DEBUG) {
+                Log.d(TAG, String.format(Locale.ENGLISH,
+                        "hasLeft = %b, hasRight = %b, hasBottom = %b, hasCenterVertical = %b",
+                        mPositionFromLeft, mPositionFromRight, mPositionFromBottom,
+                        mPositionFromCenterVertical));
+                Log.d(TAG, "region = " + region);
+                Log.d(TAG, "spec = \"" + spec + "\" rect = " + mTmpRect + " newPath = " + newPath);
+            }
+
+            if (mTmpRect.isEmpty()) {
+                return;
+            }
+
+            if (mIsShortEdgeOnTop) {
+                mIsTouchShortEdgeStart = mTmpRect.top <= 0;
+                mIsTouchShortEdgeEnd = mTmpRect.bottom >= mDisplayHeight;
+                mIsCloserToStartSide = mTmpRect.centerY() < mDisplayHeight / 2;
+            } else {
+                mIsTouchShortEdgeStart = mTmpRect.left <= 0;
+                mIsTouchShortEdgeEnd = mTmpRect.right >= mDisplayWidth;
+                mIsCloserToStartSide = mTmpRect.centerX() < mDisplayWidth / 2;
+            }
+
+            setEdgeCutout(newPath);
+        }
+
+        private void parseSpecWithoutDp(@NonNull String specWithoutDp) {
+            Region region = Region.obtain();
+            StringBuilder sb = null;
+            int currentIndex = 0;
+            int lastIndex = 0;
+            while ((currentIndex = specWithoutDp.indexOf(MARKER_START_CHAR, lastIndex)) != -1) {
+                if (sb == null) {
+                    sb = new StringBuilder(specWithoutDp.length());
+                }
+                sb.append(specWithoutDp, lastIndex, currentIndex);
+
+                if (specWithoutDp.startsWith(LEFT_MARKER, currentIndex)) {
+                    if (!mPositionFromRight) {
+                        mPositionFromLeft = true;
+                    }
+                    currentIndex += LEFT_MARKER.length();
+                } else if (specWithoutDp.startsWith(RIGHT_MARKER, currentIndex)) {
+                    if (!mPositionFromLeft) {
+                        mPositionFromRight = true;
+                    }
+                    currentIndex += RIGHT_MARKER.length();
+                } else if (specWithoutDp.startsWith(BOTTOM_MARKER, currentIndex)) {
+                    parseSvgPathSpec(region, sb.toString());
+                    currentIndex += BOTTOM_MARKER.length();
+
+                    /* prepare to parse the rest path */
+                    resetStatus(sb);
+                    mBindBottomCutout = true;
+                    mPositionFromBottom = true;
+                } else if (specWithoutDp.startsWith(CENTER_VERTICAL_MARKER, currentIndex)) {
+                    parseSvgPathSpec(region, sb.toString());
+                    currentIndex += CENTER_VERTICAL_MARKER.length();
+
+                    /* prepare to parse the rest path */
+                    resetStatus(sb);
+                    mPositionFromCenterVertical = true;
+                } else if (specWithoutDp.startsWith(CUTOUT_MARKER, currentIndex)) {
+                    parseSvgPathSpec(region, sb.toString());
+                    currentIndex += CUTOUT_MARKER.length();
+
+                    /* prepare to parse the rest path */
+                    resetStatus(sb);
+                } else if (specWithoutDp.startsWith(BIND_LEFT_CUTOUT_MARKER, currentIndex)) {
+                    mBindBottomCutout = false;
+                    mBindRightCutout = false;
+                    mBindLeftCutout = true;
+
+                    currentIndex += BIND_LEFT_CUTOUT_MARKER.length();
+                } else if (specWithoutDp.startsWith(BIND_RIGHT_CUTOUT_MARKER, currentIndex)) {
+                    mBindBottomCutout = false;
+                    mBindLeftCutout = false;
+                    mBindRightCutout = true;
+
+                    currentIndex += BIND_RIGHT_CUTOUT_MARKER.length();
+                } else {
+                    currentIndex += 1;
+                }
+
+                lastIndex = currentIndex;
+            }
+
+            if (sb == null) {
+                parseSvgPathSpec(region, specWithoutDp);
+            } else {
+                sb.append(specWithoutDp, lastIndex, specWithoutDp.length());
+                parseSvgPathSpec(region, sb.toString());
+            }
+
+            region.recycle();
+        }
+
+        /**
+         * To parse specification string as the CutoutSpecification.
+         *
+         * @param originalSpec the specification string
+         * @return the CutoutSpecification instance
+         */
+        @VisibleForTesting(visibility = PACKAGE)
+        public CutoutSpecification parse(@NonNull String originalSpec) {
+            Objects.requireNonNull(originalSpec);
+
+            int dpIndex = originalSpec.lastIndexOf(DP_MARKER);
+            mInDp = (dpIndex != -1);
+            final String spec;
+            if (dpIndex != -1) {
+                spec = originalSpec.substring(0, dpIndex)
+                        + originalSpec.substring(dpIndex + DP_MARKER.length());
+            } else {
+                spec = originalSpec;
+            }
+
+            parseSpecWithoutDp(spec);
+
+            mInsets = Insets.of(mSafeInsetLeft, mSafeInsetTop, mSafeInsetRight, mSafeInsetBottom);
+            return new CutoutSpecification(this);
+        }
+    }
+}
diff --git a/android/view/CutoutSpecificationBenchmark.java b/android/view/CutoutSpecificationBenchmark.java
new file mode 100644
index 0000000..860c134
--- /dev/null
+++ b/android/view/CutoutSpecificationBenchmark.java
@@ -0,0 +1,224 @@
+/*
+ * 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.view;
+
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.PathParser;
+
+import androidx.benchmark.BenchmarkState;
+import androidx.benchmark.junit4.BenchmarkRule;
+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 CutoutSpecificationBenchmark {
+    private static final int DISPLAY_WIDTH = 1080;
+    private static final int DISPLAY_HEIGHT = 1920;
+    private static final float DISPLAY_DENSITY = 3.5f;
+
+    private static final String TAG = "CutoutSpecificationBenchmark";
+
+    private static final String BOTTOM_MARKER = "@bottom";
+    private static final String DP_MARKER = "@dp";
+    private static final String RIGHT_MARKER = "@right";
+    private static final String LEFT_MARKER = "@left";
+
+    private static final String DOUBLE_CUTOUT_SPEC = "M 0,0\n"
+            + "L -72, 0\n"
+            + "L -69.9940446283, 20.0595537175\n"
+            + "C -69.1582133885, 28.4178661152 -65.2, 32.0 -56.8, 32.0\n"
+            + "L 56.8, 32.0\n"
+            + "C 65.2, 32.0 69.1582133885, 28.4178661152 69.9940446283, 20.0595537175\n"
+            + "L 72, 0\n"
+            + "Z\n"
+            + "@bottom\n"
+            + "M 0,0\n"
+            + "L -72, 0\n"
+            + "L -69.9940446283, -20.0595537175\n"
+            + "C -69.1582133885, -28.4178661152 -65.2, -32.0 -56.8, -32.0\n"
+            + "L 56.8, -32.0\n"
+            + "C 65.2, -32.0 69.1582133885, -28.4178661152 69.9940446283, -20.0595537175\n"
+            + "L 72, 0\n"
+            + "Z\n"
+            + "@dp";
+    @Rule
+    public BenchmarkRule mBenchmarkRule = new BenchmarkRule();
+
+    private static void toRectAndAddToRegion(Path p, Region inoutRegion, Rect inoutRect) {
+        final RectF rectF = new RectF();
+        p.computeBounds(rectF, false /* unused */);
+        rectF.round(inoutRect);
+        inoutRegion.op(inoutRect, Region.Op.UNION);
+    }
+
+    private static void oldMethodParsingSpec(String spec, int displayWidth, int displayHeight,
+            float density) {
+        Path p = null;
+        Rect boundTop = null;
+        Rect boundBottom = null;
+        Rect safeInset = new Rect();
+        String bottomSpec = null;
+        if (!TextUtils.isEmpty(spec)) {
+            spec = spec.trim();
+            final float offsetX;
+            if (spec.endsWith(RIGHT_MARKER)) {
+                offsetX = displayWidth;
+                spec = spec.substring(0, spec.length() - RIGHT_MARKER.length()).trim();
+            } else if (spec.endsWith(LEFT_MARKER)) {
+                offsetX = 0;
+                spec = spec.substring(0, spec.length() - LEFT_MARKER.length()).trim();
+            } else {
+                offsetX = displayWidth / 2f;
+            }
+            final boolean inDp = spec.endsWith(DP_MARKER);
+            if (inDp) {
+                spec = spec.substring(0, spec.length() - DP_MARKER.length());
+            }
+
+            if (spec.contains(BOTTOM_MARKER)) {
+                String[] splits = spec.split(BOTTOM_MARKER, 2);
+                spec = splits[0].trim();
+                bottomSpec = splits[1].trim();
+            }
+
+            final Matrix m = new Matrix();
+            final Region r = Region.obtain();
+            if (!spec.isEmpty()) {
+                try {
+                    p = PathParser.createPathFromPathData(spec);
+                } catch (Throwable e) {
+                    Log.wtf(TAG, "Could not inflate cutout: ", e);
+                }
+
+                if (p != null) {
+                    if (inDp) {
+                        m.postScale(density, density);
+                    }
+                    m.postTranslate(offsetX, 0);
+                    p.transform(m);
+
+                    boundTop = new Rect();
+                    toRectAndAddToRegion(p, r, boundTop);
+                    safeInset.top = boundTop.bottom;
+                }
+            }
+
+            if (bottomSpec != null) {
+                int bottomInset = 0;
+                Path bottomPath = null;
+                try {
+                    bottomPath = PathParser.createPathFromPathData(bottomSpec);
+                } catch (Throwable e) {
+                    Log.wtf(TAG, "Could not inflate bottom cutout: ", e);
+                }
+
+                if (bottomPath != null) {
+                    // Keep top transform
+                    m.postTranslate(0, displayHeight);
+                    bottomPath.transform(m);
+                    p.addPath(bottomPath);
+                    boundBottom = new Rect();
+                    toRectAndAddToRegion(bottomPath, r, boundBottom);
+                    bottomInset = displayHeight - boundBottom.top;
+                }
+                safeInset.bottom = bottomInset;
+            }
+        }
+    }
+
+    @Test
+    public void parseByOldMethodForDoubleCutout() {
+        final BenchmarkState state = mBenchmarkRule.getState();
+        while (state.keepRunning()) {
+            oldMethodParsingSpec(DOUBLE_CUTOUT_SPEC, DISPLAY_WIDTH, DISPLAY_HEIGHT,
+                    DISPLAY_DENSITY);
+        }
+    }
+
+    @Test
+    public void parseByNewMethodForDoubleCutout() {
+        final BenchmarkState state = mBenchmarkRule.getState();
+        while (state.keepRunning()) {
+            new CutoutSpecification.Parser(DISPLAY_DENSITY, DISPLAY_WIDTH, DISPLAY_HEIGHT)
+                    .parse(DOUBLE_CUTOUT_SPEC);
+        }
+    }
+
+    @Test
+    public void parseLongEdgeCutout() {
+        final String spec = "M 0,0\n"
+                + "H 48\n"
+                + "V 48\n"
+                + "H -48\n"
+                + "Z\n"
+                + "@left\n"
+                + "@center_vertical\n"
+                + "M 0,0\n"
+                + "H 48\n"
+                + "V 48\n"
+                + "H -48\n"
+                + "Z\n"
+                + "@left\n"
+                + "@center_vertical\n"
+                + "M 0,0\n"
+                + "H -48\n"
+                + "V 48\n"
+                + "H 48\n"
+                + "Z\n"
+                + "@right\n"
+                + "@dp";
+
+        final BenchmarkState state = mBenchmarkRule.getState();
+        while (state.keepRunning()) {
+            new CutoutSpecification.Parser(DISPLAY_DENSITY, DISPLAY_WIDTH, DISPLAY_HEIGHT)
+                    .parse(spec);
+        }
+    }
+
+    @Test
+    public void parseShortEdgeCutout() {
+        final String spec = "M 0,0\n"
+                + "H 48\n"
+                + "V 48\n"
+                + "H -48\n"
+                + "Z\n"
+                + "@bottom\n"
+                + "M 0,0\n"
+                + "H 48\n"
+                + "V -48\n"
+                + "H -48\n"
+                + "Z\n"
+                + "@dp";
+
+        final BenchmarkState state = mBenchmarkRule.getState();
+        while (state.keepRunning()) {
+            new CutoutSpecification.Parser(DISPLAY_DENSITY, DISPLAY_WIDTH, DISPLAY_HEIGHT)
+                    .parse(spec);
+        }
+    }
+}
diff --git a/android/view/Display.java b/android/view/Display.java
new file mode 100644
index 0000000..9cb0d1f
--- /dev/null
+++ b/android/view/Display.java
@@ -0,0 +1,2072 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.Manifest.permission.CONFIGURE_DISPLAY_COLOR_MODE;
+import static android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
+import android.app.ActivityThread;
+import android.app.KeyguardManager;
+import android.app.WindowConfiguration;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.ColorSpace;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.display.BrightnessInfo;
+import android.hardware.display.DeviceProductInfo;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.SystemClock;
+import android.util.ArraySet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+
+import com.android.internal.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Provides information about the size and density of a logical display.
+ * <p>
+ * The display area is described in two different ways.
+ * <ul>
+ * <li>The application display area specifies the part of the display that may contain
+ * an application window, excluding the system decorations.  The application display area may
+ * be smaller than the real display area because the system subtracts the space needed
+ * for decor elements such as the status bar.  Use {@link WindowMetrics#getBounds()} to query the
+ * application window bounds.</li>
+ * <li>The real display area specifies the part of the display that is accessible to an application
+ * in the current system state. The real display area may be smaller than the physical size of the
+ * display in a few scenarios. Use {@link WindowManager#getCurrentWindowMetrics()} to identify the
+ * current size of the activity window. UI-related work, such as choosing UI layouts, should rely
+ * upon {@link WindowMetrics#getBounds()}. See {@link #getRealSize} / {@link #getRealMetrics} for
+ * details.</li>
+ * </ul>
+ * </p><p>
+ * A logical display does not necessarily represent a particular physical display device
+ * such as the internal display or an external display.  The contents of a logical
+ * display may be presented on one or more physical displays according to the devices
+ * that are currently attached and whether mirroring has been enabled.
+ * </p>
+ */
+public final class Display {
+    private static final String TAG = "Display";
+    private static final boolean DEBUG = false;
+
+    private final Object mLock = new Object();
+    private final DisplayManagerGlobal mGlobal;
+    private final int mDisplayId;
+    private final int mFlags;
+    private final int mType;
+    private final int mOwnerUid;
+    private final String mOwnerPackageName;
+    private final Resources mResources;
+    private DisplayAdjustments mDisplayAdjustments;
+
+    @UnsupportedAppUsage
+    private DisplayInfo mDisplayInfo; // never null
+    private boolean mIsValid;
+
+    // Temporary display metrics structure used for compatibility mode.
+    private final DisplayMetrics mTempMetrics = new DisplayMetrics();
+
+    // We cache the app width and height properties briefly between calls
+    // to getHeight() and getWidth() to ensure that applications perceive
+    // consistent results when the size changes (most of the time).
+    // Applications should now be using WindowMetrics instead.
+    private static final int CACHED_APP_SIZE_DURATION_MILLIS = 20;
+    private long mLastCachedAppSizeUpdate;
+    private int mCachedAppWidthCompat;
+    private int mCachedAppHeightCompat;
+
+    /**
+     * Indicates that the application is started in a different rotation than the real display, so
+     * the display information may be adjusted. That ensures the methods {@link #getRotation},
+     * {@link #getRealSize}, {@link #getRealMetrics}, and {@link #getCutout} are consistent with how
+     * the application window is laid out.
+     */
+    private boolean mMayAdjustByFixedRotation;
+
+    /**
+     * Cache if the application is the recents component.
+     * TODO(b/179308296) Remove once Launcher addresses issue
+     */
+    private Optional<Boolean> mIsRecentsComponent = Optional.empty();
+
+    /**
+     * The default Display id, which is the id of the primary display assuming there is one.
+     */
+    public static final int DEFAULT_DISPLAY = 0;
+
+    /**
+     * Invalid display id.
+     */
+    public static final int INVALID_DISPLAY = -1;
+
+    /**
+     * The default display group id, which is the display group id of the primary display assuming
+     * there is one.
+     * @hide
+     */
+    public static final int DEFAULT_DISPLAY_GROUP = 0;
+
+    /**
+     * Invalid display group id.
+     * @hide
+     */
+    public static final int INVALID_DISPLAY_GROUP = -1;
+
+    /**
+     * Display flag: Indicates that the display supports compositing content
+     * that is stored in protected graphics buffers.
+     * <p>
+     * If this flag is set then the display device supports compositing protected buffers.
+     * </p><p>
+     * If this flag is not set then the display device may not support compositing
+     * protected buffers; the user may see a blank region on the screen instead of
+     * the protected content.
+     * </p><p>
+     * Secure (DRM) video decoders may allocate protected graphics buffers to request that
+     * a hardware-protected path be provided between the video decoder and the external
+     * display sink.  If a hardware-protected path is not available, then content stored
+     * in protected graphics buffers may not be composited.
+     * </p><p>
+     * An application can use the absence of this flag as a hint that it should not use protected
+     * buffers for this display because the content may not be visible.  For example,
+     * if the flag is not set then the application may choose not to show content on this
+     * display, show an informative error message, select an alternate content stream
+     * or adopt a different strategy for decoding content that does not rely on
+     * protected buffers.
+     * </p>
+     *
+     * @see #getFlags
+     */
+    public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 1 << 0;
+
+    /**
+     * Display flag: Indicates that the display has a secure video output and
+     * supports compositing secure surfaces.
+     * <p>
+     * If this flag is set then the display device has a secure video output
+     * and is capable of showing secure surfaces.  It may also be capable of
+     * showing {@link #FLAG_SUPPORTS_PROTECTED_BUFFERS protected buffers}.
+     * </p><p>
+     * If this flag is not set then the display device may not have a secure video
+     * output; the user may see a blank region on the screen instead of
+     * the contents of secure surfaces or protected buffers.
+     * </p><p>
+     * Secure surfaces are used to prevent content rendered into those surfaces
+     * by applications from appearing in screenshots or from being viewed
+     * on non-secure displays.  Protected buffers are used by secure video decoders
+     * for a similar purpose.
+     * </p><p>
+     * An application creates a window with a secure surface by specifying the
+     * {@link WindowManager.LayoutParams#FLAG_SECURE} window flag.
+     * Likewise, an application creates a {@link SurfaceView} with a secure surface
+     * by calling {@link SurfaceView#setSecure} before attaching the secure view to
+     * its containing window.
+     * </p><p>
+     * An application can use the absence of this flag as a hint that it should not create
+     * secure surfaces or protected buffers on this display because the content may
+     * not be visible.  For example, if the flag is not set then the application may
+     * choose not to show content on this display, show an informative error message,
+     * select an alternate content stream or adopt a different strategy for decoding
+     * content that does not rely on secure surfaces or protected buffers.
+     * </p>
+     *
+     * @see #getFlags
+     */
+    public static final int FLAG_SECURE = 1 << 1;
+
+    /**
+     * Display flag: Indicates that the display is private.  Only the application that
+     * owns the display and apps that are already on the display can create windows on it.
+     *
+     * @see #getFlags
+     */
+    public static final int FLAG_PRIVATE = 1 << 2;
+
+    /**
+     * Display flag: Indicates that the display is a presentation display.
+     * <p>
+     * This flag identifies secondary displays that are suitable for
+     * use as presentation displays such as external or wireless displays.  Applications
+     * may automatically project their content to presentation displays to provide
+     * richer second screen experiences.
+     * </p>
+     *
+     * @see #getFlags
+     */
+    public static final int FLAG_PRESENTATION = 1 << 3;
+
+    /**
+     * Display flag: Indicates that the display has a round shape.
+     * <p>
+     * This flag identifies displays that are circular, elliptical or otherwise
+     * do not permit the user to see all the way to the logical corners of the display.
+     * </p>
+     *
+     * @see #getFlags
+     */
+    public static final int FLAG_ROUND = 1 << 4;
+
+    /**
+     * Display flag: Indicates that the display can show its content when non-secure keyguard is
+     * shown.
+     * <p>
+     * This flag identifies secondary displays that will continue showing content if keyguard can be
+     * dismissed without entering credentials.
+     * </p><p>
+     * An example of usage is a virtual display which content is displayed on external hardware
+     * display that is not visible to the system directly.
+     * </p>
+     *
+     * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD
+     * @see KeyguardManager#isDeviceSecure()
+     * @see KeyguardManager#isDeviceLocked()
+     * @see #getFlags
+     * @hide
+     */
+    // TODO (b/114338689): Remove the flag and use IWindowManager#shouldShowWithInsecureKeyguard
+    public static final int FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 1 << 5;
+
+    /**
+     * Display flag: Indicates that the display should show system decorations.
+     * <p>
+     * This flag identifies secondary displays that should show system decorations, such as status
+     * bar, navigation bar, home activity or IME.
+     * </p>
+     * <p>Note that this flag doesn't work without {@link #FLAG_TRUSTED}</p>
+     *
+     * @see #getFlags()
+     * @hide
+     */
+    // TODO (b/114338689): Remove the flag and use IWindowManager#setShouldShowSystemDecors
+    public static final int FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 6;
+
+    /**
+     * Flag: The display is trusted to show system decorations and receive inputs without users'
+     * touch.
+     * @see #FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS
+     *
+     * @see #getFlags()
+     * @hide
+     */
+    @TestApi
+    public static final int FLAG_TRUSTED = 1 << 7;
+
+    /**
+     * Flag: Indicates that the display should not be a part of the default DisplayGroup and
+     * instead be part of a new DisplayGroup.
+     *
+     * @hide
+     * @see #getFlags()
+     */
+    public static final int FLAG_OWN_DISPLAY_GROUP = 1 << 8;
+
+    /**
+     * Display flag: Indicates that the contents of the display should not be scaled
+     * to fit the physical screen dimensions.  Used for development only to emulate
+     * devices with smaller physicals screens while preserving density.
+     *
+     * @hide
+     */
+    public static final int FLAG_SCALING_DISABLED = 1 << 30;
+
+    /**
+     * Display type: Unknown display type.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @TestApi
+    public static final int TYPE_UNKNOWN = 0;
+
+    /**
+     * Display type: Physical display connected through an internal port.
+     * @hide
+     */
+    @TestApi
+    public static final int TYPE_INTERNAL = 1;
+
+    /**
+     * Display type: Physical display connected through an external port.
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @TestApi
+    public static final int TYPE_EXTERNAL = 2;
+
+    /**
+     * Display type: WiFi display.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @TestApi
+    public static final int TYPE_WIFI = 3;
+
+    /**
+     * Display type: Overlay display.
+     * @hide
+     */
+    @TestApi
+    public static final int TYPE_OVERLAY = 4;
+
+    /**
+     * Display type: Virtual display.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @TestApi
+    public static final int TYPE_VIRTUAL = 5;
+
+    /**
+     * Display state: The display state is unknown.
+     *
+     * @see #getState
+     */
+    public static final int STATE_UNKNOWN = ViewProtoEnums.DISPLAY_STATE_UNKNOWN; // 0
+
+    /**
+     * Display state: The display is off.
+     *
+     * @see #getState
+     */
+    public static final int STATE_OFF = ViewProtoEnums.DISPLAY_STATE_OFF; // 1
+
+    /**
+     * Display state: The display is on.
+     *
+     * @see #getState
+     */
+    public static final int STATE_ON = ViewProtoEnums.DISPLAY_STATE_ON; // 2
+
+    /**
+     * Display state: The display is dozing in a low power state; it is still
+     * on but is optimized for showing system-provided content while the
+     * device is non-interactive.
+     *
+     * @see #getState
+     * @see android.os.PowerManager#isInteractive
+     */
+    public static final int STATE_DOZE = ViewProtoEnums.DISPLAY_STATE_DOZE; // 3
+
+    /**
+     * Display state: The display is dozing in a suspended low power state; it is still
+     * on but the CPU is not updating it. This may be used in one of two ways: to show
+     * static system-provided content while the device is non-interactive, or to allow
+     * a "Sidekick" compute resource to update the display. For this reason, the
+     * CPU must not control the display in this mode.
+     *
+     * @see #getState
+     * @see android.os.PowerManager#isInteractive
+     */
+    public static final int STATE_DOZE_SUSPEND = ViewProtoEnums.DISPLAY_STATE_DOZE_SUSPEND; // 4
+
+    /**
+     * Display state: The display is on and optimized for VR mode.
+     *
+     * @see #getState
+     * @see android.os.PowerManager#isInteractive
+     */
+    public static final int STATE_VR = ViewProtoEnums.DISPLAY_STATE_VR; // 5
+
+    /**
+     * Display state: The display is in a suspended full power state; it is still
+     * on but the CPU is not updating it. This may be used in one of two ways: to show
+     * static system-provided content while the device is non-interactive, or to allow
+     * a "Sidekick" compute resource to update the display. For this reason, the
+     * CPU must not control the display in this mode.
+     *
+     * @see #getState
+     * @see android.os.PowerManager#isInteractive
+     */
+    public static final int STATE_ON_SUSPEND = ViewProtoEnums.DISPLAY_STATE_ON_SUSPEND; // 6
+
+    /* The color mode constants defined below must be kept in sync with the ones in
+     * system/core/include/system/graphics-base.h */
+
+    /**
+     * Display color mode: The current color mode is unknown or invalid.
+     * @hide
+     */
+    public static final int COLOR_MODE_INVALID = -1;
+
+    /**
+     * Display color mode: The default or native gamut of the display.
+     * @hide
+     */
+    public static final int COLOR_MODE_DEFAULT = 0;
+
+    /** @hide */
+    public static final int COLOR_MODE_BT601_625 = 1;
+    /** @hide */
+    public static final int COLOR_MODE_BT601_625_UNADJUSTED = 2;
+    /** @hide */
+    public static final int COLOR_MODE_BT601_525 = 3;
+    /** @hide */
+    public static final int COLOR_MODE_BT601_525_UNADJUSTED = 4;
+    /** @hide */
+    public static final int COLOR_MODE_BT709 = 5;
+    /** @hide */
+    public static final int COLOR_MODE_DCI_P3 = 6;
+    /** @hide */
+    public static final int COLOR_MODE_SRGB = 7;
+    /** @hide */
+    public static final int COLOR_MODE_ADOBE_RGB = 8;
+    /** @hide */
+    public static final int COLOR_MODE_DISPLAY_P3 = 9;
+
+    /** @hide **/
+    @IntDef(prefix = {"COLOR_MODE_"}, value = {
+            COLOR_MODE_INVALID,
+            COLOR_MODE_DEFAULT,
+            COLOR_MODE_BT601_625,
+            COLOR_MODE_BT601_625_UNADJUSTED,
+            COLOR_MODE_BT601_525,
+            COLOR_MODE_BT601_525_UNADJUSTED,
+            COLOR_MODE_BT709,
+            COLOR_MODE_DCI_P3,
+            COLOR_MODE_SRGB,
+            COLOR_MODE_ADOBE_RGB,
+            COLOR_MODE_DISPLAY_P3
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ColorMode {}
+
+    /**
+     * Indicates that when display is removed, all its activities will be moved to the primary
+     * display and the topmost activity should become focused.
+     *
+     * @hide
+     */
+    // TODO (b/114338689): Remove the flag and use WindowManager#REMOVE_CONTENT_MODE_MOVE_TO_PRIMARY
+    public static final int REMOVE_MODE_MOVE_CONTENT_TO_PRIMARY = 0;
+    /**
+     * Indicates that when display is removed, all its stacks and tasks will be removed, all
+     * activities will be destroyed according to the usual lifecycle.
+     *
+     * @hide
+     */
+    // TODO (b/114338689): Remove the flag and use WindowManager#REMOVE_CONTENT_MODE_DESTROY
+    public static final int REMOVE_MODE_DESTROY_CONTENT = 1;
+
+    /** @hide */
+    public static final int DISPLAY_MODE_ID_FOR_FRAME_RATE_OVERRIDE = 0xFF;
+
+    /**
+     * Internal method to create a display.
+     * The display created with this method will have a static {@link DisplayAdjustments} applied.
+     * Applications should use {@link android.content.Context#getDisplay} with
+     * {@link android.app.Activity} or a context associated with a {@link Display} via
+     * {@link android.content.Context#createDisplayContext(Display)}
+     * to get a display object associated with a {@link android.app.Context}, or
+     * {@link android.hardware.display.DisplayManager#getDisplay} to get a display object by id.
+     *
+     * @see android.content.Context#getDisplay()
+     * @see android.content.Context#createDisplayContext(Display)
+     * @hide
+     */
+    public Display(DisplayManagerGlobal global, int displayId, /*@NotNull*/ DisplayInfo displayInfo,
+            DisplayAdjustments daj) {
+        this(global, displayId, displayInfo, daj, null /*res*/);
+    }
+
+    /**
+     * Internal method to create a display.
+     * The display created with this method will be adjusted based on the adjustments in the
+     * supplied {@link Resources}.
+     *
+     * @hide
+     */
+    public Display(DisplayManagerGlobal global, int displayId, /*@NotNull*/ DisplayInfo displayInfo,
+            Resources res) {
+        this(global, displayId, displayInfo, null /*daj*/, res);
+    }
+
+    private Display(DisplayManagerGlobal global, int displayId,
+            /*@NotNull*/ DisplayInfo displayInfo, DisplayAdjustments daj, Resources res) {
+        mGlobal = global;
+        mDisplayId = displayId;
+        mDisplayInfo = displayInfo;
+        mResources = res;
+        mDisplayAdjustments = mResources != null
+            ? new DisplayAdjustments(mResources.getConfiguration())
+            : daj != null ? new DisplayAdjustments(daj) : new DisplayAdjustments();
+        mIsValid = true;
+
+        // Cache properties that cannot change as long as the display is valid.
+        mFlags = displayInfo.flags;
+        mType = displayInfo.type;
+        mOwnerUid = displayInfo.ownerUid;
+        mOwnerPackageName = displayInfo.ownerPackageName;
+    }
+
+    /**
+     * Gets the display id.
+     * <p>
+     * Each logical display has a unique id.
+     * The default display has id {@link #DEFAULT_DISPLAY}.
+     * </p>
+     */
+    public int getDisplayId() {
+        return mDisplayId;
+    }
+
+    /**
+     * Gets the display unique id.
+     * <p>
+     * Unique id is different from display id because physical displays have stable unique id across
+     * reboots.
+     *
+     * @see com.android.service.display.DisplayDevice#hasStableUniqueId().
+     * @hide
+     */
+    public String getUniqueId() {
+        return mDisplayInfo.uniqueId;
+    }
+
+    /**
+     * Returns true if this display is still valid, false if the display has been removed.
+     *
+     * If the display is invalid, then the methods of this class will
+     * continue to report the most recently observed display information.
+     * However, it is unwise (and rather fruitless) to continue using a
+     * {@link Display} object after the display's demise.
+     *
+     * It's possible for a display that was previously invalid to become
+     * valid again if a display with the same id is reconnected.
+     *
+     * @return True if the display is still valid.
+     */
+    public boolean isValid() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            return mIsValid;
+        }
+    }
+
+    /**
+     * Gets a full copy of the display information.
+     *
+     * @param outDisplayInfo The object to receive the copy of the display information.
+     * @return True if the display is still valid.
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    public boolean getDisplayInfo(DisplayInfo outDisplayInfo) {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            outDisplayInfo.copyFrom(mDisplayInfo);
+            return mIsValid;
+        }
+    }
+
+    /**
+     * Gets the display's layer stack.
+     *
+     * Each display has its own independent layer stack upon which surfaces
+     * are placed to be managed by surface flinger.
+     *
+     * @return The display's layer stack number.
+     * @hide
+     */
+    public int getLayerStack() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            return mDisplayInfo.layerStack;
+        }
+    }
+
+    /**
+     * Returns a combination of flags that describe the capabilities of the display.
+     *
+     * @return The display flags.
+     *
+     * @see #FLAG_SUPPORTS_PROTECTED_BUFFERS
+     * @see #FLAG_SECURE
+     * @see #FLAG_PRIVATE
+     * @see #FLAG_ROUND
+     */
+    public int getFlags() {
+        return mFlags;
+    }
+
+    /**
+     * Gets the display type.
+     *
+     * @return The display type.
+     *
+     * @see #TYPE_UNKNOWN
+     * @see #TYPE_INTERNAL
+     * @see #TYPE_EXTERNAL
+     * @see #TYPE_WIFI
+     * @see #TYPE_OVERLAY
+     * @see #TYPE_VIRTUAL
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @TestApi
+    public int getType() {
+        return mType;
+    }
+
+    /**
+     * Gets the display address, or null if none.
+     * Interpretation varies by display type.
+     *
+     * @return The display address.
+     * @hide
+     */
+    public DisplayAddress getAddress() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            return mDisplayInfo.address;
+        }
+    }
+
+    /**
+     * Gets the UID of the application that owns this display, or zero if it is
+     * owned by the system.
+     * <p>
+     * If the display is private, then only the owner can use it.
+     * </p>
+     *
+     * @hide
+     */
+    public int getOwnerUid() {
+        return mOwnerUid;
+    }
+
+    /**
+     * Gets the package name of the application that owns this display, or null if it is
+     * owned by the system.
+     * <p>
+     * If the display is private, then only the owner can use it.
+     * </p>
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public String getOwnerPackageName() {
+        return mOwnerPackageName;
+    }
+
+    /**
+     * Gets the compatibility info used by this display instance.
+     *
+     * @return The display adjustments holder, or null if none is required.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public DisplayAdjustments getDisplayAdjustments() {
+        if (mResources != null) {
+            final DisplayAdjustments currentAdjustments = mResources.getDisplayAdjustments();
+            if (!mDisplayAdjustments.equals(currentAdjustments)) {
+                mDisplayAdjustments = new DisplayAdjustments(currentAdjustments);
+            }
+        }
+
+        return mDisplayAdjustments;
+    }
+
+    /**
+     * Gets the name of the display.
+     * <p>
+     * Note that some displays may be renamed by the user.
+     * </p>
+     *
+     * @return The display's name.
+     */
+    public String getName() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            return mDisplayInfo.name;
+        }
+    }
+
+    /**
+     * Gets the default brightness configured for the display.
+     *
+     * @return Default brightness between 0.0-1.0
+     * @hide
+     */
+    public float getBrightnessDefault() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            return mDisplayInfo.brightnessDefault;
+        }
+    }
+
+    /**
+     * @return Brightness information about the display.
+     * @hide
+     */
+    @RequiresPermission(CONTROL_DISPLAY_BRIGHTNESS)
+    public @Nullable BrightnessInfo getBrightnessInfo() {
+        return mGlobal.getBrightnessInfo(mDisplayId);
+    }
+
+    /**
+     * Gets the size of the display, in pixels.
+     * Value returned by this method does not necessarily represent the actual raw size
+     * (native resolution) of the display.
+     * <p>
+     * 1. The returned size may be adjusted to exclude certain system decor elements
+     * that are always visible.
+     * </p><p>
+     * 2. It may be scaled to provide compatibility with older applications that
+     * were originally designed for smaller displays.
+     * </p><p>
+     * 3. It can be different depending on the WindowManager to which the display belongs.
+     * </p><p>
+     * - If requested from non-Activity context (e.g. Application context via
+     * {@code (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE)})
+     * it will report the size of the entire display based on current rotation and with subtracted
+     * system decoration areas.
+     * </p><p>
+     * - If requested from activity (either using {@code getWindowManager()} or
+     * {@code (WindowManager) getSystemService(Context.WINDOW_SERVICE)}) resulting size will
+     * correspond to current app window size. In this case it can be smaller than physical size in
+     * multi-window mode.
+     * </p><p>
+     * Typically for the purposes of layout apps should make a request from activity context
+     * to obtain size available for the app content.
+     * </p>
+     *
+     * @param outSize A {@link Point} object to receive the size information.
+     * @deprecated Use {@link WindowManager#getCurrentWindowMetrics()} to obtain an instance of
+     * {@link WindowMetrics} and use {@link WindowMetrics#getBounds()} instead.
+     */
+    @Deprecated
+    public void getSize(Point outSize) {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            mDisplayInfo.getAppMetrics(mTempMetrics, getDisplayAdjustments());
+            outSize.x = mTempMetrics.widthPixels;
+            outSize.y = mTempMetrics.heightPixels;
+        }
+    }
+
+    /**
+     * Gets the size of the display as a rectangle, in pixels.
+     *
+     * @param outSize A {@link Rect} object to receive the size information.
+     * @deprecated Use {@link WindowMetrics#getBounds()} to get the dimensions of the application
+     * window area.
+     */
+    @Deprecated
+    public void getRectSize(Rect outSize) {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            mDisplayInfo.getAppMetrics(mTempMetrics, getDisplayAdjustments());
+            outSize.set(0, 0, mTempMetrics.widthPixels, mTempMetrics.heightPixels);
+        }
+    }
+
+    /**
+     * Return the range of display sizes an application can expect to encounter
+     * under normal operation, as long as there is no physical change in screen
+     * size.  This is basically the sizes you will see as the orientation
+     * changes, taking into account whatever screen decoration there is in
+     * each rotation.  For example, the status bar is always at the top of the
+     * screen, so it will reduce the height both in landscape and portrait, and
+     * the smallest height returned here will be the smaller of the two.
+     *
+     * This is intended for applications to get an idea of the range of sizes
+     * they will encounter while going through device rotations, to provide a
+     * stable UI through rotation.  The sizes here take into account all standard
+     * system decorations that reduce the size actually available to the
+     * application: the status bar, navigation bar, system bar, etc.  It does
+     * <em>not</em> take into account more transient elements like an IME
+     * soft keyboard.
+     *
+     * @param outSmallestSize Filled in with the smallest width and height
+     * that the application will encounter, in pixels (not dp units).  The x
+     * (width) dimension here directly corresponds to
+     * {@link android.content.res.Configuration#smallestScreenWidthDp
+     * Configuration.smallestScreenWidthDp}, except the value here is in raw
+     * screen pixels rather than dp units.  Your application may of course
+     * still get smaller space yet if, for example, a soft keyboard is
+     * being displayed.
+     * @param outLargestSize Filled in with the largest width and height
+     * that the application will encounter, in pixels (not dp units).  Your
+     * application may of course still get larger space than this if,
+     * for example, screen decorations like the status bar are being hidden.
+     */
+    public void getCurrentSizeRange(Point outSmallestSize, Point outLargestSize) {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            outSmallestSize.x = mDisplayInfo.smallestNominalAppWidth;
+            outSmallestSize.y = mDisplayInfo.smallestNominalAppHeight;
+            outLargestSize.x = mDisplayInfo.largestNominalAppWidth;
+            outLargestSize.y = mDisplayInfo.largestNominalAppHeight;
+        }
+    }
+
+    /**
+     * Return the maximum screen size dimension that will happen.  This is
+     * mostly for wallpapers.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public int getMaximumSizeDimension() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            return Math.max(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
+        }
+    }
+
+    /**
+     * @deprecated Use {@link WindowMetrics#getBounds#width()} instead.
+     */
+    @Deprecated
+    public int getWidth() {
+        synchronized (mLock) {
+            updateCachedAppSizeIfNeededLocked();
+            return mCachedAppWidthCompat;
+        }
+    }
+
+    /**
+     * @deprecated Use {@link WindowMetrics#getBounds()#height()} instead.
+     */
+    @Deprecated
+    public int getHeight() {
+        synchronized (mLock) {
+            updateCachedAppSizeIfNeededLocked();
+            return mCachedAppHeightCompat;
+        }
+    }
+
+    /**
+     * Returns the rotation of the screen from its "natural" orientation.
+     * The returned value may be {@link Surface#ROTATION_0 Surface.ROTATION_0}
+     * (no rotation), {@link Surface#ROTATION_90 Surface.ROTATION_90},
+     * {@link Surface#ROTATION_180 Surface.ROTATION_180}, or
+     * {@link Surface#ROTATION_270 Surface.ROTATION_270}.  For
+     * example, if a device has a naturally tall screen, and the user has
+     * turned it on its side to go into a landscape orientation, the value
+     * returned here may be either {@link Surface#ROTATION_90 Surface.ROTATION_90}
+     * or {@link Surface#ROTATION_270 Surface.ROTATION_270} depending on
+     * the direction it was turned.  The angle is the rotation of the drawn
+     * graphics on the screen, which is the opposite direction of the physical
+     * rotation of the device.  For example, if the device is rotated 90
+     * degrees counter-clockwise, to compensate rendering will be rotated by
+     * 90 degrees clockwise and thus the returned value here will be
+     * {@link Surface#ROTATION_90 Surface.ROTATION_90}.
+     */
+    @Surface.Rotation
+    public int getRotation() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            return mMayAdjustByFixedRotation
+                    ? getDisplayAdjustments().getRotation(mDisplayInfo.rotation)
+                    : mDisplayInfo.rotation;
+        }
+    }
+
+    /**
+     * @deprecated use {@link #getRotation}
+     * @return orientation of this display.
+     */
+    @Deprecated
+    @Surface.Rotation
+    public int getOrientation() {
+        return getRotation();
+    }
+
+
+    /**
+     * Returns the {@link DisplayCutout}, or {@code null} if there is none.
+     *
+     * @see DisplayCutout
+     */
+    @Nullable
+    public DisplayCutout getCutout() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            return mMayAdjustByFixedRotation
+                    ? getDisplayAdjustments().getDisplayCutout(mDisplayInfo.displayCutout)
+                    : mDisplayInfo.displayCutout;
+        }
+    }
+
+    /**
+     * Returns the {@link RoundedCorner} of the given position if there is one.
+     *
+     * @param position the position of the rounded corner on the display.
+     *
+     * @return the rounded corner of the given position. Returns {@code null} if there is none.
+     */
+    @SuppressLint("VisiblySynchronized")
+    @Nullable
+    public RoundedCorner getRoundedCorner(@RoundedCorner.Position int position) {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            RoundedCorners roundedCorners;
+            if (mMayAdjustByFixedRotation) {
+                roundedCorners = getDisplayAdjustments().adjustRoundedCorner(
+                        mDisplayInfo.roundedCorners,
+                        mDisplayInfo.rotation,
+                        mDisplayInfo.logicalWidth,
+                        mDisplayInfo.logicalHeight);
+            } else {
+                roundedCorners = mDisplayInfo.roundedCorners;
+            }
+            return roundedCorners == null ? null : roundedCorners.getRoundedCorner(position);
+        }
+    }
+
+    /**
+     * Gets the pixel format of the display.
+     * @return One of the constants defined in {@link android.graphics.PixelFormat}.
+     *
+     * @deprecated This method is no longer supported.
+     * The result is always {@link PixelFormat#RGBA_8888}.
+     */
+    @Deprecated
+    public int getPixelFormat() {
+        return PixelFormat.RGBA_8888;
+    }
+
+    /**
+     * Gets the refresh rate of this display in frames per second.
+     */
+    public float getRefreshRate() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            return mDisplayInfo.getRefreshRate();
+        }
+    }
+
+    /**
+     * Get the supported refresh rates of this display in frames per second.
+     * <p>
+     * This method only returns refresh rates for the display's default modes. For more options, use
+     * {@link #getSupportedModes()}.
+     *
+     * @deprecated use {@link #getSupportedModes()} instead
+     */
+    @Deprecated
+    public float[] getSupportedRefreshRates() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            return mDisplayInfo.getDefaultRefreshRates();
+        }
+    }
+
+    /**
+     * Returns the active mode of the display.
+     */
+    public Mode getMode() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            return mDisplayInfo.getMode();
+        }
+    }
+
+    /**
+     * Gets the supported modes of this display.
+     */
+    public Mode[] getSupportedModes() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            final Display.Mode[] modes = mDisplayInfo.supportedModes;
+            return Arrays.copyOf(modes, modes.length);
+        }
+    }
+
+    /**
+     * <p> Returns true if the connected display can be switched into a mode with minimal
+     * post processing. </p>
+     *
+     * <p> If the Display sink is connected via HDMI, this method will return true if the
+     * display supports either Auto Low Latency Mode or Game Content Type.
+     *
+     * <p> If the Display sink has an internal connection or uses some other protocol than
+     * HDMI, this method will return true if the sink can be switched into an
+     * implementation-defined low latency image processing mode. </p>
+     *
+     * <p> The ability to switch to a mode with minimal post processing may be disabled
+     * by a user setting in the system settings menu. In that case, this method returns
+     * false. </p>
+     *
+     * @see android.view.Window#setPreferMinimalPostProcessing
+     */
+    @SuppressLint("VisiblySynchronized")
+    public boolean isMinimalPostProcessingSupported() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            return mDisplayInfo.minimalPostProcessingSupported;
+        }
+    }
+
+    /**
+     * Request the display applies a color mode.
+     * @hide
+     */
+    @RequiresPermission(CONFIGURE_DISPLAY_COLOR_MODE)
+    public void requestColorMode(int colorMode) {
+        mGlobal.requestColorMode(mDisplayId, colorMode);
+    }
+
+    /**
+     * Returns the active color mode of this display
+     * @hide
+     */
+    public int getColorMode() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            return mDisplayInfo.colorMode;
+        }
+    }
+
+    /**
+     * @hide
+     * Get current remove mode of the display - what actions should be performed with the display's
+     * content when it is removed. Default behavior for public displays in this case is to move all
+     * activities to the primary display and make it focused. For private display - destroy all
+     * activities.
+     *
+     * @see #REMOVE_MODE_MOVE_CONTENT_TO_PRIMARY
+     * @see #REMOVE_MODE_DESTROY_CONTENT
+     */
+    // TODO (b/114338689): Remove the method and use IWindowManager#getRemoveContentMode
+    public int getRemoveMode() {
+        return mDisplayInfo.removeMode;
+    }
+
+    /**
+     * Returns the display's HDR capabilities.
+     *
+     * @see #isHdr()
+     */
+    public HdrCapabilities getHdrCapabilities() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            if (mDisplayInfo.userDisabledHdrTypes.length == 0) {
+                return mDisplayInfo.hdrCapabilities;
+            }
+
+            if (mDisplayInfo.hdrCapabilities == null) {
+                return null;
+            }
+
+            ArraySet<Integer> enabledTypesSet = new ArraySet<>();
+            for (int supportedType : mDisplayInfo.hdrCapabilities.getSupportedHdrTypes()) {
+                boolean typeDisabled = false;
+                for (int userDisabledType : mDisplayInfo.userDisabledHdrTypes) {
+                    if (supportedType == userDisabledType) {
+                        typeDisabled = true;
+                        break;
+                    }
+                }
+                if (!typeDisabled) {
+                    enabledTypesSet.add(supportedType);
+                }
+            }
+
+            int[] enabledTypes = new int[enabledTypesSet.size()];
+            int index = 0;
+            for (int enabledType : enabledTypesSet) {
+                enabledTypes[index++] = enabledType;
+            }
+            return new HdrCapabilities(enabledTypes,
+                    mDisplayInfo.hdrCapabilities.mMaxLuminance,
+                    mDisplayInfo.hdrCapabilities.mMaxAverageLuminance,
+                    mDisplayInfo.hdrCapabilities.mMinLuminance);
+        }
+    }
+
+    /**
+     * @hide
+     * Returns the display's HDR supported types.
+     *
+     * @see #isHdr()
+     * @see HdrCapabilities#getSupportedHdrTypes()
+     */
+    @TestApi
+    @NonNull
+    public int[] getReportedHdrTypes() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            if (mDisplayInfo.hdrCapabilities == null) {
+                return new int[0];
+            }
+            return mDisplayInfo.hdrCapabilities.getSupportedHdrTypes();
+        }
+    }
+
+    /**
+     * Returns whether this display supports any HDR type.
+     *
+     * @see #getHdrCapabilities()
+     * @see HdrCapabilities#getSupportedHdrTypes()
+     */
+    public boolean isHdr() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            HdrCapabilities hdrCapabilities = getHdrCapabilities();
+            if (hdrCapabilities == null) {
+                return false;
+            }
+            return !(hdrCapabilities.getSupportedHdrTypes().length == 0);
+        }
+    }
+
+    /**
+     * Returns whether this display can be used to display wide color gamut content.
+     * This does not necessarily mean the device itself can render wide color gamut
+     * content. To ensure wide color gamut content can be produced, refer to
+     * {@link Configuration#isScreenWideColorGamut()}.
+     */
+    public boolean isWideColorGamut() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            return mDisplayInfo.isWideColorGamut();
+        }
+    }
+
+    /**
+     * Returns the preferred wide color space of the Display.
+     * The returned wide gamut color space is based on hardware capability and
+     * is preferred by the composition pipeline.
+     * Returns null if the display doesn't support wide color gamut.
+     * {@link Display#isWideColorGamut()}.
+     */
+    @Nullable
+    public ColorSpace getPreferredWideGamutColorSpace() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            if (mDisplayInfo.isWideColorGamut()) {
+                return mGlobal.getPreferredWideGamutColorSpace();
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Gets the supported color modes of this device.
+     * @hide
+     */
+    public int[] getSupportedColorModes() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            int[] colorModes = mDisplayInfo.supportedColorModes;
+            return Arrays.copyOf(colorModes, colorModes.length);
+        }
+    }
+
+    /**
+     * Gets the supported wide color gamuts of this device.
+     *
+     * @return Supported WCG color spaces.
+     * @hide
+     */
+    @SuppressLint("VisiblySynchronized")
+    @NonNull
+    @TestApi
+    public @ColorMode ColorSpace[] getSupportedWideColorGamut() {
+        synchronized (mLock) {
+            final ColorSpace[] defaultColorSpaces = new ColorSpace[0];
+            updateDisplayInfoLocked();
+            if (!isWideColorGamut()) {
+                return defaultColorSpaces;
+            }
+
+            final int[] colorModes = getSupportedColorModes();
+            final List<ColorSpace> colorSpaces = new ArrayList<>();
+            for (int colorMode : colorModes) {
+                // Refer to DisplayInfo#isWideColorGamut.
+                switch (colorMode) {
+                    case COLOR_MODE_DCI_P3:
+                        colorSpaces.add(ColorSpace.get(ColorSpace.Named.DCI_P3));
+                        break;
+                    case COLOR_MODE_DISPLAY_P3:
+                        colorSpaces.add(ColorSpace.get(ColorSpace.Named.DISPLAY_P3));
+                        break;
+                }
+            }
+            return colorSpaces.toArray(defaultColorSpaces);
+        }
+    }
+
+    /**
+     * Gets the app VSYNC offset, in nanoseconds.  This is a positive value indicating
+     * the phase offset of the VSYNC events provided by Choreographer relative to the
+     * display refresh.  For example, if Choreographer reports that the refresh occurred
+     * at time N, it actually occurred at (N - appVsyncOffset).
+     * <p>
+     * Apps generally do not need to be aware of this.  It's only useful for fine-grained
+     * A/V synchronization.
+     */
+    public long getAppVsyncOffsetNanos() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            return mDisplayInfo.appVsyncOffsetNanos;
+        }
+    }
+
+    /**
+     * This is how far in advance a buffer must be queued for presentation at
+     * a given time.  If you want a buffer to appear on the screen at
+     * time N, you must submit the buffer before (N - presentationDeadline).
+     * <p>
+     * The desired presentation time for GLES rendering may be set with
+     * {@link android.opengl.EGLExt#eglPresentationTimeANDROID}.  For video decoding, use
+     * {@link android.media.MediaCodec#releaseOutputBuffer(int, long)}.  Times are
+     * expressed in nanoseconds, using the system monotonic clock
+     * ({@link System#nanoTime}).
+     */
+    public long getPresentationDeadlineNanos() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            return mDisplayInfo.presentationDeadlineNanos;
+        }
+    }
+
+    /**
+     * Returns the product-specific information about the display or the directly connected
+     * device on the display chain.
+     * For example, if the display is transitively connected, this field may contain product
+     * information about the intermediate device.
+     * Returns {@code null} if product information is not available.
+     */
+    @Nullable
+    public DeviceProductInfo getDeviceProductInfo() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            return mDisplayInfo.deviceProductInfo;
+        }
+    }
+
+    /**
+     * Gets display metrics that describe the size and density of this display.
+     * The size returned by this method does not necessarily represent the
+     * actual raw size (native resolution) of the display.
+     * <p>
+     * 1. The returned size may be adjusted to exclude certain system decor elements
+     * that are always visible.
+     * </p><p>
+     * 2. It may be scaled to provide compatibility with older applications that
+     * were originally designed for smaller displays.
+     * </p><p>
+     * 3. It can be different depending on the WindowManager to which the display belongs.
+     * </p><p>
+     * - If requested from non-Activity context (e.g. Application context via
+     * {@code (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE)})
+     * metrics will report the size of the entire display based on current rotation and with
+     * subtracted system decoration areas.
+     * </p><p>
+     * - If requested from activity (either using {@code getWindowManager()} or
+     * {@code (WindowManager) getSystemService(Context.WINDOW_SERVICE)}) resulting metrics will
+     * correspond to current app window metrics. In this case the size can be smaller than physical
+     * size in multi-window mode.
+     * </p>
+     *
+     * @param outMetrics A {@link DisplayMetrics} object to receive the metrics.
+     * @deprecated Use {@link WindowMetrics#getBounds()} to get the dimensions of the application
+     * window area, and {@link Configuration#densityDpi} to get the current density.
+     */
+    @Deprecated
+    public void getMetrics(DisplayMetrics outMetrics) {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            mDisplayInfo.getAppMetrics(outMetrics, getDisplayAdjustments());
+        }
+    }
+
+    /**
+     * Gets the size of the largest region of the display accessible to an app in the current system
+     * state, without subtracting any window decor or applying scaling factors.
+     * <p>
+     * The size is adjusted based on the current rotation of the display.
+     * <p></p>
+     * The returned size will fall into one of these scenarios:
+     * <ol>
+     * <li>The device has no partitions on the display. The returned value is the largest region
+     * of the display accessible to an app in the current system state, regardless of windowing
+     * mode.</li>
+     * <li>The device divides a single display into multiple partitions. An application is
+     * restricted to a portion of the display. This is common in devices where the display changes
+     * size, such as foldables or large screens. The returned size will match the portion of
+     * the display the application is restricted to.</li>
+     * <li>The window manager is emulating a different display size, using {@code adb shell wm
+     * size}. The returned size will match the emulated display size.</li>
+     * </ol>
+     * </p><p>
+     * The returned value is <b>unsuitable to use when sizing and placing UI elements</b>, since it
+     * does not reflect the application window size in any of these scenarios.
+     * {@link WindowManager#getCurrentWindowMetrics()} is an alternative that returns the size
+     * of the current application window, even if the window is on a device with a partitioned
+     * display. This helps prevent UI bugs where UI elements are misaligned or placed beyond the
+     * bounds of the window.
+     * <p></p>
+     * Handling multi-window mode correctly is necessary since applications are not always
+     * fullscreen. A user on a large screen device, such as a tablet or Chrome OS devices, is more
+     * likely to use multi-window modes.
+     * <p></p>
+     * For example, consider a device with a display partitioned into two halves. The user may have
+     * a fullscreen application open on the first partition. They may have two applications open in
+     * split screen (an example of multi-window mode) on the second partition, with each application
+     * consuming half of the partition. In this case,
+     * {@link WindowManager#getCurrentWindowMetrics()} reports the fullscreen window is half of the
+     * screen in size, and each split screen window is a quarter of the screen in size. On the other
+     * hand, {@link #getRealSize} reports half of the screen size for all windows, since the
+     * application windows are all restricted to their respective partitions.
+     * </p>
+     *
+     * @param outSize Set to the real size of the display.
+     * @deprecated Use {@link WindowManager#getCurrentWindowMetrics()} to identify the current size
+     * of the activity window. UI-related work, such as choosing UI layouts, should rely
+     * upon {@link WindowMetrics#getBounds()}.
+     */
+    @Deprecated
+    public void getRealSize(Point outSize) {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            if (shouldReportMaxBounds()) {
+                final Rect bounds = mResources.getConfiguration()
+                        .windowConfiguration.getMaxBounds();
+                outSize.x = bounds.width();
+                outSize.y = bounds.height();
+                if (DEBUG) {
+                    Log.d(TAG, "getRealSize determined from max bounds: " + outSize);
+                }
+                // Skip adjusting by fixed rotation, since if it is necessary, the configuration
+                // should already reflect the expected rotation.
+                return;
+            }
+            outSize.x = mDisplayInfo.logicalWidth;
+            outSize.y = mDisplayInfo.logicalHeight;
+            if (mMayAdjustByFixedRotation) {
+                getDisplayAdjustments().adjustSize(outSize, mDisplayInfo.rotation);
+            }
+        }
+    }
+
+    /**
+     * Gets the size of the largest region of the display accessible to an app in the current system
+     * state, without subtracting any window decor or applying scaling factors.
+     * <p>
+     * The size is adjusted based on the current rotation of the display.
+     * <p></p>
+     * The returned size will fall into one of these scenarios:
+     * <ol>
+     * <li>The device has no partitions on the display. The returned value is the largest region
+     * of the display accessible to an app in the current system state, regardless of windowing
+     * mode.</li>
+     * <li>The device divides a single display into multiple partitions. An application is
+     * restricted to a portion of the display. This is common in devices where the display changes
+     * size, such as foldables or large screens. The returned size will match the portion of
+     * the display the application is restricted to.</li>
+     * <li>The window manager is emulating a different display size, using {@code adb shell wm
+     * size}. The returned size will match the emulated display size.</li>
+     * </ol>
+     * </p><p>
+     * The returned value is <b>unsuitable to use when sizing and placing UI elements</b>, since it
+     * does not reflect the application window size in any of these scenarios.
+     * {@link WindowManager#getCurrentWindowMetrics()} is an alternative that returns the size
+     * of the current application window, even if the window is on a device with a partitioned
+     * display. This helps prevent UI bugs where UI elements are misaligned or placed beyond the
+     * bounds of the window.
+     * <p></p>
+     * Handling multi-window mode correctly is necessary since applications are not always
+     * fullscreen. A user on a large screen device, such as a tablet or Chrome OS devices, is more
+     * likely to use multi-window modes.
+     * <p></p>
+     * For example, consider a device with a display partitioned into two halves. The user may have
+     * a fullscreen application open on the first partition. They may have two applications open in
+     * split screen (an example of multi-window mode) on the second partition, with each application
+     * consuming half of the partition. In this case,
+     * {@link WindowManager#getCurrentWindowMetrics()} reports the fullscreen window is half of the
+     * screen in size, and each split screen window is a quarter of the screen in size. On the other
+     * hand, {@link #getRealMetrics} reports half of the screen size for all windows, since the
+     * application windows are all restricted to their respective partitions.
+     * </p>
+     *
+     * @param outMetrics A {@link DisplayMetrics} object to receive the metrics.
+     * @deprecated Use {@link WindowManager#getCurrentWindowMetrics()} to identify the current size
+     * of the activity window. UI-related work, such as choosing UI layouts, should rely
+     * upon {@link WindowMetrics#getBounds()}. Use {@link Configuration#densityDpi} to
+     * get the current density.
+     */
+    @Deprecated
+    public void getRealMetrics(DisplayMetrics outMetrics) {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            if (shouldReportMaxBounds()) {
+                mDisplayInfo.getMaxBoundsMetrics(outMetrics,
+                        CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO,
+                        mResources.getConfiguration());
+                if (DEBUG) {
+                    Log.d(TAG, "getRealMetrics determined from max bounds: " + outMetrics);
+                }
+                // Skip adjusting by fixed rotation, since if it is necessary, the configuration
+                // should already reflect the expected rotation.
+                return;
+            }
+            mDisplayInfo.getLogicalMetrics(outMetrics,
+                    CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+            if (mMayAdjustByFixedRotation) {
+                getDisplayAdjustments().adjustMetrics(outMetrics, mDisplayInfo.rotation);
+            }
+        }
+    }
+
+    /**
+     * Determines if {@link WindowConfiguration#getMaxBounds()} should be reported as the
+     * display dimensions. The max bounds field may be smaller than the logical dimensions
+     * when apps need to be sandboxed.
+     *
+     * Depends upon {@link WindowConfiguration#getMaxBounds()} being set in
+     * {@link com.android.server.wm.ConfigurationContainer#providesMaxBounds()}. In most cases, this
+     * value reflects the size of the current DisplayArea.
+     * @return {@code true} when max bounds should be applied.
+     */
+    private boolean shouldReportMaxBounds() {
+        if (mResources == null) {
+            return false;
+        }
+        final Configuration config = mResources.getConfiguration();
+        // TODO(b/179308296) Temporarily exclude Launcher from being given max bounds, by checking
+        // if the caller is the recents component.
+        return config != null && !config.windowConfiguration.getMaxBounds().isEmpty()
+                && !isRecentsComponent();
+    }
+
+    /**
+     * Returns {@code true} when the calling package is the recents component.
+     * TODO(b/179308296) Remove once Launcher addresses issue
+     */
+    boolean isRecentsComponent() {
+        if (mIsRecentsComponent.isPresent()) {
+            return mIsRecentsComponent.get();
+        }
+        if (mResources == null) {
+            return false;
+        }
+        try {
+            String recentsComponent = mResources.getString(R.string.config_recentsComponentName);
+            if (recentsComponent == null) {
+                return false;
+            }
+            String recentsPackage = ComponentName.unflattenFromString(recentsComponent)
+                    .getPackageName();
+            mIsRecentsComponent = Optional.of(recentsPackage != null
+                    && recentsPackage.equals(ActivityThread.currentPackageName()));
+            return mIsRecentsComponent.get();
+        } catch (Resources.NotFoundException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Gets the state of the display, such as whether it is on or off.
+     *
+     * @return The state of the display: one of {@link #STATE_OFF}, {@link #STATE_ON},
+     * {@link #STATE_DOZE}, {@link #STATE_DOZE_SUSPEND}, {@link #STATE_ON_SUSPEND}, or
+     * {@link #STATE_UNKNOWN}.
+     */
+    public int getState() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            return mIsValid ? mDisplayInfo.state : STATE_UNKNOWN;
+        }
+    }
+
+    /**
+     * Returns true if the specified UID has access to this display.
+     * @hide
+     */
+    @TestApi
+    public boolean hasAccess(int uid) {
+        return hasAccess(uid, mFlags, mOwnerUid, mDisplayId);
+    }
+
+    /** @hide */
+    public static boolean hasAccess(int uid, int flags, int ownerUid, int displayId) {
+        return (flags & Display.FLAG_PRIVATE) == 0
+                || uid == ownerUid
+                || uid == Process.SYSTEM_UID
+                || uid == 0
+                // Check if the UID is present on given display.
+                || DisplayManagerGlobal.getInstance().isUidPresentOnDisplay(uid, displayId);
+    }
+
+    /**
+     * Returns true if the display is a public presentation display.
+     * @hide
+     */
+    public boolean isPublicPresentation() {
+        return (mFlags & (Display.FLAG_PRIVATE | Display.FLAG_PRESENTATION)) ==
+                Display.FLAG_PRESENTATION;
+    }
+
+    /**
+     * @return {@code true} if the display is a trusted display.
+     *
+     * @see #FLAG_TRUSTED
+     * @hide
+     */
+    public boolean isTrusted() {
+        return (mFlags & FLAG_TRUSTED) == FLAG_TRUSTED;
+    }
+
+    private void updateDisplayInfoLocked() {
+        // Note: The display manager caches display info objects on our behalf.
+        DisplayInfo newInfo = mGlobal.getDisplayInfo(mDisplayId);
+        if (newInfo == null) {
+            // Preserve the old mDisplayInfo after the display is removed.
+            if (mIsValid) {
+                mIsValid = false;
+                if (DEBUG) {
+                    Log.d(TAG, "Logical display " + mDisplayId + " was removed.");
+                }
+            }
+        } else {
+            // Use the new display info.  (It might be the same object if nothing changed.)
+            mDisplayInfo = newInfo;
+            if (!mIsValid) {
+                mIsValid = true;
+                if (DEBUG) {
+                    Log.d(TAG, "Logical display " + mDisplayId + " was recreated.");
+                }
+            }
+        }
+
+        mMayAdjustByFixedRotation = mResources != null
+                && mResources.hasOverrideDisplayAdjustments();
+    }
+
+    private void updateCachedAppSizeIfNeededLocked() {
+        long now = SystemClock.uptimeMillis();
+        if (now > mLastCachedAppSizeUpdate + CACHED_APP_SIZE_DURATION_MILLIS) {
+            updateDisplayInfoLocked();
+            mDisplayInfo.getAppMetrics(mTempMetrics, getDisplayAdjustments());
+            mCachedAppWidthCompat = mTempMetrics.widthPixels;
+            mCachedAppHeightCompat = mTempMetrics.heightPixels;
+            mLastCachedAppSizeUpdate = now;
+        }
+    }
+
+    // For debugging purposes
+    @Override
+    public String toString() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            final DisplayAdjustments adjustments = getDisplayAdjustments();
+            mDisplayInfo.getAppMetrics(mTempMetrics, adjustments);
+            return "Display id " + mDisplayId + ": " + mDisplayInfo
+                    + (mMayAdjustByFixedRotation
+                            ? (", " + adjustments.getFixedRotationAdjustments() + ", ") : ", ")
+                    + mTempMetrics + ", isValid=" + mIsValid;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public static String typeToString(int type) {
+        switch (type) {
+            case TYPE_UNKNOWN:
+                return "UNKNOWN";
+            case TYPE_INTERNAL:
+                return "INTERNAL";
+            case TYPE_EXTERNAL:
+                return "EXTERNAL";
+            case TYPE_WIFI:
+                return "WIFI";
+            case TYPE_OVERLAY:
+                return "OVERLAY";
+            case TYPE_VIRTUAL:
+                return "VIRTUAL";
+            default:
+                return Integer.toString(type);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public static String stateToString(int state) {
+        switch (state) {
+            case STATE_UNKNOWN:
+                return "UNKNOWN";
+            case STATE_OFF:
+                return "OFF";
+            case STATE_ON:
+                return "ON";
+            case STATE_DOZE:
+                return "DOZE";
+            case STATE_DOZE_SUSPEND:
+                return "DOZE_SUSPEND";
+            case STATE_VR:
+                return "VR";
+            case STATE_ON_SUSPEND:
+                return "ON_SUSPEND";
+            default:
+                return Integer.toString(state);
+        }
+    }
+
+    /**
+     * Returns true if display updates may be suspended while in the specified
+     * display power state. In SUSPEND states, updates are absolutely forbidden.
+     * @hide
+     */
+    public static boolean isSuspendedState(int state) {
+        return state == STATE_OFF || state == STATE_DOZE_SUSPEND || state == STATE_ON_SUSPEND;
+    }
+
+    /**
+     * Returns true if the display may be in a reduced operating mode while in the
+     * specified display power state.
+     * @hide
+     */
+    public static boolean isDozeState(int state) {
+        return state == STATE_DOZE || state == STATE_DOZE_SUSPEND;
+    }
+
+    /**
+     * Returns true if the display is in active state such as {@link #STATE_ON}
+     * or {@link #STATE_VR}.
+     * @hide
+     */
+    public static boolean isActiveState(int state) {
+        return state == STATE_ON || state == STATE_VR;
+    }
+
+    /**
+     * Returns true if the display is in an off state such as {@link #STATE_OFF}.
+     * @hide
+     */
+    public static boolean isOffState(int state) {
+        return state == STATE_OFF;
+    }
+
+    /**
+     * Returns true if the display is in an on state such as {@link #STATE_ON}
+     * or {@link #STATE_VR} or {@link #STATE_ON_SUSPEND}.
+     * @hide
+     */
+    public static boolean isOnState(int state) {
+        return state == STATE_ON || state == STATE_VR || state == STATE_ON_SUSPEND;
+    }
+
+    /**
+     * A mode supported by a given display.
+     *
+     * @see Display#getSupportedModes()
+     */
+    public static final class Mode implements Parcelable {
+        /**
+         * @hide
+         */
+        public static final Mode[] EMPTY_ARRAY = new Mode[0];
+
+        private final int mModeId;
+        private final int mWidth;
+        private final int mHeight;
+        private final float mRefreshRate;
+        @NonNull
+        private final float[] mAlternativeRefreshRates;
+
+        /**
+         * @hide
+         */
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        public Mode(int modeId, int width, int height, float refreshRate) {
+            this(modeId, width, height, refreshRate, new float[0]);
+        }
+
+        /**
+         * @hide
+         */
+        public Mode(int modeId, int width, int height, float refreshRate,
+                float[] alternativeRefreshRates) {
+            mModeId = modeId;
+            mWidth = width;
+            mHeight = height;
+            mRefreshRate = refreshRate;
+            mAlternativeRefreshRates =
+                    Arrays.copyOf(alternativeRefreshRates, alternativeRefreshRates.length);
+            Arrays.sort(mAlternativeRefreshRates);
+        }
+
+        /**
+         * Returns this mode's id.
+         */
+        public int getModeId() {
+            return mModeId;
+        }
+
+        /**
+         * Returns the physical width of the display in pixels when configured in this mode's
+         * resolution.
+         * <p>
+         * Note that due to application UI scaling, the number of pixels made available to
+         * applications when the mode is active (as reported by {@link Display#getWidth()} may
+         * differ from the mode's actual resolution (as reported by this function).
+         * <p>
+         * For example, applications running on a 4K display may have their UI laid out and rendered
+         * in 1080p and then scaled up. Applications can take advantage of the extra resolution by
+         * rendering content through a {@link android.view.SurfaceView} using full size buffers.
+         */
+        public int getPhysicalWidth() {
+            return mWidth;
+        }
+
+        /**
+         * Returns the physical height of the display in pixels when configured in this mode's
+         * resolution.
+         * <p>
+         * Note that due to application UI scaling, the number of pixels made available to
+         * applications when the mode is active (as reported by {@link Display#getHeight()} may
+         * differ from the mode's actual resolution (as reported by this function).
+         * <p>
+         * For example, applications running on a 4K display may have their UI laid out and rendered
+         * in 1080p and then scaled up. Applications can take advantage of the extra resolution by
+         * rendering content through a {@link android.view.SurfaceView} using full size buffers.
+         */
+        public int getPhysicalHeight() {
+            return mHeight;
+        }
+
+        /**
+         * Returns the refresh rate in frames per second.
+         */
+        public float getRefreshRate() {
+            return mRefreshRate;
+        }
+
+        /**
+         * Returns an array of refresh rates which can be switched to seamlessly.
+         * <p>
+         * A seamless switch is one without visual interruptions, such as a black screen for
+         * a second or two.
+         * <p>
+         * Presence in this list does not guarantee a switch will occur to the desired
+         * refresh rate, but rather, if a switch does occur to a refresh rate in this list,
+         * it is guaranteed to be seamless.
+         * <p>
+         * The binary relation "refresh rate X is alternative to Y" is non-reflexive,
+         * symmetric and transitive. For example the mode 1920x1080 60Hz, will never have an
+         * alternative refresh rate of 60Hz. If 1920x1080 60Hz has an alternative of 50Hz
+         * then 1920x1080 50Hz will have alternative refresh rate of 60Hz. If 1920x1080 60Hz
+         * has an alternative of 50Hz and 1920x1080 50Hz has an alternative of 24Hz, then 1920x1080
+         * 60Hz will also have an alternative of 24Hz.
+         *
+         * @see Surface#setFrameRate
+         * @see SurfaceControl.Transaction#setFrameRate
+         */
+        @NonNull
+        public float[] getAlternativeRefreshRates() {
+            return mAlternativeRefreshRates;
+        }
+
+        /**
+         * Returns {@code true} if this mode matches the given parameters.
+         *
+         * @hide
+         */
+        public boolean matches(int width, int height, float refreshRate) {
+            return mWidth == width &&
+                    mHeight == height &&
+                    Float.floatToIntBits(mRefreshRate) == Float.floatToIntBits(refreshRate);
+        }
+
+        /**
+         * Returns {@code true} if this mode equals to the other mode in all parameters except
+         * the refresh rate.
+         *
+         * @hide
+         */
+        public boolean equalsExceptRefreshRate(@Nullable Display.Mode other) {
+            return mWidth == other.mWidth && mHeight == other.mHeight;
+        }
+
+        @Override
+        public boolean equals(@Nullable Object other) {
+            if (this == other) {
+                return true;
+            }
+            if (!(other instanceof Mode)) {
+                return false;
+            }
+            Mode that = (Mode) other;
+            return mModeId == that.mModeId && matches(that.mWidth, that.mHeight, that.mRefreshRate)
+                    && Arrays.equals(mAlternativeRefreshRates, that.mAlternativeRefreshRates);
+        }
+
+        @Override
+        public int hashCode() {
+            int hash = 1;
+            hash = hash * 17 + mModeId;
+            hash = hash * 17 + mWidth;
+            hash = hash * 17 + mHeight;
+            hash = hash * 17 + Float.floatToIntBits(mRefreshRate);
+            hash = hash * 17 + Arrays.hashCode(mAlternativeRefreshRates);
+            return hash;
+        }
+
+        @Override
+        public String toString() {
+            return new StringBuilder("{")
+                    .append("id=").append(mModeId)
+                    .append(", width=").append(mWidth)
+                    .append(", height=").append(mHeight)
+                    .append(", fps=").append(mRefreshRate)
+                    .append(", alternativeRefreshRates=")
+                    .append(Arrays.toString(mAlternativeRefreshRates))
+                    .append("}")
+                    .toString();
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        private Mode(Parcel in) {
+            this(in.readInt(), in.readInt(), in.readInt(), in.readFloat(), in.createFloatArray());
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int parcelableFlags) {
+            out.writeInt(mModeId);
+            out.writeInt(mWidth);
+            out.writeInt(mHeight);
+            out.writeFloat(mRefreshRate);
+            out.writeFloatArray(mAlternativeRefreshRates);
+        }
+
+        @SuppressWarnings("hiding")
+        public static final @android.annotation.NonNull Parcelable.Creator<Mode> CREATOR
+                = new Parcelable.Creator<Mode>() {
+            @Override
+            public Mode createFromParcel(Parcel in) {
+                return new Mode(in);
+            }
+
+            @Override
+            public Mode[] newArray(int size) {
+                return new Mode[size];
+            }
+        };
+    }
+
+    /**
+     * Encapsulates the HDR capabilities of a given display.
+     * For example, what HDR types it supports and details about the desired luminance data.
+     * <p>You can get an instance for a given {@link Display} object with
+     * {@link Display#getHdrCapabilities getHdrCapabilities()}.
+     */
+    public static final class HdrCapabilities implements Parcelable {
+        /**
+         * Invalid luminance value.
+         */
+        public static final float INVALID_LUMINANCE = -1;
+        /**
+         * Dolby Vision high dynamic range (HDR) display.
+         */
+        public static final int HDR_TYPE_DOLBY_VISION = 1;
+        /**
+         * HDR10 display.
+         */
+        public static final int HDR_TYPE_HDR10 = 2;
+        /**
+         * Hybrid Log-Gamma HDR display.
+         */
+        public static final int HDR_TYPE_HLG = 3;
+
+        /**
+         * HDR10+ display.
+         */
+        public static final int HDR_TYPE_HDR10_PLUS = 4;
+
+        /** @hide */
+        public static final int[] HDR_TYPES = {
+                HDR_TYPE_DOLBY_VISION,
+                HDR_TYPE_HDR10,
+                HDR_TYPE_HLG,
+                HDR_TYPE_HDR10_PLUS
+        };
+
+        /** @hide */
+        @IntDef(prefix = { "HDR_TYPE_" }, value = {
+                HDR_TYPE_DOLBY_VISION,
+                HDR_TYPE_HDR10,
+                HDR_TYPE_HLG,
+                HDR_TYPE_HDR10_PLUS,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface HdrType {}
+
+        private @HdrType int[] mSupportedHdrTypes = new int[0];
+        private float mMaxLuminance = INVALID_LUMINANCE;
+        private float mMaxAverageLuminance = INVALID_LUMINANCE;
+        private float mMinLuminance = INVALID_LUMINANCE;
+
+        /**
+         * @hide
+         */
+        public HdrCapabilities() {
+        }
+
+        /**
+         * @hide
+         */
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        public HdrCapabilities(int[] supportedHdrTypes, float maxLuminance,
+                float maxAverageLuminance, float minLuminance) {
+            mSupportedHdrTypes = supportedHdrTypes;
+            Arrays.sort(mSupportedHdrTypes);
+            mMaxLuminance = maxLuminance;
+            mMaxAverageLuminance = maxAverageLuminance;
+            mMinLuminance = minLuminance;
+        }
+
+        /**
+         * Gets the supported HDR types of this display.
+         * Returns empty array if HDR is not supported by the display.
+         */
+        public @HdrType int[] getSupportedHdrTypes() {
+            return mSupportedHdrTypes;
+        }
+        /**
+         * Returns the desired content max luminance data in cd/m2 for this display.
+         */
+        public float getDesiredMaxLuminance() {
+            return mMaxLuminance;
+        }
+        /**
+         * Returns the desired content max frame-average luminance data in cd/m2 for this display.
+         */
+        public float getDesiredMaxAverageLuminance() {
+            return mMaxAverageLuminance;
+        }
+        /**
+         * Returns the desired content min luminance data in cd/m2 for this display.
+         */
+        public float getDesiredMinLuminance() {
+            return mMinLuminance;
+        }
+
+        @Override
+        public boolean equals(@Nullable Object other) {
+            if (this == other) {
+                return true;
+            }
+
+            if (!(other instanceof HdrCapabilities)) {
+                return false;
+            }
+            HdrCapabilities that = (HdrCapabilities) other;
+
+            return Arrays.equals(mSupportedHdrTypes, that.mSupportedHdrTypes)
+                && mMaxLuminance == that.mMaxLuminance
+                && mMaxAverageLuminance == that.mMaxAverageLuminance
+                && mMinLuminance == that.mMinLuminance;
+        }
+
+        @Override
+        public int hashCode() {
+            int hash = 23;
+            hash = hash * 17 + Arrays.hashCode(mSupportedHdrTypes);
+            hash = hash * 17 + Float.floatToIntBits(mMaxLuminance);
+            hash = hash * 17 + Float.floatToIntBits(mMaxAverageLuminance);
+            hash = hash * 17 + Float.floatToIntBits(mMinLuminance);
+            return hash;
+        }
+
+        public static final @android.annotation.NonNull Creator<HdrCapabilities> CREATOR = new Creator<HdrCapabilities>() {
+            @Override
+            public HdrCapabilities createFromParcel(Parcel source) {
+                return new HdrCapabilities(source);
+            }
+
+            @Override
+            public HdrCapabilities[] newArray(int size) {
+                return new HdrCapabilities[size];
+            }
+        };
+
+        private HdrCapabilities(Parcel source) {
+            readFromParcel(source);
+        }
+
+        /**
+         * @hide
+         */
+        public void readFromParcel(Parcel source) {
+            int types = source.readInt();
+            mSupportedHdrTypes = new int[types];
+            for (int i = 0; i < types; ++i) {
+                mSupportedHdrTypes[i] = source.readInt();
+            }
+            mMaxLuminance = source.readFloat();
+            mMaxAverageLuminance = source.readFloat();
+            mMinLuminance = source.readFloat();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mSupportedHdrTypes.length);
+            for (int i = 0; i < mSupportedHdrTypes.length; ++i) {
+                dest.writeInt(mSupportedHdrTypes[i]);
+            }
+            dest.writeFloat(mMaxLuminance);
+            dest.writeFloat(mMaxAverageLuminance);
+            dest.writeFloat(mMinLuminance);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public String toString() {
+            return "HdrCapabilities{"
+                    + "mSupportedHdrTypes=" + Arrays.toString(mSupportedHdrTypes)
+                    + ", mMaxLuminance=" + mMaxLuminance
+                    + ", mMaxAverageLuminance=" + mMaxAverageLuminance
+                    + ", mMinLuminance=" + mMinLuminance + '}';
+        }
+    }
+}
diff --git a/android/view/DisplayAddress.java b/android/view/DisplayAddress.java
new file mode 100644
index 0000000..91a24c6
--- /dev/null
+++ b/android/view/DisplayAddress.java
@@ -0,0 +1,215 @@
+/*
+ * 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.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** Display identifier that is stable across reboots.
+ *
+ * @hide
+ */
+public abstract class DisplayAddress implements Parcelable {
+    /**
+     * Creates an address for a physical display given its stable ID.
+     *
+     * A physical display ID is stable if the display can be identified using EDID information.
+     *
+     * @param physicalDisplayId A physical display ID.
+     * @return The {@link Physical} address.
+     * @see SurfaceControl#getPhysicalDisplayIds
+     */
+    @NonNull
+    public static Physical fromPhysicalDisplayId(long physicalDisplayId) {
+        return new Physical(physicalDisplayId);
+    }
+
+    /**
+     * Creates an address for a physical display given its port and model.
+     *
+     * @param port A port in the range [0, 255].
+     * @param model A positive integer, or {@code null} if the model cannot be identified.
+     * @return The {@link Physical} address.
+     */
+    @NonNull
+    public static Physical fromPortAndModel(int port, Long model) {
+        return new Physical(port, model);
+    }
+
+    /**
+     * Creates an address for a network display given its MAC address.
+     *
+     * @param macAddress A MAC address in colon notation.
+     * @return The {@link Network} address.
+     */
+    @NonNull
+    public static Network fromMacAddress(String macAddress) {
+        return new Network(macAddress);
+    }
+
+    /**
+     * Address for a physically connected display.
+     *
+     * A {@link Physical} address is represented by a 64-bit identifier combining the port and model
+     * of a display. The port, located in the least significant byte, uniquely identifies a physical
+     * connector on the device for display output like eDP or HDMI. The model, located in the upper
+     * bits, uniquely identifies a display model across manufacturers by encoding EDID information.
+     * While the port is always stable, the model may not be available if EDID identification is not
+     * supported by the platform, in which case the address is not unique.
+     */
+    public static final class Physical extends DisplayAddress {
+        private static final long UNKNOWN_MODEL = 0;
+        private static final int MODEL_SHIFT = 8;
+
+        private final long mPhysicalDisplayId;
+
+        /**
+         * Stable display ID combining port and model.
+         *
+         * @return An ID in the range [0, 2^64) interpreted as signed.
+         * @see SurfaceControl#getPhysicalDisplayIds
+         */
+        public long getPhysicalDisplayId() {
+            return mPhysicalDisplayId;
+        }
+
+        /**
+         * Physical port to which the display is connected.
+         *
+         * @return A port in the range [0, 255].
+         */
+        public int getPort() {
+            return (int) (mPhysicalDisplayId & 0xFF);
+        }
+
+        /**
+         * Model identifier unique across manufacturers.
+         *
+         * @return A positive integer, or {@code null} if the model cannot be identified.
+         */
+        @Nullable
+        public Long getModel() {
+            final long model = mPhysicalDisplayId >>> MODEL_SHIFT;
+            return model == UNKNOWN_MODEL ? null : model;
+        }
+
+        @Override
+        public boolean equals(@Nullable Object other) {
+            return other instanceof Physical
+                    && mPhysicalDisplayId == ((Physical) other).mPhysicalDisplayId;
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder builder = new StringBuilder("{")
+                    .append("port=").append(getPort());
+
+            final Long model = getModel();
+            if (model != null) {
+                builder.append(", model=0x").append(Long.toHexString(model));
+            }
+
+            return builder.append("}").toString();
+        }
+
+        @Override
+        public int hashCode() {
+            return Long.hashCode(mPhysicalDisplayId);
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeLong(mPhysicalDisplayId);
+        }
+
+        private Physical(long physicalDisplayId) {
+            mPhysicalDisplayId = physicalDisplayId;
+        }
+
+        private Physical(int port, Long model) {
+            if (port < 0 || port > 255) {
+                throw new IllegalArgumentException("The port should be in the interval [0, 255]");
+            }
+            mPhysicalDisplayId = Integer.toUnsignedLong(port)
+                    | (model == null ? UNKNOWN_MODEL : (model << MODEL_SHIFT));
+        }
+
+        public static final @NonNull Parcelable.Creator<Physical> CREATOR =
+                new Parcelable.Creator<Physical>() {
+                    @Override
+                    public Physical createFromParcel(Parcel in) {
+                        return new Physical(in.readLong());
+                    }
+
+                    @Override
+                    public Physical[] newArray(int size) {
+                        return new Physical[size];
+                    }
+                };
+    }
+
+    /**
+     * Address for a network-connected display.
+     */
+    public static final class Network extends DisplayAddress {
+        private final String mMacAddress;
+
+        @Override
+        public boolean equals(@Nullable Object other) {
+            return other instanceof Network && mMacAddress.equals(((Network) other).mMacAddress);
+        }
+
+        @Override
+        public String toString() {
+            return mMacAddress;
+        }
+
+        @Override
+        public int hashCode() {
+            return mMacAddress.hashCode();
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeString(mMacAddress);
+        }
+
+        private Network(String macAddress) {
+            mMacAddress = macAddress;
+        }
+
+        public static final @NonNull Parcelable.Creator<Network> CREATOR =
+                new Parcelable.Creator<Network>() {
+                    @Override
+                    public Network createFromParcel(Parcel in) {
+                        return new Network(in.readString());
+                    }
+
+                    @Override
+                    public Network[] newArray(int size) {
+                        return new Network[size];
+                    }
+                };
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/android/view/DisplayAdjustments.java b/android/view/DisplayAdjustments.java
new file mode 100644
index 0000000..e307eff
--- /dev/null
+++ b/android/view/DisplayAdjustments.java
@@ -0,0 +1,291 @@
+/*
+ * 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.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.DisplayMetrics;
+
+import java.util.Objects;
+
+/** @hide */
+public class DisplayAdjustments {
+    public static final DisplayAdjustments DEFAULT_DISPLAY_ADJUSTMENTS = new DisplayAdjustments();
+
+    private volatile CompatibilityInfo mCompatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
+    private final Configuration mConfiguration = new Configuration(Configuration.EMPTY);
+    private FixedRotationAdjustments mFixedRotationAdjustments;
+
+    @UnsupportedAppUsage
+    public DisplayAdjustments() {
+    }
+
+    public DisplayAdjustments(@Nullable Configuration configuration) {
+        if (configuration != null) {
+            mConfiguration.setTo(configuration);
+        }
+    }
+
+    public DisplayAdjustments(@NonNull DisplayAdjustments daj) {
+        setCompatibilityInfo(daj.mCompatInfo);
+        mConfiguration.setTo(daj.getConfiguration());
+        mFixedRotationAdjustments = daj.mFixedRotationAdjustments;
+    }
+
+    @UnsupportedAppUsage
+    public void setCompatibilityInfo(@Nullable CompatibilityInfo compatInfo) {
+        if (this == DEFAULT_DISPLAY_ADJUSTMENTS) {
+            throw new IllegalArgumentException(
+                    "setCompatbilityInfo: Cannot modify DEFAULT_DISPLAY_ADJUSTMENTS");
+        }
+        if (compatInfo != null && (compatInfo.isScalingRequired()
+                || !compatInfo.supportsScreen())) {
+            mCompatInfo = compatInfo;
+        } else {
+            mCompatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
+        }
+    }
+
+    public CompatibilityInfo getCompatibilityInfo() {
+        return mCompatInfo;
+    }
+
+    /**
+     * Updates the configuration for the DisplayAdjustments with new configuration.
+     * Default to EMPTY configuration if new configuration is {@code null}
+     * @param configuration new configuration
+     * @throws IllegalArgumentException if trying to modify DEFAULT_DISPLAY_ADJUSTMENTS
+     */
+    public void setConfiguration(@Nullable Configuration configuration) {
+        if (this == DEFAULT_DISPLAY_ADJUSTMENTS) {
+            throw new IllegalArgumentException(
+                    "setConfiguration: Cannot modify DEFAULT_DISPLAY_ADJUSTMENTS");
+        }
+        mConfiguration.setTo(configuration != null ? configuration : Configuration.EMPTY);
+    }
+
+    @UnsupportedAppUsage
+    @NonNull
+    public Configuration getConfiguration() {
+        return mConfiguration;
+    }
+
+    public void setFixedRotationAdjustments(FixedRotationAdjustments fixedRotationAdjustments) {
+        mFixedRotationAdjustments = fixedRotationAdjustments;
+    }
+
+    public FixedRotationAdjustments getFixedRotationAdjustments() {
+        return mFixedRotationAdjustments;
+    }
+
+    /** Returns {@code false} if the width and height of display should swap. */
+    private boolean noFlip(@Surface.Rotation int realRotation) {
+        final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments;
+        if (rotationAdjustments == null) {
+            return true;
+        }
+        // Check if the delta is rotated by 90 degrees.
+        return (realRotation - rotationAdjustments.mRotation + 4) % 2 == 0;
+    }
+
+    /** Adjusts the given size if possible. */
+    public void adjustSize(@NonNull Point size, @Surface.Rotation int realRotation) {
+        if (noFlip(realRotation)) {
+            return;
+        }
+        final int w = size.x;
+        size.x = size.y;
+        size.y = w;
+    }
+
+    /** Adjusts the given metrics if possible. */
+    public void adjustMetrics(@NonNull DisplayMetrics metrics, @Surface.Rotation int realRotation) {
+        if (noFlip(realRotation)) {
+            return;
+        }
+        int w = metrics.widthPixels;
+        metrics.widthPixels = metrics.heightPixels;
+        metrics.heightPixels = w;
+
+        w = metrics.noncompatWidthPixels;
+        metrics.noncompatWidthPixels = metrics.noncompatHeightPixels;
+        metrics.noncompatHeightPixels = w;
+    }
+
+    /** Adjusts global display metrics that is available to applications. */
+    public void adjustGlobalAppMetrics(@NonNull DisplayMetrics metrics) {
+        final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments;
+        if (rotationAdjustments == null) {
+            return;
+        }
+        metrics.noncompatWidthPixels = metrics.widthPixels = rotationAdjustments.mAppWidth;
+        metrics.noncompatHeightPixels = metrics.heightPixels = rotationAdjustments.mAppHeight;
+    }
+
+    /** Returns the adjusted cutout if available. Otherwise the original cutout is returned. */
+    @Nullable
+    public DisplayCutout getDisplayCutout(@Nullable DisplayCutout realCutout) {
+        final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments;
+        return rotationAdjustments != null && rotationAdjustments.mRotatedDisplayCutout != null
+                ? rotationAdjustments.mRotatedDisplayCutout
+                : realCutout;
+    }
+
+    /**
+     * Returns the adjusted {@link RoundedCorners} if available. Otherwise the original
+     * {@link RoundedCorners} is returned.
+     */
+    @Nullable
+    public RoundedCorners adjustRoundedCorner(@Nullable RoundedCorners realRoundedCorners,
+            @Surface.Rotation int realRotation, int displayWidth, int displayHeight) {
+        final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments;
+        if (realRoundedCorners == null || rotationAdjustments == null
+                || rotationAdjustments.mRotation == realRotation) {
+            return realRoundedCorners;
+        }
+
+        return realRoundedCorners.rotate(
+                rotationAdjustments.mRotation, displayWidth, displayHeight);
+    }
+
+    /** Returns the adjusted rotation if available. Otherwise the original rotation is returned. */
+    @Surface.Rotation
+    public int getRotation(@Surface.Rotation int realRotation) {
+        final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments;
+        return rotationAdjustments != null ? rotationAdjustments.mRotation : realRotation;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 17;
+        hash = hash * 31 + Objects.hashCode(mCompatInfo);
+        hash = hash * 31 + Objects.hashCode(mConfiguration);
+        hash = hash * 31 + Objects.hashCode(mFixedRotationAdjustments);
+        return hash;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (!(o instanceof DisplayAdjustments)) {
+            return false;
+        }
+        DisplayAdjustments daj = (DisplayAdjustments)o;
+        return Objects.equals(daj.mCompatInfo, mCompatInfo)
+                && Objects.equals(daj.mConfiguration, mConfiguration)
+                && Objects.equals(daj.mFixedRotationAdjustments, mFixedRotationAdjustments);
+    }
+
+    /**
+     * An application can be launched in different rotation than the real display. This class
+     * provides the information to adjust the values returned by {@link Display}.
+     * @hide
+     */
+    public static class FixedRotationAdjustments implements Parcelable {
+        /** The application-based rotation. */
+        @Surface.Rotation
+        final int mRotation;
+
+        /**
+         * The rotated {@link DisplayInfo#appWidth}. The value cannot be simply swapped according
+         * to rotation because it minus the region of screen decorations.
+         */
+        final int mAppWidth;
+
+        /** The rotated {@link DisplayInfo#appHeight}. */
+        final int mAppHeight;
+
+        /** Non-null if the device has cutout. */
+        @Nullable
+        final DisplayCutout mRotatedDisplayCutout;
+
+        public FixedRotationAdjustments(@Surface.Rotation int rotation, int appWidth, int appHeight,
+                DisplayCutout cutout) {
+            mRotation = rotation;
+            mAppWidth = appWidth;
+            mAppHeight = appHeight;
+            mRotatedDisplayCutout = cutout;
+        }
+
+        @Override
+        public int hashCode() {
+            int hash = 17;
+            hash = hash * 31 + mRotation;
+            hash = hash * 31 + mAppWidth;
+            hash = hash * 31 + mAppHeight;
+            hash = hash * 31 + Objects.hashCode(mRotatedDisplayCutout);
+            return hash;
+        }
+
+        @Override
+        public boolean equals(@Nullable Object o) {
+            if (!(o instanceof FixedRotationAdjustments)) {
+                return false;
+            }
+            final FixedRotationAdjustments other = (FixedRotationAdjustments) o;
+            return mRotation == other.mRotation
+                    && mAppWidth == other.mAppWidth && mAppHeight == other.mAppHeight
+                    && Objects.equals(mRotatedDisplayCutout, other.mRotatedDisplayCutout);
+        }
+
+        @Override
+        public String toString() {
+            return "FixedRotationAdjustments{rotation=" + Surface.rotationToString(mRotation)
+                    + " appWidth=" + mAppWidth + " appHeight=" + mAppHeight
+                    + " cutout=" + mRotatedDisplayCutout + "}";
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mRotation);
+            dest.writeInt(mAppWidth);
+            dest.writeInt(mAppHeight);
+            dest.writeTypedObject(
+                    new DisplayCutout.ParcelableWrapper(mRotatedDisplayCutout), flags);
+        }
+
+        private FixedRotationAdjustments(Parcel in) {
+            mRotation = in.readInt();
+            mAppWidth = in.readInt();
+            mAppHeight = in.readInt();
+            final DisplayCutout.ParcelableWrapper cutoutWrapper =
+                    in.readTypedObject(DisplayCutout.ParcelableWrapper.CREATOR);
+            mRotatedDisplayCutout = cutoutWrapper != null ? cutoutWrapper.get() : null;
+        }
+
+        public static final Creator<FixedRotationAdjustments> CREATOR =
+                new Creator<FixedRotationAdjustments>() {
+            public FixedRotationAdjustments createFromParcel(Parcel in) {
+                return new FixedRotationAdjustments(in);
+            }
+
+            public FixedRotationAdjustments[] newArray(int size) {
+                return new FixedRotationAdjustments[size];
+            }
+        };
+    }
+}
diff --git a/android/view/DisplayCutout.java b/android/view/DisplayCutout.java
new file mode 100644
index 0000000..e1a4402
--- /dev/null
+++ b/android/view/DisplayCutout.java
@@ -0,0 +1,1142 @@
+/*
+ * Copyright 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.view;
+
+import static android.util.DisplayMetrics.DENSITY_DEFAULT;
+import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
+import static android.view.DisplayCutoutProto.BOUND_BOTTOM;
+import static android.view.DisplayCutoutProto.BOUND_LEFT;
+import static android.view.DisplayCutoutProto.BOUND_RIGHT;
+import static android.view.DisplayCutoutProto.BOUND_TOP;
+import static android.view.DisplayCutoutProto.INSETS;
+import static android.view.Surface.ROTATION_0;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Resources;
+import android.graphics.Insets;
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Pair;
+import android.util.RotationUtils;
+import android.util.proto.ProtoOutputStream;
+import android.view.Surface.Rotation;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represents the area of the display that is not functional for displaying content.
+ *
+ * <p>{@code DisplayCutout} is immutable.
+ */
+public final class DisplayCutout {
+
+    private static final String TAG = "DisplayCutout";
+
+    /**
+     * Category for overlays that allow emulating a display cutout on devices that don't have
+     * one.
+     *
+     * @see android.content.om.IOverlayManager
+     * @hide
+     */
+    public static final String EMULATION_OVERLAY_CATEGORY =
+            "com.android.internal.display_cutout_emulation";
+
+    private static final Rect ZERO_RECT = new Rect();
+    private static final CutoutPathParserInfo EMPTY_PARSER_INFO = new CutoutPathParserInfo(
+            0 /* displayWidth */, 0 /* displayHeight */, 0f /* density */, "" /* cutoutSpec */,
+            0 /* rotation */, 0f /* scale */);
+
+    /**
+     * An instance where {@link #isEmpty()} returns {@code true}.
+     *
+     * @hide
+     */
+    public static final DisplayCutout NO_CUTOUT = new DisplayCutout(
+            ZERO_RECT, Insets.NONE, ZERO_RECT, ZERO_RECT, ZERO_RECT, ZERO_RECT, EMPTY_PARSER_INFO,
+            false /* copyArguments */);
+
+
+    private static final Pair<Path, DisplayCutout> NULL_PAIR = new Pair<>(null, null);
+    private static final Object CACHE_LOCK = new Object();
+
+    @GuardedBy("CACHE_LOCK")
+    private static String sCachedSpec;
+    @GuardedBy("CACHE_LOCK")
+    private static int sCachedDisplayWidth;
+    @GuardedBy("CACHE_LOCK")
+    private static int sCachedDisplayHeight;
+    @GuardedBy("CACHE_LOCK")
+    private static float sCachedDensity;
+    @GuardedBy("CACHE_LOCK")
+    private static Pair<Path, DisplayCutout> sCachedCutout = NULL_PAIR;
+    @GuardedBy("CACHE_LOCK")
+    private static Insets sCachedWaterfallInsets;
+
+    @GuardedBy("CACHE_LOCK")
+    private static CutoutPathParserInfo sCachedCutoutPathParserInfo;
+    @GuardedBy("CACHE_LOCK")
+    private static Path sCachedCutoutPath;
+
+    private final Rect mSafeInsets;
+    @NonNull
+    private final Insets mWaterfallInsets;
+
+    /**
+     * The bound is at the left of the screen.
+     * @hide
+     */
+    public static final int BOUNDS_POSITION_LEFT = 0;
+
+    /**
+     * The bound is at the top of the screen.
+     * @hide
+     */
+    public static final int BOUNDS_POSITION_TOP = 1;
+
+    /**
+     * The bound is at the right of the screen.
+     * @hide
+     */
+    public static final int BOUNDS_POSITION_RIGHT = 2;
+
+    /**
+     * The bound is at the bottom of the screen.
+     * @hide
+     */
+    public static final int BOUNDS_POSITION_BOTTOM = 3;
+
+    /**
+     * The number of possible positions at which bounds can be located.
+     * @hide
+     */
+    public static final int BOUNDS_POSITION_LENGTH = 4;
+
+    /** @hide */
+    @IntDef(prefix = { "BOUNDS_POSITION_" }, value = {
+            BOUNDS_POSITION_LEFT,
+            BOUNDS_POSITION_TOP,
+            BOUNDS_POSITION_RIGHT,
+            BOUNDS_POSITION_BOTTOM
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface BoundsPosition {}
+
+    private static class Bounds {
+        private final Rect[] mRects;
+
+        private Bounds(Rect left, Rect top, Rect right, Rect bottom, boolean copyArguments) {
+            mRects = new Rect[BOUNDS_POSITION_LENGTH];
+            mRects[BOUNDS_POSITION_LEFT] = getCopyOrRef(left, copyArguments);
+            mRects[BOUNDS_POSITION_TOP] = getCopyOrRef(top, copyArguments);
+            mRects[BOUNDS_POSITION_RIGHT] = getCopyOrRef(right, copyArguments);
+            mRects[BOUNDS_POSITION_BOTTOM] = getCopyOrRef(bottom, copyArguments);
+
+        }
+
+        private Bounds(Rect[] rects, boolean copyArguments) {
+            if (rects.length != BOUNDS_POSITION_LENGTH) {
+                throw new IllegalArgumentException(
+                        "rects must have exactly 4 elements: rects=" + Arrays.toString(rects));
+            }
+            if (copyArguments) {
+                mRects = new Rect[BOUNDS_POSITION_LENGTH];
+                for (int i = 0; i < BOUNDS_POSITION_LENGTH; ++i) {
+                    mRects[i] = new Rect(rects[i]);
+                }
+            } else {
+                for (Rect rect : rects) {
+                    if (rect == null) {
+                        throw new IllegalArgumentException(
+                                "rects must have non-null elements: rects="
+                                        + Arrays.toString(rects));
+                    }
+                }
+                mRects = rects;
+            }
+        }
+
+        private boolean isEmpty() {
+            for (Rect rect : mRects) {
+                if (!rect.isEmpty()) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        private Rect getRect(@BoundsPosition int pos) {
+            return new Rect(mRects[pos]);
+        }
+
+        private Rect[] getRects() {
+            Rect[] rects = new Rect[BOUNDS_POSITION_LENGTH];
+            for (int i = 0; i < BOUNDS_POSITION_LENGTH; ++i) {
+                rects[i] = new Rect(mRects[i]);
+            }
+            return rects;
+        }
+
+        private void scale(float scale) {
+            for (int i = 0; i < BOUNDS_POSITION_LENGTH; ++i) {
+                mRects[i].scale(scale);
+            }
+        }
+
+        @Override
+        public int hashCode() {
+            int result = 0;
+            for (Rect rect : mRects) {
+                result = result * 48271 + rect.hashCode();
+            }
+            return result;
+        }
+
+        @Override
+        public boolean equals(@Nullable Object o) {
+            if (o == this) {
+                return true;
+            }
+            if (o instanceof Bounds) {
+                Bounds b = (Bounds) o;
+                return Arrays.deepEquals(mRects, b.mRects);
+            }
+            return false;
+        }
+
+        @Override
+        public String toString() {
+            return "Bounds=" + Arrays.toString(mRects);
+        }
+
+    }
+
+    private final Bounds mBounds;
+
+    /**
+     * Stores all the needed info to create the cutout paths.
+     *
+     * @hide
+     */
+    public static class CutoutPathParserInfo {
+        private final int mDisplayWidth;
+        private final int mDisplayHeight;
+        private final float mDensity;
+        private final String mCutoutSpec;
+        private final @Rotation int mRotation;
+        private final float mScale;
+
+        public CutoutPathParserInfo(int displayWidth, int displayHeight, float density,
+                String cutoutSpec, @Rotation int rotation, float scale) {
+            mDisplayWidth = displayWidth;
+            mDisplayHeight = displayHeight;
+            mDensity = density;
+            mCutoutSpec = cutoutSpec == null ? "" : cutoutSpec;
+            mRotation = rotation;
+            mScale = scale;
+        }
+
+        public CutoutPathParserInfo(CutoutPathParserInfo cutoutPathParserInfo) {
+            mDisplayWidth = cutoutPathParserInfo.mDisplayWidth;
+            mDisplayHeight = cutoutPathParserInfo.mDisplayHeight;
+            mDensity = cutoutPathParserInfo.mDensity;
+            mCutoutSpec = cutoutPathParserInfo.mCutoutSpec;
+            mRotation = cutoutPathParserInfo.mRotation;
+            mScale = cutoutPathParserInfo.mScale;
+        }
+
+        public int getDisplayWidth() {
+            return mDisplayWidth;
+        }
+
+        public int getDisplayHeight() {
+            return mDisplayHeight;
+        }
+
+        public float getDensity() {
+            return mDensity;
+        }
+
+        public @NonNull String getCutoutSpec() {
+            return mCutoutSpec;
+        }
+
+        public int getRotation() {
+            return mRotation;
+        }
+
+        public float getScale() {
+            return mScale;
+        }
+
+        private boolean hasCutout() {
+            return !mCutoutSpec.isEmpty();
+        }
+
+        @Override
+        public int hashCode() {
+            int result = 0;
+            result = result * 48271 + Integer.hashCode(mDisplayWidth);
+            result = result * 48271 + Integer.hashCode(mDisplayHeight);
+            result = result * 48271 + Float.hashCode(mDensity);
+            result = result * 48271 + mCutoutSpec.hashCode();
+            result = result * 48271 + Integer.hashCode(mRotation);
+            result = result * 48271 + Float.hashCode(mScale);
+            return result;
+        }
+
+        @Override
+        public boolean equals(@Nullable Object o) {
+            if (o == this) {
+                return true;
+            }
+            if (o instanceof CutoutPathParserInfo) {
+                CutoutPathParserInfo c = (CutoutPathParserInfo) o;
+                return mDisplayWidth == c.mDisplayWidth && mDisplayHeight == c.mDisplayHeight
+                        && mDensity == c.mDensity && mCutoutSpec.equals(c.mCutoutSpec)
+                        && mRotation == c.mRotation && mScale == c.mScale;
+            }
+            return false;
+        }
+
+        @Override
+        public String toString() {
+            return "CutoutPathParserInfo{displayWidth=" + mDisplayWidth
+                    + " displayHeight=" + mDisplayHeight
+                    + " density={" + mDensity + "}"
+                    + " cutoutSpec={" + mCutoutSpec + "}"
+                    + " rotation={" + mRotation + "}"
+                    + " scale={" + mScale + "}"
+                    + "}";
+        }
+    }
+
+    private final @NonNull CutoutPathParserInfo mCutoutPathParserInfo;
+
+    /**
+     * Creates a DisplayCutout instance.
+     *
+     * <p>Note that this is only useful for tests. For production code, developers should always
+     * use a {@link DisplayCutout} obtained from the system.</p>
+     *
+     * @param safeInsets the insets from each edge which avoid the display cutout as returned by
+     *                   {@link #getSafeInsetTop()} etc.
+     * @param boundLeft the left bounding rect of the display cutout in pixels. If null is passed,
+     *                  it's treated as an empty rectangle (0,0)-(0,0).
+     * @param boundTop the top bounding rect of the display cutout in pixels.  If null is passed,
+     *                  it's treated as an empty rectangle (0,0)-(0,0).
+     * @param boundRight the right bounding rect of the display cutout in pixels.  If null is
+     *                  passed, it's treated as an empty rectangle (0,0)-(0,0).
+     * @param boundBottom the bottom bounding rect of the display cutout in pixels.  If null is
+     *                   passed, it's treated as an empty rectangle (0,0)-(0,0).
+     */
+    // TODO(b/73953958): @VisibleForTesting(visibility = PRIVATE)
+    public DisplayCutout(@NonNull Insets safeInsets, @Nullable Rect boundLeft,
+            @Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom) {
+        this(safeInsets.toRect(), Insets.NONE, boundLeft, boundTop, boundRight, boundBottom, null,
+                true);
+    }
+
+    /**
+     * Creates a DisplayCutout instance.
+     *
+     * <p>Note that this is only useful for tests. For production code, developers should always
+     * use a {@link DisplayCutout} obtained from the system.</p>
+     *
+     * @param safeInsets the insets from each edge which avoid the display cutout as returned by
+     *                   {@link #getSafeInsetTop()} etc.
+     * @param boundLeft the left bounding rect of the display cutout in pixels. If null is passed,
+     *                  it's treated as an empty rectangle (0,0)-(0,0).
+     * @param boundTop the top bounding rect of the display cutout in pixels.  If null is passed,
+     *                  it's treated as an empty rectangle (0,0)-(0,0).
+     * @param boundRight the right bounding rect of the display cutout in pixels.  If null is
+     *                  passed, it's treated as an empty rectangle (0,0)-(0,0).
+     * @param boundBottom the bottom bounding rect of the display cutout in pixels.  If null is
+     *                   passed, it's treated as an empty rectangle (0,0)-(0,0).
+     * @param waterfallInsets the insets for the curved areas in waterfall display.
+     */
+    public DisplayCutout(@NonNull Insets safeInsets, @Nullable Rect boundLeft,
+            @Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom,
+            @NonNull Insets waterfallInsets) {
+        this(safeInsets.toRect(), waterfallInsets, boundLeft, boundTop, boundRight, boundBottom,
+                null, true);
+    }
+
+    /**
+     * Creates a DisplayCutout instance.
+     *
+     * <p>Note that this is only useful for tests. For production code, developers should always
+     * use a {@link DisplayCutout} obtained from the system.</p>
+     *
+     * @param safeInsets the insets from each edge which avoid the display cutout as returned by
+     *                   {@link #getSafeInsetTop()} etc.
+     * @param boundingRects the bounding rects of the display cutouts as returned by
+     *               {@link #getBoundingRects()} ()}.
+     * @deprecated Use {@link DisplayCutout#DisplayCutout(Insets, Rect, Rect, Rect, Rect)} instead.
+     */
+    // TODO(b/73953958): @VisibleForTesting(visibility = PRIVATE)
+    @Deprecated
+    public DisplayCutout(@Nullable Rect safeInsets, @Nullable List<Rect> boundingRects) {
+        this(safeInsets, Insets.NONE, extractBoundsFromList(safeInsets, boundingRects), null,
+                true /* copyArguments */);
+    }
+
+    /**
+     * Creates a DisplayCutout instance.
+     *
+     * @param safeInsets the insets from each edge which avoid the display cutout as returned by
+     *                   {@link #getSafeInsetTop()} etc.
+     * @param waterfallInsets the insets for the curved areas in waterfall display.
+     * @param boundLeft the left bounding rect of the display cutout in pixels. If null is passed,
+     *                  it's treated as an empty rectangle (0,0)-(0,0).
+     * @param boundTop the top bounding rect of the display cutout in pixels.  If null is passed,
+     *                 it's treated as an empty rectangle (0,0)-(0,0).
+     * @param boundRight the right bounding rect of the display cutout in pixels.  If null is
+     *                   passed, it's treated as an empty rectangle (0,0)-(0,0).
+     * @param boundBottom the bottom bounding rect of the display cutout in pixels.  If null is
+     *                    passed, it's treated as an empty rectangle (0,0)-(0,0).
+     * @param info the cutout path parser info.
+     * @param copyArguments if true, create a copy of the arguments. If false, the passed arguments
+     *                      are not copied and MUST remain unchanged forever.
+     */
+    private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Rect boundLeft, Rect boundTop,
+            Rect boundRight, Rect boundBottom, CutoutPathParserInfo info,
+            boolean copyArguments) {
+        mSafeInsets = getCopyOrRef(safeInsets, copyArguments);
+        mWaterfallInsets = waterfallInsets == null ? Insets.NONE : waterfallInsets;
+        mBounds = new Bounds(boundLeft, boundTop, boundRight, boundBottom, copyArguments);
+        mCutoutPathParserInfo = info == null ? EMPTY_PARSER_INFO : info;
+    }
+
+    private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Rect[] bounds,
+            CutoutPathParserInfo info, boolean copyArguments) {
+        mSafeInsets = getCopyOrRef(safeInsets, copyArguments);
+        mWaterfallInsets = waterfallInsets == null ? Insets.NONE : waterfallInsets;
+        mBounds = new Bounds(bounds, copyArguments);
+        mCutoutPathParserInfo = info == null ? EMPTY_PARSER_INFO : info;
+    }
+
+    private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Bounds bounds,
+            CutoutPathParserInfo info) {
+        mSafeInsets = safeInsets;
+        mWaterfallInsets = waterfallInsets == null ? Insets.NONE : waterfallInsets;
+        mBounds = bounds;
+        mCutoutPathParserInfo = info == null ? EMPTY_PARSER_INFO : info;
+    }
+
+    private static Rect getCopyOrRef(Rect r, boolean copyArguments) {
+        if (r == null) {
+            return ZERO_RECT;
+        } else if (copyArguments) {
+            return new Rect(r);
+        } else {
+            return r;
+        }
+    }
+
+    /**
+     * Returns the insets representing the curved areas of a waterfall display.
+     *
+     * A waterfall display has curved areas along the edges of the screen. Apps should be careful
+     * when showing UI and handling touch input in those insets because the curve may impair
+     * legibility and can frequently lead to unintended touch inputs.
+     *
+     * @return the insets for the curved areas of a waterfall display in pixels or {@code
+     * Insets.NONE} if there are no curved areas or they don't overlap with the window.
+     */
+    public @NonNull Insets getWaterfallInsets() {
+        return mWaterfallInsets;
+    }
+
+
+    /**
+     * Find the position of the bounding rect, and create an array of Rect whose index represents
+     * the position (= BoundsPosition).
+     *
+     * @hide
+     */
+    public static Rect[] extractBoundsFromList(Rect safeInsets, List<Rect> boundingRects) {
+        Rect[] sortedBounds = new Rect[BOUNDS_POSITION_LENGTH];
+        for (int i = 0; i < sortedBounds.length; ++i) {
+            sortedBounds[i] = ZERO_RECT;
+        }
+        if (safeInsets != null && boundingRects != null) {
+            // There is at most one non-functional area per short edge of the device, but none
+            // on the long edges, so either a) safeInsets.top and safeInsets.bottom is 0, or
+            // b) safeInsets.left and safeInset.right is 0.
+            final boolean topBottomInset = safeInsets.top > 0 || safeInsets.bottom > 0;
+            for (Rect bound : boundingRects) {
+                if (topBottomInset) {
+                    if (bound.top == 0) {
+                        sortedBounds[BOUNDS_POSITION_TOP] = bound;
+                    } else {
+                        sortedBounds[BOUNDS_POSITION_BOTTOM] = bound;
+                    }
+                } else {
+                    if (bound.left == 0) {
+                        sortedBounds[BOUNDS_POSITION_LEFT] = bound;
+                    } else {
+                        sortedBounds[BOUNDS_POSITION_RIGHT] = bound;
+                    }
+                }
+            }
+        }
+        return sortedBounds;
+    }
+
+    /**
+     * Returns true if there is no cutout, i.e. the bounds are empty.
+     *
+     * @hide
+     */
+    public boolean isBoundsEmpty() {
+        return mBounds.isEmpty();
+    }
+
+    /**
+     * Returns true if the safe insets are empty (and therefore the current view does not
+     * overlap with the cutout or cutout area).
+     *
+     * @hide
+     */
+    public boolean isEmpty() {
+        return mSafeInsets.equals(ZERO_RECT);
+    }
+
+    /**
+     * Returns the inset from the top which avoids the display cutout in pixels.
+     *
+     * @see WindowInsets.Type#displayCutout()
+     */
+    public int getSafeInsetTop() {
+        return mSafeInsets.top;
+    }
+
+    /**
+     * Returns the inset from the bottom which avoids the display cutout in pixels.
+     *
+     * @see WindowInsets.Type#displayCutout()
+     */
+    public int getSafeInsetBottom() {
+        return mSafeInsets.bottom;
+    }
+
+    /**
+     * Returns the inset from the left which avoids the display cutout in pixels.
+     *
+     * @see WindowInsets.Type#displayCutout()
+     */
+    public int getSafeInsetLeft() {
+        return mSafeInsets.left;
+    }
+
+    /**
+     * Returns the inset from the right which avoids the display cutout in pixels.
+     *
+     * @see WindowInsets.Type#displayCutout()
+     */
+    public int getSafeInsetRight() {
+        return mSafeInsets.right;
+    }
+
+    /**
+     * Returns the safe insets in a rect in pixel units.
+     *
+     * @return a rect which is set to the safe insets.
+     * @hide
+     */
+    public Rect getSafeInsets() {
+        return new Rect(mSafeInsets);
+    }
+
+    /**
+     * Returns a list of {@code Rect}s, each of which is the bounding rectangle for a non-functional
+     * area on the display.
+     *
+     * There will be at most one non-functional area per short edge of the device, and none on
+     * the long edges.
+     *
+     * @return a list of bounding {@code Rect}s, one for each display cutout area. No empty Rect is
+     * returned.
+     */
+    @NonNull
+    public List<Rect> getBoundingRects() {
+        List<Rect> result = new ArrayList<>();
+        for (Rect bound : getBoundingRectsAll()) {
+            if (!bound.isEmpty()) {
+                result.add(new Rect(bound));
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Returns an array of {@code Rect}s, each of which is the bounding rectangle for a non-
+     * functional area on the display. Ordinal value of BoundPosition is used as an index of
+     * the array.
+     *
+     * There will be at most one non-functional area per short edge of the device, and none on
+     * the long edges.
+     *
+     * @return an array of bounding {@code Rect}s, one for each display cutout area. This might
+     * contain ZERO_RECT, which means there is no cutout area at the position.
+     *
+     * @hide
+     */
+    public Rect[] getBoundingRectsAll() {
+        return mBounds.getRects();
+    }
+
+    /**
+     * Returns a bounding rectangle for a non-functional area on the display which is located on
+     * the left of the screen.
+     *
+     * @return bounding rectangle in pixels. In case of no bounding rectangle, an empty rectangle
+     * is returned.
+     */
+    public @NonNull Rect getBoundingRectLeft() {
+        return mBounds.getRect(BOUNDS_POSITION_LEFT);
+    }
+
+    /**
+     * Returns a bounding rectangle for a non-functional area on the display which is located on
+     * the top of the screen.
+     *
+     * @return bounding rectangle in pixels. In case of no bounding rectangle, an empty rectangle
+     * is returned.
+     */
+    public @NonNull Rect getBoundingRectTop() {
+        return mBounds.getRect(BOUNDS_POSITION_TOP);
+    }
+
+    /**
+     * Returns a bounding rectangle for a non-functional area on the display which is located on
+     * the right of the screen.
+     *
+     * @return bounding rectangle in pixels. In case of no bounding rectangle, an empty rectangle
+     * is returned.
+     */
+    public @NonNull Rect getBoundingRectRight() {
+        return mBounds.getRect(BOUNDS_POSITION_RIGHT);
+    }
+
+    /**
+     * Returns a bounding rectangle for a non-functional area on the display which is located on
+     * the bottom of the screen.
+     *
+     * @return bounding rectangle in pixels. In case of no bounding rectangle, an empty rectangle
+     * is returned.
+     */
+    public @NonNull Rect getBoundingRectBottom() {
+        return mBounds.getRect(BOUNDS_POSITION_BOTTOM);
+    }
+
+    /**
+     * Returns a {@link Path} that contains the cutout paths of all sides on the display.
+     *
+     * To get a cutout path for one specific side, apps can intersect the {@link Path} with the
+     * {@link Rect} obtained from {@link #getBoundingRectLeft()}, {@link #getBoundingRectTop()},
+     * {@link #getBoundingRectRight()} or {@link #getBoundingRectBottom()}.
+     *
+     * @return a {@link Path} contains all the cutout paths based on display coordinate. Returns
+     * null if there is no cutout on the display.
+     */
+    public @Nullable Path getCutoutPath() {
+        if (!mCutoutPathParserInfo.hasCutout()) {
+            return null;
+        }
+        synchronized (CACHE_LOCK) {
+            if (mCutoutPathParserInfo.equals(sCachedCutoutPathParserInfo)) {
+                return sCachedCutoutPath;
+            }
+        }
+        final CutoutSpecification cutoutSpec = new CutoutSpecification.Parser(
+                mCutoutPathParserInfo.getDensity(), mCutoutPathParserInfo.getDisplayWidth(),
+                mCutoutPathParserInfo.getDisplayHeight())
+                .parse(mCutoutPathParserInfo.getCutoutSpec());
+
+        final Path cutoutPath = cutoutSpec.getPath();
+        if (cutoutPath == null || cutoutPath.isEmpty()) {
+            return null;
+        }
+        final Matrix matrix = new Matrix();
+        if (mCutoutPathParserInfo.getRotation() != ROTATION_0) {
+            RotationUtils.transformPhysicalToLogicalCoordinates(
+                    mCutoutPathParserInfo.getRotation(),
+                    mCutoutPathParserInfo.getDisplayWidth(),
+                    mCutoutPathParserInfo.getDisplayHeight(),
+                    matrix
+            );
+        }
+        matrix.postScale(mCutoutPathParserInfo.getScale(), mCutoutPathParserInfo.getScale());
+        cutoutPath.transform(matrix);
+
+        synchronized (CACHE_LOCK) {
+            sCachedCutoutPathParserInfo = new CutoutPathParserInfo(mCutoutPathParserInfo);
+            sCachedCutoutPath = cutoutPath;
+        }
+        return cutoutPath;
+    }
+
+    /**
+     * @return the {@link CutoutPathParserInfo};
+     *
+     * @hide
+     */
+    public CutoutPathParserInfo getCutoutPathParserInfo() {
+        return mCutoutPathParserInfo;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 0;
+        result = 48271 * result + mSafeInsets.hashCode();
+        result = 48271 * result + mBounds.hashCode();
+        result = 48271 * result + mWaterfallInsets.hashCode();
+        result = 48271 * result + mCutoutPathParserInfo.hashCode();
+        return result;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o instanceof DisplayCutout) {
+            DisplayCutout c = (DisplayCutout) o;
+            return mSafeInsets.equals(c.mSafeInsets) && mBounds.equals(c.mBounds)
+                    && mWaterfallInsets.equals(c.mWaterfallInsets)
+                    && mCutoutPathParserInfo.equals(c.mCutoutPathParserInfo);
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return "DisplayCutout{insets=" + mSafeInsets
+                + " waterfall=" + mWaterfallInsets
+                + " boundingRect={" + mBounds + "}"
+                + " cutoutPathParserInfo={" + mCutoutPathParserInfo + "}"
+                + "}";
+    }
+
+    /**
+     * @hide
+     */
+    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        mSafeInsets.dumpDebug(proto, INSETS);
+        mBounds.getRect(BOUNDS_POSITION_LEFT).dumpDebug(proto, BOUND_LEFT);
+        mBounds.getRect(BOUNDS_POSITION_TOP).dumpDebug(proto, BOUND_TOP);
+        mBounds.getRect(BOUNDS_POSITION_RIGHT).dumpDebug(proto, BOUND_RIGHT);
+        mBounds.getRect(BOUNDS_POSITION_BOTTOM).dumpDebug(proto, BOUND_BOTTOM);
+        mWaterfallInsets.toRect().dumpDebug(proto, INSETS);
+        proto.end(token);
+    }
+
+    /**
+     * Insets the reference frame of the cutout in the given directions.
+     *
+     * @return a copy of this instance which has been inset
+     * @hide
+     */
+    public DisplayCutout inset(int insetLeft, int insetTop, int insetRight, int insetBottom) {
+        if (insetLeft == 0 && insetTop == 0 && insetRight == 0 && insetBottom == 0
+                || (isBoundsEmpty() && mWaterfallInsets.equals(Insets.NONE))) {
+            return this;
+        }
+
+        Rect safeInsets = insetInsets(insetLeft, insetTop, insetRight, insetBottom,
+                new Rect(mSafeInsets));
+
+        // If we are not cutting off part of the cutout by insetting it on bottom/right, and we also
+        // don't move it around, we can avoid the allocation and copy of the instance.
+        if (insetLeft == 0 && insetTop == 0 && mSafeInsets.equals(safeInsets)) {
+            return this;
+        }
+
+        Rect waterfallInsets = insetInsets(insetLeft, insetTop, insetRight, insetBottom,
+                mWaterfallInsets.toRect());
+
+        Rect[] bounds = mBounds.getRects();
+        for (int i = 0; i < bounds.length; ++i) {
+            if (!bounds[i].equals(ZERO_RECT)) {
+                bounds[i].offset(-insetLeft, -insetTop);
+            }
+        }
+
+        return new DisplayCutout(safeInsets, Insets.of(waterfallInsets), bounds,
+                mCutoutPathParserInfo, false /* copyArguments */);
+    }
+
+    private Rect insetInsets(int insetLeft, int insetTop, int insetRight, int insetBottom,
+            Rect insets) {
+        // Note: it's not really well defined what happens when the inset is negative, because we
+        // don't know if the safe inset needs to expand in general.
+        if (insetTop > 0 || insets.top > 0) {
+            insets.top = atLeastZero(insets.top - insetTop);
+        }
+        if (insetBottom > 0 || insets.bottom > 0) {
+            insets.bottom = atLeastZero(insets.bottom - insetBottom);
+        }
+        if (insetLeft > 0 || insets.left > 0) {
+            insets.left = atLeastZero(insets.left - insetLeft);
+        }
+        if (insetRight > 0 || insets.right > 0) {
+            insets.right = atLeastZero(insets.right - insetRight);
+        }
+        return insets;
+    }
+
+    /**
+     * Returns a copy of this instance with the safe insets replaced with the parameter.
+     *
+     * @param safeInsets the new safe insets in pixels
+     * @return a copy of this instance with the safe insets replaced with the argument.
+     *
+     * @hide
+     */
+    public DisplayCutout replaceSafeInsets(Rect safeInsets) {
+        return new DisplayCutout(new Rect(safeInsets), mWaterfallInsets, mBounds,
+                mCutoutPathParserInfo);
+    }
+
+    private static int atLeastZero(int value) {
+        return value < 0 ? 0 : value;
+    }
+
+
+    /**
+     * Creates an instance from a bounding rect.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static DisplayCutout fromBoundingRect(
+            int left, int top, int right, int bottom, @BoundsPosition int pos) {
+        Rect[] bounds = new Rect[BOUNDS_POSITION_LENGTH];
+        for (int i = 0; i < BOUNDS_POSITION_LENGTH; ++i) {
+            bounds[i] = (pos == i) ? new Rect(left, top, right, bottom) : new Rect();
+        }
+        return new DisplayCutout(ZERO_RECT, Insets.NONE, bounds, null, false /* copyArguments */);
+    }
+
+    /**
+     * Creates an instance from bounds, waterfall insets and CutoutPathParserInfo.
+     *
+     * @hide
+     */
+    public static DisplayCutout constructDisplayCutout(Rect[] bounds, Insets waterfallInsets,
+            CutoutPathParserInfo info) {
+        return new DisplayCutout(ZERO_RECT, waterfallInsets, bounds, info,
+                false /* copyArguments */);
+    }
+
+    /**
+     * Creates an instance from a bounding {@link Path}.
+     *
+     * @hide
+     */
+    public static DisplayCutout fromBounds(Rect[] bounds) {
+        return new DisplayCutout(ZERO_RECT, Insets.NONE, bounds, null /* cutoutPathParserInfo */,
+                false /* copyArguments */);
+    }
+
+    /**
+     * Creates the display cutout according to
+     * @android:string/config_mainBuiltInDisplayCutoutRectApproximation, which is the closest
+     * rectangle-base approximation of the cutout.
+     *
+     * @hide
+     */
+    public static DisplayCutout fromResourcesRectApproximation(Resources res, int displayWidth,
+            int displayHeight) {
+        return pathAndDisplayCutoutFromSpec(res.getString(R.string.config_mainBuiltInDisplayCutout),
+                res.getString(R.string.config_mainBuiltInDisplayCutoutRectApproximation),
+                displayWidth, displayHeight, DENSITY_DEVICE_STABLE / (float) DENSITY_DEFAULT,
+                loadWaterfallInset(res)).second;
+    }
+
+    /**
+     * Creates an instance according to @android:string/config_mainBuiltInDisplayCutout.
+     *
+     * @hide
+     */
+    public static Path pathFromResources(Resources res, int displayWidth, int displayHeight) {
+        return pathAndDisplayCutoutFromSpec(
+                res.getString(R.string.config_mainBuiltInDisplayCutout), null,
+                displayWidth, displayHeight, DENSITY_DEVICE_STABLE / (float) DENSITY_DEFAULT,
+                loadWaterfallInset(res)).first;
+    }
+
+    /**
+     * Creates an instance according to the supplied {@link android.util.PathParser.PathData} spec.
+     *
+     * @hide
+     */
+    @VisibleForTesting(visibility = PRIVATE)
+    public static DisplayCutout fromSpec(String pathSpec, int displayWidth,
+            int displayHeight, float density, Insets waterfallInsets) {
+        return pathAndDisplayCutoutFromSpec(
+                pathSpec, null, displayWidth, displayHeight, density, waterfallInsets)
+                .second;
+    }
+
+    /**
+     * Gets the cutout path and the corresponding DisplayCutout instance from the spec string.
+     *
+     * @param pathSpec the spec string read from config_mainBuiltInDisplayCutout.
+     * @param rectSpec the spec string read from config_mainBuiltInDisplayCutoutRectApproximation.
+     * @param displayWidth the display width.
+     * @param displayHeight the display height.
+     * @param density the display density.
+     * @param waterfallInsets the waterfall insets of the display.
+     * @return a Pair contains the cutout path and the corresponding DisplayCutout instance.
+     */
+    private static Pair<Path, DisplayCutout> pathAndDisplayCutoutFromSpec(
+            String pathSpec, String rectSpec, int displayWidth, int displayHeight, float density,
+            Insets waterfallInsets) {
+        // Always use the rect approximation spec to create the cutout if it's not null because
+        // transforming and sending a Region constructed from a path is very costly.
+        String spec = rectSpec != null ? rectSpec : pathSpec;
+        if (TextUtils.isEmpty(spec) && waterfallInsets.equals(Insets.NONE)) {
+            return NULL_PAIR;
+        }
+
+        synchronized (CACHE_LOCK) {
+            if (spec.equals(sCachedSpec) && sCachedDisplayWidth == displayWidth
+                    && sCachedDisplayHeight == displayHeight
+                    && sCachedDensity == density
+                    && waterfallInsets.equals(sCachedWaterfallInsets)) {
+                return sCachedCutout;
+            }
+        }
+
+        spec = spec.trim();
+
+        CutoutSpecification cutoutSpec = new CutoutSpecification.Parser(density,
+                displayWidth, displayHeight).parse(spec);
+        Rect safeInset = cutoutSpec.getSafeInset();
+        final Rect boundLeft = cutoutSpec.getLeftBound();
+        final Rect boundTop = cutoutSpec.getTopBound();
+        final Rect boundRight = cutoutSpec.getRightBound();
+        final Rect boundBottom = cutoutSpec.getBottomBound();
+
+
+        if (!waterfallInsets.equals(Insets.NONE)) {
+            safeInset.set(
+                    Math.max(waterfallInsets.left, safeInset.left),
+                    Math.max(waterfallInsets.top, safeInset.top),
+                    Math.max(waterfallInsets.right, safeInset.right),
+                    Math.max(waterfallInsets.bottom, safeInset.bottom));
+        }
+
+        final CutoutPathParserInfo cutoutPathParserInfo = new CutoutPathParserInfo(displayWidth,
+                displayHeight, density, pathSpec.trim(), ROTATION_0, 1f /* scale */);
+
+        final DisplayCutout cutout = new DisplayCutout(
+                safeInset, waterfallInsets, boundLeft, boundTop, boundRight, boundBottom,
+                cutoutPathParserInfo , false /* copyArguments */);
+        final Pair<Path, DisplayCutout> result = new Pair<>(cutoutSpec.getPath(), cutout);
+        synchronized (CACHE_LOCK) {
+            sCachedSpec = spec;
+            sCachedDisplayWidth = displayWidth;
+            sCachedDisplayHeight = displayHeight;
+            sCachedDensity = density;
+            sCachedCutout = result;
+            sCachedWaterfallInsets = waterfallInsets;
+        }
+        return result;
+    }
+
+    private static Insets loadWaterfallInset(Resources res) {
+        return Insets.of(
+                res.getDimensionPixelSize(R.dimen.waterfall_display_left_edge_size),
+                res.getDimensionPixelSize(R.dimen.waterfall_display_top_edge_size),
+                res.getDimensionPixelSize(R.dimen.waterfall_display_right_edge_size),
+                res.getDimensionPixelSize(R.dimen.waterfall_display_bottom_edge_size));
+    }
+
+    /**
+     * Helper class for passing {@link DisplayCutout} through binder.
+     *
+     * Needed, because {@code readFromParcel} cannot be used with immutable classes.
+     *
+     * @hide
+     */
+    public static final class ParcelableWrapper implements Parcelable {
+
+        private DisplayCutout mInner;
+
+        public ParcelableWrapper() {
+            this(NO_CUTOUT);
+        }
+
+        public ParcelableWrapper(DisplayCutout cutout) {
+            mInner = cutout;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            writeCutoutToParcel(mInner, out, flags);
+        }
+
+        /**
+         * Writes a DisplayCutout to a {@link Parcel}.
+         *
+         * @see #readCutoutFromParcel(Parcel)
+         */
+        public static void writeCutoutToParcel(DisplayCutout cutout, Parcel out, int flags) {
+            if (cutout == null) {
+                out.writeInt(-1);
+            } else if (cutout == NO_CUTOUT) {
+                out.writeInt(0);
+            } else {
+                out.writeInt(1);
+                out.writeTypedObject(cutout.mSafeInsets, flags);
+                out.writeTypedArray(cutout.mBounds.getRects(), flags);
+                out.writeTypedObject(cutout.mWaterfallInsets, flags);
+                out.writeInt(cutout.mCutoutPathParserInfo.getDisplayWidth());
+                out.writeInt(cutout.mCutoutPathParserInfo.getDisplayHeight());
+                out.writeFloat(cutout.mCutoutPathParserInfo.getDensity());
+                out.writeString(cutout.mCutoutPathParserInfo.getCutoutSpec());
+                out.writeInt(cutout.mCutoutPathParserInfo.getRotation());
+                out.writeFloat(cutout.mCutoutPathParserInfo.getScale());
+            }
+        }
+
+        /**
+         * Similar to {@link Creator#createFromParcel(Parcel)}, but reads into an existing
+         * instance.
+         *
+         * Needed for AIDL out parameters.
+         */
+        public void readFromParcel(Parcel in) {
+            mInner = readCutoutFromParcel(in);
+        }
+
+        public static final @android.annotation.NonNull Creator<ParcelableWrapper> CREATOR = new Creator<ParcelableWrapper>() {
+            @Override
+            public ParcelableWrapper createFromParcel(Parcel in) {
+                return new ParcelableWrapper(readCutoutFromParcel(in));
+            }
+
+            @Override
+            public ParcelableWrapper[] newArray(int size) {
+                return new ParcelableWrapper[size];
+            }
+        };
+
+        /**
+         * Reads a DisplayCutout from a {@link Parcel}.
+         *
+         * @see #writeCutoutToParcel(DisplayCutout, Parcel, int)
+         */
+        public static DisplayCutout readCutoutFromParcel(Parcel in) {
+            int variant = in.readInt();
+            if (variant == -1) {
+                return null;
+            }
+            if (variant == 0) {
+                return NO_CUTOUT;
+            }
+
+            Rect safeInsets = in.readTypedObject(Rect.CREATOR);
+            Rect[] bounds = new Rect[BOUNDS_POSITION_LENGTH];
+            in.readTypedArray(bounds, Rect.CREATOR);
+            Insets waterfallInsets = in.readTypedObject(Insets.CREATOR);
+            int displayWidth = in.readInt();
+            int displayHeight = in.readInt();
+            float density = in.readFloat();
+            String cutoutSpec = in.readString();
+            int rotation = in.readInt();
+            float scale = in.readFloat();
+            final CutoutPathParserInfo info = new CutoutPathParserInfo(
+                    displayWidth, displayHeight, density, cutoutSpec, rotation, scale);
+
+            return new DisplayCutout(
+                    safeInsets, waterfallInsets, bounds, info, false /* copyArguments */);
+        }
+
+        public DisplayCutout get() {
+            return mInner;
+        }
+
+        public void set(ParcelableWrapper cutout) {
+            mInner = cutout.get();
+        }
+
+        public void set(DisplayCutout cutout) {
+            mInner = cutout;
+        }
+
+        public void scale(float scale) {
+            final Rect safeInsets = mInner.getSafeInsets();
+            safeInsets.scale(scale);
+            final Bounds bounds = new Bounds(mInner.mBounds.mRects, true);
+            bounds.scale(scale);
+            final Rect waterfallInsets = mInner.mWaterfallInsets.toRect();
+            waterfallInsets.scale(scale);
+            final CutoutPathParserInfo info = new CutoutPathParserInfo(
+                    mInner.mCutoutPathParserInfo.getDisplayWidth(),
+                    mInner.mCutoutPathParserInfo.getDisplayHeight(),
+                    mInner.mCutoutPathParserInfo.getDensity(),
+                    mInner.mCutoutPathParserInfo.getCutoutSpec(),
+                    mInner.mCutoutPathParserInfo.getRotation(),
+                    scale);
+
+            mInner = new DisplayCutout(safeInsets, Insets.of(waterfallInsets), bounds, info);
+        }
+
+        @Override
+        public int hashCode() {
+            return mInner.hashCode();
+        }
+
+        @Override
+        public boolean equals(@Nullable Object o) {
+            return o instanceof ParcelableWrapper
+                    && mInner.equals(((ParcelableWrapper) o).mInner);
+        }
+
+        @Override
+        public String toString() {
+            return String.valueOf(mInner);
+        }
+    }
+}
diff --git a/android/view/DisplayEventReceiver.java b/android/view/DisplayEventReceiver.java
new file mode 100644
index 0000000..5c08632
--- /dev/null
+++ b/android/view/DisplayEventReceiver.java
@@ -0,0 +1,284 @@
+/*
+ * 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.view;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.FrameInfo;
+import android.os.Build;
+import android.os.Looper;
+import android.os.MessageQueue;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import dalvik.annotation.optimization.FastNative;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Provides a low-level mechanism for an application to receive display events
+ * such as vertical sync.
+ *
+ * The display event receive is NOT thread safe.  Moreover, its methods must only
+ * be called on the Looper thread to which it is attached.
+ *
+ * @hide
+ */
+public abstract class DisplayEventReceiver {
+
+    /**
+     * When retrieving vsync events, this specifies that the vsync event should happen at the normal
+     * vsync-app tick.
+     * <p>
+     * Needs to be kept in sync with frameworks/native/include/gui/ISurfaceComposer.h
+     */
+    public static final int VSYNC_SOURCE_APP = 0;
+
+    /**
+     * When retrieving vsync events, this specifies that the vsync event should happen whenever
+     * Surface Flinger is processing a frame.
+     * <p>
+     * Needs to be kept in sync with frameworks/native/include/gui/ISurfaceComposer.h
+     */
+    public static final int VSYNC_SOURCE_SURFACE_FLINGER = 1;
+
+    /**
+     * Specifies to generate mode changed events from Surface Flinger.
+     * <p>
+     * Needs to be kept in sync with frameworks/native/include/gui/ISurfaceComposer.h
+     */
+    public static final int EVENT_REGISTRATION_MODE_CHANGED_FLAG = 0x1;
+
+    /**
+     * Specifies to generate frame rate override events from Surface Flinger.
+     * <p>
+     * Needs to be kept in sync with frameworks/native/include/gui/ISurfaceComposer.h
+     */
+    public static final int EVENT_REGISTRATION_FRAME_RATE_OVERRIDE_FLAG = 0x2;
+
+    private static final String TAG = "DisplayEventReceiver";
+
+    @UnsupportedAppUsage
+    private long mReceiverPtr;
+
+    // We keep a reference message queue object here so that it is not
+    // GC'd while the native peer of the receiver is using them.
+    private MessageQueue mMessageQueue;
+
+    private static native long nativeInit(WeakReference<DisplayEventReceiver> receiver,
+            MessageQueue messageQueue, int vsyncSource, int eventRegistration);
+    private static native void nativeDispose(long receiverPtr);
+    @FastNative
+    private static native void nativeScheduleVsync(long receiverPtr);
+
+    /**
+     * Creates a display event receiver.
+     *
+     * @param looper The looper to use when invoking callbacks.
+     */
+    @UnsupportedAppUsage
+    public DisplayEventReceiver(Looper looper) {
+        this(looper, VSYNC_SOURCE_APP, 0);
+    }
+
+    /**
+     * Creates a display event receiver.
+     *
+     * @param looper The looper to use when invoking callbacks.
+     * @param vsyncSource The source of the vsync tick. Must be on of the VSYNC_SOURCE_* values.
+     * @param eventRegistration Which events to dispatch. Must be a bitfield consist of the
+     * EVENT_REGISTRATION_*_FLAG values.
+     */
+    public DisplayEventReceiver(Looper looper, int vsyncSource, int eventRegistration) {
+        if (looper == null) {
+            throw new IllegalArgumentException("looper must not be null");
+        }
+
+        mMessageQueue = looper.getQueue();
+        mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue,
+                vsyncSource, eventRegistration);
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            dispose(true);
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Disposes the receiver.
+     */
+    public void dispose() {
+        dispose(false);
+    }
+
+    private void dispose(boolean finalized) {
+        if (mReceiverPtr != 0) {
+            nativeDispose(mReceiverPtr);
+            mReceiverPtr = 0;
+        }
+        mMessageQueue = null;
+    }
+
+    static final class VsyncEventData {
+        // The frame timeline vsync id, used to correlate a frame
+        // produced by HWUI with the timeline data stored in Surface Flinger.
+        public final long id;
+
+        // The frame deadline timestamp in {@link System#nanoTime()} timebase that it is
+        // allotted for the frame to be completed.
+        public final long frameDeadline;
+
+        /**
+         * The current interval between frames in ns. This will be used to align
+         * {@link FrameInfo#VSYNC} to the current vsync in case Choreographer callback was heavily
+         * delayed by the app.
+         */
+        public final long frameInterval;
+
+        VsyncEventData(long id, long frameDeadline, long frameInterval) {
+            this.id = id;
+            this.frameDeadline = frameDeadline;
+            this.frameInterval = frameInterval;
+        }
+
+        VsyncEventData() {
+            this.id = FrameInfo.INVALID_VSYNC_ID;
+            this.frameDeadline = Long.MAX_VALUE;
+            this.frameInterval = -1;
+        }
+    }
+
+    /**
+     * Called when a vertical sync pulse is received.
+     * The recipient should render a frame and then call {@link #scheduleVsync}
+     * to schedule the next vertical sync pulse.
+     *
+     * @param timestampNanos The timestamp of the pulse, in the {@link System#nanoTime()}
+     * timebase.
+     * @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
+     * @param frame The frame number.  Increases by one for each vertical sync interval.
+     * @param vsyncEventData The vsync event data.
+     */
+    public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
+            VsyncEventData vsyncEventData) {
+    }
+
+    /**
+     * Called when a display hotplug event is received.
+     *
+     * @param timestampNanos The timestamp of the event, in the {@link System#nanoTime()}
+     * timebase.
+     * @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
+     * @param connected True if the display is connected, false if it disconnected.
+     */
+    @UnsupportedAppUsage
+    public void onHotplug(long timestampNanos, long physicalDisplayId, boolean connected) {
+    }
+
+    /**
+     * Called when a display mode changed event is received.
+     *
+     * @param timestampNanos The timestamp of the event, in the {@link System#nanoTime()}
+     * timebase.
+     * @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
+     * @param modeId The new mode Id
+     */
+    public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId) {
+    }
+
+    /**
+     * Represents a mapping between a UID and an override frame rate
+     */
+    public static class FrameRateOverride {
+        // The application uid
+        public final int uid;
+
+        // The frame rate that this application runs at
+        public final float frameRateHz;
+
+
+        @VisibleForTesting
+        public FrameRateOverride(int uid, float frameRateHz) {
+            this.uid = uid;
+            this.frameRateHz = frameRateHz;
+        }
+
+        @Override
+        public String toString() {
+            return "{uid=" + uid + " frameRateHz=" + frameRateHz + "}";
+        }
+    }
+
+    /**
+     * Called when frame rate override event is received.
+     *
+     * @param timestampNanos The timestamp of the event, in the {@link System#nanoTime()}
+     * timebase.
+     * @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
+     * @param overrides The mappings from uid to frame rates
+     */
+    public void onFrameRateOverridesChanged(long timestampNanos, long physicalDisplayId,
+            FrameRateOverride[] overrides) {
+    }
+
+    /**
+     * Schedules a single vertical sync pulse to be delivered when the next
+     * display frame begins.
+     */
+    @UnsupportedAppUsage
+    public void scheduleVsync() {
+        if (mReceiverPtr == 0) {
+            Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
+                    + "receiver has already been disposed.");
+        } else {
+            nativeScheduleVsync(mReceiverPtr);
+        }
+    }
+
+    // Called from native code.
+    @SuppressWarnings("unused")
+    private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame,
+            long frameTimelineVsyncId, long frameDeadline, long frameInterval) {
+        onVsync(timestampNanos, physicalDisplayId, frame,
+                new VsyncEventData(frameTimelineVsyncId, frameDeadline, frameInterval));
+    }
+
+    // Called from native code.
+    @SuppressWarnings("unused")
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private void dispatchHotplug(long timestampNanos, long physicalDisplayId, boolean connected) {
+        onHotplug(timestampNanos, physicalDisplayId, connected);
+    }
+
+    // Called from native code.
+    @SuppressWarnings("unused")
+    private void dispatchModeChanged(long timestampNanos, long physicalDisplayId, int modeId) {
+        onModeChanged(timestampNanos, physicalDisplayId, modeId);
+    }
+
+    // Called from native code.
+    @SuppressWarnings("unused")
+    private void dispatchFrameRateOverrides(long timestampNanos, long physicalDisplayId,
+            FrameRateOverride[] overrides) {
+        onFrameRateOverridesChanged(timestampNanos, physicalDisplayId, overrides);
+    }
+
+}
diff --git a/android/view/DisplayInfo.java b/android/view/DisplayInfo.java
new file mode 100644
index 0000000..8e5f905
--- /dev/null
+++ b/android/view/DisplayInfo.java
@@ -0,0 +1,855 @@
+/*
+ * 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.view;
+
+import static android.view.DisplayInfoProto.APP_HEIGHT;
+import static android.view.DisplayInfoProto.APP_WIDTH;
+import static android.view.DisplayInfoProto.FLAGS;
+import static android.view.DisplayInfoProto.LOGICAL_HEIGHT;
+import static android.view.DisplayInfoProto.LOGICAL_WIDTH;
+import static android.view.DisplayInfoProto.NAME;
+
+import android.annotation.Nullable;
+import android.app.WindowConfiguration;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.hardware.display.DeviceProductInfo;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Process;
+import android.util.ArraySet;
+import android.util.DisplayMetrics;
+import android.util.proto.ProtoOutputStream;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Describes the characteristics of a particular logical display.
+ * @hide
+ */
+public final class DisplayInfo implements Parcelable {
+    /**
+     * The surface flinger layer stack associated with this logical display.
+     */
+    public int layerStack;
+
+    /**
+     * Display flags.
+     */
+    public int flags;
+
+    /**
+     * Display type.
+     */
+    public int type;
+
+    /**
+     * Logical display identifier.
+     */
+    public int displayId;
+
+    /**
+     * Display Group identifier.
+     */
+    public int displayGroupId;
+
+    /**
+     * Display address, or null if none.
+     * Interpretation varies by display type.
+     */
+    public DisplayAddress address;
+
+    /**
+     * Product-specific information about the display or the directly connected device on the
+     * display chain. For example, if the display is transitively connected, this field may contain
+     * product information about the intermediate device.
+     */
+    public DeviceProductInfo deviceProductInfo;
+
+    /**
+     * The human-readable name of the display.
+     */
+    public String name;
+
+    /**
+     * Unique identifier for the display. Shouldn't be displayed to the user.
+     */
+    public String uniqueId;
+
+    /**
+     * The width of the portion of the display that is available to applications, in pixels.
+     * Represents the size of the display minus any system decorations.
+     */
+    public int appWidth;
+
+    /**
+     * The height of the portion of the display that is available to applications, in pixels.
+     * Represents the size of the display minus any system decorations.
+     */
+    public int appHeight;
+
+    /**
+     * The smallest value of {@link #appWidth} that an application is likely to encounter,
+     * in pixels, excepting cases where the width may be even smaller due to the presence
+     * of a soft keyboard, for example.
+     */
+    public int smallestNominalAppWidth;
+
+    /**
+     * The smallest value of {@link #appHeight} that an application is likely to encounter,
+     * in pixels, excepting cases where the height may be even smaller due to the presence
+     * of a soft keyboard, for example.
+     */
+    public int smallestNominalAppHeight;
+
+    /**
+     * The largest value of {@link #appWidth} that an application is likely to encounter,
+     * in pixels, excepting cases where the width may be even larger due to system decorations
+     * such as the status bar being hidden, for example.
+     */
+    public int largestNominalAppWidth;
+
+    /**
+     * The largest value of {@link #appHeight} that an application is likely to encounter,
+     * in pixels, excepting cases where the height may be even larger due to system decorations
+     * such as the status bar being hidden, for example.
+     */
+    public int largestNominalAppHeight;
+
+    /**
+     * The logical width of the display, in pixels.
+     * Represents the usable size of the display which may be smaller than the
+     * physical size when the system is emulating a smaller display.
+     */
+    @UnsupportedAppUsage
+    public int logicalWidth;
+
+    /**
+     * The logical height of the display, in pixels.
+     * Represents the usable size of the display which may be smaller than the
+     * physical size when the system is emulating a smaller display.
+     */
+    @UnsupportedAppUsage
+    public int logicalHeight;
+
+    /**
+     * The {@link DisplayCutout} if present, otherwise {@code null}.
+     *
+     * @hide
+     */
+    // Remark on @UnsupportedAppUsage: Display.getCutout should be used instead
+    @Nullable
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    public DisplayCutout displayCutout;
+
+    /**
+     * The rotation of the display relative to its natural orientation.
+     * May be one of {@link android.view.Surface#ROTATION_0},
+     * {@link android.view.Surface#ROTATION_90}, {@link android.view.Surface#ROTATION_180},
+     * {@link android.view.Surface#ROTATION_270}.
+     * <p>
+     * The value of this field is indeterminate if the logical display is presented on
+     * more than one physical display.
+     * </p>
+     */
+    @Surface.Rotation
+    @UnsupportedAppUsage
+    public int rotation;
+
+    /**
+     * The active display mode.
+     */
+    public int modeId;
+
+    /**
+     * The default display mode.
+     */
+    public int defaultModeId;
+
+    /**
+     * The supported modes of this display.
+     */
+    public Display.Mode[] supportedModes = Display.Mode.EMPTY_ARRAY;
+
+    /** The active color mode. */
+    public int colorMode;
+
+    /** The list of supported color modes */
+    public int[] supportedColorModes = { Display.COLOR_MODE_DEFAULT };
+
+    /** The display's HDR capabilities */
+    public Display.HdrCapabilities hdrCapabilities;
+
+    /** The formats disabled by user **/
+    public int[] userDisabledHdrTypes = {};
+
+    /**
+     * Indicates whether the display can be switched into a mode with minimal post
+     * processing.
+     *
+     * @see android.view.Display#isMinimalPostProcessingSupported
+     */
+    public boolean minimalPostProcessingSupported;
+
+    /**
+     * The logical display density which is the basis for density-independent
+     * pixels.
+     */
+    public int logicalDensityDpi;
+
+    /**
+     * The exact physical pixels per inch of the screen in the X dimension.
+     * <p>
+     * The value of this field is indeterminate if the logical display is presented on
+     * more than one physical display.
+     * </p>
+     */
+    public float physicalXDpi;
+
+    /**
+     * The exact physical pixels per inch of the screen in the Y dimension.
+     * <p>
+     * The value of this field is indeterminate if the logical display is presented on
+     * more than one physical display.
+     * </p>
+     */
+    public float physicalYDpi;
+
+    /**
+     * This is a positive value indicating the phase offset of the VSYNC events provided by
+     * Choreographer relative to the display refresh.  For example, if Choreographer reports
+     * that the refresh occurred at time N, it actually occurred at (N - appVsyncOffsetNanos).
+     */
+    public long appVsyncOffsetNanos;
+
+    /**
+     * This is how far in advance a buffer must be queued for presentation at
+     * a given time.  If you want a buffer to appear on the screen at
+     * time N, you must submit the buffer before (N - bufferDeadlineNanos).
+     */
+    public long presentationDeadlineNanos;
+
+    /**
+     * The state of the display, such as {@link android.view.Display#STATE_ON}.
+     */
+    public int state;
+
+    /**
+     * The UID of the application that owns this display, or zero if it is owned by the system.
+     * <p>
+     * If the display is private, then only the owner can use it.
+     * </p>
+     */
+    public int ownerUid;
+
+    /**
+     * The package name of the application that owns this display, or null if it is
+     * owned by the system.
+     * <p>
+     * If the display is private, then only the owner can use it.
+     * </p>
+     */
+    public String ownerPackageName;
+
+    /**
+     * The refresh rate override for this app. 0 means no override.
+     */
+    public float refreshRateOverride;
+
+    /**
+     * @hide
+     * Get current remove mode of the display - what actions should be performed with the display's
+     * content when it is removed.
+     *
+     * @see Display#getRemoveMode()
+     */
+    // TODO (b/114338689): Remove the flag and use IWindowManager#getRemoveContentMode
+    public int removeMode = Display.REMOVE_MODE_MOVE_CONTENT_TO_PRIMARY;
+
+    /**
+     * @hide
+     * The current minimum brightness constraint of the display. Value between 0.0 and 1.0,
+     * derived from the config constraints of the display device of this logical display.
+     */
+    public float brightnessMinimum;
+
+    /**
+     * @hide
+     * The current maximum brightness constraint of the display. Value between 0.0 and 1.0,
+     * derived from the config constraints of the display device of this logical display.
+     */
+    public float brightnessMaximum;
+
+    /**
+     * @hide
+     * The current default brightness of the display. Value between 0.0 and 1.0,
+     * derived from the configuration of the display device of this logical display.
+     */
+    public float brightnessDefault;
+
+    /**
+     * The {@link RoundedCorners} if present, otherwise {@code null}.
+     */
+    @Nullable
+    public RoundedCorners roundedCorners;
+
+    public static final @android.annotation.NonNull Creator<DisplayInfo> CREATOR = new Creator<DisplayInfo>() {
+        @Override
+        public DisplayInfo createFromParcel(Parcel source) {
+            return new DisplayInfo(source);
+        }
+
+        @Override
+        public DisplayInfo[] newArray(int size) {
+            return new DisplayInfo[size];
+        }
+    };
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769467)
+    public DisplayInfo() {
+    }
+
+    public DisplayInfo(DisplayInfo other) {
+        copyFrom(other);
+    }
+
+    private DisplayInfo(Parcel source) {
+        readFromParcel(source);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        return o instanceof DisplayInfo && equals((DisplayInfo)o);
+    }
+
+    public boolean equals(DisplayInfo other) {
+        return other != null
+                && layerStack == other.layerStack
+                && flags == other.flags
+                && type == other.type
+                && displayId == other.displayId
+                && displayGroupId == other.displayGroupId
+                && Objects.equals(address, other.address)
+                && Objects.equals(deviceProductInfo, other.deviceProductInfo)
+                && Objects.equals(uniqueId, other.uniqueId)
+                && appWidth == other.appWidth
+                && appHeight == other.appHeight
+                && smallestNominalAppWidth == other.smallestNominalAppWidth
+                && smallestNominalAppHeight == other.smallestNominalAppHeight
+                && largestNominalAppWidth == other.largestNominalAppWidth
+                && largestNominalAppHeight == other.largestNominalAppHeight
+                && logicalWidth == other.logicalWidth
+                && logicalHeight == other.logicalHeight
+                && Objects.equals(displayCutout, other.displayCutout)
+                && rotation == other.rotation
+                && modeId == other.modeId
+                && defaultModeId == other.defaultModeId
+                && Arrays.equals(supportedModes, other.supportedModes)
+                && colorMode == other.colorMode
+                && Arrays.equals(supportedColorModes, other.supportedColorModes)
+                && Objects.equals(hdrCapabilities, other.hdrCapabilities)
+                && Arrays.equals(userDisabledHdrTypes, other.userDisabledHdrTypes)
+                && minimalPostProcessingSupported == other.minimalPostProcessingSupported
+                && logicalDensityDpi == other.logicalDensityDpi
+                && physicalXDpi == other.physicalXDpi
+                && physicalYDpi == other.physicalYDpi
+                && appVsyncOffsetNanos == other.appVsyncOffsetNanos
+                && presentationDeadlineNanos == other.presentationDeadlineNanos
+                && state == other.state
+                && ownerUid == other.ownerUid
+                && Objects.equals(ownerPackageName, other.ownerPackageName)
+                && removeMode == other.removeMode
+                && getRefreshRate() == other.getRefreshRate()
+                && brightnessMinimum == other.brightnessMinimum
+                && brightnessMaximum == other.brightnessMaximum
+                && brightnessDefault == other.brightnessDefault
+                && Objects.equals(roundedCorners, other.roundedCorners);
+    }
+
+    @Override
+    public int hashCode() {
+        return 0; // don't care
+    }
+
+    public void copyFrom(DisplayInfo other) {
+        layerStack = other.layerStack;
+        flags = other.flags;
+        type = other.type;
+        displayId = other.displayId;
+        displayGroupId = other.displayGroupId;
+        address = other.address;
+        deviceProductInfo = other.deviceProductInfo;
+        name = other.name;
+        uniqueId = other.uniqueId;
+        appWidth = other.appWidth;
+        appHeight = other.appHeight;
+        smallestNominalAppWidth = other.smallestNominalAppWidth;
+        smallestNominalAppHeight = other.smallestNominalAppHeight;
+        largestNominalAppWidth = other.largestNominalAppWidth;
+        largestNominalAppHeight = other.largestNominalAppHeight;
+        logicalWidth = other.logicalWidth;
+        logicalHeight = other.logicalHeight;
+        displayCutout = other.displayCutout;
+        rotation = other.rotation;
+        modeId = other.modeId;
+        defaultModeId = other.defaultModeId;
+        supportedModes = Arrays.copyOf(other.supportedModes, other.supportedModes.length);
+        colorMode = other.colorMode;
+        supportedColorModes = Arrays.copyOf(
+                other.supportedColorModes, other.supportedColorModes.length);
+        hdrCapabilities = other.hdrCapabilities;
+        userDisabledHdrTypes = other.userDisabledHdrTypes;
+        minimalPostProcessingSupported = other.minimalPostProcessingSupported;
+        logicalDensityDpi = other.logicalDensityDpi;
+        physicalXDpi = other.physicalXDpi;
+        physicalYDpi = other.physicalYDpi;
+        appVsyncOffsetNanos = other.appVsyncOffsetNanos;
+        presentationDeadlineNanos = other.presentationDeadlineNanos;
+        state = other.state;
+        ownerUid = other.ownerUid;
+        ownerPackageName = other.ownerPackageName;
+        removeMode = other.removeMode;
+        refreshRateOverride = other.refreshRateOverride;
+        brightnessMinimum = other.brightnessMinimum;
+        brightnessMaximum = other.brightnessMaximum;
+        brightnessDefault = other.brightnessDefault;
+        roundedCorners = other.roundedCorners;
+    }
+
+    public void readFromParcel(Parcel source) {
+        layerStack = source.readInt();
+        flags = source.readInt();
+        type = source.readInt();
+        displayId = source.readInt();
+        displayGroupId = source.readInt();
+        address = source.readParcelable(null);
+        deviceProductInfo = source.readParcelable(null);
+        name = source.readString8();
+        appWidth = source.readInt();
+        appHeight = source.readInt();
+        smallestNominalAppWidth = source.readInt();
+        smallestNominalAppHeight = source.readInt();
+        largestNominalAppWidth = source.readInt();
+        largestNominalAppHeight = source.readInt();
+        logicalWidth = source.readInt();
+        logicalHeight = source.readInt();
+        displayCutout = DisplayCutout.ParcelableWrapper.readCutoutFromParcel(source);
+        rotation = source.readInt();
+        modeId = source.readInt();
+        defaultModeId = source.readInt();
+        int nModes = source.readInt();
+        supportedModes = new Display.Mode[nModes];
+        for (int i = 0; i < nModes; i++) {
+            supportedModes[i] = Display.Mode.CREATOR.createFromParcel(source);
+        }
+        colorMode = source.readInt();
+        int nColorModes = source.readInt();
+        supportedColorModes = new int[nColorModes];
+        for (int i = 0; i < nColorModes; i++) {
+            supportedColorModes[i] = source.readInt();
+        }
+        hdrCapabilities = source.readParcelable(null);
+        minimalPostProcessingSupported = source.readBoolean();
+        logicalDensityDpi = source.readInt();
+        physicalXDpi = source.readFloat();
+        physicalYDpi = source.readFloat();
+        appVsyncOffsetNanos = source.readLong();
+        presentationDeadlineNanos = source.readLong();
+        state = source.readInt();
+        ownerUid = source.readInt();
+        ownerPackageName = source.readString8();
+        uniqueId = source.readString8();
+        removeMode = source.readInt();
+        refreshRateOverride = source.readFloat();
+        brightnessMinimum = source.readFloat();
+        brightnessMaximum = source.readFloat();
+        brightnessDefault = source.readFloat();
+        roundedCorners = source.readTypedObject(RoundedCorners.CREATOR);
+        int numUserDisabledFormats = source.readInt();
+        userDisabledHdrTypes = new int[numUserDisabledFormats];
+        for (int i = 0; i < numUserDisabledFormats; i++) {
+            userDisabledHdrTypes[i] = source.readInt();
+        }
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(layerStack);
+        dest.writeInt(this.flags);
+        dest.writeInt(type);
+        dest.writeInt(displayId);
+        dest.writeInt(displayGroupId);
+        dest.writeParcelable(address, flags);
+        dest.writeParcelable(deviceProductInfo, flags);
+        dest.writeString8(name);
+        dest.writeInt(appWidth);
+        dest.writeInt(appHeight);
+        dest.writeInt(smallestNominalAppWidth);
+        dest.writeInt(smallestNominalAppHeight);
+        dest.writeInt(largestNominalAppWidth);
+        dest.writeInt(largestNominalAppHeight);
+        dest.writeInt(logicalWidth);
+        dest.writeInt(logicalHeight);
+        DisplayCutout.ParcelableWrapper.writeCutoutToParcel(displayCutout, dest, flags);
+        dest.writeInt(rotation);
+        dest.writeInt(modeId);
+        dest.writeInt(defaultModeId);
+        dest.writeInt(supportedModes.length);
+        for (int i = 0; i < supportedModes.length; i++) {
+            supportedModes[i].writeToParcel(dest, flags);
+        }
+        dest.writeInt(colorMode);
+        dest.writeInt(supportedColorModes.length);
+        for (int i = 0; i < supportedColorModes.length; i++) {
+            dest.writeInt(supportedColorModes[i]);
+        }
+        dest.writeParcelable(hdrCapabilities, flags);
+        dest.writeBoolean(minimalPostProcessingSupported);
+        dest.writeInt(logicalDensityDpi);
+        dest.writeFloat(physicalXDpi);
+        dest.writeFloat(physicalYDpi);
+        dest.writeLong(appVsyncOffsetNanos);
+        dest.writeLong(presentationDeadlineNanos);
+        dest.writeInt(state);
+        dest.writeInt(ownerUid);
+        dest.writeString8(ownerPackageName);
+        dest.writeString8(uniqueId);
+        dest.writeInt(removeMode);
+        dest.writeFloat(refreshRateOverride);
+        dest.writeFloat(brightnessMinimum);
+        dest.writeFloat(brightnessMaximum);
+        dest.writeFloat(brightnessDefault);
+        dest.writeTypedObject(roundedCorners, flags);
+        dest.writeInt(userDisabledHdrTypes.length);
+        for (int i = 0; i < userDisabledHdrTypes.length; i++) {
+            dest.writeInt(userDisabledHdrTypes[i]);
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Returns the refresh rate the application would experience.
+     */
+    public float getRefreshRate() {
+        if (refreshRateOverride > 0) {
+            return refreshRateOverride;
+        }
+
+        return getMode().getRefreshRate();
+    }
+
+    public Display.Mode getMode() {
+        return findMode(modeId);
+    }
+
+    public Display.Mode getDefaultMode() {
+        return findMode(defaultModeId);
+    }
+
+    private Display.Mode findMode(int id) {
+        for (int i = 0; i < supportedModes.length; i++) {
+            if (supportedModes[i].getModeId() == id) {
+                return supportedModes[i];
+            }
+        }
+        throw new IllegalStateException("Unable to locate mode " + id);
+    }
+
+    /**
+     * Returns the id of the "default" mode with the given refresh rate, or {@code 0} if no suitable
+     * mode could be found.
+     */
+    @Nullable
+    public Display.Mode findDefaultModeByRefreshRate(float refreshRate) {
+        Display.Mode[] modes = supportedModes;
+        Display.Mode defaultMode = getDefaultMode();
+        for (int i = 0; i < modes.length; i++) {
+            if (modes[i].matches(
+                    defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), refreshRate)) {
+                return modes[i];
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the list of supported refresh rates in the default mode.
+     */
+    public float[] getDefaultRefreshRates() {
+        Display.Mode[] modes = supportedModes;
+        ArraySet<Float> rates = new ArraySet<>();
+        Display.Mode defaultMode = getDefaultMode();
+        for (int i = 0; i < modes.length; i++) {
+            Display.Mode mode = modes[i];
+            if (mode.getPhysicalWidth() == defaultMode.getPhysicalWidth()
+                    && mode.getPhysicalHeight() == defaultMode.getPhysicalHeight()) {
+                rates.add(mode.getRefreshRate());
+            }
+        }
+        float[] result = new float[rates.size()];
+        int i = 0;
+        for (Float rate : rates) {
+            result[i++] = rate;
+        }
+        return result;
+    }
+
+    public void getAppMetrics(DisplayMetrics outMetrics) {
+        getAppMetrics(outMetrics, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+    }
+
+    public void getAppMetrics(DisplayMetrics outMetrics, DisplayAdjustments displayAdjustments) {
+        getMetricsWithSize(outMetrics, displayAdjustments.getCompatibilityInfo(),
+                displayAdjustments.getConfiguration(), appWidth, appHeight);
+    }
+
+    public void getAppMetrics(DisplayMetrics outMetrics, CompatibilityInfo ci,
+            Configuration configuration) {
+        getMetricsWithSize(outMetrics, ci, configuration, appWidth, appHeight);
+    }
+
+    /**
+     * Populates {@code outMetrics} with details of the logical display. Bounds are limited
+     * by the logical size of the display.
+     *
+     * @param outMetrics the {@link DisplayMetrics} to be populated
+     * @param compatInfo the {@link CompatibilityInfo} to be applied
+     * @param configuration the {@link Configuration}
+     */
+    public void getLogicalMetrics(DisplayMetrics outMetrics, CompatibilityInfo compatInfo,
+            Configuration configuration) {
+        getMetricsWithSize(outMetrics, compatInfo, configuration, logicalWidth, logicalHeight);
+    }
+
+    /**
+     * Similar to {@link #getLogicalMetrics}, but the limiting bounds are determined from
+     * {@link WindowConfiguration#getMaxBounds()}
+     */
+    public void getMaxBoundsMetrics(DisplayMetrics outMetrics, CompatibilityInfo compatInfo,
+            Configuration configuration) {
+        Rect bounds = configuration.windowConfiguration.getMaxBounds();
+        // Pass in null configuration to ensure width and height are not overridden to app bounds.
+        getMetricsWithSize(outMetrics, compatInfo, /* configuration= */ null,
+                bounds.width(), bounds.height());
+    }
+
+    public int getNaturalWidth() {
+        return rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180 ?
+                logicalWidth : logicalHeight;
+    }
+
+    public int getNaturalHeight() {
+        return rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180 ?
+                logicalHeight : logicalWidth;
+    }
+
+    public boolean isHdr() {
+        int[] types = hdrCapabilities != null ? hdrCapabilities.getSupportedHdrTypes() : null;
+        return types != null && types.length > 0;
+    }
+
+    public boolean isWideColorGamut() {
+        for (int colorMode : supportedColorModes) {
+            if (colorMode == Display.COLOR_MODE_DCI_P3 || colorMode > Display.COLOR_MODE_SRGB) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if the specified UID has access to this display.
+     */
+    public boolean hasAccess(int uid) {
+        return Display.hasAccess(uid, flags, ownerUid, displayId);
+    }
+
+    private void getMetricsWithSize(DisplayMetrics outMetrics, CompatibilityInfo compatInfo,
+            Configuration configuration, int width, int height) {
+        outMetrics.densityDpi = outMetrics.noncompatDensityDpi = logicalDensityDpi;
+        outMetrics.density = outMetrics.noncompatDensity =
+                logicalDensityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+        outMetrics.scaledDensity = outMetrics.noncompatScaledDensity = outMetrics.density;
+        outMetrics.xdpi = outMetrics.noncompatXdpi = physicalXDpi;
+        outMetrics.ydpi = outMetrics.noncompatYdpi = physicalYDpi;
+
+        final Rect appBounds = configuration != null
+                ? configuration.windowConfiguration.getAppBounds() : null;
+        width = appBounds != null ? appBounds.width() : width;
+        height = appBounds != null ? appBounds.height() : height;
+
+        outMetrics.noncompatWidthPixels  = outMetrics.widthPixels = width;
+        outMetrics.noncompatHeightPixels = outMetrics.heightPixels = height;
+
+        if (!compatInfo.equals(CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO)) {
+            compatInfo.applyToDisplayMetrics(outMetrics);
+        }
+    }
+
+    // For debugging purposes
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("DisplayInfo{\"");
+        sb.append(name);
+        sb.append("\", displayId ");
+        sb.append(displayId);
+        sb.append("\", displayGroupId ");
+        sb.append(displayGroupId);
+        sb.append(flagsToString(flags));
+        sb.append(", real ");
+        sb.append(logicalWidth);
+        sb.append(" x ");
+        sb.append(logicalHeight);
+        sb.append(", largest app ");
+        sb.append(largestNominalAppWidth);
+        sb.append(" x ");
+        sb.append(largestNominalAppHeight);
+        sb.append(", smallest app ");
+        sb.append(smallestNominalAppWidth);
+        sb.append(" x ");
+        sb.append(smallestNominalAppHeight);
+        sb.append(", appVsyncOff ");
+        sb.append(appVsyncOffsetNanos);
+        sb.append(", presDeadline ");
+        sb.append(presentationDeadlineNanos);
+        sb.append(", mode ");
+        sb.append(modeId);
+        sb.append(", defaultMode ");
+        sb.append(defaultModeId);
+        sb.append(", modes ");
+        sb.append(Arrays.toString(supportedModes));
+        sb.append(", hdrCapabilities ");
+        sb.append(hdrCapabilities);
+        sb.append(", userDisabledHdrTypes ");
+        sb.append(Arrays.toString(userDisabledHdrTypes));
+        sb.append(", minimalPostProcessingSupported ");
+        sb.append(minimalPostProcessingSupported);
+        sb.append(", rotation ");
+        sb.append(rotation);
+        sb.append(", state ");
+        sb.append(Display.stateToString(state));
+
+        if (Process.myUid() != Process.SYSTEM_UID) {
+            sb.append("}");
+            return sb.toString();
+        }
+
+        sb.append(", type ");
+        sb.append(Display.typeToString(type));
+        sb.append(", uniqueId \"");
+        sb.append(uniqueId);
+        sb.append("\", app ");
+        sb.append(appWidth);
+        sb.append(" x ");
+        sb.append(appHeight);
+        sb.append(", density ");
+        sb.append(logicalDensityDpi);
+        sb.append(" (");
+        sb.append(physicalXDpi);
+        sb.append(" x ");
+        sb.append(physicalYDpi);
+        sb.append(") dpi, layerStack ");
+        sb.append(layerStack);
+        sb.append(", colorMode ");
+        sb.append(colorMode);
+        sb.append(", supportedColorModes ");
+        sb.append(Arrays.toString(supportedColorModes));
+        if (address != null) {
+            sb.append(", address ").append(address);
+        }
+        sb.append(", deviceProductInfo ");
+        sb.append(deviceProductInfo);
+        if (ownerUid != 0 || ownerPackageName != null) {
+            sb.append(", owner ").append(ownerPackageName);
+            sb.append(" (uid ").append(ownerUid).append(")");
+        }
+        sb.append(", removeMode ");
+        sb.append(removeMode);
+        sb.append(", refreshRateOverride ");
+        sb.append(refreshRateOverride);
+        sb.append(", brightnessMinimum ");
+        sb.append(brightnessMinimum);
+        sb.append(", brightnessMaximum ");
+        sb.append(brightnessMaximum);
+        sb.append(", brightnessDefault ");
+        sb.append(brightnessDefault);
+        sb.append("}");
+        return sb.toString();
+    }
+
+    /**
+     * Write to a protocol buffer output stream.
+     * Protocol buffer message definition at {@link android.view.DisplayInfoProto}
+     *
+     * @param protoOutputStream Stream to write the Rect object to.
+     * @param fieldId           Field Id of the DisplayInfoProto as defined in the parent message
+     */
+    public void dumpDebug(ProtoOutputStream protoOutputStream, long fieldId) {
+        final long token = protoOutputStream.start(fieldId);
+        protoOutputStream.write(LOGICAL_WIDTH, logicalWidth);
+        protoOutputStream.write(LOGICAL_HEIGHT, logicalHeight);
+        protoOutputStream.write(APP_WIDTH, appWidth);
+        protoOutputStream.write(APP_HEIGHT, appHeight);
+        protoOutputStream.write(NAME, name);
+        protoOutputStream.write(FLAGS, flags);
+        protoOutputStream.end(token);
+    }
+
+    private static String flagsToString(int flags) {
+        StringBuilder result = new StringBuilder();
+        if ((flags & Display.FLAG_SECURE) != 0) {
+            result.append(", FLAG_SECURE");
+        }
+        if ((flags & Display.FLAG_SUPPORTS_PROTECTED_BUFFERS) != 0) {
+            result.append(", FLAG_SUPPORTS_PROTECTED_BUFFERS");
+        }
+        if ((flags & Display.FLAG_PRIVATE) != 0) {
+            result.append(", FLAG_PRIVATE");
+        }
+        if ((flags & Display.FLAG_PRESENTATION) != 0) {
+            result.append(", FLAG_PRESENTATION");
+        }
+        if ((flags & Display.FLAG_SCALING_DISABLED) != 0) {
+            result.append(", FLAG_SCALING_DISABLED");
+        }
+        if ((flags & Display.FLAG_ROUND) != 0) {
+            result.append(", FLAG_ROUND");
+        }
+        if ((flags & Display.FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD) != 0) {
+            result.append(", FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD");
+        }
+        if ((flags & Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0) {
+            result.append(", FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS");
+        }
+        if ((flags & Display.FLAG_TRUSTED) != 0) {
+            result.append(", FLAG_TRUSTED");
+        }
+        if ((flags & Display.FLAG_OWN_DISPLAY_GROUP) != 0) {
+            result.append(", FLAG_OWN_DISPLAY_GROUP");
+        }
+        return result.toString();
+    }
+}
diff --git a/android/view/Display_Delegate.java b/android/view/Display_Delegate.java
new file mode 100644
index 0000000..53dc821
--- /dev/null
+++ b/android/view/Display_Delegate.java
@@ -0,0 +1,36 @@
+/*
+ * 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.view;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link Display}
+ *
+ * Through the layoutlib_create tool, the original  methods of Display have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class Display_Delegate {
+
+    @LayoutlibDelegate
+    static void updateDisplayInfoLocked(Display theDisplay) {
+        // do nothing
+    }
+
+}
diff --git a/android/view/DragAndDropPermissions.java b/android/view/DragAndDropPermissions.java
new file mode 100644
index 0000000..973836a
--- /dev/null
+++ b/android/view/DragAndDropPermissions.java
@@ -0,0 +1,172 @@
+/*
+** Copyright 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.view;
+
+import static java.lang.Integer.toHexString;
+
+import android.app.Activity;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.view.IDragAndDropPermissions;
+
+/**
+ * {@link DragAndDropPermissions} controls the access permissions for the content URIs associated
+ * with a {@link DragEvent}.
+ * <p>
+ * Permission are granted when this object is created by {@link
+ * android.app.Activity#requestDragAndDropPermissions(DragEvent)
+ * Activity.requestDragAndDropPermissions}.
+ * Which permissions are granted is defined by the set of flags passed to {@link
+ * View#startDragAndDrop(android.content.ClipData, View.DragShadowBuilder, Object, int)
+ * View.startDragAndDrop} by the app that started the drag operation.
+ * </p>
+ * <p>
+ * The lifecycle of the permissions is bound to the activity used to call {@link
+ * android.app.Activity#requestDragAndDropPermissions(DragEvent) requestDragAndDropPermissions}. The
+ * permissions are revoked when this activity is destroyed, or when {@link #release()} is called,
+ * whichever occurs first.
+ * </p>
+ * <p>
+ * If you anticipate that your application will receive a large number of drops (e.g. document
+ * editor), you should try to call {@link #release()} on the obtained permissions as soon as they
+ * are no longer required. Permissions can be added to your activity's
+ * {@link Activity#onSaveInstanceState} bundle and later retrieved in order to manually release
+ * the permissions once they are no longer needed.
+ * </p>
+ * <p>
+ * Learn more about <a href="/guide/topics/ui/drag-drop#DragPermissionsMultiWindow">drag permissions
+ * in multi-window mode</a>.
+ * </p>
+ */
+public final class DragAndDropPermissions implements Parcelable {
+
+    private static final String TAG = "DragAndDrop";
+    private static final boolean DEBUG = false;
+
+    private final IDragAndDropPermissions mDragAndDropPermissions;
+
+    /**
+     * Create a new {@link DragAndDropPermissions} object to control the access permissions for
+     * content URIs associated with {@link DragEvent}.
+     * @param dragEvent Drag event
+     * @return {@link DragAndDropPermissions} object or null if there are no content URIs associated
+     * with the {@link DragEvent}.
+     * @hide
+     */
+    public static DragAndDropPermissions obtain(DragEvent dragEvent) {
+        if (dragEvent.getDragAndDropPermissions() == null) {
+            return null;
+        }
+        return new DragAndDropPermissions(dragEvent.getDragAndDropPermissions());
+    }
+
+    /** @hide */
+    private DragAndDropPermissions(IDragAndDropPermissions dragAndDropPermissions) {
+        mDragAndDropPermissions = dragAndDropPermissions;
+    }
+
+    /**
+     * Take permissions, binding their lifetime to the activity.
+     *
+     * <p>Note: This API is exposed to apps via
+     * {@link Activity#requestDragAndDropPermissions(DragEvent)}.
+     *
+     * @param activityToken Binder pointing to an Activity instance to bind the lifetime to.
+     * @return True if permissions are successfully taken.
+     *
+     * @hide
+     */
+    public boolean take(IBinder activityToken) {
+        try {
+            if (DEBUG) {
+                Log.d(TAG, this + ": calling take() with activity-bound token: "
+                        + toHexString(activityToken.hashCode()));
+            }
+            mDragAndDropPermissions.take(activityToken);
+        } catch (RemoteException e) {
+            Log.w(TAG, this + ": take() failed with a RemoteException", e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Take permissions transiently. Permissions will be tied to this object's lifecycle; if not
+     * released explicitly, they will be released automatically when there are no more references
+     * to this object and it's garbage collected.
+     *
+     * <p>Note: This API is not exposed to apps.
+     *
+     * @return True if permissions are successfully taken.
+     *
+     * @hide
+     */
+    public boolean takeTransient() {
+        try {
+            if (DEBUG) {
+                Log.d(TAG, this + ": calling takeTransient()");
+            }
+            mDragAndDropPermissions.takeTransient();
+        } catch (RemoteException e) {
+            Log.w(TAG, this + ": takeTransient() failed with a RemoteException", e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Revoke permissions explicitly.
+     */
+    public void release() {
+        try {
+            mDragAndDropPermissions.release();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<DragAndDropPermissions> CREATOR =
+            new Parcelable.Creator<DragAndDropPermissions> () {
+        @Override
+        public DragAndDropPermissions createFromParcel(Parcel source) {
+            return new DragAndDropPermissions(source);
+        }
+
+        @Override
+        public DragAndDropPermissions[] newArray(int size) {
+            return new DragAndDropPermissions[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel destination, int flags) {
+        destination.writeStrongInterface(mDragAndDropPermissions);
+    }
+
+    private DragAndDropPermissions(Parcel in) {
+        mDragAndDropPermissions = IDragAndDropPermissions.Stub.asInterface(in.readStrongBinder());
+    }
+}
diff --git a/android/view/DragEvent.java b/android/view/DragEvent.java
new file mode 100644
index 0000000..b6b029b
--- /dev/null
+++ b/android/view/DragEvent.java
@@ -0,0 +1,634 @@
+/*
+ * 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.view;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.view.IDragAndDropPermissions;
+
+//TODO: Improve Javadoc
+/**
+ * Represents an event that is sent out by the system at various times during a drag and drop
+ * operation. It is a data structure that contains several important pieces of data about
+ * the operation and the underlying data.
+ * <p>
+ *  View objects that receive a DragEvent call {@link #getAction()}, which returns
+ *  an action type that indicates the state of the drag and drop operation. This allows a View
+ *  object to react to a change in state by changing its appearance or performing other actions.
+ *  For example, a View can react to the {@link #ACTION_DRAG_ENTERED} action type by
+ *  by changing one or more colors in its displayed image.
+ * </p>
+ * <p>
+ *  During a drag and drop operation, the system displays an image that the user drags. This image
+ *  is called a drag shadow. Several action types reflect the position of the drag shadow relative
+ *  to the View receiving the event.
+ * </p>
+ * <p>
+ *  Most methods return valid data only for certain event actions. This is summarized in the
+ *  following table. Each possible {@link #getAction()} value is listed in the first column. The
+ *  other columns indicate which method or methods return valid data for that getAction() value:
+ * </p>
+ * <table>
+ *  <tr>
+ *      <th scope="col">getAction() Value</th>
+ *      <th scope="col">getClipDescription()</th>
+ *      <th scope="col">getLocalState()</th>
+ *      <th scope="col">getX()</th>
+ *      <th scope="col">getY()</th>
+ *      <th scope="col">getClipData()</th>
+ *      <th scope="col">getResult()</th>
+ *  </tr>
+ *  <tr>
+ *      <td>ACTION_DRAG_STARTED</td>
+ *      <td style="text-align: center;">X</td>
+ *      <td style="text-align: center;">X</td>
+ *      <td style="text-align: center;">X</td>
+ *      <td style="text-align: center;">X</td>
+ *      <td style="text-align: center;">&nbsp;</td>
+ *      <td style="text-align: center;">&nbsp;</td>
+ *  </tr>
+ *  <tr>
+ *      <td>ACTION_DRAG_ENTERED</td>
+ *      <td style="text-align: center;">X</td>
+ *      <td style="text-align: center;">X</td>
+ *      <td style="text-align: center;">&nbsp;</td>
+ *      <td style="text-align: center;">&nbsp;</td>
+ *      <td style="text-align: center;">&nbsp;</td>
+ *      <td style="text-align: center;">&nbsp;</td>
+ *  </tr>
+ *  <tr>
+ *      <td>ACTION_DRAG_LOCATION</td>
+ *      <td style="text-align: center;">X</td>
+ *      <td style="text-align: center;">X</td>
+ *      <td style="text-align: center;">X</td>
+ *      <td style="text-align: center;">X</td>
+ *      <td style="text-align: center;">&nbsp;</td>
+ *      <td style="text-align: center;">&nbsp;</td>
+ *  </tr>
+ *  <tr>
+ *      <td>ACTION_DRAG_EXITED</td>
+ *      <td style="text-align: center;">X</td>
+ *      <td style="text-align: center;">X</td>
+ *      <td style="text-align: center;">&nbsp;</td>
+ *      <td style="text-align: center;">&nbsp;</td>
+ *      <td style="text-align: center;">&nbsp;</td>
+ *      <td style="text-align: center;">&nbsp;</td>
+ *  </tr>
+ *  <tr>
+ *      <td>ACTION_DROP</td>
+ *      <td style="text-align: center;">X</td>
+ *      <td style="text-align: center;">X</td>
+ *      <td style="text-align: center;">X</td>
+ *      <td style="text-align: center;">X</td>
+ *      <td style="text-align: center;">X</td>
+ *      <td style="text-align: center;">&nbsp;</td>
+ *  </tr>
+ *  <tr>
+ *      <td>ACTION_DRAG_ENDED</td>
+ *      <td style="text-align: center;">&nbsp;</td>
+ *      <td style="text-align: center;">X</td>
+ *      <td style="text-align: center;">&nbsp;</td>
+ *      <td style="text-align: center;">&nbsp;</td>
+ *      <td style="text-align: center;">&nbsp;</td>
+ *      <td style="text-align: center;">X</td>
+ *  </tr>
+ * </table>
+ * <p>
+ *  The {@link android.view.DragEvent#getAction()},
+ *  {@link android.view.DragEvent#getLocalState()}
+ *  {@link android.view.DragEvent#describeContents()},
+ *  {@link android.view.DragEvent#writeToParcel(Parcel,int)}, and
+ *  {@link android.view.DragEvent#toString()} methods always return valid data.
+ * </p>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For a guide to implementing drag and drop features, read the
+ * <a href="{@docRoot}guide/topics/ui/drag-drop.html">Drag and Drop</a> developer guide.</p>
+ * </div>
+ */
+public class DragEvent implements Parcelable {
+    private static final boolean TRACK_RECYCLED_LOCATION = false;
+
+    int mAction;
+    float mX, mY;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    ClipDescription mClipDescription;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    ClipData mClipData;
+    IDragAndDropPermissions mDragAndDropPermissions;
+
+    Object mLocalState;
+    boolean mDragResult;
+    boolean mEventHandlerWasCalled;
+
+    /**
+     * The drag surface containing the object being dragged. Only provided if the target window
+     * has the {@link WindowManager.LayoutParams#PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP} flag
+     * and is only sent with {@link #ACTION_DROP}.
+     */
+    private SurfaceControl mDragSurface;
+
+    /**
+     * The offsets from the touch that the surface is adjusted by as the surface is moved around the
+     * screen. Necessary for the target using the drag surface to animate it properly once it takes
+     * ownership of the drag surface after the drop.
+     */
+    private float mOffsetX;
+    private float mOffsetY;
+
+    private DragEvent mNext;
+    private RuntimeException mRecycledLocation;
+    private boolean mRecycled;
+
+    private static final int MAX_RECYCLED = 10;
+    private static final Object gRecyclerLock = new Object();
+    private static int gRecyclerUsed = 0;
+    private static DragEvent gRecyclerTop = null;
+
+    /**
+     * Action constant returned by {@link #getAction()}: Signals the start of a
+     * drag and drop operation. The View should return {@code true} from its
+     * {@link View#onDragEvent(DragEvent) onDragEvent()} handler method or
+     * {@link View.OnDragListener#onDrag(View,DragEvent) OnDragListener.onDrag()} listener
+     * if it can accept a drop. The onDragEvent() or onDrag() methods usually inspect the metadata
+     * from {@link #getClipDescription()} to determine if they can accept the data contained in
+     * this drag. For an operation that doesn't represent data transfer, these methods may
+     * perform other actions to determine whether or not the View should accept the data.
+     * If the View wants to indicate that it is a valid drop target, it can also react by
+     * changing its appearance.
+     * <p>
+     *  Views added or becoming visible for the first time during a drag operation receive this
+     *  event when they are added or becoming visible.
+     * </p>
+     * <p>
+     *  A View only receives further drag events for the drag operation if it returns {@code true}
+     *  in response to ACTION_DRAG_STARTED.
+     * </p>
+     * @see #ACTION_DRAG_ENDED
+     * @see #getX()
+     * @see #getY()
+     */
+    public static final int ACTION_DRAG_STARTED = 1;
+
+    /**
+     * Action constant returned by {@link #getAction()}: Sent to a View after
+     * {@link #ACTION_DRAG_ENTERED} while the drag shadow is still within the View object's bounding
+     * box, but not within a descendant view that can accept the data. The {@link #getX()} and
+     * {@link #getY()} methods supply
+     * the X and Y position of of the drag point within the View object's bounding box.
+     * <p>
+     * A View receives an {@link #ACTION_DRAG_ENTERED} event before receiving any
+     * ACTION_DRAG_LOCATION events.
+     * </p>
+     * <p>
+     * The system stops sending ACTION_DRAG_LOCATION events to a View once the user moves the
+     * drag shadow out of the View object's bounding box or into a descendant view that can accept
+     * the data. If the user moves the drag shadow back into the View object's bounding box or out
+     * of a descendant view that can accept the data, the View receives an ACTION_DRAG_ENTERED again
+     * before receiving any more ACTION_DRAG_LOCATION events.
+     * </p>
+     * @see #ACTION_DRAG_ENTERED
+     * @see #getX()
+     * @see #getY()
+     */
+    public static final int ACTION_DRAG_LOCATION = 2;
+
+    /**
+     * Action constant returned by {@link #getAction()}: Signals to a View that the user
+     * has released the drag shadow, and the drag point is within the bounding box of the View and
+     * not within a descendant view that can accept the data.
+     * The View should retrieve the data from the DragEvent by calling {@link #getClipData()}.
+     * The methods {@link #getX()} and {@link #getY()} return the X and Y position of the drop point
+     * within the View object's bounding box.
+     * <p>
+     * The View should return {@code true} from its {@link View#onDragEvent(DragEvent)}
+     * handler or {@link View.OnDragListener#onDrag(View,DragEvent) OnDragListener.onDrag()}
+     * listener if it accepted the drop, and {@code false} if it ignored the drop.
+     * </p>
+     * <p>
+     * The View can also react to this action by changing its appearance.
+     * </p>
+     * @see #getClipData()
+     * @see #getX()
+     * @see #getY()
+     */
+    public static final int ACTION_DROP = 3;
+
+    /**
+     * Action constant returned by {@link #getAction()}:  Signals to a View that the drag and drop
+     * operation has concluded.  A View that changed its appearance during the operation should
+     * return to its usual drawing state in response to this event.
+     * <p>
+     *  All views with listeners that returned boolean <code>true</code> for the ACTION_DRAG_STARTED
+     *  event will receive the ACTION_DRAG_ENDED event even if they are not currently visible when
+     *  the drag ends. Views removed during the drag operation won't receive the ACTION_DRAG_ENDED
+     *  event.
+     * </p>
+     * <p>
+     *  The View object can call {@link #getResult()} to see the result of the operation.
+     *  If a View returned {@code true} in response to {@link #ACTION_DROP}, then
+     *  getResult() returns {@code true}, otherwise it returns {@code false}.
+     * </p>
+     * @see #ACTION_DRAG_STARTED
+     * @see #getResult()
+     */
+    public static final int ACTION_DRAG_ENDED = 4;
+
+    /**
+     * Action constant returned by {@link #getAction()}: Signals to a View that the drag point has
+     * entered the bounding box of the View.
+     * <p>
+     *  If the View can accept a drop, it can react to ACTION_DRAG_ENTERED
+     *  by changing its appearance in a way that tells the user that the View is the current
+     *  drop target.
+     * </p>
+     * The system stops sending ACTION_DRAG_LOCATION events to a View once the user moves the
+     * drag shadow out of the View object's bounding box or into a descendant view that can accept
+     * the data. If the user moves the drag shadow back into the View object's bounding box or out
+     * of a descendant view that can accept the data, the View receives an ACTION_DRAG_ENTERED again
+     * before receiving any more ACTION_DRAG_LOCATION events.
+     * </p>
+     * @see #ACTION_DRAG_ENTERED
+     * @see #ACTION_DRAG_LOCATION
+     */
+    public static final int ACTION_DRAG_ENTERED = 5;
+
+    /**
+     * Action constant returned by {@link #getAction()}: Signals that the user has moved the
+     * drag shadow out of the bounding box of the View or into a descendant view that can accept
+     * the data.
+     * The View can react by changing its appearance in a way that tells the user that
+     * View is no longer the immediate drop target.
+     * <p>
+     *  After the system sends an ACTION_DRAG_EXITED event to the View, the View receives no more
+     *  ACTION_DRAG_LOCATION events until the user drags the drag shadow back over the View.
+     * </p>
+     *
+     */
+     public static final int ACTION_DRAG_EXITED = 6;
+
+    private DragEvent() {
+    }
+
+    private void init(int action, float x, float y, float offsetX, float offsetY,
+            ClipDescription description, ClipData data, SurfaceControl dragSurface,
+            IDragAndDropPermissions dragAndDropPermissions, Object localState, boolean result) {
+        mAction = action;
+        mX = x;
+        mY = y;
+        mOffsetX = offsetX;
+        mOffsetY = offsetY;
+        mClipDescription = description;
+        mClipData = data;
+        mDragSurface = dragSurface;
+        mDragAndDropPermissions = dragAndDropPermissions;
+        mLocalState = localState;
+        mDragResult = result;
+    }
+
+    static DragEvent obtain() {
+        return DragEvent.obtain(0, 0f, 0f, 0f, 0f, null, null, null, null, null, false);
+    }
+
+    /** @hide */
+    public static DragEvent obtain(int action, float x, float y, float offsetX, float offsetY,
+            Object localState, ClipDescription description, ClipData data,
+            SurfaceControl dragSurface, IDragAndDropPermissions dragAndDropPermissions,
+            boolean result) {
+        final DragEvent ev;
+        synchronized (gRecyclerLock) {
+            if (gRecyclerTop == null) {
+                ev = new DragEvent();
+                ev.init(action, x, y, offsetX, offsetY, description, data, dragSurface,
+                        dragAndDropPermissions, localState, result);
+                return ev;
+            }
+            ev = gRecyclerTop;
+            gRecyclerTop = ev.mNext;
+            gRecyclerUsed -= 1;
+        }
+        ev.mRecycledLocation = null;
+        ev.mRecycled = false;
+        ev.mNext = null;
+
+        ev.init(action, x, y, offsetX, offsetY, description, data, dragSurface,
+                dragAndDropPermissions, localState, result);
+
+        return ev;
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public static DragEvent obtain(DragEvent source) {
+        return obtain(source.mAction, source.mX, source.mY, source.mOffsetX, source.mOffsetY,
+                source.mLocalState, source.mClipDescription, source.mClipData, source.mDragSurface,
+                source.mDragAndDropPermissions, source.mDragResult);
+    }
+
+    /**
+     * Inspect the action value of this event.
+     * @return One of the following action constants, in the order in which they usually occur
+     * during a drag and drop operation:
+     * <ul>
+     *  <li>{@link #ACTION_DRAG_STARTED}</li>
+     *  <li>{@link #ACTION_DRAG_ENTERED}</li>
+     *  <li>{@link #ACTION_DRAG_LOCATION}</li>
+     *  <li>{@link #ACTION_DROP}</li>
+     *  <li>{@link #ACTION_DRAG_EXITED}</li>
+     *  <li>{@link #ACTION_DRAG_ENDED}</li>
+     * </ul>
+     */
+    public int getAction() {
+        return mAction;
+    }
+
+    /**
+     * Gets the X coordinate of the drag point. The value is only valid if the event action is
+     * {@link #ACTION_DRAG_STARTED}, {@link #ACTION_DRAG_LOCATION} or {@link #ACTION_DROP}.
+     * @return The current drag point's X coordinate
+     */
+    public float getX() {
+        return mX;
+    }
+
+    /**
+     * Gets the Y coordinate of the drag point. The value is only valid if the event action is
+     * {@link #ACTION_DRAG_STARTED}, {@link #ACTION_DRAG_LOCATION} or {@link #ACTION_DROP}.
+     * @return The current drag point's Y coordinate
+     */
+    public float getY() {
+        return mY;
+    }
+
+    /** @hide */
+    public float getOffsetX() {
+        return mOffsetX;
+    }
+
+    /** @hide */
+    public float getOffsetY() {
+        return mOffsetY;
+    }
+
+    /**
+     * Returns the {@link android.content.ClipData} object sent to the system as part of the call
+     * to
+     * {@link android.view.View#startDragAndDrop(ClipData,View.DragShadowBuilder,Object,int)
+     * startDragAndDrop()}.
+     * This method only returns valid data if the event action is {@link #ACTION_DROP}.
+     * @return The ClipData sent to the system by startDragAndDrop().
+     */
+    public ClipData getClipData() {
+        return mClipData;
+    }
+
+    /**
+     * Returns the {@link android.content.ClipDescription} object contained in the
+     * {@link android.content.ClipData} object sent to the system as part of the call to
+     * {@link android.view.View#startDragAndDrop(ClipData,View.DragShadowBuilder,Object,int)
+     * startDragAndDrop()}.
+     * The drag handler or listener for a View can use the metadata in this object to decide if the
+     * View can accept the dragged View object's data.
+     * <p>
+     * This method returns valid data for all event actions except for {@link #ACTION_DRAG_ENDED}.
+     * @return The ClipDescription that was part of the ClipData sent to the system by
+     *     startDragAndDrop().
+     */
+    public ClipDescription getClipDescription() {
+        return mClipDescription;
+    }
+
+    /** @hide */
+    public SurfaceControl getDragSurface() {
+        return mDragSurface;
+    }
+
+    /** @hide */
+    public IDragAndDropPermissions getDragAndDropPermissions() {
+        return mDragAndDropPermissions;
+    }
+
+    /**
+     * Returns the local state object sent to the system as part of the call to
+     * {@link android.view.View#startDragAndDrop(ClipData,View.DragShadowBuilder,Object,int)
+     * startDragAndDrop()}.
+     * The object is intended to provide local information about the drag and drop operation. For
+     * example, it can indicate whether the drag and drop operation is a copy or a move.
+     * <p>
+     * The local state is available only to views in the activity which has started the drag
+     * operation. In all other activities this method will return null
+     * </p>
+     * <p>
+     *  This method returns valid data for all event actions.
+     * </p>
+     * @return The local state object sent to the system by startDragAndDrop().
+     */
+    public Object getLocalState() {
+        return mLocalState;
+    }
+
+    /**
+     * <p>
+     * Returns an indication of the result of the drag and drop operation.
+     * This method only returns valid data if the action type is {@link #ACTION_DRAG_ENDED}.
+     * The return value depends on what happens after the user releases the drag shadow.
+     * </p>
+     * <p>
+     * If the user releases the drag shadow on a View that can accept a drop, the system sends an
+     * {@link #ACTION_DROP} event to the View object's drag event listener. If the listener
+     * returns {@code true}, then getResult() will return {@code true}.
+     * If the listener returns {@code false}, then getResult() returns {@code false}.
+     * </p>
+     * <p>
+     * Notice that getResult() also returns {@code false} if no {@link #ACTION_DROP} is sent. This
+     * happens, for example, when the user releases the drag shadow over an area outside of the
+     * application. In this case, the system sends out {@link #ACTION_DRAG_ENDED} for the current
+     * operation, but never sends out {@link #ACTION_DROP}.
+     * </p>
+     * @return {@code true} if a drag event listener returned {@code true} in response to
+     * {@link #ACTION_DROP}. If the system did not send {@link #ACTION_DROP} before
+     * {@link #ACTION_DRAG_ENDED}, or if the listener returned {@code false} in response to
+     * {@link #ACTION_DROP}, then {@code false} is returned.
+     */
+    public boolean getResult() {
+        return mDragResult;
+    }
+
+    /**
+     * Recycle the DragEvent, to be re-used by a later caller.  After calling
+     * this function you must never touch the event again.
+     *
+     * @hide
+     */
+    public final void recycle() {
+        // Ensure recycle is only called once!
+        if (TRACK_RECYCLED_LOCATION) {
+            if (mRecycledLocation != null) {
+                throw new RuntimeException(toString() + " recycled twice!", mRecycledLocation);
+            }
+            mRecycledLocation = new RuntimeException("Last recycled here");
+        } else {
+            if (mRecycled) {
+                throw new RuntimeException(toString() + " recycled twice!");
+            }
+            mRecycled = true;
+        }
+
+        mClipData = null;
+        mClipDescription = null;
+        mLocalState = null;
+        mEventHandlerWasCalled = false;
+
+        synchronized (gRecyclerLock) {
+            if (gRecyclerUsed < MAX_RECYCLED) {
+                gRecyclerUsed++;
+                mNext = gRecyclerTop;
+                gRecyclerTop = this;
+            }
+        }
+    }
+
+    /**
+     * Returns a string that represents the symbolic name of the specified unmasked action
+     * such as "ACTION_DRAG_START", "ACTION_DRAG_END" or an equivalent numeric constant
+     * such as "35" if unknown.
+     *
+     * @param action The action.
+     * @return The symbolic name of the specified action.
+     * @see #getAction()
+     * @hide
+     */
+    public static String actionToString(int action) {
+        switch (action) {
+            case ACTION_DRAG_STARTED:
+                return "ACTION_DRAG_STARTED";
+            case ACTION_DRAG_LOCATION:
+                return "ACTION_DRAG_LOCATION";
+            case ACTION_DROP:
+                return "ACTION_DROP";
+            case ACTION_DRAG_ENDED:
+                return "ACTION_DRAG_ENDED";
+            case ACTION_DRAG_ENTERED:
+                return "ACTION_DRAG_ENTERED";
+            case ACTION_DRAG_EXITED:
+                return "ACTION_DRAG_EXITED";
+        }
+        return Integer.toString(action);
+    }
+
+    /**
+     * Returns a string containing a concise, human-readable representation of this DragEvent
+     * object.
+     * @return A string representation of the DragEvent object.
+     */
+    @Override
+    public String toString() {
+        return "DragEvent{" + Integer.toHexString(System.identityHashCode(this))
+        + " action=" + mAction + " @ (" + mX + ", " + mY + ") desc=" + mClipDescription
+        + " data=" + mClipData + " local=" + mLocalState + " result=" + mDragResult
+        + "}";
+    }
+
+    /* Parcelable interface */
+
+    /**
+     * Returns information about the {@link android.os.Parcel} representation of this DragEvent
+     * object.
+     * @return Information about the {@link android.os.Parcel} representation.
+     */
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Creates a {@link android.os.Parcel} object from this DragEvent object.
+     * @param dest A {@link android.os.Parcel} object in which to put the DragEvent object.
+     * @param flags Flags to store in the Parcel.
+     */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mAction);
+        dest.writeFloat(mX);
+        dest.writeFloat(mY);
+        dest.writeFloat(mOffsetX);
+        dest.writeFloat(mOffsetY);
+        dest.writeInt(mDragResult ? 1 : 0);
+        if (mClipData == null) {
+            dest.writeInt(0);
+        } else {
+            dest.writeInt(1);
+            mClipData.writeToParcel(dest, flags);
+        }
+        if (mClipDescription == null) {
+            dest.writeInt(0);
+        } else {
+            dest.writeInt(1);
+            mClipDescription.writeToParcel(dest, flags);
+        }
+        if (mDragSurface == null) {
+            dest.writeInt(0);
+        } else {
+            dest.writeInt(1);
+            mDragSurface.writeToParcel(dest, flags);
+        }
+        if (mDragAndDropPermissions == null) {
+            dest.writeInt(0);
+        } else {
+            dest.writeInt(1);
+            dest.writeStrongBinder(mDragAndDropPermissions.asBinder());
+        }
+    }
+
+    /**
+     * A container for creating a DragEvent from a Parcel.
+     */
+    public static final @android.annotation.NonNull Parcelable.Creator<DragEvent> CREATOR =
+        new Parcelable.Creator<DragEvent>() {
+        public DragEvent createFromParcel(Parcel in) {
+            DragEvent event = DragEvent.obtain();
+            event.mAction = in.readInt();
+            event.mX = in.readFloat();
+            event.mY = in.readFloat();
+            event.mOffsetX = in.readFloat();
+            event.mOffsetY = in.readFloat();
+            event.mDragResult = (in.readInt() != 0);
+            if (in.readInt() != 0) {
+                event.mClipData = ClipData.CREATOR.createFromParcel(in);
+            }
+            if (in.readInt() != 0) {
+                event.mClipDescription = ClipDescription.CREATOR.createFromParcel(in);
+            }
+            if (in.readInt() != 0) {
+                event.mDragSurface = SurfaceControl.CREATOR.createFromParcel(in);
+            }
+            if (in.readInt() != 0) {
+                event.mDragAndDropPermissions =
+                        IDragAndDropPermissions.Stub.asInterface(in.readStrongBinder());;
+            }
+            return event;
+        }
+
+        public DragEvent[] newArray(int size) {
+            return new DragEvent[size];
+        }
+    };
+}
diff --git a/android/view/FallbackEventHandler.java b/android/view/FallbackEventHandler.java
new file mode 100644
index 0000000..8e00d6d
--- /dev/null
+++ b/android/view/FallbackEventHandler.java
@@ -0,0 +1,26 @@
+/*
+ * 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.view;
+
+/**
+ * @hide
+ */
+public interface FallbackEventHandler {
+    public void setView(View v);
+    public void preDispatchKeyEvent(KeyEvent event);
+    public boolean dispatchKeyEvent(KeyEvent event);
+}
diff --git a/android/view/FocusFinder.java b/android/view/FocusFinder.java
new file mode 100644
index 0000000..064bc69
--- /dev/null
+++ b/android/view/FocusFinder.java
@@ -0,0 +1,986 @@
+/*
+ * 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.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.content.pm.PackageManager;
+import android.graphics.Rect;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * The algorithm used for finding the next focusable view in a given direction
+ * from a view that currently has focus.
+ */
+public class FocusFinder {
+
+    private static final ThreadLocal<FocusFinder> tlFocusFinder =
+            new ThreadLocal<FocusFinder>() {
+                @Override
+                protected FocusFinder initialValue() {
+                    return new FocusFinder();
+                }
+            };
+
+    /**
+     * Get the focus finder for this thread.
+     */
+    public static FocusFinder getInstance() {
+        return tlFocusFinder.get();
+    }
+
+    final Rect mFocusedRect = new Rect();
+    final Rect mOtherRect = new Rect();
+    final Rect mBestCandidateRect = new Rect();
+    private final UserSpecifiedFocusComparator mUserSpecifiedFocusComparator =
+            new UserSpecifiedFocusComparator((r, v) -> isValidId(v.getNextFocusForwardId())
+                            ? v.findUserSetNextFocus(r, View.FOCUS_FORWARD) : null);
+    private final UserSpecifiedFocusComparator mUserSpecifiedClusterComparator =
+            new UserSpecifiedFocusComparator((r, v) -> isValidId(v.getNextClusterForwardId())
+                    ? v.findUserSetNextKeyboardNavigationCluster(r, View.FOCUS_FORWARD) : null);
+    private final FocusSorter mFocusSorter = new FocusSorter();
+
+    private final ArrayList<View> mTempList = new ArrayList<View>();
+
+    // enforce thread local access
+    private FocusFinder() {}
+
+    /**
+     * Find the next view to take focus in root's descendants, starting from the view
+     * that currently is focused.
+     * @param root Contains focused. Cannot be null.
+     * @param focused Has focus now.
+     * @param direction Direction to look.
+     * @return The next focusable view, or null if none exists.
+     */
+    public final View findNextFocus(ViewGroup root, View focused, int direction) {
+        return findNextFocus(root, focused, null, direction);
+    }
+
+    /**
+     * Find the next view to take focus in root's descendants, searching from
+     * a particular rectangle in root's coordinates.
+     * @param root Contains focusedRect. Cannot be null.
+     * @param focusedRect The starting point of the search.
+     * @param direction Direction to look.
+     * @return The next focusable view, or null if none exists.
+     */
+    public View findNextFocusFromRect(ViewGroup root, Rect focusedRect, int direction) {
+        mFocusedRect.set(focusedRect);
+        return findNextFocus(root, null, mFocusedRect, direction);
+    }
+
+    private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
+        View next = null;
+        ViewGroup effectiveRoot = getEffectiveRoot(root, focused);
+        if (focused != null) {
+            next = findNextUserSpecifiedFocus(effectiveRoot, focused, direction);
+        }
+        if (next != null) {
+            return next;
+        }
+        ArrayList<View> focusables = mTempList;
+        try {
+            focusables.clear();
+            effectiveRoot.addFocusables(focusables, direction);
+            if (!focusables.isEmpty()) {
+                next = findNextFocus(effectiveRoot, focused, focusedRect, direction, focusables);
+            }
+        } finally {
+            focusables.clear();
+        }
+        return next;
+    }
+
+    /**
+     * Returns the "effective" root of a view. The "effective" root is the closest ancestor
+     * within-which focus should cycle.
+     * <p>
+     * For example: normal focus navigation would stay within a ViewGroup marked as
+     * touchscreenBlocksFocus and keyboardNavigationCluster until a cluster-jump out.
+     * @return the "effective" root of {@param focused}
+     */
+    private ViewGroup getEffectiveRoot(ViewGroup root, View focused) {
+        if (focused == null || focused == root) {
+            return root;
+        }
+        ViewGroup effective = null;
+        ViewParent nextParent = focused.getParent();
+        do {
+            if (nextParent == root) {
+                return effective != null ? effective : root;
+            }
+            ViewGroup vg = (ViewGroup) nextParent;
+            if (vg.getTouchscreenBlocksFocus()
+                    && focused.getContext().getPackageManager().hasSystemFeature(
+                            PackageManager.FEATURE_TOUCHSCREEN)
+                    && vg.isKeyboardNavigationCluster()) {
+                // Don't stop and return here because the cluster could be nested and we only
+                // care about the top-most one.
+                effective = vg;
+            }
+            nextParent = nextParent.getParent();
+        } while (nextParent instanceof ViewGroup);
+        return root;
+    }
+
+    /**
+     * Find the root of the next keyboard navigation cluster after the current one.
+     * @param root The view tree to look inside. Cannot be null
+     * @param currentCluster The starting point of the search. Null means the default cluster
+     * @param direction Direction to look
+     * @return The next cluster, or null if none exists
+     */
+    public View findNextKeyboardNavigationCluster(
+            @NonNull View root,
+            @Nullable View currentCluster,
+            @View.FocusDirection int direction) {
+        View next = null;
+        if (currentCluster != null) {
+            next = findNextUserSpecifiedKeyboardNavigationCluster(root, currentCluster, direction);
+            if (next != null) {
+                return next;
+            }
+        }
+
+        final ArrayList<View> clusters = mTempList;
+        try {
+            clusters.clear();
+            root.addKeyboardNavigationClusters(clusters, direction);
+            if (!clusters.isEmpty()) {
+                next = findNextKeyboardNavigationCluster(
+                        root, currentCluster, clusters, direction);
+            }
+        } finally {
+            clusters.clear();
+        }
+        return next;
+    }
+
+    private View findNextUserSpecifiedKeyboardNavigationCluster(View root, View currentCluster,
+            int direction) {
+        View userSetNextCluster =
+                currentCluster.findUserSetNextKeyboardNavigationCluster(root, direction);
+        if (userSetNextCluster != null && userSetNextCluster.hasFocusable()) {
+            return userSetNextCluster;
+        }
+        return null;
+    }
+
+    private View findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction) {
+        // check for user specified next focus
+        View userSetNextFocus = focused.findUserSetNextFocus(root, direction);
+        View cycleCheck = userSetNextFocus;
+        boolean cycleStep = true; // we want the first toggle to yield false
+        while (userSetNextFocus != null) {
+            if (userSetNextFocus.isFocusable()
+                    && userSetNextFocus.getVisibility() == View.VISIBLE
+                    && (!userSetNextFocus.isInTouchMode()
+                            || userSetNextFocus.isFocusableInTouchMode())) {
+                return userSetNextFocus;
+            }
+            userSetNextFocus = userSetNextFocus.findUserSetNextFocus(root, direction);
+            if (cycleStep = !cycleStep) {
+                cycleCheck = cycleCheck.findUserSetNextFocus(root, direction);
+                if (cycleCheck == userSetNextFocus) {
+                    // found a cycle, user-specified focus forms a loop and none of the views
+                    // are currently focusable.
+                    break;
+                }
+            }
+        }
+        return null;
+    }
+
+    private View findNextFocus(ViewGroup root, View focused, Rect focusedRect,
+            int direction, ArrayList<View> focusables) {
+        if (focused != null) {
+            if (focusedRect == null) {
+                focusedRect = mFocusedRect;
+            }
+            // fill in interesting rect from focused
+            focused.getFocusedRect(focusedRect);
+            root.offsetDescendantRectToMyCoords(focused, focusedRect);
+        } else {
+            if (focusedRect == null) {
+                focusedRect = mFocusedRect;
+                // make up a rect at top left or bottom right of root
+                switch (direction) {
+                    case View.FOCUS_RIGHT:
+                    case View.FOCUS_DOWN:
+                        setFocusTopLeft(root, focusedRect);
+                        break;
+                    case View.FOCUS_FORWARD:
+                        if (root.isLayoutRtl()) {
+                            setFocusBottomRight(root, focusedRect);
+                        } else {
+                            setFocusTopLeft(root, focusedRect);
+                        }
+                        break;
+
+                    case View.FOCUS_LEFT:
+                    case View.FOCUS_UP:
+                        setFocusBottomRight(root, focusedRect);
+                        break;
+                    case View.FOCUS_BACKWARD:
+                        if (root.isLayoutRtl()) {
+                            setFocusTopLeft(root, focusedRect);
+                        } else {
+                            setFocusBottomRight(root, focusedRect);
+                        break;
+                    }
+                }
+            }
+        }
+
+        switch (direction) {
+            case View.FOCUS_FORWARD:
+            case View.FOCUS_BACKWARD:
+                return findNextFocusInRelativeDirection(focusables, root, focused, focusedRect,
+                        direction);
+            case View.FOCUS_UP:
+            case View.FOCUS_DOWN:
+            case View.FOCUS_LEFT:
+            case View.FOCUS_RIGHT:
+                return findNextFocusInAbsoluteDirection(focusables, root, focused,
+                        focusedRect, direction);
+            default:
+                throw new IllegalArgumentException("Unknown direction: " + direction);
+        }
+    }
+
+    private View findNextKeyboardNavigationCluster(
+            View root,
+            View currentCluster,
+            List<View> clusters,
+            @View.FocusDirection int direction) {
+        try {
+            // Note: This sort is stable.
+            mUserSpecifiedClusterComparator.setFocusables(clusters, root);
+            Collections.sort(clusters, mUserSpecifiedClusterComparator);
+        } finally {
+            mUserSpecifiedClusterComparator.recycle();
+        }
+        final int count = clusters.size();
+
+        switch (direction) {
+            case View.FOCUS_FORWARD:
+            case View.FOCUS_DOWN:
+            case View.FOCUS_RIGHT:
+                return getNextKeyboardNavigationCluster(root, currentCluster, clusters, count);
+            case View.FOCUS_BACKWARD:
+            case View.FOCUS_UP:
+            case View.FOCUS_LEFT:
+                return getPreviousKeyboardNavigationCluster(root, currentCluster, clusters, count);
+            default:
+                throw new IllegalArgumentException("Unknown direction: " + direction);
+        }
+    }
+
+    private View findNextFocusInRelativeDirection(ArrayList<View> focusables, ViewGroup root,
+            View focused, Rect focusedRect, int direction) {
+        try {
+            // Note: This sort is stable.
+            mUserSpecifiedFocusComparator.setFocusables(focusables, root);
+            Collections.sort(focusables, mUserSpecifiedFocusComparator);
+        } finally {
+            mUserSpecifiedFocusComparator.recycle();
+        }
+
+        final int count = focusables.size();
+        if (count < 2) {
+            return null;
+        }
+        switch (direction) {
+            case View.FOCUS_FORWARD:
+                return getNextFocusable(focused, focusables, count);
+            case View.FOCUS_BACKWARD:
+                return getPreviousFocusable(focused, focusables, count);
+        }
+        return focusables.get(count - 1);
+    }
+
+    private void setFocusBottomRight(ViewGroup root, Rect focusedRect) {
+        final int rootBottom = root.getScrollY() + root.getHeight();
+        final int rootRight = root.getScrollX() + root.getWidth();
+        focusedRect.set(rootRight, rootBottom, rootRight, rootBottom);
+    }
+
+    private void setFocusTopLeft(ViewGroup root, Rect focusedRect) {
+        final int rootTop = root.getScrollY();
+        final int rootLeft = root.getScrollX();
+        focusedRect.set(rootLeft, rootTop, rootLeft, rootTop);
+    }
+
+    View findNextFocusInAbsoluteDirection(ArrayList<View> focusables, ViewGroup root, View focused,
+            Rect focusedRect, int direction) {
+        // initialize the best candidate to something impossible
+        // (so the first plausible view will become the best choice)
+        mBestCandidateRect.set(focusedRect);
+        switch(direction) {
+            case View.FOCUS_LEFT:
+                mBestCandidateRect.offset(focusedRect.width() + 1, 0);
+                break;
+            case View.FOCUS_RIGHT:
+                mBestCandidateRect.offset(-(focusedRect.width() + 1), 0);
+                break;
+            case View.FOCUS_UP:
+                mBestCandidateRect.offset(0, focusedRect.height() + 1);
+                break;
+            case View.FOCUS_DOWN:
+                mBestCandidateRect.offset(0, -(focusedRect.height() + 1));
+        }
+
+        View closest = null;
+
+        int numFocusables = focusables.size();
+        for (int i = 0; i < numFocusables; i++) {
+            View focusable = focusables.get(i);
+
+            // only interested in other non-root views
+            if (focusable == focused || focusable == root) continue;
+
+            // get focus bounds of other view in same coordinate system
+            focusable.getFocusedRect(mOtherRect);
+            root.offsetDescendantRectToMyCoords(focusable, mOtherRect);
+
+            if (isBetterCandidate(direction, focusedRect, mOtherRect, mBestCandidateRect)) {
+                mBestCandidateRect.set(mOtherRect);
+                closest = focusable;
+            }
+        }
+        return closest;
+    }
+
+    private static View getNextFocusable(View focused, ArrayList<View> focusables, int count) {
+        if (count < 2) {
+            return null;
+        }
+        if (focused != null) {
+            int position = focusables.lastIndexOf(focused);
+            if (position >= 0 && position + 1 < count) {
+                return focusables.get(position + 1);
+            }
+        }
+        return focusables.get(0);
+    }
+
+    private static View getPreviousFocusable(View focused, ArrayList<View> focusables, int count) {
+        if (count < 2) {
+            return null;
+        }
+        if (focused != null) {
+            int position = focusables.indexOf(focused);
+            if (position > 0) {
+                return focusables.get(position - 1);
+            }
+        }
+        return focusables.get(count - 1);
+    }
+
+    private static View getNextKeyboardNavigationCluster(
+            View root,
+            View currentCluster,
+            List<View> clusters,
+            int count) {
+        if (currentCluster == null) {
+            // The current cluster is the default one.
+            // The next cluster after the default one is the first one.
+            // Note that the caller guarantees that 'clusters' is not empty.
+            return clusters.get(0);
+        }
+
+        final int position = clusters.lastIndexOf(currentCluster);
+        if (position >= 0 && position + 1 < count) {
+            // Return the next non-default cluster if we can find it.
+            return clusters.get(position + 1);
+        }
+
+        // The current cluster is the last one. The next one is the default one, i.e. the
+        // root.
+        return root;
+    }
+
+    private static View getPreviousKeyboardNavigationCluster(
+            View root,
+            View currentCluster,
+            List<View> clusters,
+            int count) {
+        if (currentCluster == null) {
+            // The current cluster is the default one.
+            // The previous cluster before the default one is the last one.
+            // Note that the caller guarantees that 'clusters' is not empty.
+            return clusters.get(count - 1);
+        }
+
+        final int position = clusters.indexOf(currentCluster);
+        if (position > 0) {
+            // Return the previous non-default cluster if we can find it.
+            return clusters.get(position - 1);
+        }
+
+        // The current cluster is the first one. The previous one is the default one, i.e.
+        // the root.
+        return root;
+    }
+
+    /**
+     * Is rect1 a better candidate than rect2 for a focus search in a particular
+     * direction from a source rect?  This is the core routine that determines
+     * the order of focus searching.
+     * @param direction the direction (up, down, left, right)
+     * @param source The source we are searching from
+     * @param rect1 The candidate rectangle
+     * @param rect2 The current best candidate.
+     * @return Whether the candidate is the new best.
+     */
+    boolean isBetterCandidate(int direction, Rect source, Rect rect1, Rect rect2) {
+
+        // to be a better candidate, need to at least be a candidate in the first
+        // place :)
+        if (!isCandidate(source, rect1, direction)) {
+            return false;
+        }
+
+        // we know that rect1 is a candidate.. if rect2 is not a candidate,
+        // rect1 is better
+        if (!isCandidate(source, rect2, direction)) {
+            return true;
+        }
+
+        // if rect1 is better by beam, it wins
+        if (beamBeats(direction, source, rect1, rect2)) {
+            return true;
+        }
+
+        // if rect2 is better, then rect1 cant' be :)
+        if (beamBeats(direction, source, rect2, rect1)) {
+            return false;
+        }
+
+        // otherwise, do fudge-tastic comparison of the major and minor axis
+        return (getWeightedDistanceFor(
+                        majorAxisDistance(direction, source, rect1),
+                        minorAxisDistance(direction, source, rect1))
+                < getWeightedDistanceFor(
+                        majorAxisDistance(direction, source, rect2),
+                        minorAxisDistance(direction, source, rect2)));
+    }
+
+    /**
+     * One rectangle may be another candidate than another by virtue of being
+     * exclusively in the beam of the source rect.
+     * @return Whether rect1 is a better candidate than rect2 by virtue of it being in src's
+     *      beam
+     */
+    boolean beamBeats(int direction, Rect source, Rect rect1, Rect rect2) {
+        final boolean rect1InSrcBeam = beamsOverlap(direction, source, rect1);
+        final boolean rect2InSrcBeam = beamsOverlap(direction, source, rect2);
+
+        // if rect1 isn't exclusively in the src beam, it doesn't win
+        if (rect2InSrcBeam || !rect1InSrcBeam) {
+            return false;
+        }
+
+        // we know rect1 is in the beam, and rect2 is not
+
+        // if rect1 is to the direction of, and rect2 is not, rect1 wins.
+        // for example, for direction left, if rect1 is to the left of the source
+        // and rect2 is below, then we always prefer the in beam rect1, since rect2
+        // could be reached by going down.
+        if (!isToDirectionOf(direction, source, rect2)) {
+            return true;
+        }
+
+        // for horizontal directions, being exclusively in beam always wins
+        if ((direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT)) {
+            return true;
+        }        
+
+        // for vertical directions, beams only beat up to a point:
+        // now, as long as rect2 isn't completely closer, rect1 wins
+        // e.g for direction down, completely closer means for rect2's top
+        // edge to be closer to the source's top edge than rect1's bottom edge.
+        return (majorAxisDistance(direction, source, rect1)
+                < majorAxisDistanceToFarEdge(direction, source, rect2));
+    }
+
+    /**
+     * Fudge-factor opportunity: how to calculate distance given major and minor
+     * axis distances.  Warning: this fudge factor is finely tuned, be sure to
+     * run all focus tests if you dare tweak it.
+     */
+    long getWeightedDistanceFor(long majorAxisDistance, long minorAxisDistance) {
+        return 13 * majorAxisDistance * majorAxisDistance
+                + minorAxisDistance * minorAxisDistance;
+    }
+
+    /**
+     * Is destRect a candidate for the next focus given the direction?  This
+     * checks whether the dest is at least partially to the direction of (e.g left of)
+     * from source.
+     *
+     * Includes an edge case for an empty rect (which is used in some cases when
+     * searching from a point on the screen).
+     */
+    boolean isCandidate(Rect srcRect, Rect destRect, int direction) {
+        switch (direction) {
+            case View.FOCUS_LEFT:
+                return (srcRect.right > destRect.right || srcRect.left >= destRect.right) 
+                        && srcRect.left > destRect.left;
+            case View.FOCUS_RIGHT:
+                return (srcRect.left < destRect.left || srcRect.right <= destRect.left)
+                        && srcRect.right < destRect.right;
+            case View.FOCUS_UP:
+                return (srcRect.bottom > destRect.bottom || srcRect.top >= destRect.bottom)
+                        && srcRect.top > destRect.top;
+            case View.FOCUS_DOWN:
+                return (srcRect.top < destRect.top || srcRect.bottom <= destRect.top)
+                        && srcRect.bottom < destRect.bottom;
+        }
+        throw new IllegalArgumentException("direction must be one of "
+                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+    }
+
+
+    /**
+     * Do the "beams" w.r.t the given direction's axis of rect1 and rect2 overlap?
+     * @param direction the direction (up, down, left, right)
+     * @param rect1 The first rectangle
+     * @param rect2 The second rectangle
+     * @return whether the beams overlap
+     */
+    boolean beamsOverlap(int direction, Rect rect1, Rect rect2) {
+        switch (direction) {
+            case View.FOCUS_LEFT:
+            case View.FOCUS_RIGHT:
+                return (rect2.bottom > rect1.top) && (rect2.top < rect1.bottom);
+            case View.FOCUS_UP:
+            case View.FOCUS_DOWN:
+                return (rect2.right > rect1.left) && (rect2.left < rect1.right);
+        }
+        throw new IllegalArgumentException("direction must be one of "
+                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+    }
+
+    /**
+     * e.g for left, is 'to left of'
+     */
+    boolean isToDirectionOf(int direction, Rect src, Rect dest) {
+        switch (direction) {
+            case View.FOCUS_LEFT:
+                return src.left >= dest.right;
+            case View.FOCUS_RIGHT:
+                return src.right <= dest.left;
+            case View.FOCUS_UP:
+                return src.top >= dest.bottom;
+            case View.FOCUS_DOWN:
+                return src.bottom <= dest.top;
+        }
+        throw new IllegalArgumentException("direction must be one of "
+                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+    }
+
+    /**
+     * @return The distance from the edge furthest in the given direction
+     *   of source to the edge nearest in the given direction of dest.  If the
+     *   dest is not in the direction from source, return 0.
+     */
+    static int majorAxisDistance(int direction, Rect source, Rect dest) {
+        return Math.max(0, majorAxisDistanceRaw(direction, source, dest));
+    }
+
+    static int majorAxisDistanceRaw(int direction, Rect source, Rect dest) {
+        switch (direction) {
+            case View.FOCUS_LEFT:
+                return source.left - dest.right;
+            case View.FOCUS_RIGHT:
+                return dest.left - source.right;
+            case View.FOCUS_UP:
+                return source.top - dest.bottom;
+            case View.FOCUS_DOWN:
+                return dest.top - source.bottom;
+        }
+        throw new IllegalArgumentException("direction must be one of "
+                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+    }
+
+    /**
+     * @return The distance along the major axis w.r.t the direction from the
+     *   edge of source to the far edge of dest. If the
+     *   dest is not in the direction from source, return 1 (to break ties with
+     *   {@link #majorAxisDistance}).
+     */
+    static int majorAxisDistanceToFarEdge(int direction, Rect source, Rect dest) {
+        return Math.max(1, majorAxisDistanceToFarEdgeRaw(direction, source, dest));
+    }
+
+    static int majorAxisDistanceToFarEdgeRaw(int direction, Rect source, Rect dest) {
+        switch (direction) {
+            case View.FOCUS_LEFT:
+                return source.left - dest.left;
+            case View.FOCUS_RIGHT:
+                return dest.right - source.right;
+            case View.FOCUS_UP:
+                return source.top - dest.top;
+            case View.FOCUS_DOWN:
+                return dest.bottom - source.bottom;
+        }
+        throw new IllegalArgumentException("direction must be one of "
+                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+    }
+
+    /**
+     * Find the distance on the minor axis w.r.t the direction to the nearest
+     * edge of the destination rectangle.
+     * @param direction the direction (up, down, left, right)
+     * @param source The source rect.
+     * @param dest The destination rect.
+     * @return The distance.
+     */
+    static int minorAxisDistance(int direction, Rect source, Rect dest) {
+        switch (direction) {
+            case View.FOCUS_LEFT:
+            case View.FOCUS_RIGHT:
+                // the distance between the center verticals
+                return Math.abs(
+                        ((source.top + source.height() / 2) -
+                        ((dest.top + dest.height() / 2))));
+            case View.FOCUS_UP:
+            case View.FOCUS_DOWN:
+                // the distance between the center horizontals
+                return Math.abs(
+                        ((source.left + source.width() / 2) -
+                        ((dest.left + dest.width() / 2))));
+        }
+        throw new IllegalArgumentException("direction must be one of "
+                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+    }
+
+    /**
+     * Find the nearest touchable view to the specified view.
+     * 
+     * @param root The root of the tree in which to search
+     * @param x X coordinate from which to start the search
+     * @param y Y coordinate from which to start the search
+     * @param direction Direction to look
+     * @param deltas Offset from the <x, y> to the edge of the nearest view. Note that this array
+     *        may already be populated with values.
+     * @return The nearest touchable view, or null if none exists.
+     */
+    public View findNearestTouchable(ViewGroup root, int x, int y, int direction, int[] deltas) {
+        ArrayList<View> touchables = root.getTouchables();
+        int minDistance = Integer.MAX_VALUE;
+        View closest = null;
+
+        int numTouchables = touchables.size();
+        
+        int edgeSlop = ViewConfiguration.get(root.mContext).getScaledEdgeSlop();
+        
+        Rect closestBounds = new Rect();
+        Rect touchableBounds = mOtherRect;
+        
+        for (int i = 0; i < numTouchables; i++) {
+            View touchable = touchables.get(i);
+
+            // get visible bounds of other view in same coordinate system
+            touchable.getDrawingRect(touchableBounds);
+            
+            root.offsetRectBetweenParentAndChild(touchable, touchableBounds, true, true);
+
+            if (!isTouchCandidate(x, y, touchableBounds, direction)) {
+                continue;
+            }
+
+            int distance = Integer.MAX_VALUE;
+
+            switch (direction) {
+            case View.FOCUS_LEFT:
+                distance = x - touchableBounds.right + 1;
+                break;
+            case View.FOCUS_RIGHT:
+                distance = touchableBounds.left;
+                break;
+            case View.FOCUS_UP:
+                distance = y - touchableBounds.bottom + 1;
+                break;
+            case View.FOCUS_DOWN:
+                distance = touchableBounds.top;
+                break;
+            }
+
+            if (distance < edgeSlop) {
+                // Give preference to innermost views
+                if (closest == null ||
+                        closestBounds.contains(touchableBounds) ||
+                        (!touchableBounds.contains(closestBounds) && distance < minDistance)) {
+                    minDistance = distance;
+                    closest = touchable;
+                    closestBounds.set(touchableBounds);
+                    switch (direction) {
+                    case View.FOCUS_LEFT:
+                        deltas[0] = -distance;
+                        break;
+                    case View.FOCUS_RIGHT:
+                        deltas[0] = distance;
+                        break;
+                    case View.FOCUS_UP:
+                        deltas[1] = -distance;
+                        break;
+                    case View.FOCUS_DOWN:
+                        deltas[1] = distance;
+                        break;
+                    }
+                }
+            }
+        }
+        return closest;
+    }
+
+
+    /**
+     * Is destRect a candidate for the next touch given the direction?
+     */
+    private boolean isTouchCandidate(int x, int y, Rect destRect, int direction) {
+        switch (direction) {
+            case View.FOCUS_LEFT:
+                return destRect.left <= x && destRect.top <= y && y <= destRect.bottom;
+            case View.FOCUS_RIGHT:
+                return destRect.left >= x && destRect.top <= y && y <= destRect.bottom;
+            case View.FOCUS_UP:
+                return destRect.top <= y && destRect.left <= x && x <= destRect.right;
+            case View.FOCUS_DOWN:
+                return destRect.top >= y && destRect.left <= x && x <= destRect.right;
+        }
+        throw new IllegalArgumentException("direction must be one of "
+                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+    }
+
+    private static final boolean isValidId(final int id) {
+        return id != 0 && id != View.NO_ID;
+    }
+
+    static final class FocusSorter {
+        private ArrayList<Rect> mRectPool = new ArrayList<>();
+        private int mLastPoolRect;
+        private int mRtlMult;
+        private HashMap<View, Rect> mRectByView = null;
+
+        private Comparator<View> mTopsComparator = (first, second) -> {
+            if (first == second) {
+                return 0;
+            }
+
+            Rect firstRect = mRectByView.get(first);
+            Rect secondRect = mRectByView.get(second);
+
+            int result = firstRect.top - secondRect.top;
+            if (result == 0) {
+                return firstRect.bottom - secondRect.bottom;
+            }
+            return result;
+        };
+
+        private Comparator<View> mSidesComparator = (first, second) -> {
+            if (first == second) {
+                return 0;
+            }
+
+            Rect firstRect = mRectByView.get(first);
+            Rect secondRect = mRectByView.get(second);
+
+            int result = firstRect.left - secondRect.left;
+            if (result == 0) {
+                return firstRect.right - secondRect.right;
+            }
+            return mRtlMult * result;
+        };
+
+        public void sort(View[] views, int start, int end, ViewGroup root, boolean isRtl) {
+            int count = end - start;
+            if (count < 2) {
+                return;
+            }
+            if (mRectByView == null) {
+                mRectByView = new HashMap<>();
+            }
+            mRtlMult = isRtl ? -1 : 1;
+            for (int i = mRectPool.size(); i < count; ++i) {
+                mRectPool.add(new Rect());
+            }
+            for (int i = start; i < end; ++i) {
+                Rect next = mRectPool.get(mLastPoolRect++);
+                views[i].getDrawingRect(next);
+                root.offsetDescendantRectToMyCoords(views[i], next);
+                mRectByView.put(views[i], next);
+            }
+
+            // Sort top-to-bottom
+            Arrays.sort(views, start, count, mTopsComparator);
+            // Sweep top-to-bottom to identify rows
+            int sweepBottom = mRectByView.get(views[start]).bottom;
+            int rowStart = start;
+            int sweepIdx = start + 1;
+            for (; sweepIdx < end; ++sweepIdx) {
+                Rect currRect = mRectByView.get(views[sweepIdx]);
+                if (currRect.top >= sweepBottom) {
+                    // Next view is on a new row, sort the row we've just finished left-to-right.
+                    if ((sweepIdx - rowStart) > 1) {
+                        Arrays.sort(views, rowStart, sweepIdx, mSidesComparator);
+                    }
+                    sweepBottom = currRect.bottom;
+                    rowStart = sweepIdx;
+                } else {
+                    // Next view vertically overlaps, we need to extend our "row height"
+                    sweepBottom = Math.max(sweepBottom, currRect.bottom);
+                }
+            }
+            // Sort whatever's left (final row) left-to-right
+            if ((sweepIdx - rowStart) > 1) {
+                Arrays.sort(views, rowStart, sweepIdx, mSidesComparator);
+            }
+
+            mLastPoolRect = 0;
+            mRectByView.clear();
+        }
+    }
+
+    /**
+     * Public for testing.
+     *
+     * @hide
+     */
+    @TestApi
+    public static void sort(View[] views, int start, int end, ViewGroup root, boolean isRtl) {
+        getInstance().mFocusSorter.sort(views, start, end, root, isRtl);
+    }
+
+    /**
+     * Sorts views according to any explicitly-specified focus-chains. If there are no explicitly
+     * specified focus chains (eg. no nextFocusForward attributes defined), this should be a no-op.
+     */
+    private static final class UserSpecifiedFocusComparator implements Comparator<View> {
+        private final ArrayMap<View, View> mNextFoci = new ArrayMap<>();
+        private final ArraySet<View> mIsConnectedTo = new ArraySet<>();
+        private final ArrayMap<View, View> mHeadsOfChains = new ArrayMap<View, View>();
+        private final ArrayMap<View, Integer> mOriginalOrdinal = new ArrayMap<>();
+        private final NextFocusGetter mNextFocusGetter;
+        private View mRoot;
+
+        public interface NextFocusGetter {
+            View get(View root, View view);
+        }
+
+        UserSpecifiedFocusComparator(NextFocusGetter nextFocusGetter) {
+            mNextFocusGetter = nextFocusGetter;
+        }
+
+        public void recycle() {
+            mRoot = null;
+            mHeadsOfChains.clear();
+            mIsConnectedTo.clear();
+            mOriginalOrdinal.clear();
+            mNextFoci.clear();
+        }
+
+        public void setFocusables(List<View> focusables, View root) {
+            mRoot = root;
+            for (int i = 0; i < focusables.size(); ++i) {
+                mOriginalOrdinal.put(focusables.get(i), i);
+            }
+
+            for (int i = focusables.size() - 1; i >= 0; i--) {
+                final View view = focusables.get(i);
+                final View next = mNextFocusGetter.get(mRoot, view);
+                if (next != null && mOriginalOrdinal.containsKey(next)) {
+                    mNextFoci.put(view, next);
+                    mIsConnectedTo.add(next);
+                }
+            }
+
+            for (int i = focusables.size() - 1; i >= 0; i--) {
+                final View view = focusables.get(i);
+                final View next = mNextFoci.get(view);
+                if (next != null && !mIsConnectedTo.contains(view)) {
+                    setHeadOfChain(view);
+                }
+            }
+        }
+
+        private void setHeadOfChain(View head) {
+            for (View view = head; view != null; view = mNextFoci.get(view)) {
+                final View otherHead = mHeadsOfChains.get(view);
+                if (otherHead != null) {
+                    if (otherHead == head) {
+                        return; // This view has already had its head set properly
+                    }
+                    // A hydra -- multi-headed focus chain (e.g. A->C and B->C)
+                    // Use the one we've already chosen instead and reset this chain.
+                    view = head;
+                    head = otherHead;
+                }
+                mHeadsOfChains.put(view, head);
+            }
+        }
+
+        public int compare(View first, View second) {
+            if (first == second) {
+                return 0;
+            }
+            // Order between views within a chain is immaterial -- next/previous is
+            // within a chain is handled elsewhere.
+            View firstHead = mHeadsOfChains.get(first);
+            View secondHead = mHeadsOfChains.get(second);
+            if (firstHead == secondHead && firstHead != null) {
+                if (first == firstHead) {
+                    return -1; // first is the head, it should be first
+                } else if (second == firstHead) {
+                    return 1; // second is the head, it should be first
+                } else if (mNextFoci.get(first) != null) {
+                    return -1; // first is not the end of the chain
+                } else {
+                    return 1; // first is end of chain
+                }
+            }
+            boolean involvesChain = false;
+            if (firstHead != null) {
+                first = firstHead;
+                involvesChain = true;
+            }
+            if (secondHead != null) {
+                second = secondHead;
+                involvesChain = true;
+            }
+
+            if (involvesChain) {
+                // keep original order between chains
+                return mOriginalOrdinal.get(first) < mOriginalOrdinal.get(second) ? -1 : 1;
+            } else {
+                return 0;
+            }
+        }
+    }
+}
diff --git a/android/view/FocusFinderHelper.java b/android/view/FocusFinderHelper.java
new file mode 100644
index 0000000..69dc056
--- /dev/null
+++ b/android/view/FocusFinderHelper.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.graphics.Rect;
+
+/**
+ * A helper class that allows unit tests to access FocusFinder methods.
+ * @hide
+ */
+public class FocusFinderHelper {
+    
+    private FocusFinder mFocusFinder;
+
+    /**
+     * Wrap the FocusFinder object
+     */
+    public FocusFinderHelper(FocusFinder focusFinder) {
+        mFocusFinder = focusFinder;
+    }
+    
+    public boolean isBetterCandidate(int direction, Rect source, Rect rect1, Rect rect2) {
+        return mFocusFinder.isBetterCandidate(direction, source, rect1, rect2);
+    }
+    
+    public boolean beamBeats(int direction, Rect source, Rect rect1, Rect rect2) {
+        return mFocusFinder.beamBeats(direction, source, rect1, rect2);
+    }
+
+    public boolean isCandidate(Rect srcRect, Rect destRect, int direction) {
+        return mFocusFinder.isCandidate(srcRect, destRect, direction);
+    }
+    
+    public boolean beamsOverlap(int direction, Rect rect1, Rect rect2) {
+        return mFocusFinder.beamsOverlap(direction, rect1, rect2);
+    }
+    
+    public static int majorAxisDistance(int direction, Rect source, Rect dest) {
+        return FocusFinder.majorAxisDistance(direction, source, dest);
+    }
+    
+    public static int majorAxisDistanceToFarEdge(int direction, Rect source, Rect dest) {
+        return FocusFinder.majorAxisDistanceToFarEdge(direction, source, dest);
+    }
+}
diff --git a/android/view/FrameMetrics.java b/android/view/FrameMetrics.java
new file mode 100644
index 0000000..3cffeb0
--- /dev/null
+++ b/android/view/FrameMetrics.java
@@ -0,0 +1,358 @@
+/*
+ * 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.view;
+
+import static android.graphics.FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED;
+
+import android.annotation.IntDef;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Class containing timing data for various milestones in a frame
+ * lifecycle reported by the rendering subsystem.
+ * <p>
+ * Supported metrics can be queried via their corresponding identifier.
+ * </p>
+ */
+public final class FrameMetrics {
+
+    /**
+     * Metric identifier for unknown delay.
+     * <p>
+     * Represents the number of nanoseconds elapsed waiting for the
+     * UI thread to become responsive and process the frame. This
+     * should be 0 most of the time.
+     * </p>
+     */
+    public static final int UNKNOWN_DELAY_DURATION = 0;
+
+    /**
+     * Metric identifier for input handling duration.
+     * <p>
+     * Represents the number of nanoseconds elapsed issuing
+     * input handling callbacks.
+     * </p>
+     */
+    public static final int INPUT_HANDLING_DURATION = 1;
+
+    /**
+     * Metric identifier for animation callback duration.
+     * <p>
+     * Represents the number of nanoseconds elapsed issuing
+     * animation callbacks.
+     * </p>
+     */
+    public static final int ANIMATION_DURATION = 2;
+
+    /**
+     * Metric identifier for layout/measure duration.
+     * <p>
+     * Represents the number of nanoseconds elapsed measuring
+     * and laying out the invalidated pieces of the view hierarchy.
+     * </p>
+     */
+    public static final int LAYOUT_MEASURE_DURATION = 3;
+    /**
+     * Metric identifier for draw duration.
+     * <p>
+     * Represents the number of nanoseconds elapsed computing
+     * DisplayLists for transformations applied to the view
+     * hierarchy.
+     * </p>
+     */
+    public static final int DRAW_DURATION = 4;
+
+    /**
+     * Metric identifier for sync duration.
+     * <p>
+     * Represents the number of nanoseconds elapsed
+     * synchronizing the computed display lists with the render
+     * thread.
+     * </p>
+     */
+    public static final int SYNC_DURATION = 5;
+
+    /**
+     * Metric identifier for command issue duration.
+     * <p>
+     * Represents the number of nanoseconds elapsed
+     * issuing draw commands to the GPU.
+     * </p>
+     */
+    public static final int COMMAND_ISSUE_DURATION = 6;
+
+    /**
+     * Metric identifier for swap buffers duration.
+     * <p>
+     * Represents the number of nanoseconds elapsed issuing
+     * the frame buffer for this frame to the display
+     * subsystem.
+     * </p>
+     */
+    public static final int SWAP_BUFFERS_DURATION = 7;
+
+    /**
+     * Metric identifier for total frame duration.
+     * <p>
+     * Represents the total time in nanoseconds this frame took to render
+     * and be issued to the display subsystem.
+     * </p>
+     * <p>
+     * Equal to the sum of the values of all other time-valued metric
+     * identifiers.
+     * </p>
+     */
+    public static final int TOTAL_DURATION = 8;
+
+    /**
+     * Metric identifier for a boolean value determining whether this frame was
+     * the first to draw in a new Window layout.
+     * <p>
+     * {@link #getMetric(int)} will return 0 for false, 1 for true.
+     * </p>
+     * <p>
+     * First draw frames are expected to be slow and should usually be exempt
+     * from display jank calculations as they do not cause skips in animations
+     * and are usually hidden by window animations or other tricks.
+     * </p>
+     */
+    public static final int FIRST_DRAW_FRAME = 9;
+
+    /**
+     * Metric identifier for the timestamp of the intended vsync for this frame.
+     * <p>
+     * The intended start point for the frame. If this value is different from
+     * {@link #VSYNC_TIMESTAMP}, there was work occurring on the UI thread that
+     * prevented it from responding to the vsync signal in a timely fashion.
+     * </p>
+     */
+    public static final int INTENDED_VSYNC_TIMESTAMP = 10;
+
+    /**
+     * Metric identifier for the timestamp of the actual vsync for this frame.
+     * <p>
+     * The time value that was used in all the vsync listeners and drawing for
+     * the frame (Choreographer frame callbacks, animations,
+     * {@link View#getDrawingTime()}, etc.)
+     * </p>
+     */
+    public static final int VSYNC_TIMESTAMP = 11;
+
+    /**
+     * Metric identifier for GPU duration.
+     * <p>
+     * Represents the total time in nanoseconds this frame took to complete on the GPU.
+     * </p>
+     **/
+    public static final int GPU_DURATION = 12;
+
+    /**
+     * Metric identifier for the total duration that was available to the app to produce a frame.
+     * <p>
+     * Represents the total time in nanoseconds the system allocated for the app to produce its
+     * frame. If FrameMetrics.TOTAL_DURATION < FrameMetrics.DEADLINE, the app hit its intended
+     * deadline and there was no jank visible to the user.
+     * </p>
+     **/
+    public static final int DEADLINE = 13;
+
+    /**
+     * Identifiers for metrics available for each frame.
+     *
+     * {@see #getMetric(int)}
+     * @hide
+     */
+    @IntDef({
+            UNKNOWN_DELAY_DURATION,
+            INPUT_HANDLING_DURATION,
+            ANIMATION_DURATION,
+            LAYOUT_MEASURE_DURATION,
+            DRAW_DURATION,
+            SYNC_DURATION,
+            COMMAND_ISSUE_DURATION,
+            SWAP_BUFFERS_DURATION,
+            TOTAL_DURATION,
+            FIRST_DRAW_FRAME,
+            INTENDED_VSYNC_TIMESTAMP,
+            VSYNC_TIMESTAMP,
+            GPU_DURATION,
+            DEADLINE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Metric {}
+
+    /**
+     * Timestamp indices for frame milestones.
+     *
+     * May change from release to release.
+     *
+     * Must be kept in sync with frameworks/base/libs/hwui/FrameInfo.h.
+     *
+     * @hide
+     */
+    @IntDef ({
+            Index.FLAGS,
+            Index.FRAME_TIMELINE_VSYNC_ID,
+            Index.INTENDED_VSYNC,
+            Index.VSYNC,
+            Index.INPUT_EVENT_ID,
+            Index.HANDLE_INPUT_START,
+            Index.ANIMATION_START,
+            Index.PERFORM_TRAVERSALS_START,
+            Index.DRAW_START,
+            Index.FRAME_DEADLINE,
+            Index.SYNC_QUEUED,
+            Index.SYNC_START,
+            Index.ISSUE_DRAW_COMMANDS_START,
+            Index.SWAP_BUFFERS,
+            Index.FRAME_COMPLETED,
+            Index.DEQUEUE_BUFFER_DURATION,
+            Index.QUEUE_BUFFER_DURATION,
+            Index.GPU_COMPLETED,
+            Index.SWAP_BUFFERS_COMPLETED,
+            Index.DISPLAY_PRESENT_TIME,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Index {
+        int FLAGS = 0;
+        int FRAME_TIMELINE_VSYNC_ID = 1;
+        int INTENDED_VSYNC = 2;
+        int VSYNC = 3;
+        int INPUT_EVENT_ID = 4;
+        int HANDLE_INPUT_START = 5;
+        int ANIMATION_START = 6;
+        int PERFORM_TRAVERSALS_START = 7;
+        int DRAW_START = 8;
+        int FRAME_DEADLINE = 9;
+        int FRAME_START_TIME = 10;
+        int FRAME_INTERVAL = 11;
+        int SYNC_QUEUED = 12;
+        int SYNC_START = 13;
+        int ISSUE_DRAW_COMMANDS_START = 14;
+        int SWAP_BUFFERS = 15;
+        int FRAME_COMPLETED = 16;
+        int DEQUEUE_BUFFER_DURATION = 17;
+        int QUEUE_BUFFER_DURATION = 18;
+        int GPU_COMPLETED = 19;
+        int SWAP_BUFFERS_COMPLETED = 20;
+        int DISPLAY_PRESENT_TIME = 21;
+
+        int FRAME_STATS_COUNT = 22; // must always be last and in sync with
+                                    // FrameInfoIndex::NumIndexes in libs/hwui/FrameInfo.h
+    }
+
+    /*
+     * Bucket endpoints for each Metric defined above.
+     *
+     * Each defined metric *must* have a corresponding entry
+     * in this list.
+     */
+    private static final int[] DURATIONS = new int[] {
+        // UNKNOWN_DELAY
+        Index.INTENDED_VSYNC, Index.HANDLE_INPUT_START,
+        // INPUT_HANDLING
+        Index.HANDLE_INPUT_START, Index.ANIMATION_START,
+        // ANIMATION
+        Index.ANIMATION_START, Index.PERFORM_TRAVERSALS_START,
+        // LAYOUT_MEASURE
+        Index.PERFORM_TRAVERSALS_START, Index.DRAW_START,
+        // DRAW
+        Index.DRAW_START, Index.SYNC_QUEUED,
+        // SYNC
+        Index.SYNC_START, Index.ISSUE_DRAW_COMMANDS_START,
+        // COMMAND_ISSUE
+        Index.ISSUE_DRAW_COMMANDS_START, Index.SWAP_BUFFERS,
+        // SWAP_BUFFERS
+        Index.SWAP_BUFFERS, Index.SWAP_BUFFERS_COMPLETED,
+        // TOTAL_DURATION
+        Index.INTENDED_VSYNC, Index.FRAME_COMPLETED,
+        // RESERVED for FIRST_DRAW_FRAME
+        0, 0,
+        // RESERVED forINTENDED_VSYNC_TIMESTAMP
+        0, 0,
+        // RESERVED VSYNC_TIMESTAMP
+        0, 0,
+        // GPU_DURATION
+        Index.SWAP_BUFFERS, Index.GPU_COMPLETED,
+        // DEADLINE
+        Index.INTENDED_VSYNC, Index.FRAME_DEADLINE,
+    };
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public final long[] mTimingData;
+
+    /**
+     * Constructs a FrameMetrics object as a copy.
+     * <p>
+     * Use this method to copy out metrics reported by
+     * {@link Window.OnFrameMetricsAvailableListener#onFrameMetricsAvailable(
+     * Window, FrameMetrics, int)}
+     * </p>
+     * @param other the FrameMetrics object to copy.
+     */
+    public FrameMetrics(FrameMetrics other) {
+        mTimingData = new long[Index.FRAME_STATS_COUNT];
+        System.arraycopy(other.mTimingData, 0, mTimingData, 0, mTimingData.length);
+    }
+
+    /**
+     * @hide
+     */
+    public FrameMetrics() {
+        mTimingData = new long[Index.FRAME_STATS_COUNT];
+    }
+
+    /**
+     * Retrieves the value associated with Metric identifier {@code id}
+     * for this frame.
+     * <p>
+     * Boolean metrics are represented in [0,1], with 0 corresponding to
+     * false, and 1 corresponding to true.
+     * </p>
+     * @param id the metric to retrieve
+     * @return the value of the metric or -1 if it is not available.
+     */
+    public long getMetric(@Metric int id) {
+        if (id < UNKNOWN_DELAY_DURATION || id > DEADLINE) {
+            return -1;
+        }
+
+        if (mTimingData == null) {
+            return -1;
+        }
+
+        if (id == FIRST_DRAW_FRAME) {
+            return (mTimingData[Index.FLAGS] & FLAG_WINDOW_VISIBILITY_CHANGED) != 0 ? 1 : 0;
+        } else if (id == INTENDED_VSYNC_TIMESTAMP) {
+            return mTimingData[Index.INTENDED_VSYNC];
+        } else if (id == VSYNC_TIMESTAMP) {
+            return mTimingData[Index.VSYNC];
+        }
+
+        int durationsIdx = 2 * id;
+        return mTimingData[DURATIONS[durationsIdx + 1]]
+                - mTimingData[DURATIONS[durationsIdx]];
+    }
+}
+
diff --git a/android/view/FrameMetricsObserver.java b/android/view/FrameMetricsObserver.java
new file mode 100644
index 0000000..35d95be
--- /dev/null
+++ b/android/view/FrameMetricsObserver.java
@@ -0,0 +1,67 @@
+/*
+ * 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.view;
+
+import android.annotation.NonNull;
+import android.graphics.HardwareRendererObserver;
+import android.os.Handler;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Provides streaming access to frame stats information from the rendering
+ * subsystem to apps.
+ *
+ * @hide
+ */
+public class FrameMetricsObserver
+        implements HardwareRendererObserver.OnFrameMetricsAvailableListener {
+    private final WeakReference<Window> mWindow;
+    private final FrameMetrics mFrameMetrics;
+    private final HardwareRendererObserver mObserver;
+    /*package*/ final Window.OnFrameMetricsAvailableListener mListener;
+
+    /**
+     * Creates a FrameMetricsObserver
+     *
+     * @param handler the Handler to use when invoking callbacks
+     */
+    FrameMetricsObserver(@NonNull Window window, @NonNull Handler handler,
+            @NonNull Window.OnFrameMetricsAvailableListener listener) {
+        mWindow = new WeakReference<>(window);
+        mListener = listener;
+        mFrameMetrics = new FrameMetrics();
+        mObserver = new HardwareRendererObserver(this,  mFrameMetrics.mTimingData, handler,
+                false /*waitForPresentTime*/);
+    }
+
+    /**
+     * Implementation of OnFrameMetricsAvailableListener
+     * @param dropCountSinceLastInvocation the number of reports dropped since the last time
+     * @Override
+     */
+    public void onFrameMetricsAvailable(int dropCountSinceLastInvocation) {
+        if (mWindow.get() != null) {
+            mListener.onFrameMetricsAvailable(mWindow.get(), mFrameMetrics,
+                    dropCountSinceLastInvocation);
+        }
+    }
+
+    /*package*/ HardwareRendererObserver getRendererObserver() {
+        return mObserver;
+    }
+}
diff --git a/android/view/FrameStats.java b/android/view/FrameStats.java
new file mode 100644
index 0000000..3fbe6fe
--- /dev/null
+++ b/android/view/FrameStats.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+/**
+ * This is the base class for frame statistics.
+ */
+public abstract class FrameStats {
+    /**
+     * Undefined time.
+     */
+    public static final long UNDEFINED_TIME_NANO = -1;
+
+    /** @hide */
+    protected long mRefreshPeriodNano;
+
+    /** @hide */
+    protected long[] mFramesPresentedTimeNano;
+
+    /**
+     * Gets the refresh period of the display hosting the window(s) for
+     * which these statistics apply.
+     *
+     * @return The refresh period in nanoseconds.
+     */
+    public final long getRefreshPeriodNano() {
+        return mRefreshPeriodNano;
+    }
+
+    /**
+     * Gets the number of frames for which there is data.
+     *
+     * @return The number of frames.
+     */
+    public final int getFrameCount() {
+        return mFramesPresentedTimeNano != null
+                ? mFramesPresentedTimeNano.length : 0;
+    }
+
+    /**
+     * Gets the start time of the interval for which these statistics
+     * apply. The start interval is the time when the first frame was
+     * presented.
+     *
+     * @return The start time in nanoseconds or {@link #UNDEFINED_TIME_NANO}
+     *         if there is no frame data.
+     */
+    public final long getStartTimeNano() {
+        if (getFrameCount() <= 0) {
+            return UNDEFINED_TIME_NANO;
+        }
+        return mFramesPresentedTimeNano[0];
+    }
+
+    /**
+     * Gets the end time of the interval for which these statistics
+     * apply. The end interval is the time when the last frame was
+     * presented.
+     *
+     * @return The end time in nanoseconds or {@link #UNDEFINED_TIME_NANO}
+     *         if there is no frame data.
+     */
+    public final long getEndTimeNano() {
+        if (getFrameCount() <= 0) {
+            return UNDEFINED_TIME_NANO;
+        }
+        return mFramesPresentedTimeNano[mFramesPresentedTimeNano.length - 1];
+    }
+
+    /**
+     * Get the time a frame at a given index was presented.
+     *
+     * @param index The frame index.
+     * @return The presented time in nanoseconds or {@link #UNDEFINED_TIME_NANO}
+     *         if the frame is not presented yet.
+     */
+    public final long getFramePresentedTimeNano(int index) {
+        if (mFramesPresentedTimeNano == null) {
+            throw new IndexOutOfBoundsException();
+        }
+        return mFramesPresentedTimeNano[index];
+    }
+}
diff --git a/android/view/GestureDetector.java b/android/view/GestureDetector.java
new file mode 100644
index 0000000..63a8300
--- /dev/null
+++ b/android/view/GestureDetector.java
@@ -0,0 +1,910 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS;
+import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP;
+import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS;
+import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL;
+import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP;
+import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION;
+
+import android.annotation.UiContext;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.StrictMode;
+import android.os.SystemClock;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+/**
+ * Detects various gestures and events using the supplied {@link MotionEvent}s.
+ * The {@link OnGestureListener} callback will notify users when a particular
+ * motion event has occurred. This class should only be used with {@link MotionEvent}s
+ * reported via touch (don't use for trackball events).
+ *
+ * To use this class:
+ * <ul>
+ *  <li>Create an instance of the {@code GestureDetector} for your {@link View}
+ *  <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call
+ *          {@link #onTouchEvent(MotionEvent)}. The methods defined in your callback
+ *          will be executed when the events occur.
+ *  <li>If listening for {@link OnContextClickListener#onContextClick(MotionEvent)}
+ *          you must call {@link #onGenericMotionEvent(MotionEvent)}
+ *          in {@link View#onGenericMotionEvent(MotionEvent)}.
+ * </ul>
+ */
+public class GestureDetector {
+    /**
+     * The listener that is used to notify when gestures occur.
+     * If you want to listen for all the different gestures then implement
+     * this interface. If you only want to listen for a subset it might
+     * be easier to extend {@link SimpleOnGestureListener}.
+     */
+    public interface OnGestureListener {
+
+        /**
+         * Notified when a tap occurs with the down {@link MotionEvent}
+         * that triggered it. This will be triggered immediately for
+         * every down event. All other events should be preceded by this.
+         *
+         * @param e The down motion event.
+         */
+        boolean onDown(MotionEvent e);
+
+        /**
+         * The user has performed a down {@link MotionEvent} and not performed
+         * a move or up yet. This event is commonly used to provide visual
+         * feedback to the user to let them know that their action has been
+         * recognized i.e. highlight an element.
+         *
+         * @param e The down motion event
+         */
+        void onShowPress(MotionEvent e);
+
+        /**
+         * Notified when a tap occurs with the up {@link MotionEvent}
+         * that triggered it.
+         *
+         * @param e The up motion event that completed the first tap
+         * @return true if the event is consumed, else false
+         */
+        boolean onSingleTapUp(MotionEvent e);
+
+        /**
+         * Notified when a scroll occurs with the initial on down {@link MotionEvent} and the
+         * current move {@link MotionEvent}. The distance in x and y is also supplied for
+         * convenience.
+         *
+         * @param e1 The first down motion event that started the scrolling.
+         * @param e2 The move motion event that triggered the current onScroll.
+         * @param distanceX The distance along the X axis that has been scrolled since the last
+         *              call to onScroll. This is NOT the distance between {@code e1}
+         *              and {@code e2}.
+         * @param distanceY The distance along the Y axis that has been scrolled since the last
+         *              call to onScroll. This is NOT the distance between {@code e1}
+         *              and {@code e2}.
+         * @return true if the event is consumed, else false
+         */
+        boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
+
+        /**
+         * Notified when a long press occurs with the initial on down {@link MotionEvent}
+         * that trigged it.
+         *
+         * @param e The initial on down motion event that started the longpress.
+         */
+        void onLongPress(MotionEvent e);
+
+        /**
+         * Notified of a fling event when it occurs with the initial on down {@link MotionEvent}
+         * and the matching up {@link MotionEvent}. The calculated velocity is supplied along
+         * the x and y axis in pixels per second.
+         *
+         * @param e1 The first down motion event that started the fling.
+         * @param e2 The move motion event that triggered the current onFling.
+         * @param velocityX The velocity of this fling measured in pixels per second
+         *              along the x axis.
+         * @param velocityY The velocity of this fling measured in pixels per second
+         *              along the y axis.
+         * @return true if the event is consumed, else false
+         */
+        boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
+    }
+
+    /**
+     * The listener that is used to notify when a double-tap or a confirmed
+     * single-tap occur.
+     */
+    public interface OnDoubleTapListener {
+        /**
+         * Notified when a single-tap occurs.
+         * <p>
+         * Unlike {@link OnGestureListener#onSingleTapUp(MotionEvent)}, this
+         * will only be called after the detector is confident that the user's
+         * first tap is not followed by a second tap leading to a double-tap
+         * gesture.
+         *
+         * @param e The down motion event of the single-tap.
+         * @return true if the event is consumed, else false
+         */
+        boolean onSingleTapConfirmed(MotionEvent e);
+ 
+        /**
+         * Notified when a double-tap occurs. Triggered on the down event of second tap.
+         *
+         * @param e The down motion event of the first tap of the double-tap.
+         * @return true if the event is consumed, else false
+         */
+        boolean onDoubleTap(MotionEvent e);
+
+        /**
+         * Notified when an event within a double-tap gesture occurs, including
+         * the down, move, and up events.
+         *
+         * @param e The motion event that occurred during the double-tap gesture.
+         * @return true if the event is consumed, else false
+         */
+        boolean onDoubleTapEvent(MotionEvent e);
+    }
+
+    /**
+     * The listener that is used to notify when a context click occurs. When listening for a
+     * context click ensure that you call {@link #onGenericMotionEvent(MotionEvent)} in
+     * {@link View#onGenericMotionEvent(MotionEvent)}.
+     */
+    public interface OnContextClickListener {
+        /**
+         * Notified when a context click occurs.
+         *
+         * @param e The motion event that occurred during the context click.
+         * @return true if the event is consumed, else false
+         */
+        boolean onContextClick(MotionEvent e);
+    }
+
+    /**
+     * A convenience class to extend when you only want to listen for a subset
+     * of all the gestures. This implements all methods in the
+     * {@link OnGestureListener}, {@link OnDoubleTapListener}, and {@link OnContextClickListener}
+     * but does nothing and return {@code false} for all applicable methods.
+     */
+    public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener,
+            OnContextClickListener {
+
+        public boolean onSingleTapUp(MotionEvent e) {
+            return false;
+        }
+
+        public void onLongPress(MotionEvent e) {
+        }
+
+        public boolean onScroll(MotionEvent e1, MotionEvent e2,
+                float distanceX, float distanceY) {
+            return false;
+        }
+
+        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+                float velocityY) {
+            return false;
+        }
+
+        public void onShowPress(MotionEvent e) {
+        }
+
+        public boolean onDown(MotionEvent e) {
+            return false;
+        }
+
+        public boolean onDoubleTap(MotionEvent e) {
+            return false;
+        }
+
+        public boolean onDoubleTapEvent(MotionEvent e) {
+            return false;
+        }
+
+        public boolean onSingleTapConfirmed(MotionEvent e) {
+            return false;
+        }
+
+        public boolean onContextClick(MotionEvent e) {
+            return false;
+        }
+    }
+
+    private static final String TAG = GestureDetector.class.getSimpleName();
+    @UnsupportedAppUsage
+    private int mTouchSlopSquare;
+    private int mDoubleTapTouchSlopSquare;
+    private int mDoubleTapSlopSquare;
+    private float mAmbiguousGestureMultiplier;
+    @UnsupportedAppUsage
+    private int mMinimumFlingVelocity;
+    private int mMaximumFlingVelocity;
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
+    private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
+    private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
+    private static final int DOUBLE_TAP_MIN_TIME = ViewConfiguration.getDoubleTapMinTime();
+
+    // constants for Message.what used by GestureHandler below
+    private static final int SHOW_PRESS = 1;
+    private static final int LONG_PRESS = 2;
+    private static final int TAP = 3;
+
+    private final Handler mHandler;
+    @UnsupportedAppUsage
+    private final OnGestureListener mListener;
+    private OnDoubleTapListener mDoubleTapListener;
+    private OnContextClickListener mContextClickListener;
+
+    private boolean mStillDown;
+    private boolean mDeferConfirmSingleTap;
+    private boolean mInLongPress;
+    private boolean mInContextClick;
+    @UnsupportedAppUsage
+    private boolean mAlwaysInTapRegion;
+    private boolean mAlwaysInBiggerTapRegion;
+    private boolean mIgnoreNextUpEvent;
+    // Whether a classification has been recorded by statsd for the current event stream. Reset on
+    // ACTION_DOWN.
+    private boolean mHasRecordedClassification;
+
+    private MotionEvent mCurrentDownEvent;
+    private MotionEvent mCurrentMotionEvent;
+    private MotionEvent mPreviousUpEvent;
+
+    /**
+     * True when the user is still touching for the second tap (down, move, and
+     * up events). Can only be true if there is a double tap listener attached.
+     */
+    private boolean mIsDoubleTapping;
+
+    private float mLastFocusX;
+    private float mLastFocusY;
+    private float mDownFocusX;
+    private float mDownFocusY;
+
+    private boolean mIsLongpressEnabled;
+
+    /**
+     * Determines speed during touch scrolling
+     */
+    private VelocityTracker mVelocityTracker;
+
+    /**
+     * Consistency verifier for debugging purposes.
+     */
+    private final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
+            InputEventConsistencyVerifier.isInstrumentationEnabled() ?
+                    new InputEventConsistencyVerifier(this, 0) : null;
+
+    private class GestureHandler extends Handler {
+        GestureHandler() {
+            super();
+        }
+
+        GestureHandler(Handler handler) {
+            super(handler.getLooper());
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case SHOW_PRESS:
+                    mListener.onShowPress(mCurrentDownEvent);
+                    break;
+
+                case LONG_PRESS:
+                    recordGestureClassification(msg.arg1);
+                    dispatchLongPress();
+                    break;
+
+                case TAP:
+                    // If the user's finger is still down, do not count it as a tap
+                    if (mDoubleTapListener != null) {
+                        if (!mStillDown) {
+                            recordGestureClassification(
+                                    TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP);
+                            mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
+                        } else {
+                            mDeferConfirmSingleTap = true;
+                        }
+                    }
+                    break;
+
+                default:
+                    throw new RuntimeException("Unknown message " + msg); //never
+            }
+        }
+    }
+
+    /**
+     * Creates a GestureDetector with the supplied listener.
+     * This variant of the constructor should be used from a non-UI thread 
+     * (as it allows specifying the Handler).
+     * 
+     * @param listener the listener invoked for all the callbacks, this must
+     * not be null.
+     * @param handler the handler to use
+     *
+     * @throws NullPointerException if either {@code listener} or
+     * {@code handler} is null.
+     *
+     * @deprecated Use {@link #GestureDetector(android.content.Context,
+     *      android.view.GestureDetector.OnGestureListener, android.os.Handler)} instead.
+     */
+    @Deprecated
+    public GestureDetector(OnGestureListener listener, Handler handler) {
+        this(null, listener, handler);
+    }
+
+    /**
+     * Creates a GestureDetector with the supplied listener.
+     * You may only use this constructor from a UI thread (this is the usual situation).
+     * @see android.os.Handler#Handler()
+     * 
+     * @param listener the listener invoked for all the callbacks, this must
+     * not be null.
+     * 
+     * @throws NullPointerException if {@code listener} is null.
+     *
+     * @deprecated Use {@link #GestureDetector(android.content.Context,
+     *      android.view.GestureDetector.OnGestureListener)} instead.
+     */
+    @Deprecated
+    public GestureDetector(OnGestureListener listener) {
+        this(null, listener, null);
+    }
+
+    /**
+     * Creates a GestureDetector with the supplied listener.
+     * You may only use this constructor from a {@link android.os.Looper} thread.
+     * @see android.os.Handler#Handler()
+     *
+     * @param context An {@link android.app.Activity} or a {@link Context} created from
+     * {@link Context#createWindowContext(int, Bundle)}
+     * @param listener the listener invoked for all the callbacks, this must
+     * not be null. If the listener implements the {@link OnDoubleTapListener} or
+     * {@link OnContextClickListener} then it will also be set as the listener for
+     * these callbacks (for example when using the {@link SimpleOnGestureListener}).
+     *
+     * @throws NullPointerException if {@code listener} is null.
+     */
+    // TODO(b/182007470): Use @ConfigurationContext instead
+    public GestureDetector(@UiContext Context context, OnGestureListener listener) {
+        this(context, listener, null);
+    }
+
+    /**
+     * Creates a GestureDetector with the supplied listener that runs deferred events on the
+     * thread associated with the supplied {@link android.os.Handler}.
+     * @see android.os.Handler#Handler()
+     *
+     * @param context An {@link android.app.Activity} or a {@link Context} created from
+     * {@link Context#createWindowContext(int, Bundle)}
+     * @param listener the listener invoked for all the callbacks, this must
+     * not be null. If the listener implements the {@link OnDoubleTapListener} or
+     * {@link OnContextClickListener} then it will also be set as the listener for
+     * these callbacks (for example when using the {@link SimpleOnGestureListener}).
+     * @param handler the handler to use for running deferred listener events.
+     *
+     * @throws NullPointerException if {@code listener} is null.
+     */
+    public GestureDetector(@UiContext Context context, OnGestureListener listener,
+            Handler handler) {
+        if (handler != null) {
+            mHandler = new GestureHandler(handler);
+        } else {
+            mHandler = new GestureHandler();
+        }
+        mListener = listener;
+        if (listener instanceof OnDoubleTapListener) {
+            setOnDoubleTapListener((OnDoubleTapListener) listener);
+        }
+        if (listener instanceof OnContextClickListener) {
+            setContextClickListener((OnContextClickListener) listener);
+        }
+        init(context);
+    }
+    
+    /**
+     * Creates a GestureDetector with the supplied listener that runs deferred events on the
+     * thread associated with the supplied {@link android.os.Handler}.
+     * @see android.os.Handler#Handler()
+     *
+     * @param context An {@link android.app.Activity} or a {@link Context} created from
+     * {@link Context#createWindowContext(int, Bundle)}
+     * @param listener the listener invoked for all the callbacks, this must
+     * not be null.
+     * @param handler the handler to use for running deferred listener events.
+     * @param unused currently not used.
+     *
+     * @throws NullPointerException if {@code listener} is null.
+     */
+    public GestureDetector(@UiContext Context context, OnGestureListener listener, Handler handler,
+            boolean unused) {
+        this(context, listener, handler);
+    }
+
+    private void init(@UiContext Context context) {
+        if (mListener == null) {
+            throw new NullPointerException("OnGestureListener must not be null");
+        }
+        mIsLongpressEnabled = true;
+
+        // Fallback to support pre-donuts releases
+        int touchSlop, doubleTapSlop, doubleTapTouchSlop;
+        if (context == null) {
+            //noinspection deprecation
+            touchSlop = ViewConfiguration.getTouchSlop();
+            doubleTapTouchSlop = touchSlop; // Hack rather than adding a hidden method for this
+            doubleTapSlop = ViewConfiguration.getDoubleTapSlop();
+            //noinspection deprecation
+            mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
+            mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity();
+            mAmbiguousGestureMultiplier = ViewConfiguration.getAmbiguousGestureMultiplier();
+        } else {
+            StrictMode.assertConfigurationContext(context, "GestureDetector#init");
+            final ViewConfiguration configuration = ViewConfiguration.get(context);
+            touchSlop = configuration.getScaledTouchSlop();
+            doubleTapTouchSlop = configuration.getScaledDoubleTapTouchSlop();
+            doubleTapSlop = configuration.getScaledDoubleTapSlop();
+            mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
+            mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity();
+            mAmbiguousGestureMultiplier = configuration.getScaledAmbiguousGestureMultiplier();
+        }
+        mTouchSlopSquare = touchSlop * touchSlop;
+        mDoubleTapTouchSlopSquare = doubleTapTouchSlop * doubleTapTouchSlop;
+        mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
+    }
+
+    /**
+     * Sets the listener which will be called for double-tap and related
+     * gestures.
+     * 
+     * @param onDoubleTapListener the listener invoked for all the callbacks, or
+     *        null to stop listening for double-tap gestures.
+     */
+    public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) {
+        mDoubleTapListener = onDoubleTapListener;
+    }
+
+    /**
+     * Sets the listener which will be called for context clicks.
+     *
+     * @param onContextClickListener the listener invoked for all the callbacks, or null to stop
+     *            listening for context clicks.
+     */
+    public void setContextClickListener(OnContextClickListener onContextClickListener) {
+        mContextClickListener = onContextClickListener;
+    }
+
+    /**
+     * Set whether longpress is enabled, if this is enabled when a user
+     * presses and holds down you get a longpress event and nothing further.
+     * If it's disabled the user can press and hold down and then later
+     * moved their finger and you will get scroll events. By default
+     * longpress is enabled.
+     *
+     * @param isLongpressEnabled whether longpress should be enabled.
+     */
+    public void setIsLongpressEnabled(boolean isLongpressEnabled) {
+        mIsLongpressEnabled = isLongpressEnabled;
+    }
+
+    /**
+     * @return true if longpress is enabled, else false.
+     */
+    public boolean isLongpressEnabled() {
+        return mIsLongpressEnabled;
+    }
+
+    /**
+     * Analyzes the given motion event and if applicable triggers the
+     * appropriate callbacks on the {@link OnGestureListener} supplied.
+     *
+     * @param ev The current motion event.
+     * @return true if the {@link OnGestureListener} consumed the event,
+     *              else false.
+     */
+    public boolean onTouchEvent(MotionEvent ev) {
+        if (mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onTouchEvent(ev, 0);
+        }
+
+        final int action = ev.getAction();
+
+        if (mCurrentMotionEvent != null) {
+            mCurrentMotionEvent.recycle();
+        }
+        mCurrentMotionEvent = MotionEvent.obtain(ev);
+
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+        mVelocityTracker.addMovement(ev);
+
+        final boolean pointerUp =
+                (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP;
+        final int skipIndex = pointerUp ? ev.getActionIndex() : -1;
+        final boolean isGeneratedGesture =
+                (ev.getFlags() & MotionEvent.FLAG_IS_GENERATED_GESTURE) != 0;
+
+        // Determine focal point
+        float sumX = 0, sumY = 0;
+        final int count = ev.getPointerCount();
+        for (int i = 0; i < count; i++) {
+            if (skipIndex == i) continue;
+            sumX += ev.getX(i);
+            sumY += ev.getY(i);
+        }
+        final int div = pointerUp ? count - 1 : count;
+        final float focusX = sumX / div;
+        final float focusY = sumY / div;
+
+        boolean handled = false;
+
+        switch (action & MotionEvent.ACTION_MASK) {
+            case MotionEvent.ACTION_POINTER_DOWN:
+                mDownFocusX = mLastFocusX = focusX;
+                mDownFocusY = mLastFocusY = focusY;
+                // Cancel long press and taps
+                cancelTaps();
+                break;
+
+            case MotionEvent.ACTION_POINTER_UP:
+                mDownFocusX = mLastFocusX = focusX;
+                mDownFocusY = mLastFocusY = focusY;
+
+                // Check the dot product of current velocities.
+                // If the pointer that left was opposing another velocity vector, clear.
+                mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
+                final int upIndex = ev.getActionIndex();
+                final int id1 = ev.getPointerId(upIndex);
+                final float x1 = mVelocityTracker.getXVelocity(id1);
+                final float y1 = mVelocityTracker.getYVelocity(id1);
+                for (int i = 0; i < count; i++) {
+                    if (i == upIndex) continue;
+
+                    final int id2 = ev.getPointerId(i);
+                    final float x = x1 * mVelocityTracker.getXVelocity(id2);
+                    final float y = y1 * mVelocityTracker.getYVelocity(id2);
+
+                    final float dot = x + y;
+                    if (dot < 0) {
+                        mVelocityTracker.clear();
+                        break;
+                    }
+                }
+                break;
+
+            case MotionEvent.ACTION_DOWN:
+                if (mDoubleTapListener != null) {
+                    boolean hadTapMessage = mHandler.hasMessages(TAP);
+                    if (hadTapMessage) mHandler.removeMessages(TAP);
+                    if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null)
+                            && hadTapMessage
+                            && isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
+                        // This is a second tap
+                        mIsDoubleTapping = true;
+                        recordGestureClassification(
+                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP);
+                        // Give a callback with the first tap of the double-tap
+                        handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
+                        // Give a callback with down event of the double-tap
+                        handled |= mDoubleTapListener.onDoubleTapEvent(ev);
+                    } else {
+                        // This is a first tap
+                        mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
+                    }
+                }
+
+                mDownFocusX = mLastFocusX = focusX;
+                mDownFocusY = mLastFocusY = focusY;
+                if (mCurrentDownEvent != null) {
+                    mCurrentDownEvent.recycle();
+                }
+                mCurrentDownEvent = MotionEvent.obtain(ev);
+                mAlwaysInTapRegion = true;
+                mAlwaysInBiggerTapRegion = true;
+                mStillDown = true;
+                mInLongPress = false;
+                mDeferConfirmSingleTap = false;
+                mHasRecordedClassification = false;
+
+                if (mIsLongpressEnabled) {
+                    mHandler.removeMessages(LONG_PRESS);
+                    mHandler.sendMessageAtTime(
+                            mHandler.obtainMessage(
+                                    LONG_PRESS,
+                                    TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS,
+                                    0 /* arg2 */),
+                            mCurrentDownEvent.getDownTime()
+                                    + ViewConfiguration.getLongPressTimeout());
+                }
+                mHandler.sendEmptyMessageAtTime(SHOW_PRESS,
+                        mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
+                handled |= mListener.onDown(ev);
+                break;
+
+            case MotionEvent.ACTION_MOVE:
+                if (mInLongPress || mInContextClick) {
+                    break;
+                }
+
+                final int motionClassification = ev.getClassification();
+                final boolean hasPendingLongPress = mHandler.hasMessages(LONG_PRESS);
+
+                final float scrollX = mLastFocusX - focusX;
+                final float scrollY = mLastFocusY - focusY;
+                if (mIsDoubleTapping) {
+                    // Give the move events of the double-tap
+                    recordGestureClassification(
+                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP);
+                    handled |= mDoubleTapListener.onDoubleTapEvent(ev);
+                } else if (mAlwaysInTapRegion) {
+                    final int deltaX = (int) (focusX - mDownFocusX);
+                    final int deltaY = (int) (focusY - mDownFocusY);
+                    int distance = (deltaX * deltaX) + (deltaY * deltaY);
+                    int slopSquare = isGeneratedGesture ? 0 : mTouchSlopSquare;
+
+                    final boolean ambiguousGesture =
+                            motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
+                    final boolean shouldInhibitDefaultAction =
+                            hasPendingLongPress && ambiguousGesture;
+                    if (shouldInhibitDefaultAction) {
+                        // Inhibit default long press
+                        if (distance > slopSquare) {
+                            // The default action here is to remove long press. But if the touch
+                            // slop below gets increased, and we never exceed the modified touch
+                            // slop while still receiving AMBIGUOUS_GESTURE, we risk that *nothing*
+                            // will happen in response to user input. To prevent this,
+                            // reschedule long press with a modified timeout.
+                            mHandler.removeMessages(LONG_PRESS);
+                            final long longPressTimeout = ViewConfiguration.getLongPressTimeout();
+                            mHandler.sendMessageAtTime(
+                                    mHandler.obtainMessage(
+                                            LONG_PRESS,
+                                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS,
+                                            0 /* arg2 */),
+                                    ev.getDownTime()
+                                        + (long) (longPressTimeout * mAmbiguousGestureMultiplier));
+                        }
+                        // Inhibit default scroll. If a gesture is ambiguous, we prevent scroll
+                        // until the gesture is resolved.
+                        // However, for safety, simply increase the touch slop in case the
+                        // classification is erroneous. Since the value is squared, multiply twice.
+                        slopSquare *= mAmbiguousGestureMultiplier * mAmbiguousGestureMultiplier;
+                    }
+
+                    if (distance > slopSquare) {
+                        recordGestureClassification(
+                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL);
+                        handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
+                        mLastFocusX = focusX;
+                        mLastFocusY = focusY;
+                        mAlwaysInTapRegion = false;
+                        mHandler.removeMessages(TAP);
+                        mHandler.removeMessages(SHOW_PRESS);
+                        mHandler.removeMessages(LONG_PRESS);
+                    }
+                    int doubleTapSlopSquare = isGeneratedGesture ? 0 : mDoubleTapTouchSlopSquare;
+                    if (distance > doubleTapSlopSquare) {
+                        mAlwaysInBiggerTapRegion = false;
+                    }
+                } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
+                    recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL);
+                    handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
+                    mLastFocusX = focusX;
+                    mLastFocusY = focusY;
+                }
+                final boolean deepPress =
+                        motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
+                if (deepPress && hasPendingLongPress) {
+                    mHandler.removeMessages(LONG_PRESS);
+                    mHandler.sendMessage(
+                            mHandler.obtainMessage(
+                                  LONG_PRESS,
+                                  TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS,
+                                  0 /* arg2 */));
+                }
+                break;
+
+            case MotionEvent.ACTION_UP:
+                mStillDown = false;
+                MotionEvent currentUpEvent = MotionEvent.obtain(ev);
+                if (mIsDoubleTapping) {
+                    // Finally, give the up event of the double-tap
+                    recordGestureClassification(
+                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP);
+                    handled |= mDoubleTapListener.onDoubleTapEvent(ev);
+                } else if (mInLongPress) {
+                    mHandler.removeMessages(TAP);
+                    mInLongPress = false;
+                } else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) {
+                    recordGestureClassification(
+                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP);
+                    handled = mListener.onSingleTapUp(ev);
+                    if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
+                        mDoubleTapListener.onSingleTapConfirmed(ev);
+                    }
+                } else if (!mIgnoreNextUpEvent) {
+
+                    // A fling must travel the minimum tap distance
+                    final VelocityTracker velocityTracker = mVelocityTracker;
+                    final int pointerId = ev.getPointerId(0);
+                    velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
+                    final float velocityY = velocityTracker.getYVelocity(pointerId);
+                    final float velocityX = velocityTracker.getXVelocity(pointerId);
+
+                    if ((Math.abs(velocityY) > mMinimumFlingVelocity)
+                            || (Math.abs(velocityX) > mMinimumFlingVelocity)) {
+                        handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY);
+                    }
+                }
+                if (mPreviousUpEvent != null) {
+                    mPreviousUpEvent.recycle();
+                }
+                // Hold the event we obtained above - listeners may have changed the original.
+                mPreviousUpEvent = currentUpEvent;
+                if (mVelocityTracker != null) {
+                    // This may have been cleared when we called out to the
+                    // application above.
+                    mVelocityTracker.recycle();
+                    mVelocityTracker = null;
+                }
+                mIsDoubleTapping = false;
+                mDeferConfirmSingleTap = false;
+                mIgnoreNextUpEvent = false;
+                mHandler.removeMessages(SHOW_PRESS);
+                mHandler.removeMessages(LONG_PRESS);
+                break;
+
+            case MotionEvent.ACTION_CANCEL:
+                cancel();
+                break;
+        }
+
+        if (!handled && mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0);
+        }
+        return handled;
+    }
+
+    /**
+     * Analyzes the given generic motion event and if applicable triggers the
+     * appropriate callbacks on the {@link OnGestureListener} supplied.
+     *
+     * @param ev The current motion event.
+     * @return true if the {@link OnGestureListener} consumed the event,
+     *              else false.
+     */
+    public boolean onGenericMotionEvent(MotionEvent ev) {
+        if (mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onGenericMotionEvent(ev, 0);
+        }
+
+        final int actionButton = ev.getActionButton();
+        switch (ev.getActionMasked()) {
+            case MotionEvent.ACTION_BUTTON_PRESS:
+                if (mContextClickListener != null && !mInContextClick && !mInLongPress
+                        && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
+                        || actionButton == MotionEvent.BUTTON_SECONDARY)) {
+                    if (mContextClickListener.onContextClick(ev)) {
+                        mInContextClick = true;
+                        mHandler.removeMessages(LONG_PRESS);
+                        mHandler.removeMessages(TAP);
+                        return true;
+                    }
+                }
+                break;
+
+            case MotionEvent.ACTION_BUTTON_RELEASE:
+                if (mInContextClick && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
+                        || actionButton == MotionEvent.BUTTON_SECONDARY)) {
+                    mInContextClick = false;
+                    mIgnoreNextUpEvent = true;
+                }
+                break;
+        }
+        return false;
+    }
+
+    private void cancel() {
+        mHandler.removeMessages(SHOW_PRESS);
+        mHandler.removeMessages(LONG_PRESS);
+        mHandler.removeMessages(TAP);
+        mVelocityTracker.recycle();
+        mVelocityTracker = null;
+        mIsDoubleTapping = false;
+        mStillDown = false;
+        mAlwaysInTapRegion = false;
+        mAlwaysInBiggerTapRegion = false;
+        mDeferConfirmSingleTap = false;
+        mInLongPress = false;
+        mInContextClick = false;
+        mIgnoreNextUpEvent = false;
+    }
+
+    private void cancelTaps() {
+        mHandler.removeMessages(SHOW_PRESS);
+        mHandler.removeMessages(LONG_PRESS);
+        mHandler.removeMessages(TAP);
+        mIsDoubleTapping = false;
+        mAlwaysInTapRegion = false;
+        mAlwaysInBiggerTapRegion = false;
+        mDeferConfirmSingleTap = false;
+        mInLongPress = false;
+        mInContextClick = false;
+        mIgnoreNextUpEvent = false;
+    }
+
+    private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp,
+            MotionEvent secondDown) {
+        if (!mAlwaysInBiggerTapRegion) {
+            return false;
+        }
+
+        final long deltaTime = secondDown.getEventTime() - firstUp.getEventTime();
+        if (deltaTime > DOUBLE_TAP_TIMEOUT || deltaTime < DOUBLE_TAP_MIN_TIME) {
+            return false;
+        }
+
+        int deltaX = (int) firstDown.getX() - (int) secondDown.getX();
+        int deltaY = (int) firstDown.getY() - (int) secondDown.getY();
+        final boolean isGeneratedGesture =
+                (firstDown.getFlags() & MotionEvent.FLAG_IS_GENERATED_GESTURE) != 0;
+        int slopSquare = isGeneratedGesture ? 0 : mDoubleTapSlopSquare;
+        return (deltaX * deltaX + deltaY * deltaY < slopSquare);
+    }
+
+    private void dispatchLongPress() {
+        mHandler.removeMessages(TAP);
+        mDeferConfirmSingleTap = false;
+        mInLongPress = true;
+        mListener.onLongPress(mCurrentDownEvent);
+    }
+
+    private void recordGestureClassification(int classification) {
+        if (mHasRecordedClassification
+                || classification
+                    == TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION) {
+            // Only record the first classification for an event stream.
+            return;
+        }
+        if (mCurrentDownEvent == null || mCurrentMotionEvent == null) {
+            // If the complete event stream wasn't seen, don't record anything.
+            mHasRecordedClassification = true;
+            return;
+        }
+        FrameworkStatsLog.write(
+                FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED,
+                getClass().getName(),
+                classification,
+                (int) (SystemClock.uptimeMillis() - mCurrentMotionEvent.getDownTime()),
+                (float) Math.hypot(mCurrentMotionEvent.getRawX() - mCurrentDownEvent.getRawX(),
+                                   mCurrentMotionEvent.getRawY() - mCurrentDownEvent.getRawY()));
+        mHasRecordedClassification = true;
+    }
+}
diff --git a/android/view/GestureExclusionTracker.java b/android/view/GestureExclusionTracker.java
new file mode 100644
index 0000000..fffb323
--- /dev/null
+++ b/android/view/GestureExclusionTracker.java
@@ -0,0 +1,142 @@
+/*
+ * 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.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Rect;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Used by {@link ViewRootImpl} to track system gesture exclusion rects reported by views.
+ */
+class GestureExclusionTracker {
+    private boolean mGestureExclusionViewsChanged = false;
+    private boolean mRootGestureExclusionRectsChanged = false;
+    private List<Rect> mRootGestureExclusionRects = Collections.emptyList();
+    private List<GestureExclusionViewInfo> mGestureExclusionViewInfos = new ArrayList<>();
+    private List<Rect> mGestureExclusionRects = Collections.emptyList();
+
+    public void updateRectsForView(@NonNull View view) {
+        boolean found = false;
+        final Iterator<GestureExclusionViewInfo> i = mGestureExclusionViewInfos.iterator();
+        while (i.hasNext()) {
+            final GestureExclusionViewInfo info = i.next();
+            final View v = info.getView();
+            if (v == null || !v.isAttachedToWindow() || !v.isAggregatedVisible()) {
+                mGestureExclusionViewsChanged = true;
+                i.remove();
+                continue;
+            }
+            if (v == view) {
+                found = true;
+                info.mDirty = true;
+                break;
+            }
+        }
+        if (!found && view.isAttachedToWindow()) {
+            mGestureExclusionViewInfos.add(new GestureExclusionViewInfo(view));
+            mGestureExclusionViewsChanged = true;
+        }
+    }
+
+    @Nullable
+    public List<Rect> computeChangedRects() {
+        boolean changed = mRootGestureExclusionRectsChanged;
+        final Iterator<GestureExclusionViewInfo> i = mGestureExclusionViewInfos.iterator();
+        final List<Rect> rects = new ArrayList<>(mRootGestureExclusionRects);
+        while (i.hasNext()) {
+            final GestureExclusionViewInfo info = i.next();
+            switch (info.update()) {
+                case GestureExclusionViewInfo.CHANGED:
+                    changed = true;
+                    // Deliberate fall-through
+                case GestureExclusionViewInfo.UNCHANGED:
+                    rects.addAll(info.mExclusionRects);
+                    break;
+                case GestureExclusionViewInfo.GONE:
+                    mGestureExclusionViewsChanged = true;
+                    i.remove();
+                    break;
+            }
+        }
+        if (changed || mGestureExclusionViewsChanged) {
+            mGestureExclusionViewsChanged = false;
+            mRootGestureExclusionRectsChanged = false;
+            if (!mGestureExclusionRects.equals(rects)) {
+                mGestureExclusionRects = rects;
+                return rects;
+            }
+        }
+        return null;
+    }
+
+    public void setRootSystemGestureExclusionRects(@NonNull List<Rect> rects) {
+        Preconditions.checkNotNull(rects, "rects must not be null");
+        mRootGestureExclusionRects = rects;
+        mRootGestureExclusionRectsChanged = true;
+    }
+
+    @NonNull
+    public List<Rect> getRootSystemGestureExclusionRects() {
+        return mRootGestureExclusionRects;
+    }
+
+    private static class GestureExclusionViewInfo {
+        public static final int CHANGED = 0;
+        public static final int UNCHANGED = 1;
+        public static final int GONE = 2;
+
+        private final WeakReference<View> mView;
+        boolean mDirty = true;
+        List<Rect> mExclusionRects = Collections.emptyList();
+
+        GestureExclusionViewInfo(View view) {
+            mView = new WeakReference<>(view);
+        }
+
+        public View getView() {
+            return mView.get();
+        }
+
+        public int update() {
+            final View excludedView = getView();
+            if (excludedView == null || !excludedView.isAttachedToWindow()
+                    || !excludedView.isAggregatedVisible()) return GONE;
+            final List<Rect> localRects = excludedView.getSystemGestureExclusionRects();
+            final List<Rect> newRects = new ArrayList<>(localRects.size());
+            for (Rect src : localRects) {
+                Rect mappedRect = new Rect(src);
+                ViewParent p = excludedView.getParent();
+                if (p != null && p.getChildVisibleRect(excludedView, mappedRect, null)) {
+                    newRects.add(mappedRect);
+                }
+            }
+
+            if (mExclusionRects.equals(localRects)) return UNCHANGED;
+            mExclusionRects = newRects;
+            return CHANGED;
+        }
+    }
+}
diff --git a/android/view/GhostView.java b/android/view/GhostView.java
new file mode 100644
index 0000000..46d3c64
--- /dev/null
+++ b/android/view/GhostView.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.RecordingCanvas;
+import android.graphics.RenderNode;
+import android.os.Build;
+import android.widget.FrameLayout;
+
+import java.util.ArrayList;
+
+/**
+ * This view draws another View in an Overlay without changing the parent. It will not be drawn
+ * by its parent because its visibility is set to INVISIBLE, but will be drawn
+ * here using its render node. When the GhostView is set to INVISIBLE, the View it is
+ * shadowing will become VISIBLE and when the GhostView becomes VISIBLE, the shadowed
+ * view becomes INVISIBLE.
+ * @hide
+ */
+public class GhostView extends View {
+    private final View mView;
+    private int mReferences;
+    private boolean mBeingMoved;
+
+    private GhostView(View view) {
+        super(view.getContext());
+        mView = view;
+        mView.mGhostView = this;
+        final ViewGroup parent = (ViewGroup) mView.getParent();
+        mView.setTransitionVisibility(View.INVISIBLE);
+        parent.invalidate();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        if (canvas instanceof RecordingCanvas) {
+            RecordingCanvas dlCanvas = (RecordingCanvas) canvas;
+            mView.mRecreateDisplayList = true;
+            RenderNode renderNode = mView.updateDisplayListIfDirty();
+            if (renderNode.hasDisplayList()) {
+                dlCanvas.enableZ(); // enable shadow for this rendernode
+                dlCanvas.drawRenderNode(renderNode);
+                dlCanvas.disableZ(); // re-disable reordering/shadows
+            }
+        }
+    }
+
+    public void setMatrix(Matrix matrix) {
+        mRenderNode.setAnimationMatrix(matrix);
+    }
+
+    @Override
+    public void setVisibility(@Visibility int visibility) {
+        super.setVisibility(visibility);
+        if (mView.mGhostView == this) {
+            int inverseVisibility = (visibility == View.VISIBLE) ? View.INVISIBLE : View.VISIBLE;
+            mView.setTransitionVisibility(inverseVisibility);
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        if (!mBeingMoved) {
+            mView.setTransitionVisibility(View.VISIBLE);
+            mView.mGhostView = null;
+            final ViewGroup parent = (ViewGroup) mView.getParent();
+            if (parent != null) {
+                parent.invalidate();
+            }
+        }
+    }
+
+    public static void calculateMatrix(View view, ViewGroup host, Matrix matrix) {
+        ViewGroup parent = (ViewGroup) view.getParent();
+        matrix.reset();
+        parent.transformMatrixToGlobal(matrix);
+        matrix.preTranslate(-parent.getScrollX(), -parent.getScrollY());
+        host.transformMatrixToLocal(matrix);
+    }
+
+    @UnsupportedAppUsage
+    public static GhostView addGhost(View view, ViewGroup viewGroup, Matrix matrix) {
+        if (!(view.getParent() instanceof ViewGroup)) {
+            throw new IllegalArgumentException("Ghosted views must be parented by a ViewGroup");
+        }
+        ViewGroupOverlay overlay = viewGroup.getOverlay();
+        ViewOverlay.OverlayViewGroup overlayViewGroup = overlay.mOverlayViewGroup;
+        GhostView ghostView = view.mGhostView;
+        int previousRefCount = 0;
+        if (ghostView != null) {
+            View oldParent = (View) ghostView.getParent();
+            ViewGroup oldGrandParent = (ViewGroup) oldParent.getParent();
+            if (oldGrandParent != overlayViewGroup) {
+                previousRefCount = ghostView.mReferences;
+                oldGrandParent.removeView(oldParent);
+                ghostView = null;
+            }
+        }
+        if (ghostView == null) {
+            if (matrix == null) {
+                matrix = new Matrix();
+                calculateMatrix(view, viewGroup, matrix);
+            }
+            ghostView = new GhostView(view);
+            ghostView.setMatrix(matrix);
+            FrameLayout parent = new FrameLayout(view.getContext());
+            parent.setClipChildren(false);
+            copySize(viewGroup, parent);
+            copySize(viewGroup, ghostView);
+            parent.addView(ghostView);
+            ArrayList<View> tempViews = new ArrayList<View>();
+            int firstGhost = moveGhostViewsToTop(overlay.mOverlayViewGroup, tempViews);
+            insertIntoOverlay(overlay.mOverlayViewGroup, parent, ghostView, tempViews, firstGhost);
+            ghostView.mReferences = previousRefCount;
+        } else if (matrix != null) {
+            ghostView.setMatrix(matrix);
+        }
+        ghostView.mReferences++;
+        return ghostView;
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    public static GhostView addGhost(View view, ViewGroup viewGroup) {
+        return addGhost(view, viewGroup, null);
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    public static void removeGhost(View view) {
+        GhostView ghostView = view.mGhostView;
+        if (ghostView != null) {
+            ghostView.mReferences--;
+            if (ghostView.mReferences == 0) {
+                ViewGroup parent = (ViewGroup) ghostView.getParent();
+                ViewGroup grandParent = (ViewGroup) parent.getParent();
+                grandParent.removeView(parent);
+            }
+        }
+    }
+
+    public static GhostView getGhost(View view) {
+        return view.mGhostView;
+    }
+
+    private static void copySize(View from, View to) {
+        to.setLeft(0);
+        to.setTop(0);
+        to.setRight(from.getWidth());
+        to.setBottom(from.getHeight());
+    }
+
+    /**
+     * Move the GhostViews to the end so that they are on top of other views and it is easier
+     * to do binary search for the correct location for the GhostViews in insertIntoOverlay.
+     *
+     * @return The index of the first GhostView or -1 if no GhostView is in the ViewGroup
+     */
+    private static int moveGhostViewsToTop(ViewGroup viewGroup, ArrayList<View> tempViews) {
+        final int numChildren = viewGroup.getChildCount();
+        if (numChildren == 0) {
+            return -1;
+        } else if (isGhostWrapper(viewGroup.getChildAt(numChildren - 1))) {
+            // GhostViews are already at the end
+            int firstGhost = numChildren - 1;
+            for (int i = numChildren - 2; i >= 0; i--) {
+                if (!isGhostWrapper(viewGroup.getChildAt(i))) {
+                    break;
+                }
+                firstGhost = i;
+            }
+            return firstGhost;
+        }
+
+        // Remove all GhostViews from the middle
+        for (int i = numChildren - 2; i >= 0; i--) {
+            View child = viewGroup.getChildAt(i);
+            if (isGhostWrapper(child)) {
+                tempViews.add(child);
+                GhostView ghostView = (GhostView)((ViewGroup)child).getChildAt(0);
+                ghostView.mBeingMoved = true;
+                viewGroup.removeViewAt(i);
+                ghostView.mBeingMoved = false;
+            }
+        }
+
+        final int firstGhost;
+        if (tempViews.isEmpty()) {
+            firstGhost = -1;
+        } else {
+            firstGhost = viewGroup.getChildCount();
+            // Add the GhostViews to the end
+            for (int i = tempViews.size() - 1; i >= 0; i--) {
+                viewGroup.addView(tempViews.get(i));
+            }
+            tempViews.clear();
+        }
+        return firstGhost;
+    }
+
+    /**
+     * Inserts a GhostView into the overlay's ViewGroup in the order in which they
+     * should be displayed by the UI.
+     */
+    private static void insertIntoOverlay(ViewGroup viewGroup, ViewGroup wrapper,
+            GhostView ghostView, ArrayList<View> tempParents, int firstGhost) {
+        if (firstGhost == -1) {
+            viewGroup.addView(wrapper);
+        } else {
+            ArrayList<View> viewParents = new ArrayList<View>();
+            getParents(ghostView.mView, viewParents);
+
+            int index = getInsertIndex(viewGroup, viewParents, tempParents, firstGhost);
+            if (index < 0 || index >= viewGroup.getChildCount()) {
+                viewGroup.addView(wrapper);
+            } else {
+                viewGroup.addView(wrapper, index);
+            }
+        }
+    }
+
+    /**
+     * Find the index into the overlay to insert the GhostView based on the order that the
+     * views should be drawn. This keeps GhostViews layered in the same order
+     * that they are ordered in the UI.
+     */
+    private static int getInsertIndex(ViewGroup overlayViewGroup, ArrayList<View> viewParents,
+            ArrayList<View> tempParents, int firstGhost) {
+        int low = firstGhost;
+        int high = overlayViewGroup.getChildCount() - 1;
+
+        while (low <= high) {
+            int mid = (low + high) / 2;
+            ViewGroup wrapper = (ViewGroup) overlayViewGroup.getChildAt(mid);
+            GhostView midView = (GhostView) wrapper.getChildAt(0);
+            getParents(midView.mView, tempParents);
+            if (isOnTop(viewParents, tempParents)) {
+                low = mid + 1;
+            } else {
+                high = mid - 1;
+            }
+            tempParents.clear();
+        }
+
+        return low;
+    }
+
+    /**
+     * Returns true if view is a GhostView's FrameLayout wrapper.
+     */
+    private static boolean isGhostWrapper(View view) {
+        if (view instanceof FrameLayout) {
+            FrameLayout frameLayout = (FrameLayout) view;
+            if (frameLayout.getChildCount() == 1) {
+                View child = frameLayout.getChildAt(0);
+                return child instanceof GhostView;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if viewParents is from a View that is on top of the comparedWith's view.
+     * The ArrayLists contain the ancestors of views in order from top most grandparent, to
+     * the view itself, in order. The goal is to find the first matching parent and then
+     * compare the draw order of the siblings.
+     */
+    private static boolean isOnTop(ArrayList<View> viewParents, ArrayList<View> comparedWith) {
+        if (viewParents.isEmpty() || comparedWith.isEmpty() ||
+                viewParents.get(0) != comparedWith.get(0)) {
+            // Not the same decorView -- arbitrary ordering
+            return true;
+        }
+        int depth = Math.min(viewParents.size(), comparedWith.size());
+        for (int i = 1; i < depth; i++) {
+            View viewParent = viewParents.get(i);
+            View comparedWithParent = comparedWith.get(i);
+
+            if (viewParent != comparedWithParent) {
+                // i - 1 is the same parent, but these are different children.
+                return isOnTop(viewParent, comparedWithParent);
+            }
+        }
+
+        // one of these is the parent of the other
+        boolean isComparedWithTheParent = (comparedWith.size() == depth);
+        return isComparedWithTheParent;
+    }
+
+    /**
+     * Adds all the parents, grandparents, etc. of view to parents.
+     */
+    private static void getParents(View view, ArrayList<View> parents) {
+        ViewParent parent = view.getParent();
+        if (parent != null && parent instanceof ViewGroup) {
+            getParents((View) parent, parents);
+        }
+        parents.add(view);
+    }
+
+    /**
+     * Returns true if view would be drawn on top of comparedWith or false otherwise.
+     * view and comparedWith are siblings with the same parent. This uses the logic
+     * that dispatchDraw uses to determine which View should be drawn first.
+     */
+    private static boolean isOnTop(View view, View comparedWith) {
+        ViewGroup parent = (ViewGroup) view.getParent();
+
+        final int childrenCount = parent.getChildCount();
+        final ArrayList<View> preorderedList = parent.buildOrderedChildList();
+        final boolean customOrder = preorderedList == null
+                && parent.isChildrenDrawingOrderEnabled();
+
+        // This default value shouldn't be used because both view and comparedWith
+        // should be in the list. If there is an error, then just return an arbitrary
+        // view is on top.
+        boolean isOnTop = true;
+        for (int i = 0; i < childrenCount; i++) {
+            int childIndex = customOrder ? parent.getChildDrawingOrder(childrenCount, i) : i;
+            final View child = (preorderedList == null)
+                    ? parent.getChildAt(childIndex) : preorderedList.get(childIndex);
+            if (child == view) {
+                isOnTop = false;
+                break;
+            } else if (child == comparedWith) {
+                isOnTop = true;
+                break;
+            }
+        }
+
+        if (preorderedList != null) {
+            preorderedList.clear();
+        }
+        return isOnTop;
+    }
+}
diff --git a/android/view/Gravity.java b/android/view/Gravity.java
new file mode 100644
index 0000000..c8bfd36
--- /dev/null
+++ b/android/view/Gravity.java
@@ -0,0 +1,530 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.IntDef;
+import android.graphics.Rect;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Standard constants and tools for placing an object within a potentially
+ * larger container.
+ */
+public class Gravity
+{
+    /** Constant indicating that no gravity has been set **/
+    public static final int NO_GRAVITY = 0x0000;
+    
+    /** Raw bit indicating the gravity for an axis has been specified. */
+    public static final int AXIS_SPECIFIED = 0x0001;
+
+    /** Raw bit controlling how the left/top edge is placed. */
+    public static final int AXIS_PULL_BEFORE = 0x0002;
+    /** Raw bit controlling how the right/bottom edge is placed. */
+    public static final int AXIS_PULL_AFTER = 0x0004;
+    /** Raw bit controlling whether the right/bottom edge is clipped to its
+     * container, based on the gravity direction being applied. */
+    public static final int AXIS_CLIP = 0x0008;
+
+    /** Bits defining the horizontal axis. */
+    public static final int AXIS_X_SHIFT = 0;
+    /** Bits defining the vertical axis. */
+    public static final int AXIS_Y_SHIFT = 4;
+
+    /** Push object to the top of its container, not changing its size. */
+    public static final int TOP = (AXIS_PULL_BEFORE|AXIS_SPECIFIED)<<AXIS_Y_SHIFT;
+    /** Push object to the bottom of its container, not changing its size. */
+    public static final int BOTTOM = (AXIS_PULL_AFTER|AXIS_SPECIFIED)<<AXIS_Y_SHIFT;
+    /** Push object to the left of its container, not changing its size. */
+    public static final int LEFT = (AXIS_PULL_BEFORE|AXIS_SPECIFIED)<<AXIS_X_SHIFT;
+    /** Push object to the right of its container, not changing its size. */
+    public static final int RIGHT = (AXIS_PULL_AFTER|AXIS_SPECIFIED)<<AXIS_X_SHIFT;
+
+    /** Place object in the vertical center of its container, not changing its
+     *  size. */
+    public static final int CENTER_VERTICAL = AXIS_SPECIFIED<<AXIS_Y_SHIFT;
+    /** Grow the vertical size of the object if needed so it completely fills
+     *  its container. */
+    public static final int FILL_VERTICAL = TOP|BOTTOM;
+
+    /** Place object in the horizontal center of its container, not changing its
+     *  size. */
+    public static final int CENTER_HORIZONTAL = AXIS_SPECIFIED<<AXIS_X_SHIFT;
+    /** Grow the horizontal size of the object if needed so it completely fills
+     *  its container. */
+    public static final int FILL_HORIZONTAL = LEFT|RIGHT;
+
+    /** Place the object in the center of its container in both the vertical
+     *  and horizontal axis, not changing its size. */
+    public static final int CENTER = CENTER_VERTICAL|CENTER_HORIZONTAL;
+
+    /** Grow the horizontal and vertical size of the object if needed so it
+     *  completely fills its container. */
+    public static final int FILL = FILL_VERTICAL|FILL_HORIZONTAL;
+
+    /** Flag to clip the edges of the object to its container along the
+     *  vertical axis. */
+    public static final int CLIP_VERTICAL = AXIS_CLIP<<AXIS_Y_SHIFT;
+    
+    /** Flag to clip the edges of the object to its container along the
+     *  horizontal axis. */
+    public static final int CLIP_HORIZONTAL = AXIS_CLIP<<AXIS_X_SHIFT;
+
+    /** Raw bit controlling whether the layout direction is relative or not (START/END instead of
+     * absolute LEFT/RIGHT).
+     */
+    public static final int RELATIVE_LAYOUT_DIRECTION = 0x00800000;
+
+    /**
+     * Binary mask to get the absolute horizontal gravity of a gravity.
+     */
+    public static final int HORIZONTAL_GRAVITY_MASK = (AXIS_SPECIFIED |
+            AXIS_PULL_BEFORE | AXIS_PULL_AFTER) << AXIS_X_SHIFT;
+    /**
+     * Binary mask to get the vertical gravity of a gravity.
+     */
+    public static final int VERTICAL_GRAVITY_MASK = (AXIS_SPECIFIED |
+            AXIS_PULL_BEFORE | AXIS_PULL_AFTER) << AXIS_Y_SHIFT;
+
+    /** Special constant to enable clipping to an overall display along the
+     *  vertical dimension.  This is not applied by default by
+     *  {@link #apply(int, int, int, Rect, int, int, Rect)}; you must do so
+     *  yourself by calling {@link #applyDisplay}.
+     */
+    public static final int DISPLAY_CLIP_VERTICAL = 0x10000000;
+    
+    /** Special constant to enable clipping to an overall display along the
+     *  horizontal dimension.  This is not applied by default by
+     *  {@link #apply(int, int, int, Rect, int, int, Rect)}; you must do so
+     *  yourself by calling {@link #applyDisplay}.
+     */
+    public static final int DISPLAY_CLIP_HORIZONTAL = 0x01000000;
+    
+    /** Push object to x-axis position at the start of its container, not changing its size. */
+    public static final int START = RELATIVE_LAYOUT_DIRECTION | LEFT;
+
+    /** Push object to x-axis position at the end of its container, not changing its size. */
+    public static final int END = RELATIVE_LAYOUT_DIRECTION | RIGHT;
+
+    /**
+     * Binary mask for the horizontal gravity and script specific direction bit.
+     */
+    public static final int RELATIVE_HORIZONTAL_GRAVITY_MASK = START | END;
+
+
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, value = {
+        Gravity.FILL,
+        Gravity.FILL_HORIZONTAL,
+        Gravity.FILL_VERTICAL,
+        Gravity.START,
+        Gravity.END,
+        Gravity.LEFT,
+        Gravity.RIGHT,
+        Gravity.TOP,
+        Gravity.BOTTOM,
+        Gravity.CENTER,
+        Gravity.CENTER_HORIZONTAL,
+        Gravity.CENTER_VERTICAL,
+        Gravity.DISPLAY_CLIP_HORIZONTAL,
+        Gravity.DISPLAY_CLIP_VERTICAL,
+        Gravity.CLIP_HORIZONTAL,
+        Gravity.CLIP_VERTICAL,
+        Gravity.NO_GRAVITY
+    })
+    public @interface GravityFlags {}
+
+    /**
+     * Apply a gravity constant to an object. This supposes that the layout direction is LTR.
+     * 
+     * @param gravity The desired placement of the object, as defined by the
+     *                constants in this class.
+     * @param w The horizontal size of the object.
+     * @param h The vertical size of the object.
+     * @param container The frame of the containing space, in which the object
+     *                  will be placed.  Should be large enough to contain the
+     *                  width and height of the object.
+     * @param outRect Receives the computed frame of the object in its
+     *                container.
+     */
+    public static void apply(int gravity, int w, int h, Rect container, Rect outRect) {
+        apply(gravity, w, h, container, 0, 0, outRect);
+    }
+
+    /**
+     * Apply a gravity constant to an object and take care if layout direction is RTL or not.
+     *
+     * @param gravity The desired placement of the object, as defined by the
+     *                constants in this class.
+     * @param w The horizontal size of the object.
+     * @param h The vertical size of the object.
+     * @param container The frame of the containing space, in which the object
+     *                  will be placed.  Should be large enough to contain the
+     *                  width and height of the object.
+     * @param outRect Receives the computed frame of the object in its
+     *                container.
+     * @param layoutDirection The layout direction.
+     *
+     * @see View#LAYOUT_DIRECTION_LTR
+     * @see View#LAYOUT_DIRECTION_RTL
+     */
+    public static void apply(int gravity, int w, int h, Rect container,
+            Rect outRect, int layoutDirection) {
+        int absGravity = getAbsoluteGravity(gravity, layoutDirection);
+        apply(absGravity, w, h, container, 0, 0, outRect);
+    }
+
+    /**
+     * Apply a gravity constant to an object.
+     * 
+     * @param gravity The desired placement of the object, as defined by the
+     *                constants in this class.
+     * @param w The horizontal size of the object.
+     * @param h The vertical size of the object.
+     * @param container The frame of the containing space, in which the object
+     *                  will be placed.  Should be large enough to contain the
+     *                  width and height of the object.
+     * @param xAdj Offset to apply to the X axis.  If gravity is LEFT this
+     *             pushes it to the right; if gravity is RIGHT it pushes it to
+     *             the left; if gravity is CENTER_HORIZONTAL it pushes it to the
+     *             right or left; otherwise it is ignored.
+     * @param yAdj Offset to apply to the Y axis.  If gravity is TOP this pushes
+     *             it down; if gravity is BOTTOM it pushes it up; if gravity is
+     *             CENTER_VERTICAL it pushes it down or up; otherwise it is
+     *             ignored.
+     * @param outRect Receives the computed frame of the object in its
+     *                container.
+     */
+    public static void apply(int gravity, int w, int h, Rect container,
+            int xAdj, int yAdj, Rect outRect) {
+        switch (gravity&((AXIS_PULL_BEFORE|AXIS_PULL_AFTER)<<AXIS_X_SHIFT)) {
+            case 0:
+                outRect.left = container.left
+                        + ((container.right - container.left - w)/2) + xAdj;
+                outRect.right = outRect.left + w;
+                if ((gravity&(AXIS_CLIP<<AXIS_X_SHIFT))
+                        == (AXIS_CLIP<<AXIS_X_SHIFT)) {
+                    if (outRect.left < container.left) {
+                        outRect.left = container.left;
+                    }
+                    if (outRect.right > container.right) {
+                        outRect.right = container.right;
+                    }
+                }
+                break;
+            case AXIS_PULL_BEFORE<<AXIS_X_SHIFT:
+                outRect.left = container.left + xAdj;
+                outRect.right = outRect.left + w;
+                if ((gravity&(AXIS_CLIP<<AXIS_X_SHIFT))
+                        == (AXIS_CLIP<<AXIS_X_SHIFT)) {
+                    if (outRect.right > container.right) {
+                        outRect.right = container.right;
+                    }
+                }
+                break;
+            case AXIS_PULL_AFTER<<AXIS_X_SHIFT:
+                outRect.right = container.right - xAdj;
+                outRect.left = outRect.right - w;
+                if ((gravity&(AXIS_CLIP<<AXIS_X_SHIFT))
+                        == (AXIS_CLIP<<AXIS_X_SHIFT)) {
+                    if (outRect.left < container.left) {
+                        outRect.left = container.left;
+                    }
+                }
+                break;
+            default:
+                outRect.left = container.left + xAdj;
+                outRect.right = container.right + xAdj;
+                break;
+        }
+        
+        switch (gravity&((AXIS_PULL_BEFORE|AXIS_PULL_AFTER)<<AXIS_Y_SHIFT)) {
+            case 0:
+                outRect.top = container.top
+                        + ((container.bottom - container.top - h)/2) + yAdj;
+                outRect.bottom = outRect.top + h;
+                if ((gravity&(AXIS_CLIP<<AXIS_Y_SHIFT))
+                        == (AXIS_CLIP<<AXIS_Y_SHIFT)) {
+                    if (outRect.top < container.top) {
+                        outRect.top = container.top;
+                    }
+                    if (outRect.bottom > container.bottom) {
+                        outRect.bottom = container.bottom;
+                    }
+                }
+                break;
+            case AXIS_PULL_BEFORE<<AXIS_Y_SHIFT:
+                outRect.top = container.top + yAdj;
+                outRect.bottom = outRect.top + h;
+                if ((gravity&(AXIS_CLIP<<AXIS_Y_SHIFT))
+                        == (AXIS_CLIP<<AXIS_Y_SHIFT)) {
+                    if (outRect.bottom > container.bottom) {
+                        outRect.bottom = container.bottom;
+                    }
+                }
+                break;
+            case AXIS_PULL_AFTER<<AXIS_Y_SHIFT:
+                outRect.bottom = container.bottom - yAdj;
+                outRect.top = outRect.bottom - h;
+                if ((gravity&(AXIS_CLIP<<AXIS_Y_SHIFT))
+                        == (AXIS_CLIP<<AXIS_Y_SHIFT)) {
+                    if (outRect.top < container.top) {
+                        outRect.top = container.top;
+                    }
+                }
+                break;
+            default:
+                outRect.top = container.top + yAdj;
+                outRect.bottom = container.bottom + yAdj;
+                break;
+        }
+    }
+
+    /**
+     * Apply a gravity constant to an object.
+     *
+     * @param gravity The desired placement of the object, as defined by the
+     *                constants in this class.
+     * @param w The horizontal size of the object.
+     * @param h The vertical size of the object.
+     * @param container The frame of the containing space, in which the object
+     *                  will be placed.  Should be large enough to contain the
+     *                  width and height of the object.
+     * @param xAdj Offset to apply to the X axis.  If gravity is LEFT this
+     *             pushes it to the right; if gravity is RIGHT it pushes it to
+     *             the left; if gravity is CENTER_HORIZONTAL it pushes it to the
+     *             right or left; otherwise it is ignored.
+     * @param yAdj Offset to apply to the Y axis.  If gravity is TOP this pushes
+     *             it down; if gravity is BOTTOM it pushes it up; if gravity is
+     *             CENTER_VERTICAL it pushes it down or up; otherwise it is
+     *             ignored.
+     * @param outRect Receives the computed frame of the object in its
+     *                container.
+     * @param layoutDirection The layout direction.
+     *
+     * @see View#LAYOUT_DIRECTION_LTR
+     * @see View#LAYOUT_DIRECTION_RTL
+     */
+    public static void apply(int gravity, int w, int h, Rect container,
+                             int xAdj, int yAdj, Rect outRect, int layoutDirection) {
+        int absGravity = getAbsoluteGravity(gravity, layoutDirection);
+        apply(absGravity, w, h, container, xAdj, yAdj, outRect);
+    }
+
+    /**
+     * Apply additional gravity behavior based on the overall "display" that an
+     * object exists in.  This can be used after
+     * {@link #apply(int, int, int, Rect, int, int, Rect)} to place the object
+     * within a visible display.  By default this moves or clips the object
+     * to be visible in the display; the gravity flags
+     * {@link #DISPLAY_CLIP_HORIZONTAL} and {@link #DISPLAY_CLIP_VERTICAL}
+     * can be used to change this behavior.
+     * 
+     * @param gravity Gravity constants to modify the placement within the
+     * display.
+     * @param display The rectangle of the display in which the object is
+     * being placed.
+     * @param inoutObj Supplies the current object position; returns with it
+     * modified if needed to fit in the display.
+     */
+    public static void applyDisplay(int gravity, Rect display, Rect inoutObj) {
+        if ((gravity&DISPLAY_CLIP_VERTICAL) != 0) {
+            if (inoutObj.top < display.top) inoutObj.top = display.top;
+            if (inoutObj.bottom > display.bottom) inoutObj.bottom = display.bottom;
+        } else {
+            int off = 0;
+            if (inoutObj.top < display.top) off = display.top-inoutObj.top;
+            else if (inoutObj.bottom > display.bottom) off = display.bottom-inoutObj.bottom;
+            if (off != 0) {
+                if (inoutObj.height() > (display.bottom-display.top)) {
+                    inoutObj.top = display.top;
+                    inoutObj.bottom = display.bottom;
+                } else {
+                    inoutObj.top += off;
+                    inoutObj.bottom += off;
+                }
+            }
+        }
+        
+        if ((gravity&DISPLAY_CLIP_HORIZONTAL) != 0) {
+            if (inoutObj.left < display.left) inoutObj.left = display.left;
+            if (inoutObj.right > display.right) inoutObj.right = display.right;
+        } else {
+            int off = 0;
+            if (inoutObj.left < display.left) off = display.left-inoutObj.left;
+            else if (inoutObj.right > display.right) off = display.right-inoutObj.right;
+            if (off != 0) {
+                if (inoutObj.width() > (display.right-display.left)) {
+                    inoutObj.left = display.left;
+                    inoutObj.right = display.right;
+                } else {
+                    inoutObj.left += off;
+                    inoutObj.right += off;
+                }
+            }
+        }
+    }
+
+    /**
+     * Apply additional gravity behavior based on the overall "display" that an
+     * object exists in.  This can be used after
+     * {@link #apply(int, int, int, Rect, int, int, Rect)} to place the object
+     * within a visible display.  By default this moves or clips the object
+     * to be visible in the display; the gravity flags
+     * {@link #DISPLAY_CLIP_HORIZONTAL} and {@link #DISPLAY_CLIP_VERTICAL}
+     * can be used to change this behavior.
+     *
+     * @param gravity Gravity constants to modify the placement within the
+     * display.
+     * @param display The rectangle of the display in which the object is
+     * being placed.
+     * @param inoutObj Supplies the current object position; returns with it
+     * modified if needed to fit in the display.
+     * @param layoutDirection The layout direction.
+     *
+     * @see View#LAYOUT_DIRECTION_LTR
+     * @see View#LAYOUT_DIRECTION_RTL
+     */
+    public static void applyDisplay(int gravity, Rect display, Rect inoutObj, int layoutDirection) {
+        int absGravity = getAbsoluteGravity(gravity, layoutDirection);
+        applyDisplay(absGravity, display, inoutObj);
+    }
+
+    /**
+     * <p>Indicate whether the supplied gravity has a vertical pull.</p>
+     *
+     * @param gravity the gravity to check for vertical pull
+     * @return true if the supplied gravity has a vertical pull
+     */
+    public static boolean isVertical(int gravity) {
+        return gravity > 0 && (gravity & VERTICAL_GRAVITY_MASK) != 0;
+    }
+
+    /**
+     * <p>Indicate whether the supplied gravity has an horizontal pull.</p>
+     *
+     * @param gravity the gravity to check for horizontal pull
+     * @return true if the supplied gravity has an horizontal pull
+     */
+    public static boolean isHorizontal(int gravity) {
+        return gravity > 0 && (gravity & RELATIVE_HORIZONTAL_GRAVITY_MASK) != 0;
+    }
+
+    /**
+     * <p>Convert script specific gravity to absolute horizontal value.</p>
+     *
+     * if horizontal direction is LTR, then START will set LEFT and END will set RIGHT.
+     * if horizontal direction is RTL, then START will set RIGHT and END will set LEFT.
+     *
+     *
+     * @param gravity The gravity to convert to absolute (horizontal) values.
+     * @param layoutDirection The layout direction.
+     * @return gravity converted to absolute (horizontal) values.
+     */
+    public static int getAbsoluteGravity(int gravity, int layoutDirection) {
+        int result = gravity;
+        // If layout is script specific and gravity is horizontal relative (START or END)
+        if ((result & RELATIVE_LAYOUT_DIRECTION) > 0) {
+            if ((result & Gravity.START) == Gravity.START) {
+                // Remove the START bit
+                result &= ~START;
+                if (layoutDirection == View.LAYOUT_DIRECTION_RTL) {
+                    // Set the RIGHT bit
+                    result |= RIGHT;
+                } else {
+                    // Set the LEFT bit
+                    result |= LEFT;
+                }
+            } else if ((result & Gravity.END) == Gravity.END) {
+                // Remove the END bit
+                result &= ~END;
+                if (layoutDirection == View.LAYOUT_DIRECTION_RTL) {
+                    // Set the LEFT bit
+                    result |= LEFT;
+                } else {
+                    // Set the RIGHT bit
+                    result |= RIGHT;
+                }
+            }
+            // Don't need the script specific bit any more, so remove it as we are converting to
+            // absolute values (LEFT or RIGHT)
+            result &= ~RELATIVE_LAYOUT_DIRECTION;
+        }
+        return result;
+    }
+
+    /**
+     * @hide
+     */
+    public static String toString(int gravity) {
+        final StringBuilder result = new StringBuilder();
+        if ((gravity & FILL) == FILL) {
+            result.append("FILL").append(' ');
+        } else {
+            if ((gravity & FILL_VERTICAL) == FILL_VERTICAL) {
+                result.append("FILL_VERTICAL").append(' ');
+            } else {
+                if ((gravity & TOP) == TOP) {
+                    result.append("TOP").append(' ');
+                }
+                if ((gravity & BOTTOM) == BOTTOM) {
+                    result.append("BOTTOM").append(' ');
+                }
+            }
+            if ((gravity & FILL_HORIZONTAL) == FILL_HORIZONTAL) {
+                result.append("FILL_HORIZONTAL").append(' ');
+            } else {
+                if ((gravity & START) == START) {
+                    result.append("START").append(' ');
+                } else if ((gravity & LEFT) == LEFT) {
+                    result.append("LEFT").append(' ');
+                }
+                if ((gravity & END) == END) {
+                    result.append("END").append(' ');
+                } else if ((gravity & RIGHT) == RIGHT) {
+                    result.append("RIGHT").append(' ');
+                }
+            }
+        }
+        if ((gravity & CENTER) == CENTER) {
+            result.append("CENTER").append(' ');
+        } else {
+            if ((gravity & CENTER_VERTICAL) == CENTER_VERTICAL) {
+                result.append("CENTER_VERTICAL").append(' ');
+            }
+            if ((gravity & CENTER_HORIZONTAL) == CENTER_HORIZONTAL) {
+                result.append("CENTER_HORIZONTAL").append(' ');
+            }
+        }
+        if (result.length() == 0) {
+            result.append("NO GRAVITY").append(' ');
+        }
+        if ((gravity & DISPLAY_CLIP_VERTICAL) == DISPLAY_CLIP_VERTICAL) {
+            result.append("DISPLAY_CLIP_VERTICAL").append(' ');
+        }
+        if ((gravity & DISPLAY_CLIP_HORIZONTAL) == DISPLAY_CLIP_HORIZONTAL) {
+            result.append("DISPLAY_CLIP_HORIZONTAL").append(' ');
+        }
+        result.deleteCharAt(result.length() - 1);
+        return result.toString();
+    }
+}
diff --git a/android/view/HandlerActionQueue.java b/android/view/HandlerActionQueue.java
new file mode 100644
index 0000000..d016a74
--- /dev/null
+++ b/android/view/HandlerActionQueue.java
@@ -0,0 +1,125 @@
+/*
+ * 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.view;
+
+import android.os.Handler;
+
+import com.android.internal.util.GrowingArrayUtils;
+
+/**
+ * Class used to enqueue pending work from Views when no Handler is attached.
+ *
+ * @hide Exposed for test framework only.
+ */
+public class HandlerActionQueue {
+    private HandlerAction[] mActions;
+    private int mCount;
+
+    public void post(Runnable action) {
+        postDelayed(action, 0);
+    }
+
+    public void postDelayed(Runnable action, long delayMillis) {
+        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
+
+        synchronized (this) {
+            if (mActions == null) {
+                mActions = new HandlerAction[4];
+            }
+            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
+            mCount++;
+        }
+    }
+
+    public void removeCallbacks(Runnable action) {
+        synchronized (this) {
+            final int count = mCount;
+            int j = 0;
+
+            final HandlerAction[] actions = mActions;
+            for (int i = 0; i < count; i++) {
+                if (actions[i].matches(action)) {
+                    // Remove this action by overwriting it within
+                    // this loop or nulling it out later.
+                    continue;
+                }
+
+                if (j != i) {
+                    // At least one previous entry was removed, so
+                    // this one needs to move to the "new" list.
+                    actions[j] = actions[i];
+                }
+
+                j++;
+            }
+
+            // The "new" list only has j entries.
+            mCount = j;
+
+            // Null out any remaining entries.
+            for (; j < count; j++) {
+                actions[j] = null;
+            }
+        }
+    }
+
+    public void executeActions(Handler handler) {
+        synchronized (this) {
+            final HandlerAction[] actions = mActions;
+            for (int i = 0, count = mCount; i < count; i++) {
+                final HandlerAction handlerAction = actions[i];
+                handler.postDelayed(handlerAction.action, handlerAction.delay);
+            }
+
+            mActions = null;
+            mCount = 0;
+        }
+    }
+
+    public int size() {
+        return mCount;
+    }
+
+    public Runnable getRunnable(int index) {
+        if (index >= mCount) {
+            throw new IndexOutOfBoundsException();
+        }
+        return mActions[index].action;
+    }
+
+    public long getDelay(int index) {
+        if (index >= mCount) {
+            throw new IndexOutOfBoundsException();
+        }
+        return mActions[index].delay;
+    }
+
+    private static class HandlerAction {
+        final Runnable action;
+        final long delay;
+
+        public HandlerAction(Runnable action, long delay) {
+            this.action = action;
+            this.delay = delay;
+        }
+
+        public boolean matches(Runnable otherAction) {
+            return otherAction == null && action == null
+                    || action != null && action.equals(otherAction);
+        }
+    }
+}
diff --git a/android/view/HandlerActionQueue_Delegate.java b/android/view/HandlerActionQueue_Delegate.java
new file mode 100644
index 0000000..e580ed0
--- /dev/null
+++ b/android/view/HandlerActionQueue_Delegate.java
@@ -0,0 +1,37 @@
+/*
+ * 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.view;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of
+ * {@link HandlerActionQueue}
+ *
+ * Through the layoutlib_create tool, the original  methods of ViewRootImpl.RunQueue have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ */
+public class HandlerActionQueue_Delegate {
+
+    @LayoutlibDelegate
+    /*package*/ static void postDelayed(HandlerActionQueue thisQueue, Runnable action, long
+            delayMillis) {
+        // The actual HandlerActionQueue is never run and therefore never cleared. This method
+        // avoids runnables to be added to the RunQueue so they do not leak resources.
+    }
+}
diff --git a/android/view/HapticFeedbackConstants.java b/android/view/HapticFeedbackConstants.java
new file mode 100644
index 0000000..9f63500
--- /dev/null
+++ b/android/view/HapticFeedbackConstants.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+/**
+ * Constants to be used to perform haptic feedback effects via
+ * {@link View#performHapticFeedback(int)} 
+ */
+public class HapticFeedbackConstants {
+
+    private HapticFeedbackConstants() {}
+
+    /**
+     * The user has performed a long press on an object that is resulting
+     * in an action being performed.
+     */
+    public static final int LONG_PRESS = 0;
+
+    /**
+     * The user has pressed on a virtual on-screen key.
+     */
+    public static final int VIRTUAL_KEY = 1;
+
+    /**
+     * The user has pressed a soft keyboard key.
+     */
+    public static final int KEYBOARD_TAP = 3;
+
+    /**
+     * The user has pressed either an hour or minute tick of a Clock.
+     */
+    public static final int CLOCK_TICK = 4;
+
+    /**
+     * The user has pressed either a day or month or year date of a Calendar.
+     * @hide
+     */
+    public static final int CALENDAR_DATE = 5;
+
+    /**
+     * The user has performed a context click on an object.
+     */
+    public static final int CONTEXT_CLICK = 6;
+
+    /**
+     * The user has pressed a virtual or software keyboard key.
+     */
+    public static final int KEYBOARD_PRESS = KEYBOARD_TAP;
+
+    /**
+     * The user has released a virtual keyboard key.
+     */
+    public static final int KEYBOARD_RELEASE = 7;
+
+    /**
+     * The user has released a virtual key.
+     */
+    public static final int VIRTUAL_KEY_RELEASE = 8;
+
+    /**
+     * The user has performed a selection/insertion handle move on text field.
+     */
+    public static final int TEXT_HANDLE_MOVE = 9;
+
+    /**
+     * The user unlocked the device
+     * @hide
+     */
+    public static final int ENTRY_BUMP = 10;
+
+    /**
+     * The user has moved the dragged object within a droppable area.
+     * @hide
+     */
+    public static final int DRAG_CROSSING = 11;
+
+    /**
+     * The user has started a gesture (e.g. on the soft keyboard).
+     */
+    public static final int GESTURE_START = 12;
+
+    /**
+     * The user has finished a gesture (e.g. on the soft keyboard).
+     */
+    public static final int GESTURE_END = 13;
+
+    /**
+     * The user's squeeze crossed the gesture's initiation threshold.
+     * @hide
+     */
+    public static final int EDGE_SQUEEZE = 14;
+
+    /**
+     * The user's squeeze crossed the gesture's release threshold.
+     * @hide
+     */
+    public static final int EDGE_RELEASE = 15;
+
+    /**
+     * A haptic effect to signal the confirmation or successful completion of a user
+     * interaction.
+     */
+    public static final int CONFIRM = 16;
+
+    /**
+     * A haptic effect to signal the rejection or failure of a user interaction.
+     */
+    public static final int REJECT = 17;
+
+    /**
+     * The phone has booted with safe mode enabled.
+     * This is a private constant.  Feel free to renumber as desired.
+     * @hide
+     */
+    public static final int SAFE_MODE_ENABLED = 10001;
+
+    /**
+     * Invocation of the voice assistant via hardware button.
+     * @hide
+     */
+    public static final int ASSISTANT_BUTTON = 10002;
+
+    /**
+     * Flag for {@link View#performHapticFeedback(int, int)
+     * View.performHapticFeedback(int, int)}: Ignore the setting in the
+     * view for whether to perform haptic feedback, do it always.
+     */
+    public static final int FLAG_IGNORE_VIEW_SETTING = 0x0001;
+
+    /**
+     * Flag for {@link View#performHapticFeedback(int, int)
+     * View.performHapticFeedback(int, int)}: Ignore the global setting
+     * for whether to perform haptic feedback, do it always.
+     */
+    public static final int FLAG_IGNORE_GLOBAL_SETTING = 0x0002;
+}
diff --git a/android/view/IWindowManagerImpl.java b/android/view/IWindowManagerImpl.java
new file mode 100644
index 0000000..efa8a9a
--- /dev/null
+++ b/android/view/IWindowManagerImpl.java
@@ -0,0 +1,60 @@
+/*
+ * 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.view;
+
+import android.content.res.Configuration;
+import android.os.RemoteException;
+import android.util.DisplayMetrics;
+
+/**
+ * Basic implementation of {@link IWindowManager} so that {@link Display} (and
+ * {@link Display_Delegate}) can return a valid instance.
+ */
+public class IWindowManagerImpl extends IWindowManager.Default {
+
+    private final Configuration mConfig;
+    private final DisplayMetrics mMetrics;
+    private final int mRotation;
+    private final boolean mHasNavigationBar;
+
+    public IWindowManagerImpl(Configuration config, DisplayMetrics metrics, int rotation,
+            boolean hasNavigationBar) {
+        mConfig = config;
+        mMetrics = metrics;
+        mRotation = rotation;
+        mHasNavigationBar = hasNavigationBar;
+    }
+
+    // custom API.
+
+    public DisplayMetrics getMetrics() {
+        return mMetrics;
+    }
+
+    // ---- implementation of IWindowManager that we care about ----
+
+    @Override
+    public int getDefaultDisplayRotation() throws RemoteException {
+        return mRotation;
+    }
+
+    @Override
+    public boolean hasNavigationBar(int displayId) {
+        // TODO(multi-display): Change it once we need it per display.
+        return mHasNavigationBar;
+    }
+}
diff --git a/android/view/ImeFocusController.java b/android/view/ImeFocusController.java
new file mode 100644
index 0000000..d23a1e5
--- /dev/null
+++ b/android/view/ImeFocusController.java
@@ -0,0 +1,319 @@
+/*
+ * 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.view;
+
+import static android.view.ImeFocusControllerProto.HAS_IME_FOCUS;
+import static android.view.ImeFocusControllerProto.NEXT_SERVED_VIEW;
+import static android.view.ImeFocusControllerProto.SERVED_VIEW;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.UiThread;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+import android.view.inputmethod.InputMethodManager;
+
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.internal.inputmethod.StartInputFlags;
+import com.android.internal.inputmethod.StartInputReason;
+
+import java.util.Objects;
+
+/**
+ * Responsible for IME focus handling inside {@link ViewRootImpl}.
+ * @hide
+ */
+public final class ImeFocusController {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "ImeFocusController";
+
+    private final ViewRootImpl mViewRootImpl;
+    private boolean mHasImeFocus = false;
+    private View mServedView;
+    private View mNextServedView;
+    private InputMethodManagerDelegate mDelegate;
+
+    @UiThread
+    ImeFocusController(@NonNull ViewRootImpl viewRootImpl) {
+        mViewRootImpl = viewRootImpl;
+    }
+
+    @NonNull
+    private InputMethodManagerDelegate getImmDelegate() {
+        InputMethodManagerDelegate delegate = mDelegate;
+        if (delegate != null) {
+            return delegate;
+        }
+        delegate = mViewRootImpl.mContext.getSystemService(InputMethodManager.class).getDelegate();
+        mDelegate = delegate;
+        return delegate;
+    }
+
+    /** Called when the view root is moved to a different display. */
+    @UiThread
+    void onMovedToDisplay() {
+        // InputMethodManager managed its instances for different displays. So if the associated
+        // display is changed, the delegate also needs to be refreshed (by getImmDelegate).
+        // See the comment in {@link android.app.SystemServiceRegistry} for InputMethodManager
+        // and {@link android.view.inputmethod.InputMethodManager#forContext}.
+        mDelegate = null;
+    }
+
+    @UiThread
+    void onTraversal(boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute) {
+        final boolean hasImeFocus = updateImeFocusable(windowAttribute, false /* force */);
+        if (!hasWindowFocus || isInLocalFocusMode(windowAttribute)) {
+            return;
+        }
+        if (hasImeFocus == mHasImeFocus) {
+            return;
+        }
+        mHasImeFocus = hasImeFocus;
+        if (mHasImeFocus) {
+            onPreWindowFocus(true /* hasWindowFocus */, windowAttribute);
+            onPostWindowFocus(mViewRootImpl.mView.findFocus(), true /* hasWindowFocus */,
+                    windowAttribute);
+        }
+    }
+
+    @UiThread
+    void onPreWindowFocus(boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute) {
+        if (!mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
+            return;
+        }
+        if (hasWindowFocus) {
+            getImmDelegate().setCurrentRootView(mViewRootImpl);
+        }
+    }
+
+    @UiThread
+    boolean updateImeFocusable(WindowManager.LayoutParams windowAttribute, boolean force) {
+        final boolean hasImeFocus = WindowManager.LayoutParams.mayUseInputMethod(
+                windowAttribute.flags);
+        if (force) {
+            mHasImeFocus = hasImeFocus;
+        }
+        return hasImeFocus;
+    }
+
+    @UiThread
+    void onPostWindowFocus(View focusedView, boolean hasWindowFocus,
+            WindowManager.LayoutParams windowAttribute) {
+        if (!hasWindowFocus || !mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
+            return;
+        }
+        View viewForWindowFocus = focusedView != null ? focusedView : mViewRootImpl.mView;
+        if (DEBUG) {
+            Log.v(TAG, "onWindowFocus: " + viewForWindowFocus
+                    + " softInputMode=" + InputMethodDebug.softInputModeToString(
+                    windowAttribute.softInputMode));
+        }
+
+        boolean forceFocus = false;
+        final InputMethodManagerDelegate immDelegate = getImmDelegate();
+        if (immDelegate.isRestartOnNextWindowFocus(true /* reset */)) {
+            if (DEBUG) Log.v(TAG, "Restarting due to isRestartOnNextWindowFocus as true");
+            forceFocus = true;
+        }
+
+        // Update mNextServedView when focusedView changed.
+        onViewFocusChanged(viewForWindowFocus, true);
+
+        // Starting new input when the next focused view is same as served view but the currently
+        // active connection (if any) is not associated with it.
+        final boolean nextFocusIsServedView = mServedView == viewForWindowFocus;
+        if (nextFocusIsServedView && !immDelegate.hasActiveConnection(viewForWindowFocus)) {
+            forceFocus = true;
+        }
+
+        immDelegate.startInputAsyncOnWindowFocusGain(viewForWindowFocus,
+                windowAttribute.softInputMode, windowAttribute.flags, forceFocus);
+    }
+
+    public boolean checkFocus(boolean forceNewFocus, boolean startInput) {
+        final InputMethodManagerDelegate immDelegate = getImmDelegate();
+        if (!immDelegate.isCurrentRootView(mViewRootImpl)
+                || (mServedView == mNextServedView && !forceNewFocus)) {
+            return false;
+        }
+        if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView
+                + " next=" + mNextServedView
+                + " force=" + forceNewFocus
+                + " package="
+                + (mServedView != null ? mServedView.getContext().getPackageName() : "<none>"));
+
+        // Close the connection when no next served view coming.
+        if (mNextServedView == null) {
+            immDelegate.finishInput();
+            immDelegate.closeCurrentIme();
+            return false;
+        }
+        mServedView = mNextServedView;
+        immDelegate.finishComposingText();
+
+        if (startInput) {
+            immDelegate.startInput(StartInputReason.CHECK_FOCUS, null /* focusedView */,
+                    0 /* startInputFlags */, 0 /* softInputMode */, 0 /* windowFlags */);
+        }
+        return true;
+    }
+
+    @UiThread
+    void onViewFocusChanged(View view, boolean hasFocus) {
+        if (view == null || view.isTemporarilyDetached()) {
+            return;
+        }
+        if (!getImmDelegate().isCurrentRootView(view.getViewRootImpl())) {
+            return;
+        }
+        if (!view.hasImeFocus() || !view.hasWindowFocus()) {
+            return;
+        }
+        if (DEBUG) Log.d(TAG, "onViewFocusChanged, view=" + view + ", mServedView=" + mServedView);
+
+        // We don't need to track the next served view when the view lost focus here because:
+        // 1) The current view focus may be cleared temporary when in touch mode, closing input
+        //    at this moment isn't the right way.
+        // 2) We only care about the served view change when it focused, since changing input
+        //    connection when the focus target changed is reasonable.
+        // 3) Setting the next served view as null when no more served view should be handled in
+        //    other special events (e.g. view detached from window or the window dismissed).
+        if (hasFocus) {
+            mNextServedView = view;
+        }
+        mViewRootImpl.dispatchCheckFocus();
+    }
+
+    @UiThread
+    void onViewDetachedFromWindow(View view) {
+        if (!getImmDelegate().isCurrentRootView(view.getViewRootImpl())) {
+            return;
+        }
+        if (mServedView == view) {
+            mNextServedView = null;
+            mViewRootImpl.dispatchCheckFocus();
+        }
+    }
+
+    @UiThread
+    void onWindowDismissed() {
+        final InputMethodManagerDelegate immDelegate = getImmDelegate();
+        if (!immDelegate.isCurrentRootView(mViewRootImpl)) {
+            return;
+        }
+        if (mServedView != null) {
+            immDelegate.finishInput();
+        }
+        immDelegate.setCurrentRootView(null);
+        mHasImeFocus = false;
+    }
+
+    /**
+     * To handle the lifecycle of the input connection when the device interactivity state changed.
+     * (i.e. Calling IMS#onFinishInput when the device screen-off and Calling IMS#onStartInput
+     * when the device screen-on again).
+     */
+    @UiThread
+    public void onInteractiveChanged(boolean interactive) {
+        final InputMethodManagerDelegate immDelegate = getImmDelegate();
+        if (!immDelegate.isCurrentRootView(mViewRootImpl)) {
+            return;
+        }
+        if (interactive) {
+            final View focusedView = mViewRootImpl.mView.findFocus();
+            onViewFocusChanged(focusedView, focusedView != null);
+        } else {
+            mDelegate.finishInputAndReportToIme();
+        }
+    }
+
+    /**
+     * @param windowAttribute {@link WindowManager.LayoutParams} to be checked.
+     * @return Whether the window is in local focus mode or not.
+     */
+    @AnyThread
+    private static boolean isInLocalFocusMode(WindowManager.LayoutParams windowAttribute) {
+        return (windowAttribute.flags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0;
+    }
+
+    int onProcessImeInputStage(Object token, InputEvent event,
+            WindowManager.LayoutParams windowAttribute,
+            InputMethodManager.FinishedInputEventCallback callback) {
+        if (!mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
+            return InputMethodManager.DISPATCH_NOT_HANDLED;
+        }
+        final InputMethodManager imm =
+                mViewRootImpl.mContext.getSystemService(InputMethodManager.class);
+        if (imm == null) {
+            return InputMethodManager.DISPATCH_NOT_HANDLED;
+        }
+        return imm.dispatchInputEvent(event, token, callback, mViewRootImpl.mHandler);
+    }
+
+    /**
+     * A delegate implementing some basic {@link InputMethodManager} APIs.
+     * @hide
+     */
+    public interface InputMethodManagerDelegate {
+        boolean startInput(@StartInputReason int startInputReason, View focusedView,
+                @StartInputFlags int startInputFlags,
+                @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags);
+        void startInputAsyncOnWindowFocusGain(View rootView,
+                @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags,
+                boolean forceNewFocus);
+        void finishInput();
+        void finishInputAndReportToIme();
+        void closeCurrentIme();
+        void finishComposingText();
+        void setCurrentRootView(ViewRootImpl rootView);
+        boolean isCurrentRootView(ViewRootImpl rootView);
+        boolean isRestartOnNextWindowFocus(boolean reset);
+        boolean hasActiveConnection(View view);
+    }
+
+    public View getServedView() {
+        return mServedView;
+    }
+
+    public View getNextServedView() {
+        return mNextServedView;
+    }
+
+    public void setServedView(View view) {
+        mServedView = view;
+    }
+
+    public void setNextServedView(View view) {
+        mNextServedView = view;
+    }
+
+    /**
+     * Indicates whether the view's window has IME focused.
+     */
+    @UiThread
+    boolean hasImeFocus() {
+        return mHasImeFocus;
+    }
+
+    void dumpDebug(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(HAS_IME_FOCUS, mHasImeFocus);
+        proto.write(SERVED_VIEW, Objects.toString(mServedView));
+        proto.write(NEXT_SERVED_VIEW, Objects.toString(mNextServedView));
+        proto.end(token);
+    }
+}
diff --git a/android/view/ImeInsetsSourceConsumer.java b/android/view/ImeInsetsSourceConsumer.java
new file mode 100644
index 0000000..0686104
--- /dev/null
+++ b/android/view/ImeInsetsSourceConsumer.java
@@ -0,0 +1,161 @@
+/*
+ * 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.view;
+
+import static android.os.Trace.TRACE_TAG_VIEW;
+import static android.view.ImeInsetsSourceConsumerProto.INSETS_SOURCE_CONSUMER;
+import static android.view.ImeInsetsSourceConsumerProto.IS_REQUESTED_VISIBLE_AWAITING_CONTROL;
+import static android.view.InsetsController.AnimationType;
+import static android.view.InsetsState.ITYPE_IME;
+
+import android.annotation.Nullable;
+import android.inputmethodservice.InputMethodService;
+import android.os.IBinder;
+import android.os.Trace;
+import android.util.proto.ProtoOutputStream;
+import android.view.SurfaceControl.Transaction;
+import android.view.inputmethod.InputMethodManager;
+
+import java.util.function.Supplier;
+
+/**
+ * Controls the visibility and animations of IME window insets source.
+ * @hide
+ */
+public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
+
+    /**
+     * Tracks whether we have an outstanding request from the IME to show, but weren't able to
+     * execute it because we didn't have control yet.
+     */
+    private boolean mIsRequestedVisibleAwaitingControl;
+
+    public ImeInsetsSourceConsumer(
+            InsetsState state, Supplier<Transaction> transactionSupplier,
+            InsetsController controller) {
+        super(ITYPE_IME, state, transactionSupplier, controller);
+    }
+
+    @Override
+    public void onWindowFocusGained(boolean hasViewFocus) {
+        super.onWindowFocusGained(hasViewFocus);
+        getImm().registerImeConsumer(this);
+    }
+
+    @Override
+    public void onWindowFocusLost() {
+        super.onWindowFocusLost();
+        getImm().unregisterImeConsumer(this);
+        mIsRequestedVisibleAwaitingControl = false;
+    }
+
+    @Override
+    public void hide() {
+        super.hide();
+        mIsRequestedVisibleAwaitingControl = false;
+    }
+
+    @Override
+    void hide(boolean animationFinished, @AnimationType int animationType) {
+        hide();
+
+        if (animationFinished) {
+            // remove IME surface as IME has finished hide animation.
+            notifyHidden();
+            removeSurface();
+        }
+    }
+
+    /**
+     * Request {@link InputMethodManager} to show the IME.
+     * @return @see {@link android.view.InsetsSourceConsumer.ShowResult}.
+     */
+    @Override
+    public @ShowResult int requestShow(boolean fromIme) {
+        // TODO: ResultReceiver for IME.
+        // TODO: Set mShowOnNextImeRender to automatically show IME and guard it with a flag.
+        if (getControl() == null) {
+            // If control is null, schedule to show IME when control is available.
+            mIsRequestedVisibleAwaitingControl = true;
+        }
+        // If we had a request before to show from IME (tracked with mImeRequestedShow), reaching
+        // this code here means that we now got control, so we can start the animation immediately.
+        // If client window is trying to control IME and IME is already visible, it is immediate.
+        if (fromIme || mState.getSource(getType()).isVisible() && getControl() != null) {
+            return ShowResult.SHOW_IMMEDIATELY;
+        }
+
+        return getImm().requestImeShow(mController.getHost().getWindowToken())
+                ? ShowResult.IME_SHOW_DELAYED : ShowResult.IME_SHOW_FAILED;
+    }
+
+    /**
+     * Notify {@link InputMethodService} that IME window is hidden.
+     */
+    @Override
+    void notifyHidden() {
+        getImm().notifyImeHidden(mController.getHost().getWindowToken());
+        Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.hideRequestFromApi", 0);
+    }
+
+    @Override
+    public void removeSurface() {
+        final IBinder window = mController.getHost().getWindowToken();
+        if (window != null) {
+            getImm().removeImeSurface(window);
+        }
+    }
+
+    @Override
+    public void setControl(@Nullable InsetsSourceControl control, int[] showTypes,
+            int[] hideTypes) {
+        super.setControl(control, showTypes, hideTypes);
+        if (control == null && !mIsRequestedVisibleAwaitingControl) {
+            hide();
+            removeSurface();
+        }
+        if (control != null) {
+            mIsRequestedVisibleAwaitingControl = false;
+        }
+    }
+
+    @Override
+    protected boolean isRequestedVisibleAwaitingControl() {
+        return mIsRequestedVisibleAwaitingControl || isRequestedVisible();
+    }
+
+    @Override
+    public void onPerceptible(boolean perceptible) {
+        super.onPerceptible(perceptible);
+        final IBinder window = mController.getHost().getWindowToken();
+        if (window != null) {
+            getImm().reportPerceptible(window, perceptible);
+        }
+    }
+
+    @Override
+    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        super.dumpDebug(proto, INSETS_SOURCE_CONSUMER);
+        proto.write(IS_REQUESTED_VISIBLE_AWAITING_CONTROL, mIsRequestedVisibleAwaitingControl);
+        proto.end(token);
+    }
+
+    private InputMethodManager getImm() {
+        return mController.getHost().getInputMethodManager();
+    }
+}
diff --git a/android/view/InflateException.java b/android/view/InflateException.java
new file mode 100644
index 0000000..7b39d33
--- /dev/null
+++ b/android/view/InflateException.java
@@ -0,0 +1,40 @@
+/*
+ * 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.view;
+
+/**
+ * This exception is thrown by an inflater on error conditions.
+ */
+public class InflateException extends RuntimeException {
+
+    public InflateException() {
+        super();
+    }
+
+    public InflateException(String detailMessage, Throwable throwable) {
+        super(detailMessage, throwable);
+    }
+
+    public InflateException(String detailMessage) {
+        super(detailMessage);
+    }
+
+    public InflateException(Throwable throwable) {
+        super(throwable);
+    }
+
+}
diff --git a/android/view/InputApplicationHandle.java b/android/view/InputApplicationHandle.java
new file mode 100644
index 0000000..4abffde
--- /dev/null
+++ b/android/view/InputApplicationHandle.java
@@ -0,0 +1,65 @@
+/*
+ * 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.view;
+
+import android.annotation.NonNull;
+import android.os.IBinder;
+
+/**
+ * Functions as a handle for an application that can receive input.
+ * Enables the native input dispatcher to refer indirectly to the window manager's
+ * application window token.
+ * @hide
+ */
+public final class InputApplicationHandle {
+    // Pointer to the native input application handle.
+    // This field is lazily initialized via JNI.
+    @SuppressWarnings("unused")
+    private long ptr;
+
+    // Application name.
+    public final @NonNull String name;
+
+    // Dispatching timeout.
+    public final long dispatchingTimeoutMillis;
+
+    public final @NonNull IBinder token;
+
+    private native void nativeDispose();
+
+    public InputApplicationHandle(@NonNull IBinder token, @NonNull String name,
+            long dispatchingTimeoutMillis) {
+        this.token = token;
+        this.name = name;
+        this.dispatchingTimeoutMillis = dispatchingTimeoutMillis;
+    }
+
+    public InputApplicationHandle(InputApplicationHandle handle) {
+        this.token = handle.token;
+        this.dispatchingTimeoutMillis = handle.dispatchingTimeoutMillis;
+        this.name = handle.name;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            nativeDispose();
+        } finally {
+            super.finalize();
+        }
+    }
+}
diff --git a/android/view/InputChannel.java b/android/view/InputChannel.java
new file mode 100644
index 0000000..f76b1f8
--- /dev/null
+++ b/android/view/InputChannel.java
@@ -0,0 +1,211 @@
+/*
+ * 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.view;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Slog;
+
+import libcore.util.NativeAllocationRegistry;
+
+/**
+ * An input channel specifies the file descriptors used to send input events to
+ * a window in another process.  It is Parcelable so that it can be sent
+ * to the process that is to receive events.  Only one thread should be reading
+ * from an InputChannel at a time.
+ * @hide
+ */
+public final class InputChannel implements Parcelable {
+    private static final String TAG = "InputChannel";
+
+    private static final boolean DEBUG = false;
+    private static final NativeAllocationRegistry sRegistry =
+            NativeAllocationRegistry.createMalloced(
+                    InputChannel.class.getClassLoader(),
+                    nativeGetFinalizer());
+
+    @UnsupportedAppUsage
+    public static final @android.annotation.NonNull Parcelable.Creator<InputChannel> CREATOR
+            = new Parcelable.Creator<InputChannel>() {
+        public InputChannel createFromParcel(Parcel source) {
+            InputChannel result = new InputChannel();
+            result.readFromParcel(source);
+            return result;
+        }
+
+        public InputChannel[] newArray(int size) {
+            return new InputChannel[size];
+        }
+    };
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private long mPtr; // used by native code
+
+    private static native long[] nativeOpenInputChannelPair(String name);
+
+    private static native long nativeGetFinalizer();
+    private native void nativeDispose(long channel);
+    private native long nativeReadFromParcel(Parcel parcel);
+    private native void nativeWriteToParcel(Parcel parcel, long channel);
+    private native long nativeDup(long channel);
+    private native IBinder nativeGetToken(long channel);
+
+    private native String nativeGetName(long channel);
+
+    /**
+     * Creates an uninitialized input channel.
+     * It can be initialized by reading from a Parcel or by transferring the state of
+     * another input channel into this one.
+     */
+    @UnsupportedAppUsage
+    public InputChannel() {
+    }
+
+    /**
+     *  Set Native input channel object from native space.
+     *  @param nativeChannel the native channel object.
+     *
+     *  @hide
+     */
+    private void setNativeInputChannel(long nativeChannel) {
+        if (nativeChannel == 0) {
+            throw new IllegalArgumentException("Attempting to set native input channel to null.");
+        }
+        if (mPtr != 0) {
+            throw new IllegalArgumentException("Already has native input channel.");
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "setNativeInputChannel : " +  String.format("%x", nativeChannel));
+        }
+        sRegistry.registerNativeAllocation(this, nativeChannel);
+        mPtr = nativeChannel;
+    }
+
+    /**
+     * Creates a new input channel pair.  One channel should be provided to the input
+     * dispatcher and the other to the application's input queue.
+     * @param name The descriptive (non-unique) name of the channel pair.
+     * @return A pair of input channels.  The first channel is designated as the
+     * server channel and should be used to publish input events.  The second channel
+     * is designated as the client channel and should be used to consume input events.
+     */
+    public static InputChannel[] openInputChannelPair(String name) {
+        if (name == null) {
+            throw new IllegalArgumentException("name must not be null");
+        }
+
+        if (DEBUG) {
+            Slog.d(TAG, "Opening input channel pair '" + name + "'");
+        }
+        InputChannel channels[] = new InputChannel[2];
+        long[] nativeChannels = nativeOpenInputChannelPair(name);
+        for (int i = 0; i< 2; i++) {
+            channels[i] = new InputChannel();
+            channels[i].setNativeInputChannel(nativeChannels[i]);
+        }
+        return channels;
+    }
+
+    /**
+     * Gets the name of the input channel.
+     * @return The input channel name.
+     */
+    public String getName() {
+        String name = nativeGetName(mPtr);
+        return name != null ? name : "uninitialized";
+    }
+
+    /**
+     * Disposes the input channel.
+     * Explicitly releases the reference this object is holding on the input channel.
+     * When all references are released, the input channel will be closed.
+     */
+    public void dispose() {
+        nativeDispose(mPtr);
+    }
+
+    /**
+     * Release the Java objects hold over the native InputChannel. If other references
+     * still exist in native-land, then the channel may continue to exist.
+     */
+    public void release() {
+    }
+
+    /**
+     * Creates a copy of this instance to the outParameter. This is used to pass an input channel
+     * as an out parameter in a binder call.
+     * @param other The other input channel instance.
+     */
+    public void copyTo(InputChannel outParameter) {
+        if (outParameter == null) {
+            throw new IllegalArgumentException("outParameter must not be null");
+        }
+        if (outParameter.mPtr != 0) {
+            throw new IllegalArgumentException("Other object already has a native input channel.");
+        }
+        outParameter.setNativeInputChannel(nativeDup(mPtr));
+    }
+
+    /**
+     * Duplicates the input channel.
+     */
+    public InputChannel dup() {
+        InputChannel target = new InputChannel();
+        target.setNativeInputChannel(nativeDup(mPtr));
+        return target;
+    }
+
+    @Override
+    public int describeContents() {
+        return Parcelable.CONTENTS_FILE_DESCRIPTOR;
+    }
+
+    public void readFromParcel(Parcel in) {
+        if (in == null) {
+            throw new IllegalArgumentException("in must not be null");
+        }
+        long nativeIn = nativeReadFromParcel(in);
+        if (nativeIn != 0) {
+            setNativeInputChannel(nativeIn);
+        }
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        if (out == null) {
+            throw new IllegalArgumentException("out must not be null");
+        }
+
+        nativeWriteToParcel(out, mPtr);
+
+        if ((flags & PARCELABLE_WRITE_RETURN_VALUE) != 0) {
+            dispose();
+        }
+    }
+
+    @Override
+    public String toString() {
+        return getName();
+    }
+
+    public IBinder getToken() {
+        return nativeGetToken(mPtr);
+    }
+}
diff --git a/android/view/InputDevice.java b/android/view/InputDevice.java
new file mode 100644
index 0000000..4f1354d
--- /dev/null
+++ b/android/view/InputDevice.java
@@ -0,0 +1,1189 @@
+/*
+ * 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.view;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.hardware.BatteryState;
+import android.hardware.SensorManager;
+import android.hardware.input.InputDeviceIdentifier;
+import android.hardware.input.InputManager;
+import android.hardware.lights.LightsManager;
+import android.os.Build;
+import android.os.NullVibrator;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Vibrator;
+import android.os.VibratorManager;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Describes the capabilities of a particular input device.
+ * <p>
+ * Each input device may support multiple classes of input.  For example, a multi-function
+ * keyboard may compose the capabilities of a standard keyboard together with a track pad mouse
+ * or other pointing device.
+ * </p><p>
+ * Some input devices present multiple distinguishable sources of input.
+ * Applications can query the framework about the characteristics of each distinct source.
+ * </p><p>
+ * As a further wrinkle, different kinds of input sources uses different coordinate systems
+ * to describe motion events.  Refer to the comments on the input source constants for
+ * the appropriate interpretation.
+ * </p>
+ */
+public final class InputDevice implements Parcelable {
+    private final int mId;
+    private final int mGeneration;
+    private final int mControllerNumber;
+    private final String mName;
+    private final int mVendorId;
+    private final int mProductId;
+    private final String mDescriptor;
+    private final InputDeviceIdentifier mIdentifier;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    private final boolean mIsExternal;
+    private final int mSources;
+    private final int mKeyboardType;
+    private final KeyCharacterMap mKeyCharacterMap;
+    private final boolean mHasVibrator;
+    private final boolean mHasMicrophone;
+    private final boolean mHasButtonUnderPad;
+    private final boolean mHasSensor;
+    private final boolean mHasBattery;
+    private final ArrayList<MotionRange> mMotionRanges = new ArrayList<MotionRange>();
+
+    @GuardedBy("mMotionRanges")
+    private Vibrator mVibrator; // guarded by mMotionRanges during initialization
+
+    @GuardedBy("mMotionRanges")
+    private VibratorManager mVibratorManager;
+
+    @GuardedBy("mMotionRanges")
+    private SensorManager mSensorManager;
+
+    @GuardedBy("mMotionRanges")
+    private BatteryState mBatteryState;
+
+    @GuardedBy("mMotionRanges")
+    private LightsManager mLightsManager;
+
+    /**
+     * A mask for input source classes.
+     *
+     * Each distinct input source constant has one or more input source class bits set to
+     * specify the desired interpretation for its input events.
+     */
+    public static final int SOURCE_CLASS_MASK = 0x000000ff;
+
+    /**
+     * The input source has no class.
+     *
+     * It is up to the application to determine how to handle the device based on the device type.
+     */
+    public static final int SOURCE_CLASS_NONE = 0x00000000;
+
+    /**
+     * The input source has buttons or keys.
+     * Examples: {@link #SOURCE_KEYBOARD}, {@link #SOURCE_DPAD}.
+     *
+     * A {@link KeyEvent} should be interpreted as a button or key press.
+     *
+     * Use {@link #getKeyCharacterMap} to query the device's button and key mappings.
+     */
+    public static final int SOURCE_CLASS_BUTTON = 0x00000001;
+
+    /**
+     * The input source is a pointing device associated with a display.
+     * Examples: {@link #SOURCE_TOUCHSCREEN}, {@link #SOURCE_MOUSE}.
+     *
+     * A {@link MotionEvent} should be interpreted as absolute coordinates in
+     * display units according to the {@link View} hierarchy.  Pointer down/up indicated when
+     * the finger touches the display or when the selection button is pressed/released.
+     *
+     * Use {@link #getMotionRange} to query the range of the pointing device.  Some devices permit
+     * touches outside the display area so the effective range may be somewhat smaller or larger
+     * than the actual display size.
+     */
+    public static final int SOURCE_CLASS_POINTER = 0x00000002;
+
+    /**
+     * The input source is a trackball navigation device.
+     * Examples: {@link #SOURCE_TRACKBALL}.
+     *
+     * A {@link MotionEvent} should be interpreted as relative movements in device-specific
+     * units used for navigation purposes.  Pointer down/up indicates when the selection button
+     * is pressed/released.
+     *
+     * Use {@link #getMotionRange} to query the range of motion.
+     */
+    public static final int SOURCE_CLASS_TRACKBALL = 0x00000004;
+
+    /**
+     * The input source is an absolute positioning device not associated with a display
+     * (unlike {@link #SOURCE_CLASS_POINTER}).
+     *
+     * A {@link MotionEvent} should be interpreted as absolute coordinates in
+     * device-specific surface units.
+     *
+     * Use {@link #getMotionRange} to query the range of positions.
+     */
+    public static final int SOURCE_CLASS_POSITION = 0x00000008;
+
+    /**
+     * The input source is a joystick.
+     *
+     * A {@link MotionEvent} should be interpreted as absolute joystick movements.
+     *
+     * Use {@link #getMotionRange} to query the range of positions.
+     */
+    public static final int SOURCE_CLASS_JOYSTICK = 0x00000010;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "SOURCE_CLASS_" }, value = {
+            SOURCE_CLASS_NONE,
+            SOURCE_CLASS_BUTTON,
+            SOURCE_CLASS_POINTER,
+            SOURCE_CLASS_TRACKBALL,
+            SOURCE_CLASS_POSITION,
+            SOURCE_CLASS_JOYSTICK
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface InputSourceClass {}
+
+    /**
+     * The input source is unknown.
+     */
+    public static final int SOURCE_UNKNOWN = 0x00000000;
+
+    /**
+     * The input source is a keyboard.
+     *
+     * This source indicates pretty much anything that has buttons.  Use
+     * {@link #getKeyboardType()} to determine whether the keyboard has alphabetic keys
+     * and can be used to enter text.
+     *
+     * @see #SOURCE_CLASS_BUTTON
+     */
+    public static final int SOURCE_KEYBOARD = 0x00000100 | SOURCE_CLASS_BUTTON;
+
+    /**
+     * The input source is a DPad.
+     *
+     * @see #SOURCE_CLASS_BUTTON
+     */
+    public static final int SOURCE_DPAD = 0x00000200 | SOURCE_CLASS_BUTTON;
+
+    /**
+     * The input source is a game pad.
+     * (It may also be a {@link #SOURCE_JOYSTICK}).
+     *
+     * @see #SOURCE_CLASS_BUTTON
+     */
+    public static final int SOURCE_GAMEPAD = 0x00000400 | SOURCE_CLASS_BUTTON;
+
+    /**
+     * The input source is a touch screen pointing device.
+     *
+     * @see #SOURCE_CLASS_POINTER
+     */
+    public static final int SOURCE_TOUCHSCREEN = 0x00001000 | SOURCE_CLASS_POINTER;
+
+    /**
+     * The input source is a mouse pointing device.
+     * This code is also used for other mouse-like pointing devices such as trackpads
+     * and trackpoints.
+     *
+     * @see #SOURCE_CLASS_POINTER
+     */
+    public static final int SOURCE_MOUSE = 0x00002000 | SOURCE_CLASS_POINTER;
+
+    /**
+     * The input source is a stylus pointing device.
+     * <p>
+     * Note that this bit merely indicates that an input device is capable of obtaining
+     * input from a stylus.  To determine whether a given touch event was produced
+     * by a stylus, examine the tool type returned by {@link MotionEvent#getToolType(int)}
+     * for each individual pointer.
+     * </p><p>
+     * A single touch event may multiple pointers with different tool types,
+     * such as an event that has one pointer with tool type
+     * {@link MotionEvent#TOOL_TYPE_FINGER} and another pointer with tool type
+     * {@link MotionEvent#TOOL_TYPE_STYLUS}.  So it is important to examine
+     * the tool type of each pointer, regardless of the source reported
+     * by {@link MotionEvent#getSource()}.
+     * </p>
+     *
+     * @see #SOURCE_CLASS_POINTER
+     */
+    public static final int SOURCE_STYLUS = 0x00004000 | SOURCE_CLASS_POINTER;
+
+    /**
+     * The input device is a Bluetooth stylus.
+     * <p>
+     * Note that this bit merely indicates that an input device is capable of
+     * obtaining input from a Bluetooth stylus.  To determine whether a given
+     * touch event was produced by a stylus, examine the tool type returned by
+     * {@link MotionEvent#getToolType(int)} for each individual pointer.
+     * </p><p>
+     * A single touch event may multiple pointers with different tool types,
+     * such as an event that has one pointer with tool type
+     * {@link MotionEvent#TOOL_TYPE_FINGER} and another pointer with tool type
+     * {@link MotionEvent#TOOL_TYPE_STYLUS}.  So it is important to examine
+     * the tool type of each pointer, regardless of the source reported
+     * by {@link MotionEvent#getSource()}.
+     * </p><p>
+     * A bluetooth stylus generally receives its pressure and button state
+     * information from the stylus itself, and derives the rest from another
+     * source. For example, a Bluetooth stylus used in conjunction with a
+     * touchscreen would derive its contact position and pointer size from the
+     * touchscreen and may not be any more accurate than other tools such as
+     * fingers.
+     * </p>
+     *
+     * @see #SOURCE_STYLUS
+     * @see #SOURCE_CLASS_POINTER
+     */
+    public static final int SOURCE_BLUETOOTH_STYLUS =
+            0x00008000 | SOURCE_STYLUS;
+
+    /**
+     * The input source is a trackball.
+     *
+     * @see #SOURCE_CLASS_TRACKBALL
+     */
+    public static final int SOURCE_TRACKBALL = 0x00010000 | SOURCE_CLASS_TRACKBALL;
+
+    /**
+     * The input source is a mouse device whose relative motions should be interpreted as
+     * navigation events.
+     *
+     * @see #SOURCE_CLASS_TRACKBALL
+     */
+    public static final int SOURCE_MOUSE_RELATIVE = 0x00020000 | SOURCE_CLASS_TRACKBALL;
+
+    /**
+     * The input source is a touch pad or digitizer tablet that is not
+     * associated with a display (unlike {@link #SOURCE_TOUCHSCREEN}).
+     *
+     * @see #SOURCE_CLASS_POSITION
+     */
+    public static final int SOURCE_TOUCHPAD = 0x00100000 | SOURCE_CLASS_POSITION;
+
+    /**
+     * The input source is a touch device whose motions should be interpreted as navigation events.
+     *
+     * For example, an upward swipe should be as an upward focus traversal in the same manner as
+     * pressing up on a D-Pad would be. Swipes to the left, right and down should be treated in a
+     * similar manner.
+     *
+     * @see #SOURCE_CLASS_NONE
+     */
+    public static final int SOURCE_TOUCH_NAVIGATION = 0x00200000 | SOURCE_CLASS_NONE;
+
+    /**
+     * The input source is a rotating encoder device whose motions should be interpreted as akin to
+     * those of a scroll wheel.
+     *
+     * @see #SOURCE_CLASS_NONE
+     */
+    public static final int SOURCE_ROTARY_ENCODER = 0x00400000 | SOURCE_CLASS_NONE;
+
+    /**
+     * The input source is a joystick.
+     * (It may also be a {@link #SOURCE_GAMEPAD}).
+     *
+     * @see #SOURCE_CLASS_JOYSTICK
+     */
+    public static final int SOURCE_JOYSTICK = 0x01000000 | SOURCE_CLASS_JOYSTICK;
+
+    /**
+     * The input source is a device connected through HDMI-based bus.
+     *
+     * The key comes in through HDMI-CEC or MHL signal line, and is treated as if it were
+     * generated by a locally connected DPAD or keyboard.
+     */
+    public static final int SOURCE_HDMI = 0x02000000 | SOURCE_CLASS_BUTTON;
+
+    /**
+     * The input source is a sensor associated with the input device.
+     *
+     * @see #SOURCE_CLASS_NONE
+     */
+    public static final int SOURCE_SENSOR = 0x04000000 | SOURCE_CLASS_NONE;
+
+    /**
+     * A special input source constant that is used when filtering input devices
+     * to match devices that provide any type of input source.
+     */
+    public static final int SOURCE_ANY = 0xffffff00;
+
+    /**
+     * Constant for retrieving the range of values for {@link MotionEvent#AXIS_X}.
+     *
+     * @see #getMotionRange
+     * @deprecated Use {@link MotionEvent#AXIS_X} instead.
+     */
+    @Deprecated
+    public static final int MOTION_RANGE_X = MotionEvent.AXIS_X;
+
+    /**
+     * Constant for retrieving the range of values for {@link MotionEvent#AXIS_Y}.
+     *
+     * @see #getMotionRange
+     * @deprecated Use {@link MotionEvent#AXIS_Y} instead.
+     */
+    @Deprecated
+    public static final int MOTION_RANGE_Y = MotionEvent.AXIS_Y;
+
+    /**
+     * Constant for retrieving the range of values for {@link MotionEvent#AXIS_PRESSURE}.
+     *
+     * @see #getMotionRange
+     * @deprecated Use {@link MotionEvent#AXIS_PRESSURE} instead.
+     */
+    @Deprecated
+    public static final int MOTION_RANGE_PRESSURE = MotionEvent.AXIS_PRESSURE;
+
+    /**
+     * Constant for retrieving the range of values for {@link MotionEvent#AXIS_SIZE}.
+     *
+     * @see #getMotionRange
+     * @deprecated Use {@link MotionEvent#AXIS_SIZE} instead.
+     */
+    @Deprecated
+    public static final int MOTION_RANGE_SIZE = MotionEvent.AXIS_SIZE;
+
+    /**
+     * Constant for retrieving the range of values for {@link MotionEvent#AXIS_TOUCH_MAJOR}.
+     *
+     * @see #getMotionRange
+     * @deprecated Use {@link MotionEvent#AXIS_TOUCH_MAJOR} instead.
+     */
+    @Deprecated
+    public static final int MOTION_RANGE_TOUCH_MAJOR = MotionEvent.AXIS_TOUCH_MAJOR;
+
+    /**
+     * Constant for retrieving the range of values for {@link MotionEvent#AXIS_TOUCH_MINOR}.
+     *
+     * @see #getMotionRange
+     * @deprecated Use {@link MotionEvent#AXIS_TOUCH_MINOR} instead.
+     */
+    @Deprecated
+    public static final int MOTION_RANGE_TOUCH_MINOR = MotionEvent.AXIS_TOUCH_MINOR;
+
+    /**
+     * Constant for retrieving the range of values for {@link MotionEvent#AXIS_TOOL_MAJOR}.
+     *
+     * @see #getMotionRange
+     * @deprecated Use {@link MotionEvent#AXIS_TOOL_MAJOR} instead.
+     */
+    @Deprecated
+    public static final int MOTION_RANGE_TOOL_MAJOR = MotionEvent.AXIS_TOOL_MAJOR;
+
+    /**
+     * Constant for retrieving the range of values for {@link MotionEvent#AXIS_TOOL_MINOR}.
+     *
+     * @see #getMotionRange
+     * @deprecated Use {@link MotionEvent#AXIS_TOOL_MINOR} instead.
+     */
+    @Deprecated
+    public static final int MOTION_RANGE_TOOL_MINOR = MotionEvent.AXIS_TOOL_MINOR;
+
+    /**
+     * Constant for retrieving the range of values for {@link MotionEvent#AXIS_ORIENTATION}.
+     *
+     * @see #getMotionRange
+     * @deprecated Use {@link MotionEvent#AXIS_ORIENTATION} instead.
+     */
+    @Deprecated
+    public static final int MOTION_RANGE_ORIENTATION = MotionEvent.AXIS_ORIENTATION;
+
+    /**
+     * There is no keyboard.
+     */
+    public static final int KEYBOARD_TYPE_NONE = 0;
+
+    /**
+     * The keyboard is not fully alphabetic.  It may be a numeric keypad or an assortment
+     * of buttons that are not mapped as alphabetic keys suitable for text input.
+     */
+    public static final int KEYBOARD_TYPE_NON_ALPHABETIC = 1;
+
+    /**
+     * The keyboard supports a complement of alphabetic keys.
+     */
+    public static final int KEYBOARD_TYPE_ALPHABETIC = 2;
+
+    private static final int MAX_RANGES = 1000;
+
+    private static final int VIBRATOR_ID_ALL = -1;
+
+    public static final @android.annotation.NonNull Parcelable.Creator<InputDevice> CREATOR =
+            new Parcelable.Creator<InputDevice>() {
+        public InputDevice createFromParcel(Parcel in) {
+            return new InputDevice(in);
+        }
+        public InputDevice[] newArray(int size) {
+            return new InputDevice[size];
+        }
+    };
+
+    /**
+     * Called by native code
+     * @hide
+     */
+    @VisibleForTesting
+    public InputDevice(int id, int generation, int controllerNumber, String name, int vendorId,
+            int productId, String descriptor, boolean isExternal, int sources, int keyboardType,
+            KeyCharacterMap keyCharacterMap, boolean hasVibrator, boolean hasMicrophone,
+            boolean hasButtonUnderPad, boolean hasSensor, boolean hasBattery) {
+        mId = id;
+        mGeneration = generation;
+        mControllerNumber = controllerNumber;
+        mName = name;
+        mVendorId = vendorId;
+        mProductId = productId;
+        mDescriptor = descriptor;
+        mIsExternal = isExternal;
+        mSources = sources;
+        mKeyboardType = keyboardType;
+        mKeyCharacterMap = keyCharacterMap;
+        mHasVibrator = hasVibrator;
+        mHasMicrophone = hasMicrophone;
+        mHasButtonUnderPad = hasButtonUnderPad;
+        mHasSensor = hasSensor;
+        mHasBattery = hasBattery;
+        mIdentifier = new InputDeviceIdentifier(descriptor, vendorId, productId);
+    }
+
+    private InputDevice(Parcel in) {
+        mKeyCharacterMap = KeyCharacterMap.CREATOR.createFromParcel(in);
+        mId = in.readInt();
+        mGeneration = in.readInt();
+        mControllerNumber = in.readInt();
+        mName = in.readString();
+        mVendorId = in.readInt();
+        mProductId = in.readInt();
+        mDescriptor = in.readString();
+        mIsExternal = in.readInt() != 0;
+        mSources = in.readInt();
+        mKeyboardType = in.readInt();
+        mHasVibrator = in.readInt() != 0;
+        mHasMicrophone = in.readInt() != 0;
+        mHasButtonUnderPad = in.readInt() != 0;
+        mHasSensor = in.readInt() != 0;
+        mHasBattery = in.readInt() != 0;
+        mIdentifier = new InputDeviceIdentifier(mDescriptor, mVendorId, mProductId);
+
+        int numRanges = in.readInt();
+        if (numRanges > MAX_RANGES) {
+            numRanges = MAX_RANGES;
+        }
+
+        for (int i = 0; i < numRanges; i++) {
+            addMotionRange(in.readInt(), in.readInt(), in.readFloat(), in.readFloat(),
+                    in.readFloat(), in.readFloat(), in.readFloat());
+        }
+    }
+
+    /**
+     * Gets information about the input device with the specified id.
+     * @param id The device id.
+     * @return The input device or null if not found.
+     */
+    public static InputDevice getDevice(int id) {
+        return InputManager.getInstance().getInputDevice(id);
+    }
+
+    /**
+     * Gets the ids of all input devices in the system.
+     * @return The input device ids.
+     */
+    public static int[] getDeviceIds() {
+        return InputManager.getInstance().getInputDeviceIds();
+    }
+
+    /**
+     * Gets the input device id.
+     * <p>
+     * Each input device receives a unique id when it is first configured
+     * by the system.  The input device id may change when the system is restarted or if the
+     * input device is disconnected, reconnected or reconfigured at any time.
+     * If you require a stable identifier for a device that persists across
+     * boots and reconfigurations, use {@link #getDescriptor()}.
+     * </p>
+     *
+     * @return The input device id.
+     */
+    public int getId() {
+        return mId;
+    }
+
+    /**
+     * The controller number for a given input device.
+     * <p>
+     * Each gamepad or joystick is given a unique, positive controller number when initially
+     * configured by the system. This number may change due to events such as device disconnects /
+     * reconnects or user initiated reassignment. Any change in number will trigger an event that
+     * can be observed by registering an {@link InputManager.InputDeviceListener}.
+     * </p>
+     * <p>
+     * All input devices which are not gamepads or joysticks will be assigned a controller number
+     * of 0.
+     * </p>
+     *
+     * @return The controller number of the device.
+     */
+    public int getControllerNumber() {
+        return mControllerNumber;
+    }
+
+    /**
+     * The set of identifying information for type of input device. This
+     * information can be used by the system to configure appropriate settings
+     * for the device.
+     *
+     * @return The identifier object for this device
+     * @hide
+     */
+    public InputDeviceIdentifier getIdentifier() {
+        return mIdentifier;
+    }
+
+    /**
+     * Gets a generation number for this input device.
+     * The generation number is incremented whenever the device is reconfigured and its
+     * properties may have changed.
+     *
+     * @return The generation number.
+     *
+     * @hide
+     */
+    public int getGeneration() {
+        return mGeneration;
+    }
+
+    /**
+     * Gets the vendor id for the given device, if available.
+     * <p>
+     * A vendor id uniquely identifies the company who manufactured the device. A value of 0 will
+     * be assigned where a vendor id is not available.
+     * </p>
+     *
+     * @return The vendor id of a given device
+     */
+    public int getVendorId() {
+        return mVendorId;
+    }
+
+    /**
+     * Gets the product id for the given device, if available.
+     * <p>
+     * A product id uniquely identifies which product within the address space of a given vendor,
+     * identified by the device's vendor id. A value of 0 will be assigned where a product id is
+     * not available.
+     * </p>
+     *
+     * @return The product id of a given device
+     */
+    public int getProductId() {
+        return mProductId;
+    }
+
+    /**
+     * Gets the input device descriptor, which is a stable identifier for an input device.
+     * <p>
+     * An input device descriptor uniquely identifies an input device.  Its value
+     * is intended to be persistent across system restarts, and should not change even
+     * if the input device is disconnected, reconnected or reconfigured at any time.
+     * </p><p>
+     * It is possible for there to be multiple {@link InputDevice} instances that have the
+     * same input device descriptor.  This might happen in situations where a single
+     * human input device registers multiple {@link InputDevice} instances (HID collections)
+     * that describe separate features of the device, such as a keyboard that also
+     * has a trackpad.  Alternately, it may be that the input devices are simply
+     * indistinguishable, such as two keyboards made by the same manufacturer.
+     * </p><p>
+     * The input device descriptor returned by {@link #getDescriptor} should only be
+     * used when an application needs to remember settings associated with a particular
+     * input device.  For all other purposes when referring to a logical
+     * {@link InputDevice} instance at runtime use the id returned by {@link #getId()}.
+     * </p>
+     *
+     * @return The input device descriptor.
+     */
+    public String getDescriptor() {
+        return mDescriptor;
+    }
+
+    /**
+     * Returns true if the device is a virtual input device rather than a real one,
+     * such as the virtual keyboard (see {@link KeyCharacterMap#VIRTUAL_KEYBOARD}).
+     * <p>
+     * Virtual input devices are provided to implement system-level functionality
+     * and should not be seen or configured by users.
+     * </p>
+     *
+     * @return True if the device is virtual.
+     *
+     * @see KeyCharacterMap#VIRTUAL_KEYBOARD
+     */
+    public boolean isVirtual() {
+        return mId < 0;
+    }
+
+    /**
+     * Returns true if the device is external (connected to USB or Bluetooth or some other
+     * peripheral bus), otherwise it is built-in.
+     *
+     * @return True if the device is external.
+     */
+    public boolean isExternal() {
+        return mIsExternal;
+    }
+
+    /**
+     * Returns true if the device is a full keyboard.
+     *
+     * @return True if the device is a full keyboard.
+     *
+     * @hide
+     */
+    public boolean isFullKeyboard() {
+        return (mSources & SOURCE_KEYBOARD) == SOURCE_KEYBOARD
+                && mKeyboardType == KEYBOARD_TYPE_ALPHABETIC;
+    }
+
+    /**
+     * Gets the name of this input device.
+     * @return The input device name.
+     */
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * Gets the input sources supported by this input device as a combined bitfield.
+     * @return The supported input sources.
+     */
+    public int getSources() {
+        return mSources;
+    }
+
+    /**
+     * Determines whether the input device supports the given source or sources.
+     *
+     * @param source The input source or sources to check against. This can be a generic device
+     * type such as {@link InputDevice#SOURCE_MOUSE}, a more generic device class, such as
+     * {@link InputDevice#SOURCE_CLASS_POINTER}, or a combination of sources bitwise ORed together.
+     * @return Whether the device can produce all of the given sources.
+     */
+    public boolean supportsSource(int source) {
+        return (mSources & source) == source;
+    }
+
+    /**
+     * Gets the keyboard type.
+     * @return The keyboard type.
+     */
+    public int getKeyboardType() {
+        return mKeyboardType;
+    }
+
+    /**
+     * Gets the key character map associated with this input device.
+     * @return The key character map.
+     */
+    public KeyCharacterMap getKeyCharacterMap() {
+        return mKeyCharacterMap;
+    }
+
+    /**
+     * Gets whether the device is capable of producing the list of keycodes.
+     * @param keys The list of android keycodes to check for.
+     * @return An array of booleans where each member specifies whether the device is capable of
+     * generating the keycode given by the corresponding value at the same index in the keys array.
+     */
+    public boolean[] hasKeys(int... keys) {
+        return InputManager.getInstance().deviceHasKeys(mId, keys);
+    }
+
+    /**
+     * Gets information about the range of values for a particular {@link MotionEvent} axis.
+     * If the device supports multiple sources, the same axis may have different meanings
+     * for each source.  Returns information about the first axis found for any source.
+     * To obtain information about the axis for a specific source, use
+     * {@link #getMotionRange(int, int)}.
+     *
+     * @param axis The axis constant.
+     * @return The range of values, or null if the requested axis is not
+     * supported by the device.
+     *
+     * @see MotionEvent#AXIS_X
+     * @see MotionEvent#AXIS_Y
+     */
+    public MotionRange getMotionRange(int axis) {
+        final int numRanges = mMotionRanges.size();
+        for (int i = 0; i < numRanges; i++) {
+            final MotionRange range = mMotionRanges.get(i);
+            if (range.mAxis == axis) {
+                return range;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets information about the range of values for a particular {@link MotionEvent} axis
+     * used by a particular source on the device.
+     * If the device supports multiple sources, the same axis may have different meanings
+     * for each source.
+     *
+     * @param axis The axis constant.
+     * @param source The source for which to return information.
+     * @return The range of values, or null if the requested axis is not
+     * supported by the device.
+     *
+     * @see MotionEvent#AXIS_X
+     * @see MotionEvent#AXIS_Y
+     */
+    public MotionRange getMotionRange(int axis, int source) {
+        final int numRanges = mMotionRanges.size();
+        for (int i = 0; i < numRanges; i++) {
+            final MotionRange range = mMotionRanges.get(i);
+            if (range.mAxis == axis && range.mSource == source) {
+                return range;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets the ranges for all axes supported by the device.
+     * @return The motion ranges for the device.
+     *
+     * @see #getMotionRange(int, int)
+     */
+    public List<MotionRange> getMotionRanges() {
+        return mMotionRanges;
+    }
+
+    // Called from native code.
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private void addMotionRange(int axis, int source,
+            float min, float max, float flat, float fuzz, float resolution) {
+        mMotionRanges.add(new MotionRange(axis, source, min, max, flat, fuzz, resolution));
+    }
+
+    /**
+     * Gets the vibrator service associated with the device, if there is one.
+     * Even if the device does not have a vibrator, the result is never null.
+     * Use {@link Vibrator#hasVibrator} to determine whether a vibrator is
+     * present.
+     *
+     * Note that the vibrator associated with the device may be different from
+     * the system vibrator.  To obtain an instance of the system vibrator instead, call
+     * {@link Context#getSystemService} with {@link Context#VIBRATOR_SERVICE} as argument.
+     *
+     * @return The vibrator service associated with the device, never null.
+     * @deprecated Use {@link #getVibratorManager()} to retrieve the default device vibrator.
+     */
+    @Deprecated
+    public Vibrator getVibrator() {
+        synchronized (mMotionRanges) {
+            if (mVibrator == null) {
+                if (mHasVibrator) {
+                    mVibrator = InputManager.getInstance().getInputDeviceVibrator(mId,
+                            VIBRATOR_ID_ALL);
+                } else {
+                    mVibrator = NullVibrator.getInstance();
+                }
+            }
+            return mVibrator;
+        }
+    }
+
+    /**
+     * Gets the vibrator manager associated with the device.
+     * Even if the device does not have a vibrator manager, the result is never null.
+     * Use {@link VibratorManager#getVibratorIds} to determine whether any vibrator is
+     * present.
+     *
+     * @return The vibrator manager associated with the device, never null.
+     */
+    @NonNull
+    public VibratorManager getVibratorManager() {
+        synchronized (mMotionRanges) {
+            if (mVibratorManager == null) {
+                mVibratorManager = InputManager.getInstance().getInputDeviceVibratorManager(mId);
+            }
+        }
+        return mVibratorManager;
+    }
+
+    /**
+     * Gets the battery state object associated with the device, if there is one.
+     * Even if the device does not have a battery, the result is never null.
+     * Use {@link BatteryState#isPresent} to determine whether a battery is
+     * present.
+     *
+     * @return The battery object associated with the device, never null.
+     */
+    @NonNull
+    public BatteryState getBatteryState() {
+        if (mBatteryState == null) {
+            mBatteryState = InputManager.getInstance().getInputDeviceBatteryState(mId, mHasBattery);
+        }
+        return mBatteryState;
+    }
+
+    /**
+     * Gets the lights manager associated with the device, if there is one.
+     * Even if the device does not have lights, the result is never null.
+     * Use {@link LightsManager#getLights} to determine whether any lights is
+     * present.
+     *
+     * @return The lights manager associated with the device, never null.
+     */
+    public @NonNull LightsManager getLightsManager() {
+        if (mLightsManager == null) {
+            mLightsManager = InputManager.getInstance().getInputDeviceLightsManager(mId);
+        }
+        return mLightsManager;
+    }
+
+    /**
+     * Gets the sensor manager service associated with the input device.
+     * Even if the device does not have a sensor, the result is never null.
+     * Use {@link SensorManager#getSensorList} to get a full list of all supported sensors.
+     *
+     * Note that the sensors associated with the device may be different from
+     * the system sensors, as typically they are builtin sensors physically attached to
+     * input devices.
+     *
+     * @return The sensor manager service associated with the device, never null.
+     */
+    public @NonNull SensorManager getSensorManager() {
+        synchronized (mMotionRanges) {
+            if (mSensorManager == null) {
+                mSensorManager = InputManager.getInstance().getInputDeviceSensorManager(mId);
+            }
+        }
+        return mSensorManager;
+    }
+
+    /**
+     * Returns true if input device is enabled.
+     * @return Whether the input device is enabled.
+     */
+    public boolean isEnabled() {
+        return InputManager.getInstance().isInputDeviceEnabled(mId);
+    }
+
+    /**
+     * Enables the input device.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.DISABLE_INPUT_DEVICE)
+    @TestApi
+    public void enable() {
+        InputManager.getInstance().enableInputDevice(mId);
+    }
+
+    /**
+     * Disables the input device.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.DISABLE_INPUT_DEVICE)
+    @TestApi
+    public void disable() {
+        InputManager.getInstance().disableInputDevice(mId);
+    }
+
+    /**
+     * Reports whether the device has a built-in microphone.
+     * @return Whether the device has a built-in microphone.
+     */
+    public boolean hasMicrophone() {
+        return mHasMicrophone;
+    }
+
+    /**
+     * Reports whether the device has a button under its touchpad
+     * @return Whether the device has a button under its touchpad
+     * @hide
+     */
+    public boolean hasButtonUnderPad() {
+        return mHasButtonUnderPad;
+    }
+
+    /**
+     * Reports whether the device has a sensor.
+     * @return Whether the device has a sensor.
+     * @hide
+     */
+    public boolean hasSensor() {
+        return mHasSensor;
+    }
+
+    /**
+     * Sets the current pointer type.
+     * @param pointerType the type of the pointer icon.
+     * @hide
+     */
+    public void setPointerType(int pointerType) {
+        InputManager.getInstance().setPointerIconType(pointerType);
+    }
+
+    /**
+     * Specifies the current custom pointer.
+     * @param icon the icon data.
+     * @hide
+     */
+    public void setCustomPointerIcon(PointerIcon icon) {
+        InputManager.getInstance().setCustomPointerIcon(icon);
+    }
+
+    /**
+     * Provides information about the range of values for a particular {@link MotionEvent} axis.
+     *
+     * @see InputDevice#getMotionRange(int)
+     */
+    public static final class MotionRange {
+        private int mAxis;
+        private int mSource;
+        private float mMin;
+        private float mMax;
+        private float mFlat;
+        private float mFuzz;
+        private float mResolution;
+
+        private MotionRange(int axis, int source, float min, float max, float flat, float fuzz,
+                float resolution) {
+            mAxis = axis;
+            mSource = source;
+            mMin = min;
+            mMax = max;
+            mFlat = flat;
+            mFuzz = fuzz;
+            mResolution = resolution;
+        }
+
+        /**
+         * Gets the axis id.
+         * @return The axis id.
+         */
+        public int getAxis() {
+            return mAxis;
+        }
+
+        /**
+         * Gets the source for which the axis is defined.
+         * @return The source.
+         */
+        public int getSource() {
+            return mSource;
+        }
+
+
+        /**
+         * Determines whether the event is from the given source.
+         *
+         * @param source The input source to check against. This can be a specific device type,
+         * such as {@link InputDevice#SOURCE_TOUCH_NAVIGATION}, or a more generic device class,
+         * such as {@link InputDevice#SOURCE_CLASS_POINTER}.
+         * @return Whether the event is from the given source.
+         */
+        public boolean isFromSource(int source) {
+            return (getSource() & source) == source;
+        }
+
+        /**
+         * Gets the inclusive minimum value for the axis.
+         * @return The inclusive minimum value.
+         */
+        public float getMin() {
+            return mMin;
+        }
+
+        /**
+         * Gets the inclusive maximum value for the axis.
+         * @return The inclusive maximum value.
+         */
+        public float getMax() {
+            return mMax;
+        }
+
+        /**
+         * Gets the range of the axis (difference between maximum and minimum).
+         * @return The range of values.
+         */
+        public float getRange() {
+            return mMax - mMin;
+        }
+
+        /**
+         * Gets the extent of the center flat position with respect to this axis.
+         * <p>
+         * For example, a flat value of 8 means that the center position is between -8 and +8.
+         * This value is mainly useful for calibrating self-centering devices.
+         * </p>
+         * @return The extent of the center flat position.
+         */
+        public float getFlat() {
+            return mFlat;
+        }
+
+        /**
+         * Gets the error tolerance for input device measurements with respect to this axis.
+         * <p>
+         * For example, a value of 2 indicates that the measured value may be up to +/- 2 units
+         * away from the actual value due to noise and device sensitivity limitations.
+         * </p>
+         * @return The error tolerance.
+         */
+        public float getFuzz() {
+            return mFuzz;
+        }
+
+        /**
+         * Gets the resolution for input device measurements with respect to this axis.
+         * @return The resolution in units per millimeter, or units per radian for rotational axes.
+         */
+        public float getResolution() {
+            return mResolution;
+        }
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        mKeyCharacterMap.writeToParcel(out, flags);
+        out.writeInt(mId);
+        out.writeInt(mGeneration);
+        out.writeInt(mControllerNumber);
+        out.writeString(mName);
+        out.writeInt(mVendorId);
+        out.writeInt(mProductId);
+        out.writeString(mDescriptor);
+        out.writeInt(mIsExternal ? 1 : 0);
+        out.writeInt(mSources);
+        out.writeInt(mKeyboardType);
+        out.writeInt(mHasVibrator ? 1 : 0);
+        out.writeInt(mHasMicrophone ? 1 : 0);
+        out.writeInt(mHasButtonUnderPad ? 1 : 0);
+        out.writeInt(mHasSensor ? 1 : 0);
+        out.writeInt(mHasBattery ? 1 : 0);
+
+        final int numRanges = mMotionRanges.size();
+        out.writeInt(numRanges);
+        for (int i = 0; i < numRanges; i++) {
+            MotionRange range = mMotionRanges.get(i);
+            out.writeInt(range.mAxis);
+            out.writeInt(range.mSource);
+            out.writeFloat(range.mMin);
+            out.writeFloat(range.mMax);
+            out.writeFloat(range.mFlat);
+            out.writeFloat(range.mFuzz);
+            out.writeFloat(range.mResolution);
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder description = new StringBuilder();
+        description.append("Input Device ").append(mId).append(": ").append(mName).append("\n");
+        description.append("  Descriptor: ").append(mDescriptor).append("\n");
+        description.append("  Generation: ").append(mGeneration).append("\n");
+        description.append("  Location: ").append(mIsExternal ? "external" : "built-in").append("\n");
+
+        description.append("  Keyboard Type: ");
+        switch (mKeyboardType) {
+            case KEYBOARD_TYPE_NONE:
+                description.append("none");
+                break;
+            case KEYBOARD_TYPE_NON_ALPHABETIC:
+                description.append("non-alphabetic");
+                break;
+            case KEYBOARD_TYPE_ALPHABETIC:
+                description.append("alphabetic");
+                break;
+        }
+        description.append("\n");
+
+        description.append("  Has Vibrator: ").append(mHasVibrator).append("\n");
+
+        description.append("  Has Sensor: ").append(mHasSensor).append("\n");
+
+        description.append("  Has battery: ").append(mHasBattery).append("\n");
+
+        description.append("  Has mic: ").append(mHasMicrophone).append("\n");
+
+        description.append("  Sources: 0x").append(Integer.toHexString(mSources)).append(" (");
+        appendSourceDescriptionIfApplicable(description, SOURCE_KEYBOARD, "keyboard");
+        appendSourceDescriptionIfApplicable(description, SOURCE_DPAD, "dpad");
+        appendSourceDescriptionIfApplicable(description, SOURCE_TOUCHSCREEN, "touchscreen");
+        appendSourceDescriptionIfApplicable(description, SOURCE_MOUSE, "mouse");
+        appendSourceDescriptionIfApplicable(description, SOURCE_STYLUS, "stylus");
+        appendSourceDescriptionIfApplicable(description, SOURCE_TRACKBALL, "trackball");
+        appendSourceDescriptionIfApplicable(description, SOURCE_MOUSE_RELATIVE, "mouse_relative");
+        appendSourceDescriptionIfApplicable(description, SOURCE_TOUCHPAD, "touchpad");
+        appendSourceDescriptionIfApplicable(description, SOURCE_JOYSTICK, "joystick");
+        appendSourceDescriptionIfApplicable(description, SOURCE_GAMEPAD, "gamepad");
+        description.append(" )\n");
+
+        final int numAxes = mMotionRanges.size();
+        for (int i = 0; i < numAxes; i++) {
+            MotionRange range = mMotionRanges.get(i);
+            description.append("    ").append(MotionEvent.axisToString(range.mAxis));
+            description.append(": source=0x").append(Integer.toHexString(range.mSource));
+            description.append(" min=").append(range.mMin);
+            description.append(" max=").append(range.mMax);
+            description.append(" flat=").append(range.mFlat);
+            description.append(" fuzz=").append(range.mFuzz);
+            description.append(" resolution=").append(range.mResolution);
+            description.append("\n");
+        }
+        return description.toString();
+    }
+
+    private void appendSourceDescriptionIfApplicable(StringBuilder description, int source,
+            String sourceName) {
+        if ((mSources & source) == source) {
+            description.append(" ");
+            description.append(sourceName);
+        }
+    }
+}
diff --git a/android/view/InputEvent.java b/android/view/InputEvent.java
new file mode 100644
index 0000000..cb9e746
--- /dev/null
+++ b/android/view/InputEvent.java
@@ -0,0 +1,272 @@
+/*
+ * 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.view;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Common base class for input events.
+ */
+public abstract class InputEvent implements Parcelable {
+    /** @hide */
+    protected static final int PARCEL_TOKEN_MOTION_EVENT = 1;
+    /** @hide */
+    protected static final int PARCEL_TOKEN_KEY_EVENT = 2;
+
+    // Next sequence number.
+    private static final AtomicInteger mNextSeq = new AtomicInteger();
+
+    /** @hide */
+    protected int mSeq;
+
+    /** @hide */
+    protected boolean mRecycled;
+
+    private static final boolean TRACK_RECYCLED_LOCATION = false;
+    private RuntimeException mRecycledLocation;
+
+    /*package*/ InputEvent() {
+        mSeq = mNextSeq.getAndIncrement();
+    }
+
+    /**
+     * Gets the id for the device that this event came from.  An id of
+     * zero indicates that the event didn't come from a physical device
+     * and maps to the default keymap.  The other numbers are arbitrary and
+     * you shouldn't depend on the values.
+     *
+     * @return The device id.
+     * @see InputDevice#getDevice
+     */
+    public abstract int getDeviceId();
+
+    /**
+     * Gets the device that this event came from.
+     *
+     * @return The device, or null if unknown.
+     */
+    public final InputDevice getDevice() {
+        return InputDevice.getDevice(getDeviceId());
+    }
+
+    /**
+     * Gets the source of the event.
+     *
+     * @return The event source or {@link InputDevice#SOURCE_UNKNOWN} if unknown.
+     * @see InputDevice#getSources
+     */
+    public abstract int getSource();
+
+    /**
+     * Modifies the source of the event.
+     *
+     * @param source The new source.
+     * @hide
+     */
+    public abstract void setSource(int source);
+
+    /**
+     * Determines whether the event is from the given source.
+     *
+     * @param source The input source to check against. This can be a specific device type, such as
+     * {@link InputDevice#SOURCE_TOUCH_NAVIGATION}, or a more generic device class, such as
+     * {@link InputDevice#SOURCE_CLASS_POINTER}.
+     * @return Whether the event is from the given source.
+     */
+    public boolean isFromSource(int source) {
+        return (getSource() & source) == source;
+    }
+
+    /**
+     * Gets the display id of the event.
+     * @return The display id associated with the event.
+     * @hide
+     */
+    public abstract int getDisplayId();
+
+    /**
+     * Modifies the display id associated with the event
+     * @param displayId
+     * @hide
+     */
+    public abstract void setDisplayId(int displayId);
+    /**
+     * Copies the event.
+     *
+     * @return A deep copy of the event.
+     * @hide
+     */
+    public abstract InputEvent copy();
+
+    /**
+     * Recycles the event.
+     * This method should only be used by the system since applications do not
+     * expect {@link KeyEvent} objects to be recycled, although {@link MotionEvent}
+     * objects are fine.  See {@link KeyEvent#recycle()} for details.
+     * @hide
+     */
+    public void recycle() {
+        if (TRACK_RECYCLED_LOCATION) {
+            if (mRecycledLocation != null) {
+                throw new RuntimeException(toString() + " recycled twice!", mRecycledLocation);
+            }
+            mRecycledLocation = new RuntimeException("Last recycled here");
+        } else {
+            if (mRecycled) {
+                throw new RuntimeException(toString() + " recycled twice!");
+            }
+            mRecycled = true;
+        }
+    }
+
+    /**
+     * Conditionally recycled the event if it is appropriate to do so after
+     * dispatching the event to an application.
+     *
+     * If the event is a {@link MotionEvent} then it is recycled.
+     *
+     * If the event is a {@link KeyEvent} then it is NOT recycled, because applications
+     * expect key events to be immutable so once the event has been dispatched to
+     * the application we can no longer recycle it.
+     * @hide
+     */
+    public void recycleIfNeededAfterDispatch() {
+        recycle();
+    }
+
+    /**
+     * Reinitializes the event on reuse (after recycling).
+     * @hide
+     */
+    protected void prepareForReuse() {
+        mRecycled = false;
+        mRecycledLocation = null;
+        mSeq = mNextSeq.getAndIncrement();
+    }
+
+    /**
+     * Gets a private flag that indicates when the system has detected that this input event
+     * may be inconsistent with respect to the sequence of previously delivered input events,
+     * such as when a key up event is sent but the key was not down or when a pointer
+     * move event is sent but the pointer is not down.
+     *
+     * @return True if this event is tainted.
+     * @hide
+     */
+    public abstract boolean isTainted();
+
+    /**
+     * Sets a private flag that indicates when the system has detected that this input event
+     * may be inconsistent with respect to the sequence of previously delivered input events,
+     * such as when a key up event is sent but the key was not down or when a pointer
+     * move event is sent but the pointer is not down.
+     *
+     * @param tainted True if this event is tainted.
+     * @hide
+     */
+    public abstract void setTainted(boolean tainted);
+
+    /**
+     * Retrieve the time this event occurred,
+     * in the {@link android.os.SystemClock#uptimeMillis} time base.
+     *
+     * @return Returns the time this event occurred,
+     * in the {@link android.os.SystemClock#uptimeMillis} time base.
+     */
+    public abstract long getEventTime();
+
+    /**
+     * Retrieve the time this event occurred,
+     * in the {@link android.os.SystemClock#uptimeMillis} time base but with
+     * nanosecond (instead of millisecond) precision.
+     * <p>
+     * The value is in nanosecond precision but it may not have nanosecond accuracy.
+     * </p>
+     *
+     * @return Returns the time this event occurred,
+     * in the {@link android.os.SystemClock#uptimeMillis} time base but with
+     * nanosecond (instead of millisecond) precision.
+     *
+     * @hide
+     */
+    public abstract long getEventTimeNano();
+
+    /**
+     * Marks the input event as being canceled.
+     *
+     * @hide
+     */
+    public abstract void cancel();
+
+    /**
+     * Gets the unique sequence number of this event.
+     * Every input event that is created or received by a process has a
+     * unique sequence number.  Moreover, a new sequence number is obtained
+     * each time an event object is recycled.
+     *
+     * Sequence numbers are only guaranteed to be locally unique within a process.
+     * Sequence numbers are not preserved when events are parceled.
+     *
+     * @return The unique sequence number of this event.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public int getSequenceNumber() {
+        return mSeq;
+    }
+
+    /**
+     * Gets the ID of this event. This is generated when an event is created and preserved until its
+     * last stage. It won't change just because the event crosses process boundary, but should
+     * change when making a copy with modifications.
+     * <p>
+     * To avoid exposing app usage to other processes this ID is generated from a CSPRNG. Therefore
+     * there isn't 100% guarantee on the uniqueness of this ID, though the chance of ID collisions
+     * is considerably low. The rule of thumb is not to rely on the uniqueness for production logic,
+     * but a good source for tracking an event (e.g. logging and profiling).
+     *
+     * @return The ID of this event.
+     * @hide
+     */
+    public abstract int getId();
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<InputEvent> CREATOR
+            = new Parcelable.Creator<InputEvent>() {
+        public InputEvent createFromParcel(Parcel in) {
+            int token = in.readInt();
+            if (token == PARCEL_TOKEN_KEY_EVENT) {
+                return KeyEvent.createFromParcelBody(in);
+            } else if (token == PARCEL_TOKEN_MOTION_EVENT) {
+                return MotionEvent.createFromParcelBody(in);
+            } else {
+                throw new IllegalStateException("Unexpected input event type token in parcel.");
+            }
+        }
+
+        public InputEvent[] newArray(int size) {
+            return new InputEvent[size];
+        }
+    };
+}
diff --git a/android/view/InputEventAssigner.java b/android/view/InputEventAssigner.java
new file mode 100644
index 0000000..7fac6c5
--- /dev/null
+++ b/android/view/InputEventAssigner.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.os.IInputConstants.INVALID_INPUT_EVENT_ID;
+import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
+
+/**
+ * Process input events and assign input event id to a specific frame.
+ *
+ * The assigned input event id is determined by where the current gesture is relative to the vsync.
+ * In the middle of the gesture (we already processed some input events, and already received at
+ * least 1 vsync), the latest InputEvent is assigned to the next frame.
+ * If a gesture just started, then the ACTION_DOWN event will be assigned to the next frame.
+ *
+ * Consider the following sequence:
+ * DOWN -> VSYNC 1 -> MOVE 1 -> MOVE 2 -> VSYNC 2.
+ *
+ * For VSYNC 1, we will assign the "DOWN" input event.
+ * For VSYNC 2, we will assign the "MOVE 2" input event.
+ *
+ * Consider another sequence:
+ * DOWN -> MOVE 1 -> MOVE 2 -> VSYNC 1 -> MOVE 3 -> VSYNC 2.
+ *
+ * For VSYNC 1, we will still assign the "DOWN" input event. That means that "MOVE 1" and "MOVE 2"
+ * events are not attributed to any frame.
+ * For VSYNC 2, the "MOVE 3" input event will be assigned.
+ *
+ * @hide
+ */
+public class InputEventAssigner {
+    private static final String TAG = "InputEventAssigner";
+    private boolean mHasUnprocessedDown = false;
+    private int mDownEventId = INVALID_INPUT_EVENT_ID;
+
+    /**
+     * Notify InputEventAssigner that a frame has been processed. We no longer need to keep track of
+     * the DOWN event because a frame has already been produced for it.
+     */
+    public void notifyFrameProcessed() {
+        // Mark completion of this frame. Use newest input event from now on.
+        mHasUnprocessedDown = false;
+    }
+
+    /**
+     * Process the provided input event to determine which event id to assign to the current frame.
+     * @param event the input event currently being processed
+     * @return the id of the input event to use for the current frame
+     */
+    public int processEvent(InputEvent event) {
+        if (event instanceof MotionEvent) {
+            MotionEvent motionEvent = (MotionEvent) event;
+            if (motionEvent.isFromSource(SOURCE_TOUCHSCREEN)) {
+                final int action = motionEvent.getActionMasked();
+                if (action == MotionEvent.ACTION_DOWN) {
+                    mHasUnprocessedDown = true;
+                    mDownEventId = event.getId();
+                }
+                if (mHasUnprocessedDown && action == MotionEvent.ACTION_MOVE) {
+                    return mDownEventId;
+                }
+                if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
+                    mHasUnprocessedDown = false;
+                }
+            }
+        }
+        return event.getId();
+    }
+}
diff --git a/android/view/InputEventCompatProcessor.java b/android/view/InputEventCompatProcessor.java
new file mode 100644
index 0000000..ff8407a
--- /dev/null
+++ b/android/view/InputEventCompatProcessor.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.content.Context;
+import android.os.Build;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Compatibility processor for InputEvents that allows events to be adjusted before and
+ * after it is sent to the application.
+ *
+ * {@hide}
+ */
+public class InputEventCompatProcessor {
+
+    protected Context mContext;
+    protected int mTargetSdkVersion;
+
+    /** List of events to be used to return the processed events */
+    private List<InputEvent> mProcessedEvents;
+
+    public InputEventCompatProcessor(Context context) {
+        mContext = context;
+        mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
+        mProcessedEvents = new ArrayList<>();
+    }
+
+    /**
+     * Processes the InputEvent for compatibility before it is sent to the app, allowing for the
+     * generation of more than one event if necessary.
+     *
+     * @param e The InputEvent to process
+     * @return The list of adjusted events, or null if no adjustments are needed. Do not keep a
+     *         reference to the output as the list is reused.
+     */
+    public List<InputEvent> processInputEventForCompatibility(InputEvent e) {
+        if (mTargetSdkVersion < Build.VERSION_CODES.M && e instanceof MotionEvent) {
+            mProcessedEvents.clear();
+            MotionEvent motion = (MotionEvent) e;
+            final int mask =
+                    MotionEvent.BUTTON_STYLUS_PRIMARY | MotionEvent.BUTTON_STYLUS_SECONDARY;
+            final int buttonState = motion.getButtonState();
+            final int compatButtonState = (buttonState & mask) >> 4;
+            if (compatButtonState != 0) {
+                motion.setButtonState(buttonState | compatButtonState);
+            }
+            mProcessedEvents.add(motion);
+            return mProcessedEvents;
+        }
+        return null;
+    }
+
+    /**
+     * Processes the InputEvent for compatibility before it is finished by calling
+     * InputEventReceiver#finishInputEvent().
+     *
+     * @param e The InputEvent to process
+     * @return The InputEvent to finish, or null if it should not be finished
+     */
+    public InputEvent processInputEventBeforeFinish(InputEvent e) {
+        // No changes needed
+        return e;
+    }
+}
diff --git a/android/view/InputEventConsistencyVerifier.java b/android/view/InputEventConsistencyVerifier.java
new file mode 100644
index 0000000..c0a3cec
--- /dev/null
+++ b/android/view/InputEventConsistencyVerifier.java
@@ -0,0 +1,809 @@
+/*
+ * 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.view;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.util.Log;
+
+/**
+ * Checks whether a sequence of input events is self-consistent.
+ * Logs a description of each problem detected.
+ * <p>
+ * When a problem is detected, the event is tainted.  This mechanism prevents the same
+ * error from being reported multiple times.
+ * </p>
+ *
+ * @hide
+ */
+public final class InputEventConsistencyVerifier {
+    private static final boolean IS_ENG_BUILD = Build.IS_ENG;
+
+    private static final String EVENT_TYPE_KEY = "KeyEvent";
+    private static final String EVENT_TYPE_TRACKBALL = "TrackballEvent";
+    private static final String EVENT_TYPE_TOUCH = "TouchEvent";
+    private static final String EVENT_TYPE_GENERIC_MOTION = "GenericMotionEvent";
+
+    // The number of recent events to log when a problem is detected.
+    // Can be set to 0 to disable logging recent events but the runtime overhead of
+    // this feature is negligible on current hardware.
+    private static final int RECENT_EVENTS_TO_LOG = 5;
+
+    // The object to which the verifier is attached.
+    private final Object mCaller;
+
+    // Consistency verifier flags.
+    private final int mFlags;
+
+    // Tag for logging which a client can set to help distinguish the output
+    // from different verifiers since several can be active at the same time.
+    // If not provided defaults to the simple class name.
+    private final String mLogTag;
+
+    // The most recently checked event and the nesting level at which it was checked.
+    // This is only set when the verifier is called from a nesting level greater than 0
+    // so that the verifier can detect when it has been asked to verify the same event twice.
+    // It does not make sense to examine the contents of the last event since it may have
+    // been recycled.
+    private int mLastEventSeq;
+    private String mLastEventType;
+    private int mLastNestingLevel;
+
+    // Copy of the most recent events.
+    private InputEvent[] mRecentEvents;
+    private boolean[] mRecentEventsUnhandled;
+    private int mMostRecentEventIndex;
+
+    // Current event and its type.
+    private InputEvent mCurrentEvent;
+    private String mCurrentEventType;
+
+    // Linked list of key state objects.
+    private KeyState mKeyStateList;
+
+    // Current state of the trackball.
+    private boolean mTrackballDown;
+    private boolean mTrackballUnhandled;
+
+    // Bitfield of pointer ids that are currently down.
+    // Assumes that the largest possible pointer id is 31, which is potentially subject to change.
+    // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h)
+    private int mTouchEventStreamPointers;
+
+    // The device id and source of the current stream of touch events.
+    private int mTouchEventStreamDeviceId = -1;
+    private int mTouchEventStreamSource;
+
+    // Set to true when we discover that the touch event stream is inconsistent.
+    // Reset on down or cancel.
+    private boolean mTouchEventStreamIsTainted;
+
+    // Set to true if the touch event stream is partially unhandled.
+    private boolean mTouchEventStreamUnhandled;
+
+    // Set to true if we received hover enter.
+    private boolean mHoverEntered;
+
+    // The bitset of buttons which we've received ACTION_BUTTON_PRESS for.
+    private int mButtonsPressed;
+
+    // The current violation message.
+    private StringBuilder mViolationMessage;
+
+    /**
+     * Indicates that the verifier is intended to act on raw device input event streams.
+     * Disables certain checks for invariants that are established by the input dispatcher
+     * itself as it delivers input events, such as key repeating behavior.
+     */
+    public static final int FLAG_RAW_DEVICE_INPUT = 1 << 0;
+
+    /**
+     * Creates an input consistency verifier.
+     * @param caller The object to which the verifier is attached.
+     * @param flags Flags to the verifier, or 0 if none.
+     */
+    @UnsupportedAppUsage
+    public InputEventConsistencyVerifier(Object caller, int flags) {
+        this(caller, flags, null);
+    }
+
+    /**
+     * Creates an input consistency verifier.
+     * @param caller The object to which the verifier is attached.
+     * @param flags Flags to the verifier, or 0 if none.
+     * @param logTag Tag for logging. If null defaults to the short class name.
+     */
+    public InputEventConsistencyVerifier(Object caller, int flags, String logTag) {
+        this.mCaller = caller;
+        this.mFlags = flags;
+        this.mLogTag = (logTag != null) ? logTag : "InputEventConsistencyVerifier";
+    }
+
+    /**
+     * Determines whether the instrumentation should be enabled.
+     * @return True if it should be enabled.
+     */
+    @UnsupportedAppUsage
+    public static boolean isInstrumentationEnabled() {
+        return IS_ENG_BUILD;
+    }
+
+    /**
+     * Resets the state of the input event consistency verifier.
+     */
+    public void reset() {
+        mLastEventSeq = -1;
+        mLastNestingLevel = 0;
+        mTrackballDown = false;
+        mTrackballUnhandled = false;
+        mTouchEventStreamPointers = 0;
+        mTouchEventStreamIsTainted = false;
+        mTouchEventStreamUnhandled = false;
+        mHoverEntered = false;
+        mButtonsPressed = 0;
+
+        while (mKeyStateList != null) {
+            final KeyState state = mKeyStateList;
+            mKeyStateList = state.next;
+            state.recycle();
+        }
+    }
+
+    /**
+     * Checks an arbitrary input event.
+     * @param event The event.
+     * @param nestingLevel The nesting level: 0 if called from the base class,
+     * or 1 from a subclass.  If the event was already checked by this consistency verifier
+     * at a higher nesting level, it will not be checked again.  Used to handle the situation
+     * where a subclass dispatching method delegates to its superclass's dispatching method
+     * and both dispatching methods call into the consistency verifier.
+     */
+    public void onInputEvent(InputEvent event, int nestingLevel) {
+        if (event instanceof KeyEvent) {
+            final KeyEvent keyEvent = (KeyEvent)event;
+            onKeyEvent(keyEvent, nestingLevel);
+        } else {
+            final MotionEvent motionEvent = (MotionEvent)event;
+            if (motionEvent.isTouchEvent()) {
+                onTouchEvent(motionEvent, nestingLevel);
+            } else if ((motionEvent.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+                onTrackballEvent(motionEvent, nestingLevel);
+            } else {
+                onGenericMotionEvent(motionEvent, nestingLevel);
+            }
+        }
+    }
+
+    /**
+     * Checks a key event.
+     * @param event The event.
+     * @param nestingLevel The nesting level: 0 if called from the base class,
+     * or 1 from a subclass.  If the event was already checked by this consistency verifier
+     * at a higher nesting level, it will not be checked again.  Used to handle the situation
+     * where a subclass dispatching method delegates to its superclass's dispatching method
+     * and both dispatching methods call into the consistency verifier.
+     */
+    public void onKeyEvent(KeyEvent event, int nestingLevel) {
+        if (!startEvent(event, nestingLevel, EVENT_TYPE_KEY)) {
+            return;
+        }
+
+        try {
+            ensureMetaStateIsNormalized(event.getMetaState());
+
+            final int action = event.getAction();
+            final int deviceId = event.getDeviceId();
+            final int source = event.getSource();
+            final int keyCode = event.getKeyCode();
+            switch (action) {
+                case KeyEvent.ACTION_DOWN: {
+                    KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ false);
+                    if (state != null) {
+                        // If the key is already down, ensure it is a repeat.
+                        // We don't perform this check when processing raw device input
+                        // because the input dispatcher itself is responsible for setting
+                        // the key repeat count before it delivers input events.
+                        if (state.unhandled) {
+                            state.unhandled = false;
+                        } else if ((mFlags & FLAG_RAW_DEVICE_INPUT) == 0
+                                && event.getRepeatCount() == 0) {
+                            problem("ACTION_DOWN but key is already down and this event "
+                                    + "is not a key repeat.");
+                        }
+                    } else {
+                        addKeyState(deviceId, source, keyCode);
+                    }
+                    break;
+                }
+                case KeyEvent.ACTION_UP: {
+                    KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ true);
+                    if (state == null) {
+                        problem("ACTION_UP but key was not down.");
+                    } else {
+                        state.recycle();
+                    }
+                    break;
+                }
+                case KeyEvent.ACTION_MULTIPLE:
+                    break;
+                default:
+                    problem("Invalid action " + KeyEvent.actionToString(action)
+                            + " for key event.");
+                    break;
+            }
+        } finally {
+            finishEvent();
+        }
+    }
+
+    /**
+     * Checks a trackball event.
+     * @param event The event.
+     * @param nestingLevel The nesting level: 0 if called from the base class,
+     * or 1 from a subclass.  If the event was already checked by this consistency verifier
+     * at a higher nesting level, it will not be checked again.  Used to handle the situation
+     * where a subclass dispatching method delegates to its superclass's dispatching method
+     * and both dispatching methods call into the consistency verifier.
+     */
+    public void onTrackballEvent(MotionEvent event, int nestingLevel) {
+        if (!startEvent(event, nestingLevel, EVENT_TYPE_TRACKBALL)) {
+            return;
+        }
+
+        try {
+            ensureMetaStateIsNormalized(event.getMetaState());
+
+            final int action = event.getAction();
+            final int source = event.getSource();
+            if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+                switch (action) {
+                    case MotionEvent.ACTION_DOWN:
+                        if (mTrackballDown && !mTrackballUnhandled) {
+                            problem("ACTION_DOWN but trackball is already down.");
+                        } else {
+                            mTrackballDown = true;
+                            mTrackballUnhandled = false;
+                        }
+                        ensureHistorySizeIsZeroForThisAction(event);
+                        ensurePointerCountIsOneForThisAction(event);
+                        break;
+                    case MotionEvent.ACTION_UP:
+                        if (!mTrackballDown) {
+                            problem("ACTION_UP but trackball is not down.");
+                        } else {
+                            mTrackballDown = false;
+                            mTrackballUnhandled = false;
+                        }
+                        ensureHistorySizeIsZeroForThisAction(event);
+                        ensurePointerCountIsOneForThisAction(event);
+                        break;
+                    case MotionEvent.ACTION_MOVE:
+                        ensurePointerCountIsOneForThisAction(event);
+                        break;
+                    default:
+                        problem("Invalid action " + MotionEvent.actionToString(action)
+                                + " for trackball event.");
+                        break;
+                }
+
+                if (mTrackballDown && event.getPressure() <= 0) {
+                    problem("Trackball is down but pressure is not greater than 0.");
+                } else if (!mTrackballDown && event.getPressure() != 0) {
+                    problem("Trackball is up but pressure is not equal to 0.");
+                }
+            } else {
+                problem("Source was not SOURCE_CLASS_TRACKBALL.");
+            }
+        } finally {
+            finishEvent();
+        }
+    }
+
+    /**
+     * Checks a touch event.
+     * @param event The event.
+     * @param nestingLevel The nesting level: 0 if called from the base class,
+     * or 1 from a subclass.  If the event was already checked by this consistency verifier
+     * at a higher nesting level, it will not be checked again.  Used to handle the situation
+     * where a subclass dispatching method delegates to its superclass's dispatching method
+     * and both dispatching methods call into the consistency verifier.
+     */
+    @UnsupportedAppUsage
+    public void onTouchEvent(MotionEvent event, int nestingLevel) {
+        if (!startEvent(event, nestingLevel, EVENT_TYPE_TOUCH)) {
+            return;
+        }
+
+        final int action = event.getAction();
+        final boolean newStream = action == MotionEvent.ACTION_DOWN
+                || action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_OUTSIDE;
+        if (newStream && (mTouchEventStreamIsTainted || mTouchEventStreamUnhandled)) {
+            mTouchEventStreamIsTainted = false;
+            mTouchEventStreamUnhandled = false;
+            mTouchEventStreamPointers = 0;
+        }
+        if (mTouchEventStreamIsTainted) {
+            event.setTainted(true);
+        }
+
+        try {
+            ensureMetaStateIsNormalized(event.getMetaState());
+
+            final int deviceId = event.getDeviceId();
+            final int source = event.getSource();
+
+            if (!newStream && mTouchEventStreamDeviceId != -1
+                    && (mTouchEventStreamDeviceId != deviceId
+                            || mTouchEventStreamSource != source)) {
+                problem("Touch event stream contains events from multiple sources: "
+                        + "previous device id " + mTouchEventStreamDeviceId
+                        + ", previous source " + Integer.toHexString(mTouchEventStreamSource)
+                        + ", new device id " + deviceId
+                        + ", new source " + Integer.toHexString(source));
+            }
+            mTouchEventStreamDeviceId = deviceId;
+            mTouchEventStreamSource = source;
+
+            final int pointerCount = event.getPointerCount();
+            if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+                switch (action) {
+                    case MotionEvent.ACTION_DOWN:
+                        if (mTouchEventStreamPointers != 0) {
+                            problem("ACTION_DOWN but pointers are already down.  "
+                                    + "Probably missing ACTION_UP from previous gesture.");
+                        }
+                        ensureHistorySizeIsZeroForThisAction(event);
+                        ensurePointerCountIsOneForThisAction(event);
+                        mTouchEventStreamPointers = 1 << event.getPointerId(0);
+                        break;
+                    case MotionEvent.ACTION_UP:
+                        ensureHistorySizeIsZeroForThisAction(event);
+                        ensurePointerCountIsOneForThisAction(event);
+                        mTouchEventStreamPointers = 0;
+                        mTouchEventStreamIsTainted = false;
+                        break;
+                    case MotionEvent.ACTION_MOVE: {
+                        final int expectedPointerCount =
+                                Integer.bitCount(mTouchEventStreamPointers);
+                        if (pointerCount != expectedPointerCount) {
+                            problem("ACTION_MOVE contained " + pointerCount
+                                    + " pointers but there are currently "
+                                    + expectedPointerCount + " pointers down.");
+                            mTouchEventStreamIsTainted = true;
+                        }
+                        break;
+                    }
+                    case MotionEvent.ACTION_CANCEL:
+                        mTouchEventStreamPointers = 0;
+                        mTouchEventStreamIsTainted = false;
+                        break;
+                    case MotionEvent.ACTION_OUTSIDE:
+                        if (mTouchEventStreamPointers != 0) {
+                            problem("ACTION_OUTSIDE but pointers are still down.");
+                        }
+                        ensureHistorySizeIsZeroForThisAction(event);
+                        ensurePointerCountIsOneForThisAction(event);
+                        mTouchEventStreamIsTainted = false;
+                        break;
+                    default: {
+                        final int actionMasked = event.getActionMasked();
+                        final int actionIndex = event.getActionIndex();
+                        if (actionMasked == MotionEvent.ACTION_POINTER_DOWN) {
+                            if (mTouchEventStreamPointers == 0) {
+                                problem("ACTION_POINTER_DOWN but no other pointers were down.");
+                                mTouchEventStreamIsTainted = true;
+                            }
+                            if (actionIndex < 0 || actionIndex >= pointerCount) {
+                                problem("ACTION_POINTER_DOWN index is " + actionIndex
+                                        + " but the pointer count is " + pointerCount + ".");
+                                mTouchEventStreamIsTainted = true;
+                            } else {
+                                final int id = event.getPointerId(actionIndex);
+                                final int idBit = 1 << id;
+                                if ((mTouchEventStreamPointers & idBit) != 0) {
+                                    problem("ACTION_POINTER_DOWN specified pointer id " + id
+                                            + " which is already down.");
+                                    mTouchEventStreamIsTainted = true;
+                                } else {
+                                    mTouchEventStreamPointers |= idBit;
+                                }
+                            }
+                            ensureHistorySizeIsZeroForThisAction(event);
+                        } else if (actionMasked == MotionEvent.ACTION_POINTER_UP) {
+                            if (actionIndex < 0 || actionIndex >= pointerCount) {
+                                problem("ACTION_POINTER_UP index is " + actionIndex
+                                        + " but the pointer count is " + pointerCount + ".");
+                                mTouchEventStreamIsTainted = true;
+                            } else {
+                                final int id = event.getPointerId(actionIndex);
+                                final int idBit = 1 << id;
+                                if ((mTouchEventStreamPointers & idBit) == 0) {
+                                    problem("ACTION_POINTER_UP specified pointer id " + id
+                                            + " which is not currently down.");
+                                    mTouchEventStreamIsTainted = true;
+                                } else {
+                                    mTouchEventStreamPointers &= ~idBit;
+                                }
+                            }
+                            ensureHistorySizeIsZeroForThisAction(event);
+                        } else {
+                            problem("Invalid action " + MotionEvent.actionToString(action)
+                                    + " for touch event.");
+                        }
+                        break;
+                    }
+                }
+            } else {
+                problem("Source was not SOURCE_CLASS_POINTER.");
+            }
+        } finally {
+            finishEvent();
+        }
+    }
+
+    /**
+     * Checks a generic motion event.
+     * @param event The event.
+     * @param nestingLevel The nesting level: 0 if called from the base class,
+     * or 1 from a subclass.  If the event was already checked by this consistency verifier
+     * at a higher nesting level, it will not be checked again.  Used to handle the situation
+     * where a subclass dispatching method delegates to its superclass's dispatching method
+     * and both dispatching methods call into the consistency verifier.
+     */
+    public void onGenericMotionEvent(MotionEvent event, int nestingLevel) {
+        if (!startEvent(event, nestingLevel, EVENT_TYPE_GENERIC_MOTION)) {
+            return;
+        }
+
+        try {
+            ensureMetaStateIsNormalized(event.getMetaState());
+
+            final int action = event.getAction();
+            final int source = event.getSource();
+            final int buttonState = event.getButtonState();
+            final int actionButton = event.getActionButton();
+            if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+                switch (action) {
+                    case MotionEvent.ACTION_HOVER_ENTER:
+                        ensurePointerCountIsOneForThisAction(event);
+                        mHoverEntered = true;
+                        break;
+                    case MotionEvent.ACTION_HOVER_MOVE:
+                        ensurePointerCountIsOneForThisAction(event);
+                        break;
+                    case MotionEvent.ACTION_HOVER_EXIT:
+                        ensurePointerCountIsOneForThisAction(event);
+                        if (!mHoverEntered) {
+                            problem("ACTION_HOVER_EXIT without prior ACTION_HOVER_ENTER");
+                        }
+                        mHoverEntered = false;
+                        break;
+                    case MotionEvent.ACTION_SCROLL:
+                        ensureHistorySizeIsZeroForThisAction(event);
+                        ensurePointerCountIsOneForThisAction(event);
+                        break;
+                    case MotionEvent.ACTION_BUTTON_PRESS:
+                        ensureActionButtonIsNonZeroForThisAction(event);
+                        if ((mButtonsPressed & actionButton) != 0) {
+                            problem("Action button for ACTION_BUTTON_PRESS event is " +
+                                    actionButton + ", but it has already been pressed and " +
+                                    "has yet to be released.");
+                        }
+
+                        mButtonsPressed |= actionButton;
+                        // The system will automatically mirror the stylus buttons onto the button
+                        // state as the old set of generic buttons for apps targeting pre-M. If
+                        // it looks this has happened, go ahead and set the generic buttons as
+                        // pressed to prevent spurious errors.
+                        if (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY &&
+                                (buttonState & MotionEvent.BUTTON_SECONDARY) != 0) {
+                            mButtonsPressed |= MotionEvent.BUTTON_SECONDARY;
+                        } else if (actionButton == MotionEvent.BUTTON_STYLUS_SECONDARY &&
+                                (buttonState & MotionEvent.BUTTON_TERTIARY) != 0) {
+                            mButtonsPressed |= MotionEvent.BUTTON_TERTIARY;
+                        }
+
+                        if (mButtonsPressed != buttonState) {
+                            problem(String.format("Reported button state differs from " +
+                                    "expected button state based on press and release events. " +
+                                    "Is 0x%08x but expected 0x%08x.",
+                                    buttonState, mButtonsPressed));
+                        }
+                        break;
+                    case MotionEvent.ACTION_BUTTON_RELEASE:
+                        ensureActionButtonIsNonZeroForThisAction(event);
+                        if ((mButtonsPressed & actionButton) != actionButton) {
+                            problem("Action button for ACTION_BUTTON_RELEASE event is " +
+                                    actionButton + ", but it was either never pressed or has " +
+                                    "already been released.");
+                        }
+
+                        mButtonsPressed &= ~actionButton;
+                        // The system will automatically mirror the stylus buttons onto the button
+                        // state as the old set of generic buttons for apps targeting pre-M. If
+                        // it looks this has happened, go ahead and set the generic buttons as
+                        // released to prevent spurious errors.
+                        if (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY &&
+                                (buttonState & MotionEvent.BUTTON_SECONDARY) == 0) {
+                            mButtonsPressed &= ~MotionEvent.BUTTON_SECONDARY;
+                        } else if (actionButton == MotionEvent.BUTTON_STYLUS_SECONDARY &&
+                                (buttonState & MotionEvent.BUTTON_TERTIARY) == 0) {
+                            mButtonsPressed &= ~MotionEvent.BUTTON_TERTIARY;
+                        }
+
+                        if (mButtonsPressed != buttonState) {
+                            problem(String.format("Reported button state differs from " +
+                                    "expected button state based on press and release events. " +
+                                    "Is 0x%08x but expected 0x%08x.",
+                                    buttonState, mButtonsPressed));
+                        }
+                        break;
+                    default:
+                        problem("Invalid action for generic pointer event.");
+                        break;
+                }
+            } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
+                switch (action) {
+                    case MotionEvent.ACTION_MOVE:
+                        ensurePointerCountIsOneForThisAction(event);
+                        break;
+                    default:
+                        problem("Invalid action for generic joystick event.");
+                        break;
+                }
+            }
+        } finally {
+            finishEvent();
+        }
+    }
+
+    /**
+     * Notifies the verifier that a given event was unhandled and the rest of the
+     * trace for the event should be ignored.
+     * This method should only be called if the event was previously checked by
+     * the consistency verifier using {@link #onInputEvent} and other methods.
+     * @param event The event.
+     * @param nestingLevel The nesting level: 0 if called from the base class,
+     * or 1 from a subclass.  If the event was already checked by this consistency verifier
+     * at a higher nesting level, it will not be checked again.  Used to handle the situation
+     * where a subclass dispatching method delegates to its superclass's dispatching method
+     * and both dispatching methods call into the consistency verifier.
+     */
+    @UnsupportedAppUsage
+    public void onUnhandledEvent(InputEvent event, int nestingLevel) {
+        if (nestingLevel != mLastNestingLevel) {
+            return;
+        }
+
+        if (mRecentEventsUnhandled != null) {
+            mRecentEventsUnhandled[mMostRecentEventIndex] = true;
+        }
+
+        if (event instanceof KeyEvent) {
+            final KeyEvent keyEvent = (KeyEvent)event;
+            final int deviceId = keyEvent.getDeviceId();
+            final int source = keyEvent.getSource();
+            final int keyCode = keyEvent.getKeyCode();
+            final KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ false);
+            if (state != null) {
+                state.unhandled = true;
+            }
+        } else {
+            final MotionEvent motionEvent = (MotionEvent)event;
+            if (motionEvent.isTouchEvent()) {
+                mTouchEventStreamUnhandled = true;
+            } else if ((motionEvent.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+                if (mTrackballDown) {
+                    mTrackballUnhandled = true;
+                }
+            }
+        }
+    }
+
+    private void ensureMetaStateIsNormalized(int metaState) {
+        final int normalizedMetaState = KeyEvent.normalizeMetaState(metaState);
+        if (normalizedMetaState != metaState) {
+            problem(String.format("Metastate not normalized.  Was 0x%08x but expected 0x%08x.",
+                    metaState, normalizedMetaState));
+        }
+    }
+
+    private void ensurePointerCountIsOneForThisAction(MotionEvent event) {
+        final int pointerCount = event.getPointerCount();
+        if (pointerCount != 1) {
+            problem("Pointer count is " + pointerCount + " but it should always be 1 for "
+                    + MotionEvent.actionToString(event.getAction()));
+        }
+    }
+
+    private void ensureActionButtonIsNonZeroForThisAction(MotionEvent event) {
+        final int actionButton = event.getActionButton();
+        if (actionButton == 0) {
+            problem("No action button set. Action button should always be non-zero for " +
+                    MotionEvent.actionToString(event.getAction()));
+
+        }
+    }
+
+    private void ensureHistorySizeIsZeroForThisAction(MotionEvent event) {
+        final int historySize = event.getHistorySize();
+        if (historySize != 0) {
+            problem("History size is " + historySize + " but it should always be 0 for "
+                    + MotionEvent.actionToString(event.getAction()));
+        }
+    }
+
+    private boolean startEvent(InputEvent event, int nestingLevel, String eventType) {
+        // Ignore the event if we already checked it at a higher nesting level.
+        final int seq = event.getSequenceNumber();
+        if (seq == mLastEventSeq && nestingLevel < mLastNestingLevel
+                && eventType == mLastEventType) {
+            return false;
+        }
+
+        if (nestingLevel > 0) {
+            mLastEventSeq = seq;
+            mLastEventType = eventType;
+            mLastNestingLevel = nestingLevel;
+        } else {
+            mLastEventSeq = -1;
+            mLastEventType = null;
+            mLastNestingLevel = 0;
+        }
+
+        mCurrentEvent = event;
+        mCurrentEventType = eventType;
+        return true;
+    }
+
+    private void finishEvent() {
+        if (mViolationMessage != null && mViolationMessage.length() != 0) {
+            if (!mCurrentEvent.isTainted()) {
+                // Write a log message only if the event was not already tainted.
+                mViolationMessage.append("\n  in ").append(mCaller);
+                mViolationMessage.append("\n  ");
+                appendEvent(mViolationMessage, 0, mCurrentEvent, false);
+
+                if (RECENT_EVENTS_TO_LOG != 0 && mRecentEvents != null) {
+                    mViolationMessage.append("\n  -- recent events --");
+                    for (int i = 0; i < RECENT_EVENTS_TO_LOG; i++) {
+                        final int index = (mMostRecentEventIndex + RECENT_EVENTS_TO_LOG - i)
+                                % RECENT_EVENTS_TO_LOG;
+                        final InputEvent event = mRecentEvents[index];
+                        if (event == null) {
+                            break;
+                        }
+                        mViolationMessage.append("\n  ");
+                        appendEvent(mViolationMessage, i + 1, event, mRecentEventsUnhandled[index]);
+                    }
+                }
+
+                Log.d(mLogTag, mViolationMessage.toString());
+
+                // Taint the event so that we do not generate additional violations from it
+                // further downstream.
+                mCurrentEvent.setTainted(true);
+            }
+            mViolationMessage.setLength(0);
+        }
+
+        if (RECENT_EVENTS_TO_LOG != 0) {
+            if (mRecentEvents == null) {
+                mRecentEvents = new InputEvent[RECENT_EVENTS_TO_LOG];
+                mRecentEventsUnhandled = new boolean[RECENT_EVENTS_TO_LOG];
+            }
+            final int index = (mMostRecentEventIndex + 1) % RECENT_EVENTS_TO_LOG;
+            mMostRecentEventIndex = index;
+            if (mRecentEvents[index] != null) {
+                mRecentEvents[index].recycle();
+            }
+            mRecentEvents[index] = mCurrentEvent.copy();
+            mRecentEventsUnhandled[index] = false;
+        }
+
+        mCurrentEvent = null;
+        mCurrentEventType = null;
+    }
+
+    private static void appendEvent(StringBuilder message, int index,
+            InputEvent event, boolean unhandled) {
+        message.append(index).append(": sent at ").append(event.getEventTimeNano());
+        message.append(", ");
+        if (unhandled) {
+            message.append("(unhandled) ");
+        }
+        message.append(event);
+    }
+
+    private void problem(String message) {
+        if (mViolationMessage == null) {
+            mViolationMessage = new StringBuilder();
+        }
+        if (mViolationMessage.length() == 0) {
+            mViolationMessage.append(mCurrentEventType).append(": ");
+        } else {
+            mViolationMessage.append("\n  ");
+        }
+        mViolationMessage.append(message);
+    }
+
+    private KeyState findKeyState(int deviceId, int source, int keyCode, boolean remove) {
+        KeyState last = null;
+        KeyState state = mKeyStateList;
+        while (state != null) {
+            if (state.deviceId == deviceId && state.source == source
+                    && state.keyCode == keyCode) {
+                if (remove) {
+                    if (last != null) {
+                        last.next = state.next;
+                    } else {
+                        mKeyStateList = state.next;
+                    }
+                    state.next = null;
+                }
+                return state;
+            }
+            last = state;
+            state = state.next;
+        }
+        return null;
+    }
+
+    private void addKeyState(int deviceId, int source, int keyCode) {
+        KeyState state = KeyState.obtain(deviceId, source, keyCode);
+        state.next = mKeyStateList;
+        mKeyStateList = state;
+    }
+
+    private static final class KeyState {
+        private static Object mRecycledListLock = new Object();
+        private static KeyState mRecycledList;
+
+        public KeyState next;
+        public int deviceId;
+        public int source;
+        public int keyCode;
+        public boolean unhandled;
+
+        private KeyState() {
+        }
+
+        public static KeyState obtain(int deviceId, int source, int keyCode) {
+            KeyState state;
+            synchronized (mRecycledListLock) {
+                state = mRecycledList;
+                if (state != null) {
+                    mRecycledList = state.next;
+                } else {
+                    state = new KeyState();
+                }
+            }
+            state.deviceId = deviceId;
+            state.source = source;
+            state.keyCode = keyCode;
+            state.unhandled = false;
+            return state;
+        }
+
+        public void recycle() {
+            synchronized (mRecycledListLock) {
+                next = mRecycledList;
+                mRecycledList = next;
+            }
+        }
+    }
+}
diff --git a/android/view/InputEventReceiver.java b/android/view/InputEventReceiver.java
new file mode 100644
index 0000000..25dda5b
--- /dev/null
+++ b/android/view/InputEventReceiver.java
@@ -0,0 +1,283 @@
+/*
+ * 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.view;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.MessageQueue;
+import android.os.Trace;
+import android.util.Log;
+import android.util.SparseIntArray;
+
+import dalvik.system.CloseGuard;
+
+import java.io.PrintWriter;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+
+/**
+ * Provides a low-level mechanism for an application to receive input events.
+ * @hide
+ */
+public abstract class InputEventReceiver {
+    private static final String TAG = "InputEventReceiver";
+
+    private final CloseGuard mCloseGuard = CloseGuard.get();
+
+    private long mReceiverPtr;
+
+    // We keep references to the input channel and message queue objects here so that
+    // they are not GC'd while the native peer of the receiver is using them.
+    private InputChannel mInputChannel;
+    private MessageQueue mMessageQueue;
+
+    // Map from InputEvent sequence numbers to dispatcher sequence numbers.
+    private final SparseIntArray mSeqMap = new SparseIntArray();
+
+    private static native long nativeInit(WeakReference<InputEventReceiver> receiver,
+            InputChannel inputChannel, MessageQueue messageQueue);
+    private static native void nativeDispose(long receiverPtr);
+    private static native void nativeFinishInputEvent(long receiverPtr, int seq, boolean handled);
+    private static native void nativeReportTimeline(long receiverPtr, int inputEventId,
+            long gpuCompletedTime, long presentTime);
+    private static native boolean nativeConsumeBatchedInputEvents(long receiverPtr,
+            long frameTimeNanos);
+    private static native String nativeDump(long receiverPtr, String prefix);
+
+    /**
+     * Creates an input event receiver bound to the specified input channel.
+     *
+     * @param inputChannel The input channel.
+     * @param looper The looper to use when invoking callbacks.
+     */
+    public InputEventReceiver(InputChannel inputChannel, Looper looper) {
+        if (inputChannel == null) {
+            throw new IllegalArgumentException("inputChannel must not be null");
+        }
+        if (looper == null) {
+            throw new IllegalArgumentException("looper must not be null");
+        }
+
+        mInputChannel = inputChannel;
+        mMessageQueue = looper.getQueue();
+        mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
+                inputChannel, mMessageQueue);
+
+        mCloseGuard.open("dispose");
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            dispose(true);
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Disposes the receiver.
+     * Must be called on the same Looper thread to which the receiver is attached.
+     */
+    public void dispose() {
+        dispose(false);
+    }
+
+    private void dispose(boolean finalized) {
+        if (mCloseGuard != null) {
+            if (finalized) {
+                mCloseGuard.warnIfOpen();
+            }
+            mCloseGuard.close();
+        }
+
+        if (mReceiverPtr != 0) {
+            nativeDispose(mReceiverPtr);
+            mReceiverPtr = 0;
+        }
+
+        if (mInputChannel != null) {
+            mInputChannel.dispose();
+            mInputChannel = null;
+        }
+        mMessageQueue = null;
+        Reference.reachabilityFence(this);
+    }
+
+    /**
+     * Called when an input event is received.
+     * The recipient should process the input event and then call {@link #finishInputEvent}
+     * to indicate whether the event was handled.  No new input events will be received
+     * until {@link #finishInputEvent} is called.
+     *
+     * @param event The input event that was received.
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public void onInputEvent(InputEvent event) {
+        finishInputEvent(event, false);
+    }
+
+    /**
+     * Called when a focus event is received.
+     *
+     * @param hasFocus if true, the window associated with this input channel has just received
+     *                 focus
+     *                 if false, the window associated with this input channel has just lost focus
+     * @param inTouchMode if true, the device is in touch mode
+     *                    if false, the device is not in touch mode
+     */
+    // Called from native code.
+    public void onFocusEvent(boolean hasFocus, boolean inTouchMode) {
+    }
+
+    /**
+     * Called when a Pointer Capture event is received.
+     *
+     * @param pointerCaptureEnabled if true, the window associated with this input channel has just
+     *                              received Pointer Capture
+     *                              if false, the window associated with this input channel has just
+     *                              lost Pointer Capture
+     * @see View#requestPointerCapture()
+     * @see View#releasePointerCapture()
+     */
+    // Called from native code.
+    public void onPointerCaptureEvent(boolean pointerCaptureEnabled) {
+    }
+
+    /**
+     * Called when a drag event is received, from native code.
+     *
+     * @param isExiting if false, the window associated with this input channel has just received
+     *                 drag
+     *                 if true, the window associated with this input channel has just lost drag
+     */
+    public void onDragEvent(boolean isExiting, float x, float y) {
+    }
+
+    /**
+     * Called when a batched input event is pending.
+     *
+     * The batched input event will continue to accumulate additional movement
+     * samples until the recipient calls {@link #consumeBatchedInputEvents} or
+     * an event is received that ends the batch and causes it to be consumed
+     * immediately (such as a pointer up event).
+     * @param source The source of the batched event.
+     */
+    public void onBatchedInputEventPending(int source) {
+        consumeBatchedInputEvents(-1);
+    }
+
+    /**
+     * Finishes an input event and indicates whether it was handled.
+     * Must be called on the same Looper thread to which the receiver is attached.
+     *
+     * @param event The input event that was finished.
+     * @param handled True if the event was handled.
+     */
+    public final void finishInputEvent(InputEvent event, boolean handled) {
+        if (event == null) {
+            throw new IllegalArgumentException("event must not be null");
+        }
+        if (mReceiverPtr == 0) {
+            Log.w(TAG, "Attempted to finish an input event but the input event "
+                    + "receiver has already been disposed.");
+        } else {
+            int index = mSeqMap.indexOfKey(event.getSequenceNumber());
+            if (index < 0) {
+                Log.w(TAG, "Attempted to finish an input event that is not in progress.");
+            } else {
+                int seq = mSeqMap.valueAt(index);
+                mSeqMap.removeAt(index);
+                nativeFinishInputEvent(mReceiverPtr, seq, handled);
+            }
+        }
+        event.recycleIfNeededAfterDispatch();
+    }
+
+    /**
+     * Report the timing / latency information for a specific input event.
+     */
+    public final void reportTimeline(int inputEventId, long gpuCompletedTime, long presentTime) {
+        Trace.traceBegin(Trace.TRACE_TAG_INPUT, "reportTimeline");
+        nativeReportTimeline(mReceiverPtr, inputEventId, gpuCompletedTime, presentTime);
+        Trace.traceEnd(Trace.TRACE_TAG_INPUT);
+    }
+
+    /**
+     * Consumes all pending batched input events.
+     * Must be called on the same Looper thread to which the receiver is attached.
+     *
+     * This method forces all batched input events to be delivered immediately.
+     * Should be called just before animating or drawing a new frame in the UI.
+     *
+     * @param frameTimeNanos The time in the {@link System#nanoTime()} time base
+     * when the current display frame started rendering, or -1 if unknown.
+     *
+     * @return Whether a batch was consumed
+     */
+    public final boolean consumeBatchedInputEvents(long frameTimeNanos) {
+        if (mReceiverPtr == 0) {
+            Log.w(TAG, "Attempted to consume batched input events but the input event "
+                    + "receiver has already been disposed.");
+        } else {
+            return nativeConsumeBatchedInputEvents(mReceiverPtr, frameTimeNanos);
+        }
+        return false;
+    }
+
+    /**
+     * @return Returns a token to identify the input channel.
+     */
+    public IBinder getToken() {
+        if (mInputChannel == null) {
+            return null;
+        }
+        return mInputChannel.getToken();
+    }
+
+    // Called from native code.
+    @SuppressWarnings("unused")
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private void dispatchInputEvent(int seq, InputEvent event) {
+        mSeqMap.put(event.getSequenceNumber(), seq);
+        onInputEvent(event);
+    }
+
+    /**
+     * Dump the state of this InputEventReceiver to the writer.
+     * @param prefix the prefix (typically whitespace padding) to append in front of each line
+     * @param writer the writer where the dump should be written
+     */
+    public void dump(String prefix, PrintWriter writer) {
+        writer.println(prefix + getClass().getName());
+        writer.println(prefix + " mInputChannel: " + mInputChannel);
+        writer.println(prefix + " mSeqMap: " + mSeqMap);
+        writer.println(prefix + " mReceiverPtr:\n" + nativeDump(mReceiverPtr, prefix + "  "));
+    }
+
+    /**
+     * Factory for InputEventReceiver
+     */
+    public interface Factory {
+        /**
+         * Create a new InputReceiver for a given inputChannel
+         */
+        InputEventReceiver createInputEventReceiver(InputChannel inputChannel, Looper looper);
+    }
+}
diff --git a/android/view/InputEventSender.java b/android/view/InputEventSender.java
new file mode 100644
index 0000000..d144218
--- /dev/null
+++ b/android/view/InputEventSender.java
@@ -0,0 +1,163 @@
+/*
+ * 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.view;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Looper;
+import android.os.MessageQueue;
+import android.util.Log;
+
+import dalvik.system.CloseGuard;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Provides a low-level mechanism for an application to send input events.
+ * @hide
+ */
+public abstract class InputEventSender {
+    private static final String TAG = "InputEventSender";
+
+    private final CloseGuard mCloseGuard = CloseGuard.get();
+
+    private long mSenderPtr;
+
+    // We keep references to the input channel and message queue objects here so that
+    // they are not GC'd while the native peer of the receiver is using them.
+    private InputChannel mInputChannel;
+    private MessageQueue mMessageQueue;
+
+    private static native long nativeInit(WeakReference<InputEventSender> sender,
+            InputChannel inputChannel, MessageQueue messageQueue);
+    private static native void nativeDispose(long senderPtr);
+    private static native boolean nativeSendKeyEvent(long senderPtr, int seq, KeyEvent event);
+    private static native boolean nativeSendMotionEvent(long senderPtr, int seq, MotionEvent event);
+
+    /**
+     * Creates an input event sender bound to the specified input channel.
+     *
+     * @param inputChannel The input channel.
+     * @param looper The looper to use when invoking callbacks.
+     */
+    public InputEventSender(InputChannel inputChannel, Looper looper) {
+        if (inputChannel == null) {
+            throw new IllegalArgumentException("inputChannel must not be null");
+        }
+        if (looper == null) {
+            throw new IllegalArgumentException("looper must not be null");
+        }
+
+        mInputChannel = inputChannel;
+        mMessageQueue = looper.getQueue();
+        mSenderPtr = nativeInit(new WeakReference<InputEventSender>(this),
+                inputChannel, mMessageQueue);
+
+        mCloseGuard.open("dispose");
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            dispose(true);
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Disposes the receiver.
+     */
+    public void dispose() {
+        dispose(false);
+    }
+
+    private void dispose(boolean finalized) {
+        if (mCloseGuard != null) {
+            if (finalized) {
+                mCloseGuard.warnIfOpen();
+            }
+            mCloseGuard.close();
+        }
+
+        if (mSenderPtr != 0) {
+            nativeDispose(mSenderPtr);
+            mSenderPtr = 0;
+        }
+        mInputChannel = null;
+        mMessageQueue = null;
+    }
+
+    /**
+     * Called when an input event is finished.
+     *
+     * @param seq The input event sequence number.
+     * @param handled True if the input event was handled.
+     */
+    public void onInputEventFinished(int seq, boolean handled) {
+    }
+
+    /**
+     * Called when timeline is sent to the publisher.
+     *
+     * @param inputEventId The id of the input event that caused the frame being reported
+     * @param gpuCompletedTime The time when the frame left the app process
+     * @param presentTime The time when the frame was presented on screen
+     */
+    public void onTimelineReported(int inputEventId, long gpuCompletedTime, long presentTime) {
+    }
+
+    /**
+     * Sends an input event.
+     * Must be called on the same Looper thread to which the sender is attached.
+     *
+     * @param seq The input event sequence number.
+     * @param event The input event to send.
+     * @return True if the entire event was sent successfully.  May return false
+     * if the input channel buffer filled before all samples were dispatched.
+     */
+    public final boolean sendInputEvent(int seq, InputEvent event) {
+        if (event == null) {
+            throw new IllegalArgumentException("event must not be null");
+        }
+        if (mSenderPtr == 0) {
+            Log.w(TAG, "Attempted to send an input event but the input event "
+                    + "sender has already been disposed.");
+            return false;
+        }
+
+        if (event instanceof KeyEvent) {
+            return nativeSendKeyEvent(mSenderPtr, seq, (KeyEvent)event);
+        } else {
+            return nativeSendMotionEvent(mSenderPtr, seq, (MotionEvent)event);
+        }
+    }
+
+    // Called from native code.
+    @SuppressWarnings("unused")
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private void dispatchInputEventFinished(int seq, boolean handled) {
+        onInputEventFinished(seq, handled);
+    }
+
+    // Called from native code.
+    @SuppressWarnings("unused")
+    private void dispatchTimelineReported(
+            int inputEventId, long gpuCompletedTime, long presentTime) {
+        onTimelineReported(inputEventId, gpuCompletedTime, presentTime);
+    }
+}
diff --git a/android/view/InputFilter.java b/android/view/InputFilter.java
new file mode 100644
index 0000000..b18c5cd
--- /dev/null
+++ b/android/view/InputFilter.java
@@ -0,0 +1,259 @@
+/*
+ * 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.view;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+
+/**
+ * Filters input events before they are dispatched to the system.
+ * <p>
+ * At most one input filter can be installed by calling
+ * {@link WindowManagerService#setInputFilter}.  When an input filter is installed, the
+ * system's behavior changes as follows:
+ * <ul>
+ * <li>Input events are first delivered to the {@link WindowManagerPolicy}
+ * interception methods before queuing as usual.  This critical step takes care of managing
+ * the power state of the device and handling wake keys.</li>
+ * <li>Input events are then asynchronously delivered to the input filter's
+ * {@link #onInputEvent(InputEvent)} method instead of being enqueued for dispatch to
+ * applications as usual.  The input filter only receives input events that were
+ * generated by an input device; the input filter will not receive input events that were
+ * injected into the system by other means, such as by instrumentation.</li>
+ * <li>The input filter processes and optionally transforms the stream of events.  For example,
+ * it may transform a sequence of motion events representing an accessibility gesture into
+ * a different sequence of motion events, key presses or other system-level interactions.
+ * The input filter can send events to be dispatched by calling
+ * {@link #sendInputEvent(InputEvent)} and passing appropriate policy flags for the
+ * input event.</li>
+ * </ul>
+ * </p>
+ * <h3>The importance of input event consistency</h3>
+ * <p>
+ * The input filter mechanism is very low-level.  At a minimum, it needs to ensure that it
+ * sends an internally consistent stream of input events to the dispatcher.  There are
+ * very important invariants to be maintained.
+ * </p><p>
+ * For example, if a key down is sent, a corresponding key up should also be sent eventually.
+ * Likewise, for touch events, each pointer must individually go down with
+ * {@link MotionEvent#ACTION_DOWN} or {@link MotionEvent#ACTION_POINTER_DOWN} and then
+ * individually go up with {@link MotionEvent#ACTION_POINTER_UP} or {@link MotionEvent#ACTION_UP}
+ * and the sequence of pointer ids used must be consistent throughout the gesture.
+ * </p><p>
+ * Sometimes a filter may wish to cancel a previously dispatched key or motion.  It should
+ * use {@link KeyEvent#FLAG_CANCELED} or {@link MotionEvent#ACTION_CANCEL} accordingly.
+ * </p><p>
+ * The input filter must take into account the fact that the input events coming from different
+ * devices or even different sources all consist of distinct streams of input.
+ * Use {@link InputEvent#getDeviceId()} and {@link InputEvent#getSource()} to identify
+ * the source of the event and its semantics.  There may be multiple sources of keys,
+ * touches and other input: they must be kept separate.
+ * </p>
+ * <h3>Policy flags</h3>
+ * <p>
+ * Input events received from the dispatcher and sent to the dispatcher have policy flags
+ * associated with them.  Policy flags control some functions of the dispatcher.
+ * </p><p>
+ * The early policy interception decides whether an input event should be delivered
+ * to applications or dropped.  The policy indicates its decision by setting the
+ * {@link WindowManagerPolicyConstants#FLAG_PASS_TO_USER} policy flag.  The input filter may
+ * sometimes receive events that do not have this flag set.  It should take note of
+ * the fact that the policy intends to drop the event, clean up its state, and
+ * then send appropriate cancellation events to the dispatcher if needed.
+ * </p><p>
+ * For example, suppose the input filter is processing a gesture and one of the touch events
+ * it receives does not have the {@link WindowManagerPolicyConstants#FLAG_PASS_TO_USER} flag set.
+ * The input filter should clear its internal state about the gesture and then send key or
+ * motion events to the dispatcher to cancel any keys or pointers that are down.
+ * </p><p>
+ * Corollary: Events that get sent to the dispatcher should usually include the
+ * {@link WindowManagerPolicyConstants#FLAG_PASS_TO_USER} flag.  Otherwise, they will be dropped!
+ * </p><p>
+ * It may be prudent to disable automatic key repeating for synthetic key events
+ * by setting the {@link WindowManagerPolicyConstants#FLAG_DISABLE_KEY_REPEAT} policy flag.
+ * </p>
+ *
+ * @hide
+ */
+public abstract class InputFilter extends IInputFilter.Stub {
+    private static final int MSG_INSTALL = 1;
+    private static final int MSG_UNINSTALL = 2;
+    private static final int MSG_INPUT_EVENT = 3;
+
+    // Consistency verifiers for debugging purposes.
+    private final InputEventConsistencyVerifier mInboundInputEventConsistencyVerifier =
+            InputEventConsistencyVerifier.isInstrumentationEnabled() ?
+                    new InputEventConsistencyVerifier(this,
+                            InputEventConsistencyVerifier.FLAG_RAW_DEVICE_INPUT,
+                            "InputFilter#InboundInputEventConsistencyVerifier") : null;
+    private final InputEventConsistencyVerifier mOutboundInputEventConsistencyVerifier =
+            InputEventConsistencyVerifier.isInstrumentationEnabled() ?
+                    new InputEventConsistencyVerifier(this,
+                            InputEventConsistencyVerifier.FLAG_RAW_DEVICE_INPUT,
+                            "InputFilter#OutboundInputEventConsistencyVerifier") : null;
+
+    private final H mH;
+
+    private IInputFilterHost mHost;
+
+    /**
+     * Creates the input filter.
+     *
+     * @param looper The looper to run callbacks on.
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public InputFilter(Looper looper) {
+        mH = new H(looper);
+    }
+
+    /**
+     * Called when the input filter is installed.
+     * This method is guaranteed to be non-reentrant.
+     *
+     * @param host The input filter host environment.
+     */
+    public final void install(IInputFilterHost host) {
+        mH.obtainMessage(MSG_INSTALL, host).sendToTarget();
+    }
+
+    /**
+     * Called when the input filter is uninstalled.
+     * This method is guaranteed to be non-reentrant.
+     */
+    public final void uninstall() {
+        mH.obtainMessage(MSG_UNINSTALL).sendToTarget();
+    }
+
+    /**
+     * Called to enqueue the input event for filtering.
+     * The event will be recycled after the input filter processes it.
+     * This method is guaranteed to be non-reentrant.
+     *
+     * @param event The input event to enqueue.
+     */
+    final public void filterInputEvent(InputEvent event, int policyFlags) {
+        mH.obtainMessage(MSG_INPUT_EVENT, policyFlags, 0, event).sendToTarget();
+    }
+
+    /**
+     * Sends an input event to the dispatcher.
+     *
+     * @param event The input event to publish.
+     * @param policyFlags The input event policy flags.
+     */
+    public void sendInputEvent(InputEvent event, int policyFlags) {
+        if (event == null) {
+            throw new IllegalArgumentException("event must not be null");
+        }
+        if (mHost == null) {
+            throw new IllegalStateException("Cannot send input event because the input filter " +
+                    "is not installed.");
+        }
+        if (mOutboundInputEventConsistencyVerifier != null) {
+            mOutboundInputEventConsistencyVerifier.onInputEvent(event, 0);
+        }
+        try {
+            mHost.sendInputEvent(event, policyFlags);
+        } catch (RemoteException re) {
+            /* ignore */
+        }
+    }
+
+    /**
+     * Called when an input event has been received from the dispatcher.
+     * <p>
+     * The default implementation sends the input event back to the dispatcher, unchanged.
+     * </p><p>
+     * The event will be recycled when this method returns.  If you want to keep it around,
+     * make a copy!
+     * </p>
+     *
+     * @param event The input event that was received.
+     * @param policyFlags The input event policy flags.
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public void onInputEvent(InputEvent event, int policyFlags) {
+        sendInputEvent(event, policyFlags);
+    }
+
+    /**
+     * Called when the filter is installed into the dispatch pipeline.
+     * <p>
+     * This method is called before the input filter receives any input events.
+     * The input filter should take this opportunity to prepare itself.
+     * </p>
+     */
+    public void onInstalled() {
+    }
+
+    /**
+     * Called when the filter is uninstalled from the dispatch pipeline.
+     * <p>
+     * This method is called after the input filter receives its last input event.
+     * The input filter should take this opportunity to clean up.
+     * </p>
+     */
+    public void onUninstalled() {
+    }
+
+    private final class H extends Handler {
+        public H(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_INSTALL:
+                    mHost = (IInputFilterHost) msg.obj;
+                    if (mInboundInputEventConsistencyVerifier != null) {
+                        mInboundInputEventConsistencyVerifier.reset();
+                    }
+                    if (mOutboundInputEventConsistencyVerifier != null) {
+                        mOutboundInputEventConsistencyVerifier.reset();
+                    }
+                    onInstalled();
+                    break;
+
+                case MSG_UNINSTALL:
+                    try {
+                        onUninstalled();
+                    } finally {
+                        mHost = null;
+                    }
+                    break;
+
+                case MSG_INPUT_EVENT: {
+                    final InputEvent event = (InputEvent)msg.obj;
+                    try {
+                        if (mInboundInputEventConsistencyVerifier != null) {
+                            mInboundInputEventConsistencyVerifier.onInputEvent(event, 0);
+                        }
+                        onInputEvent(event, msg.arg1);
+                    } finally {
+                        event.recycle();
+                    }
+                    break;
+                }
+            }
+        }
+    }
+}
diff --git a/android/view/InputMonitor.java b/android/view/InputMonitor.java
new file mode 100644
index 0000000..ad1f201
--- /dev/null
+++ b/android/view/InputMonitor.java
@@ -0,0 +1,183 @@
+/*
+ * 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.view;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * An {@code InputMonitor} allows privileged applications and components to monitor streams of
+ * {@link InputEvent}s without having to be the designated recipient for the event.
+ *
+ * For example, focus dispatched events would normally only go to the focused window on the
+ * targeted display, but an {@code InputMonitor} will also receive a copy of that event if they're
+ * registered to monitor that type of event on the targeted display.
+ *
+ * @hide
+ */
+@DataClass(genToString = true)
+public final class InputMonitor implements Parcelable {
+    private static final String TAG = "InputMonitor";
+
+    private static final boolean DEBUG = false;
+
+    @NonNull
+    private final InputChannel mInputChannel;
+    @NonNull
+    private final IInputMonitorHost mHost;
+
+
+    /**
+     * Takes all of the current pointer events streams that are currently being sent to this
+     * monitor and generates appropriate cancellations for the windows that would normally get
+     * them.
+     *
+     * This method should be used with caution as unexpected pilfering can break fundamental user
+     * interactions.
+     */
+    public void pilferPointers() {
+        try {
+            mHost.pilferPointers();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Disposes the input monitor.
+     *
+     * Explicitly release all of the resources this monitor is holding on to (e.g. the
+     * InputChannel). Once this method is called, this monitor and any resources it's provided may
+     * no longer be used.
+     */
+    public void dispose() {
+        mInputChannel.dispose();
+        try {
+            mHost.dispose();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+
+
+    // Code below generated by codegen v1.0.7.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/InputMonitor.java
+
+
+    @DataClass.Generated.Member
+    public InputMonitor(
+            @NonNull InputChannel inputChannel,
+            @NonNull IInputMonitorHost host) {
+        this.mInputChannel = inputChannel;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mInputChannel);
+        this.mHost = host;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mHost);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull InputChannel getInputChannel() {
+        return mInputChannel;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull IInputMonitorHost getHost() {
+        return mHost;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "InputMonitor { " +
+                "inputChannel = " + mInputChannel + ", " +
+                "host = " + mHost +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeTypedObject(mInputChannel, flags);
+        dest.writeStrongInterface(mHost);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ InputMonitor(Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        InputChannel inputChannel = (InputChannel) in.readTypedObject(InputChannel.CREATOR);
+        IInputMonitorHost host = IInputMonitorHost.Stub.asInterface(in.readStrongBinder());
+
+        this.mInputChannel = inputChannel;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mInputChannel);
+        this.mHost = host;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mHost);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<InputMonitor> CREATOR
+            = new Parcelable.Creator<InputMonitor>() {
+        @Override
+        public InputMonitor[] newArray(int size) {
+            return new InputMonitor[size];
+        }
+
+        @Override
+        public InputMonitor createFromParcel(Parcel in) {
+            return new InputMonitor(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1571177265149L,
+            codegenVersion = "1.0.7",
+            sourceFile = "frameworks/base/core/java/android/view/InputMonitor.java",
+            inputSignatures = "private static final  java.lang.String TAG\nprivate static final  boolean DEBUG\nprivate final @android.annotation.NonNull android.view.InputChannel mInputChannel\nprivate final @android.annotation.NonNull android.view.IInputMonitorHost mHost\npublic  void pilferPointers()\npublic  void dispose()\nclass InputMonitor extends java.lang.Object implements [android.os.Parcelable]\[email protected](genToString=true)")
+    @Deprecated
+    private void __metadata() {}
+
+}
diff --git a/android/view/InputQueue.java b/android/view/InputQueue.java
new file mode 100644
index 0000000..7accb66
--- /dev/null
+++ b/android/view/InputQueue.java
@@ -0,0 +1,165 @@
+/*
+ * 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.view;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Looper;
+import android.os.MessageQueue;
+import android.util.LongSparseArray;
+import android.util.Pools.Pool;
+import android.util.Pools.SimplePool;
+
+import dalvik.system.CloseGuard;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * An input queue provides a mechanism for an application to receive incoming
+ * input events.  Currently only usable from native code.
+ */
+public final class InputQueue {
+    private final LongSparseArray<ActiveInputEvent> mActiveEventArray =
+            new LongSparseArray<ActiveInputEvent>(20);
+    private final Pool<ActiveInputEvent> mActiveInputEventPool =
+            new SimplePool<ActiveInputEvent>(20);
+
+    private final CloseGuard mCloseGuard = CloseGuard.get();
+
+    private long mPtr;
+
+    private static native long nativeInit(WeakReference<InputQueue> weakQueue,
+            MessageQueue messageQueue);
+    private static native long nativeSendKeyEvent(long ptr, KeyEvent e, boolean preDispatch);
+    private static native long nativeSendMotionEvent(long ptr, MotionEvent e);
+    private static native void nativeDispose(long ptr);
+
+    /** @hide */
+    public InputQueue() {
+        mPtr = nativeInit(new WeakReference<InputQueue>(this), Looper.myQueue());
+
+        mCloseGuard.open("dispose");
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            dispose(true);
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /** @hide */
+    public void dispose() {
+        dispose(false);
+    }
+
+    /** @hide */
+    public void dispose(boolean finalized) {
+        if (mCloseGuard != null) {
+            if (finalized) {
+                mCloseGuard.warnIfOpen();
+            }
+            mCloseGuard.close();
+        }
+
+        if (mPtr != 0) {
+            nativeDispose(mPtr);
+            mPtr = 0;
+        }
+    }
+
+    /** @hide */
+    public long getNativePtr() {
+        return mPtr;
+    }
+
+    /** @hide */
+    public void sendInputEvent(InputEvent e, Object token, boolean predispatch,
+            FinishedInputEventCallback callback) {
+        ActiveInputEvent event = obtainActiveInputEvent(token, callback);
+        long id;
+        if (e instanceof KeyEvent) {
+            id = nativeSendKeyEvent(mPtr, (KeyEvent) e, predispatch);
+        } else {
+            id = nativeSendMotionEvent(mPtr, (MotionEvent) e);
+        }
+        mActiveEventArray.put(id, event);
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private void finishInputEvent(long id, boolean handled) {
+        int index = mActiveEventArray.indexOfKey(id);
+        if (index >= 0) {
+            ActiveInputEvent e = mActiveEventArray.valueAt(index);
+            mActiveEventArray.removeAt(index);
+            e.mCallback.onFinishedInputEvent(e.mToken, handled);
+            recycleActiveInputEvent(e);
+        }
+    }
+
+    private ActiveInputEvent obtainActiveInputEvent(Object token,
+            FinishedInputEventCallback callback) {
+        ActiveInputEvent e = mActiveInputEventPool.acquire();
+        if (e == null) {
+            e = new ActiveInputEvent();
+        }
+        e.mToken = token;
+        e.mCallback = callback;
+        return e;
+    }
+
+    private void recycleActiveInputEvent(ActiveInputEvent e) {
+        e.recycle();
+        mActiveInputEventPool.release(e);
+    }
+
+    private final class ActiveInputEvent {
+        public Object mToken;
+        public FinishedInputEventCallback mCallback;
+
+        public void recycle() {
+            mToken = null;
+            mCallback = null;
+        }
+    }
+
+    /**
+     * Interface to receive notification of when an InputQueue is associated
+     * and dissociated with a thread.
+     */
+    public static interface Callback {
+        /**
+         * Called when the given InputQueue is now associated with the
+         * thread making this call, so it can start receiving events from it.
+         */
+        void onInputQueueCreated(InputQueue queue);
+
+        /**
+         * Called when the given InputQueue is no longer associated with
+         * the thread and thus not dispatching events.
+         */
+        void onInputQueueDestroyed(InputQueue queue);
+    }
+
+    /** @hide */
+    public static interface FinishedInputEventCallback {
+        void onFinishedInputEvent(Object token, boolean handled);
+    }
+
+}
diff --git a/android/view/InputStageBenchmark.java b/android/view/InputStageBenchmark.java
new file mode 100644
index 0000000..b45dbcf
--- /dev/null
+++ b/android/view/InputStageBenchmark.java
@@ -0,0 +1,237 @@
+/*
+ * 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.view;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.perftests.utils.PerfTestActivity;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@LargeTest
+@RunWith(Parameterized.class)
+public class InputStageBenchmark {
+    @Parameterized.Parameters(name = "mShowIme({0}), mHandlePreIme({1})")
+    public static Collection cases() {
+        return Arrays.asList(new Object[][] {
+                { false /* no ime */, false /* skip preime */},
+                { true /* show ime */, false /* skip preime */},
+                { true /* show ime */, true /* handle preime */}
+        });
+    }
+
+    @Rule
+    public final ActivityTestRule<PerfTestActivity> mActivityRule =
+            new ActivityTestRule<>(PerfTestActivity.class);
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Parameterized.Parameter(0)
+    public boolean mShowIme;
+    @Parameterized.Parameter(1)
+    public boolean mHandlePreIme;
+
+    private Instrumentation mInstrumentation;
+    private Window mWindow;
+    private CountDownLatch mWaitForReceiveInput;
+    private static final long TIMEOUT_MS = 5000;
+
+    class InstrumentedView extends View {
+        InstrumentedView(Context context) {
+            super(context);
+            setFocusable(true);
+        }
+
+        @Override
+        public boolean dispatchKeyEventPreIme(KeyEvent event) {
+            if (mHandlePreIme) {
+                mWaitForReceiveInput.countDown();
+            }
+            return mHandlePreIme;
+        }
+
+        @Override
+        public boolean dispatchTouchEvent(MotionEvent event) {
+            mWaitForReceiveInput.countDown();
+            return true;
+        }
+
+        @Override
+        public boolean dispatchKeyEvent(KeyEvent event) {
+            mWaitForReceiveInput.countDown();
+            return true;
+        }
+    }
+
+    class InstrumentedEditText extends EditText {
+        InstrumentedEditText(Context context) {
+            super(context);
+            setFocusable(true);
+        }
+
+        @Override
+        public boolean dispatchKeyEventPreIme(KeyEvent event) {
+            if (mHandlePreIme) {
+                mWaitForReceiveInput.countDown();
+            }
+            return mHandlePreIme;
+        }
+
+        @Override
+        public boolean dispatchTouchEvent(MotionEvent event) {
+            mWaitForReceiveInput.countDown();
+            return true;
+        }
+
+        @Override
+        public boolean dispatchKeyEvent(KeyEvent event) {
+            mWaitForReceiveInput.countDown();
+            return true;
+        }
+    }
+
+    private CountDownLatch showSoftKeyboard(View view) {
+        final CountDownLatch waitForIme = new CountDownLatch(1);
+        view.setOnApplyWindowInsetsListener((v, insets) -> {
+            if (insets.isVisible(WindowInsets.Type.ime())) {
+                waitForIme.countDown();
+            }
+            return insets;
+        });
+
+        assertTrue("Failed to request focus.", view.requestFocus());
+        final InputMethodManager imm =
+                mActivityRule.getActivity().getSystemService(InputMethodManager.class);
+        imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
+
+        return waitForIme;
+    }
+
+    @Before
+    public void setUp() {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        final Activity activity = mActivityRule.getActivity();
+
+        final CountDownLatch[] waitForIme = new CountDownLatch[1];
+        mInstrumentation.runOnMainSync(() -> {
+            mWindow = mActivityRule.getActivity().getWindow();
+
+            if (mShowIme) {
+                final EditText edit = new InstrumentedEditText(activity);
+                mWindow.setContentView(edit);
+                waitForIme[0] = showSoftKeyboard(edit);
+            } else {
+                final View v = new InstrumentedView(activity);
+                // set FLAG_LOCAL_FOCUS_MODE to prevent delivering input events to the ime
+                // in ImeInputStage.
+                mWindow.addFlags(WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE);
+                mWindow.setContentView(v);
+                assertTrue("Failed to request focus.", v.requestFocus());
+            }
+        });
+        if (waitForIme[0] != null) {
+            try {
+                assertTrue("Failed to show InputMethod.",
+                        waitForIme[0].await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+        mInstrumentation.waitForIdleSync();
+    }
+
+    private void injectInputEvent(InputEvent event) {
+        mWaitForReceiveInput = new CountDownLatch(1);
+        mInstrumentation.runOnMainSync(() -> mWindow.injectInputEvent(event));
+        try {
+            mWaitForReceiveInput.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Test
+    public void testKeyEvent() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            final KeyEvent eventDown =
+                    new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACKSLASH);
+            injectInputEvent(eventDown);
+
+            state.pauseTiming();
+            final KeyEvent eventUp =
+                    new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACKSLASH);
+            injectInputEvent(eventUp);
+            state.resumeTiming();
+        }
+    }
+
+    @Test
+    public void testMotionEvent() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final Rect contentFrame = new Rect();
+        mInstrumentation.runOnMainSync(() ->
+                mWindow.getDecorView().getBoundsOnScreen(contentFrame));
+        final int x = contentFrame.centerX();
+        final int y = contentFrame.centerY();
+        final long eventTime = SystemClock.uptimeMillis();
+
+        while (state.keepRunning()) {
+            final MotionEvent eventDown = MotionEvent.obtain(eventTime, eventTime,
+                    MotionEvent.ACTION_DOWN, x, y,
+                    1.0f /* pressure */, 1.0f /* size */, 0 /* metaState */,
+                    1.0f /* xPrecision */, 1.0f /* yPrecision */,
+                    0 /* deviceId */, 0 /* edgeFlags */,
+                    InputDevice.SOURCE_TOUCHSCREEN, DEFAULT_DISPLAY);
+            injectInputEvent(eventDown);
+
+            state.pauseTiming();
+            final MotionEvent eventUp = MotionEvent.obtain(eventTime, eventTime,
+                    MotionEvent.ACTION_UP, x, y,
+                    1.0f /* pressure */, 1.0f /* size */, 0 /* metaState */,
+                    1.0f /* xPrecision */, 1.0f /* yPrecision */,
+                    0 /* deviceId */, 0 /* edgeFlags */,
+                    InputDevice.SOURCE_TOUCHSCREEN, DEFAULT_DISPLAY);
+            injectInputEvent(eventUp);
+            state.resumeTiming();
+        }
+    }
+}
diff --git a/android/view/InputWindowHandle.java b/android/view/InputWindowHandle.java
new file mode 100644
index 0000000..5a34a92
--- /dev/null
+++ b/android/view/InputWindowHandle.java
@@ -0,0 +1,170 @@
+/*
+ * 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.view;
+
+import static android.view.Display.INVALID_DISPLAY;
+
+import android.annotation.Nullable;
+import android.graphics.Region;
+import android.os.IBinder;
+import android.os.TouchOcclusionMode;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Functions as a handle for a window that can receive input.
+ * Enables the native input dispatcher to refer indirectly to the window manager's window state.
+ * @hide
+ */
+public final class InputWindowHandle {
+    // Pointer to the native input window handle.
+    // This field is lazily initialized via JNI.
+    @SuppressWarnings("unused")
+    private long ptr;
+
+    // The input application handle.
+    public InputApplicationHandle inputApplicationHandle;
+
+    // The token associates input data with a window and its input channel. The client input
+    // channel and the server input channel will both contain this token.
+    public IBinder token;
+
+    // The window name.
+    public String name;
+
+    // Window layout params attributes.  (WindowManager.LayoutParams)
+    public int layoutParamsFlags;
+    public int layoutParamsType;
+
+    // Dispatching timeout.
+    public long dispatchingTimeoutMillis;
+
+    // Window frame.
+    public int frameLeft;
+    public int frameTop;
+    public int frameRight;
+    public int frameBottom;
+
+    public int surfaceInset;
+
+    // Global scaling factor applied to touch events when they are dispatched
+    // to the window
+    public float scaleFactor;
+
+    // Window touchable region.
+    public final Region touchableRegion = new Region();
+
+    // Window is visible.
+    public boolean visible;
+
+    // Window can be focused.
+    public boolean focusable;
+
+    // Window has wallpaper.  (window is the current wallpaper target)
+    public boolean hasWallpaper;
+
+    // Input event dispatching is paused.
+    public boolean paused;
+
+    // Window is trusted overlay.
+    public boolean trustedOverlay;
+
+    // What effect this window has on touch occlusion if it lets touches pass through
+    // By default windows will block touches if they are untrusted and from a different UID due to
+    // security concerns
+    public int touchOcclusionMode = TouchOcclusionMode.BLOCK_UNTRUSTED;
+
+    // Id of process and user that owns the window.
+    public int ownerPid;
+    public int ownerUid;
+
+    // Owner package of the window
+    public String packageName;
+
+    // Window input features.
+    public int inputFeatures;
+
+    // Display this input is on.
+    public int displayId;
+
+    // If this value is set to a valid display ID, it indicates this window is a portal which
+    // transports the touch of this window to the display indicated by portalToDisplayId.
+    public int portalToDisplayId = INVALID_DISPLAY;
+
+    /**
+     * Crops the touchable region to the bounds of the surface provided.
+     *
+     * This can be used in cases where the window is not
+     * {@link android.view.WindowManager#FLAG_NOT_TOUCH_MODAL} but should be constrained to the
+     * bounds of a parent window. That is the window should receive touch events outside its
+     * window but be limited to its stack bounds, such as in the case of split screen.
+     */
+    public WeakReference<SurfaceControl> touchableRegionSurfaceControl = new WeakReference<>(null);
+
+    /**
+     * Replace {@link touchableRegion} with the bounds of {@link touchableRegionSurfaceControl}. If
+     * the handle is {@code null}, the bounds of the surface associated with this window is used
+     * as the touchable region.
+     */
+    public boolean replaceTouchableRegionWithCrop;
+
+    private native void nativeDispose();
+
+    public InputWindowHandle(InputApplicationHandle inputApplicationHandle, int displayId) {
+        this.inputApplicationHandle = inputApplicationHandle;
+        this.displayId = displayId;
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuilder(name != null ? name : "")
+                .append(", frame=[").append(frameLeft).append(",").append(frameTop).append(",")
+                        .append(frameRight).append(",").append(frameBottom).append("]")
+                .append(", touchableRegion=").append(touchableRegion)
+                .append(", visible=").append(visible)
+                .toString();
+
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            nativeDispose();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Set the window touchable region to the bounds of {@link touchableRegionBounds} ignoring any
+     * touchable region provided.
+     *
+     * @param bounds surface to set the touchable region to. Set to {@code null} to set the bounds
+     * to the current surface.
+     */
+    public void replaceTouchableRegionWithCrop(@Nullable SurfaceControl bounds) {
+        setTouchableRegionCrop(bounds);
+        replaceTouchableRegionWithCrop = true;
+    }
+
+    /**
+     * Crop the window touchable region to the bounds of the surface provided.
+     */
+    public void setTouchableRegionCrop(@Nullable SurfaceControl bounds) {
+        touchableRegionSurfaceControl = new WeakReference<>(bounds);
+    }
+}
diff --git a/android/view/InsetsAnimationControlCallbacks.java b/android/view/InsetsAnimationControlCallbacks.java
new file mode 100644
index 0000000..3431c3e
--- /dev/null
+++ b/android/view/InsetsAnimationControlCallbacks.java
@@ -0,0 +1,79 @@
+/*
+ * 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.view;
+
+import android.view.WindowInsets.Type.InsetsType;
+import android.view.WindowInsetsAnimation.Bounds;
+
+/**
+ * Provide an interface to let InsetsAnimationControlImpl call back into its owner.
+ * @hide
+ */
+public interface InsetsAnimationControlCallbacks {
+
+    /**
+     * Executes the necessary code to start the animation in the correct order, including:
+     * <ul>
+     *     <li>Dispatch {@link WindowInsetsAnimation.Callback#onPrepare}</li>
+     *     <li>Update insets state and run layout according to {@code layoutDuringAnimation}</li>
+     *     <li>Dispatch {@link WindowInsetsAnimation.Callback#onStart}</li>
+     *     <li>Dispatch {@link WindowInsetsAnimationControlListener#onReady}</li>
+     * </ul>
+     */
+    void startAnimation(InsetsAnimationControlImpl controller,
+            WindowInsetsAnimationControlListener listener, int types,
+            WindowInsetsAnimation animation,
+            Bounds bounds);
+
+    /**
+     * Schedule the apply by posting the animation callback.
+     *
+     * @param runner The runner that requested applying insets
+     */
+    void scheduleApplyChangeInsets(InsetsAnimationControlRunner runner);
+
+    /**
+     * Finish the final steps after the animation.
+     * @param runner The runner used to run the animation.
+     * @param shown {@code true} if the insets are shown.
+     */
+    void notifyFinished(InsetsAnimationControlRunner runner, boolean shown);
+
+    /**
+     * Apply the new params to the surface.
+     * @param params The {@link android.view.SyncRtSurfaceTransactionApplier.SurfaceParams} to
+     *               apply.
+     */
+    void applySurfaceParams(SyncRtSurfaceTransactionApplier.SurfaceParams... params);
+
+    /**
+     * Post a message to release the Surface, guaranteed to happen after all
+     * previous calls to applySurfaceParams.
+     */
+    void releaseSurfaceControlFromRt(SurfaceControl sc);
+
+    /**
+     * Reports that the perceptibility of the given types has changed to the given value.
+     *
+     * A type is perceptible if it is not (almost) entirely off-screen and not (almost) entirely
+     * transparent.
+     *
+     * @param types the (public) types whose perceptibility has changed
+     * @param perceptible true, if the types are now perceptible, false if they are not perceptible
+     */
+    void reportPerceptible(@InsetsType int types, boolean perceptible);
+}
diff --git a/android/view/InsetsAnimationControlImpl.java b/android/view/InsetsAnimationControlImpl.java
new file mode 100644
index 0000000..17b3020
--- /dev/null
+++ b/android/view/InsetsAnimationControlImpl.java
@@ -0,0 +1,516 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.view;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.InsetsAnimationControlImplProto.CURRENT_ALPHA;
+import static android.view.InsetsAnimationControlImplProto.IS_CANCELLED;
+import static android.view.InsetsAnimationControlImplProto.IS_FINISHED;
+import static android.view.InsetsAnimationControlImplProto.PENDING_ALPHA;
+import static android.view.InsetsAnimationControlImplProto.PENDING_FRACTION;
+import static android.view.InsetsAnimationControlImplProto.PENDING_INSETS;
+import static android.view.InsetsAnimationControlImplProto.SHOWN_ON_FINISH;
+import static android.view.InsetsAnimationControlImplProto.TMP_MATRIX;
+import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
+import static android.view.InsetsController.AnimationType;
+import static android.view.InsetsController.DEBUG;
+import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
+import static android.view.InsetsController.LayoutInsetsDuringAnimation;
+import static android.view.InsetsState.ISIDE_BOTTOM;
+import static android.view.InsetsState.ISIDE_FLOATING;
+import static android.view.InsetsState.ISIDE_LEFT;
+import static android.view.InsetsState.ISIDE_RIGHT;
+import static android.view.InsetsState.ISIDE_TOP;
+import static android.view.InsetsState.ITYPE_IME;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
+import android.annotation.Nullable;
+import android.content.res.CompatibilityInfo;
+import android.graphics.Insets;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.util.SparseSetArray;
+import android.util.proto.ProtoOutputStream;
+import android.view.InsetsState.InternalInsetsSide;
+import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
+import android.view.WindowInsets.Type.InsetsType;
+import android.view.WindowInsetsAnimation.Bounds;
+import android.view.WindowManager.LayoutParams;
+import android.view.animation.Interpolator;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+/**
+ * Implements {@link WindowInsetsAnimationController}
+ * @hide
+ */
+@VisibleForTesting
+public class InsetsAnimationControlImpl implements WindowInsetsAnimationController,
+        InsetsAnimationControlRunner {
+
+    private static final String TAG = "InsetsAnimationCtrlImpl";
+
+    private final Rect mTmpFrame = new Rect();
+
+    private final WindowInsetsAnimationControlListener mListener;
+    private final SparseArray<InsetsSourceControl> mControls;
+    private final SparseSetArray<InsetsSourceControl> mSideControlsMap = new SparseSetArray<>();
+
+    /** @see WindowInsetsAnimationController#getHiddenStateInsets */
+    private final Insets mHiddenInsets;
+
+    /** @see WindowInsetsAnimationController#getShownStateInsets */
+    private final Insets mShownInsets;
+    private final Matrix mTmpMatrix = new Matrix();
+    private final InsetsState mInitialInsetsState;
+    private final @AnimationType int mAnimationType;
+    private final @LayoutInsetsDuringAnimation int mLayoutInsetsDuringAnimation;
+    private final @InsetsType int mTypes;
+    private @InsetsType int mControllingTypes;
+    private final InsetsAnimationControlCallbacks mController;
+    private final WindowInsetsAnimation mAnimation;
+    /** @see WindowInsetsAnimationController#hasZeroInsetsIme */
+    private final boolean mHasZeroInsetsIme;
+    private final CompatibilityInfo.Translator mTranslator;
+    private Insets mCurrentInsets;
+    private Insets mPendingInsets;
+    private float mPendingFraction;
+    private boolean mFinished;
+    private boolean mCancelled;
+    private boolean mShownOnFinish;
+    private float mCurrentAlpha = 1.0f;
+    private float mPendingAlpha = 1.0f;
+    @VisibleForTesting(visibility = PACKAGE)
+    public boolean mReadyDispatched;
+    private Boolean mPerceptible;
+
+    @VisibleForTesting
+    public InsetsAnimationControlImpl(SparseArray<InsetsSourceControl> controls,
+            @Nullable Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener,
+            @InsetsType int types, InsetsAnimationControlCallbacks controller, long durationMs,
+            Interpolator interpolator, @AnimationType int animationType,
+            @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
+            CompatibilityInfo.Translator translator) {
+        mControls = controls;
+        mListener = listener;
+        mTypes = types;
+        mControllingTypes = types;
+        mController = controller;
+        mInitialInsetsState = new InsetsState(state, true /* copySources */);
+        if (frame != null) {
+            final SparseIntArray typeSideMap = new SparseIntArray();
+            mCurrentInsets = getInsetsFromState(mInitialInsetsState, frame, null /* typeSideMap */);
+            mHiddenInsets = calculateInsets(mInitialInsetsState, frame, controls, false /* shown */,
+                    null /* typeSideMap */);
+            mShownInsets = calculateInsets(mInitialInsetsState, frame, controls, true /* shown */,
+                    typeSideMap);
+            mHasZeroInsetsIme = mShownInsets.bottom == 0 && controlsInternalType(ITYPE_IME);
+            if (mHasZeroInsetsIme) {
+                // IME has shownInsets of ZERO, and can't map to a side by default.
+                // Map zero insets IME to bottom, making it a special case of bottom insets.
+                typeSideMap.put(ITYPE_IME, ISIDE_BOTTOM);
+            }
+            buildSideControlsMap(typeSideMap, mSideControlsMap, controls);
+        } else {
+            // Passing a null frame indicates the caller wants to play the insets animation anyway,
+            // no matter the source provides insets to the frame or not.
+            mCurrentInsets = calculateInsets(mInitialInsetsState, controls, true /* shown */);
+            mHiddenInsets = calculateInsets(null, controls, false /* shown */);
+            mShownInsets = calculateInsets(null, controls, true /* shown */);
+            mHasZeroInsetsIme = mShownInsets.bottom == 0 && controlsInternalType(ITYPE_IME);
+            buildSideControlsMap(mSideControlsMap, controls);
+        }
+        mPendingInsets = mCurrentInsets;
+
+        mAnimation = new WindowInsetsAnimation(mTypes, interpolator,
+                durationMs);
+        mAnimation.setAlpha(getCurrentAlpha());
+        mAnimationType = animationType;
+        mLayoutInsetsDuringAnimation = layoutInsetsDuringAnimation;
+        mTranslator = translator;
+        mController.startAnimation(this, listener, types, mAnimation,
+                new Bounds(mHiddenInsets, mShownInsets));
+    }
+
+    private boolean calculatePerceptible(Insets currentInsets, float currentAlpha) {
+        return 100 * currentInsets.left >= 5 * (mShownInsets.left - mHiddenInsets.left)
+                && 100 * currentInsets.top >= 5 * (mShownInsets.top - mHiddenInsets.top)
+                && 100 * currentInsets.right >= 5 * (mShownInsets.right - mHiddenInsets.right)
+                && 100 * currentInsets.bottom >= 5 * (mShownInsets.bottom - mHiddenInsets.bottom)
+                && currentAlpha >= 0.5f;
+    }
+
+    @Override
+    public boolean hasZeroInsetsIme() {
+        return mHasZeroInsetsIme;
+    }
+
+    @Override
+    public Insets getHiddenStateInsets() {
+        return mHiddenInsets;
+    }
+
+    @Override
+    public Insets getShownStateInsets() {
+        return mShownInsets;
+    }
+
+    @Override
+    public Insets getCurrentInsets() {
+        return mCurrentInsets;
+    }
+
+    @Override
+    public float getCurrentAlpha() {
+        return mCurrentAlpha;
+    }
+
+    @Override
+    @InsetsType public int getTypes() {
+        return mTypes;
+    }
+
+    @Override
+    public int getControllingTypes() {
+        return mControllingTypes;
+    }
+
+    @Override
+    public void notifyControlRevoked(@InsetsType int types) {
+        mControllingTypes &= ~types;
+    }
+
+    @Override
+    public void updateSurfacePosition(SparseArray<InsetsSourceControl> controls) {
+        for (int i = controls.size() - 1; i >= 0; i--) {
+            final InsetsSourceControl control = controls.valueAt(i);
+            final InsetsSourceControl c = mControls.get(control.getType());
+            if (c == null) {
+                continue;
+            }
+            final Point position = control.getSurfacePosition();
+            c.setSurfacePosition(position.x, position.y);
+        }
+    }
+
+    @Override
+    public @AnimationType int getAnimationType() {
+        return mAnimationType;
+    }
+
+    @Override
+    public void setInsetsAndAlpha(Insets insets, float alpha, float fraction) {
+        setInsetsAndAlpha(insets, alpha, fraction, false /* allowWhenFinished */);
+    }
+
+    private void setInsetsAndAlpha(Insets insets, float alpha, float fraction,
+            boolean allowWhenFinished) {
+        if (!allowWhenFinished && mFinished) {
+            throw new IllegalStateException(
+                    "Can't change insets on an animation that is finished.");
+        }
+        if (mCancelled) {
+            throw new IllegalStateException(
+                    "Can't change insets on an animation that is cancelled.");
+        }
+        mPendingFraction = sanitize(fraction);
+        mPendingInsets = sanitize(insets);
+        mPendingAlpha = sanitize(alpha);
+        mController.scheduleApplyChangeInsets(this);
+        boolean perceptible = calculatePerceptible(mPendingInsets, mPendingAlpha);
+        if (mPerceptible == null || perceptible != mPerceptible) {
+            mController.reportPerceptible(mTypes, perceptible);
+            mPerceptible = perceptible;
+        }
+    }
+
+    @VisibleForTesting
+    /**
+     * @return Whether the finish callback of this animation should be invoked.
+     */
+    public boolean applyChangeInsets(@Nullable InsetsState outState) {
+        if (mCancelled) {
+            if (DEBUG) Log.d(TAG, "applyChangeInsets canceled");
+            return false;
+        }
+        final Insets offset = Insets.subtract(mShownInsets, mPendingInsets);
+        ArrayList<SurfaceParams> params = new ArrayList<>();
+        updateLeashesForSide(ISIDE_LEFT, offset.left, mPendingInsets.left, params, outState,
+                mPendingAlpha);
+        updateLeashesForSide(ISIDE_TOP, offset.top, mPendingInsets.top, params, outState,
+                mPendingAlpha);
+        updateLeashesForSide(ISIDE_RIGHT, offset.right, mPendingInsets.right, params, outState,
+                mPendingAlpha);
+        updateLeashesForSide(ISIDE_BOTTOM, offset.bottom, mPendingInsets.bottom, params, outState,
+                mPendingAlpha);
+
+        mController.applySurfaceParams(params.toArray(new SurfaceParams[params.size()]));
+        mCurrentInsets = mPendingInsets;
+        mAnimation.setFraction(mPendingFraction);
+        mCurrentAlpha = mPendingAlpha;
+        mAnimation.setAlpha(mPendingAlpha);
+        if (mFinished) {
+            if (DEBUG) Log.d(TAG, String.format(
+                    "notifyFinished shown: %s, currentAlpha: %f, currentInsets: %s",
+                    mShownOnFinish, mCurrentAlpha, mCurrentInsets));
+            mController.notifyFinished(this, mShownOnFinish);
+            releaseLeashes();
+        }
+        if (DEBUG) Log.d(TAG, "Animation finished abruptly.");
+        return mFinished;
+    }
+
+    private void releaseLeashes() {
+        for (int i = mControls.size() - 1; i >= 0; i--) {
+            final InsetsSourceControl c = mControls.valueAt(i);
+            if (c == null) continue;
+            c.release(mController::releaseSurfaceControlFromRt);
+        }
+    }
+
+    @Override
+    public void finish(boolean shown) {
+        if (mCancelled || mFinished) {
+            if (DEBUG) Log.d(TAG, "Animation already canceled or finished, not notifying.");
+            return;
+        }
+        mShownOnFinish = shown;
+        mFinished = true;
+        setInsetsAndAlpha(shown ? mShownInsets : mHiddenInsets, mPendingAlpha, 1f /* fraction */,
+                true /* allowWhenFinished */);
+
+        if (DEBUG) Log.d(TAG, "notify control request finished for types: " + mTypes);
+        mListener.onFinished(this);
+    }
+
+    @Override
+    @VisibleForTesting
+    public float getCurrentFraction() {
+        return mAnimation.getFraction();
+    }
+
+    @Override
+    public void cancel() {
+        if (mFinished) {
+            return;
+        }
+        mPendingInsets = mLayoutInsetsDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN
+                ? mShownInsets : mHiddenInsets;
+        mPendingAlpha = 1f;
+        applyChangeInsets(null);
+        mCancelled = true;
+        mListener.onCancelled(mReadyDispatched ? this : null);
+        if (DEBUG) Log.d(TAG, "notify Control request cancelled for types: " + mTypes);
+
+        releaseLeashes();
+    }
+
+    @Override
+    public boolean isFinished() {
+        return mFinished;
+    }
+
+    @Override
+    public boolean isCancelled() {
+        return mCancelled;
+    }
+
+    @Override
+    public WindowInsetsAnimation getAnimation() {
+        return mAnimation;
+    }
+
+    @Override
+    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(IS_CANCELLED, mCancelled);
+        proto.write(IS_FINISHED, mFinished);
+        proto.write(TMP_MATRIX, Objects.toString(mTmpMatrix));
+        proto.write(PENDING_INSETS, Objects.toString(mPendingInsets));
+        proto.write(PENDING_FRACTION, mPendingFraction);
+        proto.write(SHOWN_ON_FINISH, mShownOnFinish);
+        proto.write(CURRENT_ALPHA, mCurrentAlpha);
+        proto.write(PENDING_ALPHA, mPendingAlpha);
+        proto.end(token);
+    }
+
+    SparseArray<InsetsSourceControl> getControls() {
+        return mControls;
+    }
+
+    private Insets getInsetsFromState(InsetsState state, Rect frame,
+            @Nullable @InternalInsetsSide SparseIntArray typeSideMap) {
+        return state.calculateInsets(frame, null /* ignoringVisibilityState */,
+                false /* isScreenRound */, false /* alwaysConsumeSystemBars */,
+                LayoutParams.SOFT_INPUT_ADJUST_RESIZE /* legacySoftInputMode*/,
+                0 /* legacyWindowFlags */, 0 /* legacySystemUiFlags */, TYPE_APPLICATION,
+                WINDOWING_MODE_UNDEFINED, typeSideMap).getInsets(mTypes);
+    }
+
+    /** Computes the insets relative to the given frame. */
+    private Insets calculateInsets(InsetsState state, Rect frame,
+            SparseArray<InsetsSourceControl> controls, boolean shown,
+            @Nullable @InternalInsetsSide SparseIntArray typeSideMap) {
+        for (int i = controls.size() - 1; i >= 0; i--) {
+            final InsetsSourceControl control  = controls.valueAt(i);
+            if (control == null) {
+                // control may be null if it got revoked.
+                continue;
+            }
+            state.getSource(control.getType()).setVisible(shown);
+        }
+        return getInsetsFromState(state, frame, typeSideMap);
+    }
+
+    /** Computes the insets from the insets hints of controls. */
+    private Insets calculateInsets(InsetsState state, SparseArray<InsetsSourceControl> controls,
+            boolean shownOrCurrent) {
+        Insets insets = Insets.NONE;
+        if (!shownOrCurrent) {
+            return insets;
+        }
+        for (int i = controls.size() - 1; i >= 0; i--) {
+            final InsetsSourceControl control  = controls.valueAt(i);
+            if (control == null) {
+                // control may be null if it got revoked.
+                continue;
+            }
+            if (state == null || state.getSource(control.getType()).isVisible()) {
+                insets = Insets.max(insets, control.getInsetsHint());
+            }
+        }
+        return insets;
+    }
+
+    private Insets sanitize(Insets insets) {
+        if (insets == null) {
+            insets = getCurrentInsets();
+        }
+        if (hasZeroInsetsIme()) {
+            return insets;
+        }
+        return Insets.max(Insets.min(insets, mShownInsets), mHiddenInsets);
+    }
+
+    private static float sanitize(float alpha) {
+        return alpha >= 1 ? 1 : (alpha <= 0 ? 0 : alpha);
+    }
+
+    private void updateLeashesForSide(@InternalInsetsSide int side, int offset, int inset,
+            ArrayList<SurfaceParams> surfaceParams, @Nullable InsetsState outState, float alpha) {
+        final ArraySet<InsetsSourceControl> controls = mSideControlsMap.get(side);
+        if (controls == null) {
+            return;
+        }
+        // TODO: Implement behavior when inset spans over multiple types
+        for (int i = controls.size() - 1; i >= 0; i--) {
+            final InsetsSourceControl control = controls.valueAt(i);
+            final InsetsSource source = mInitialInsetsState.getSource(control.getType());
+            final SurfaceControl leash = control.getLeash();
+
+            mTmpMatrix.setTranslate(control.getSurfacePosition().x, control.getSurfacePosition().y);
+            mTmpFrame.set(source.getFrame());
+            addTranslationToMatrix(side, offset, mTmpMatrix, mTmpFrame);
+
+            final boolean visible = mHasZeroInsetsIme && side == ISIDE_BOTTOM
+                    ? (mAnimationType == ANIMATION_TYPE_SHOW || !mFinished)
+                    : inset != 0;
+
+            if (outState != null) {
+                outState.getSource(source.getType()).setVisible(visible);
+                outState.getSource(source.getType()).setFrame(mTmpFrame);
+            }
+
+            // If the system is controlling the insets source, the leash can be null.
+            if (leash != null) {
+                SurfaceParams params = new SurfaceParams.Builder(leash)
+                        .withAlpha(alpha)
+                        .withMatrix(mTmpMatrix)
+                        .withVisibility(visible)
+                        .build();
+                surfaceParams.add(params);
+            }
+        }
+    }
+
+    private void addTranslationToMatrix(@InternalInsetsSide int side, int offset, Matrix m,
+            Rect frame) {
+        final float surfaceOffset = mTranslator != null
+                ? mTranslator.translateLengthInAppWindowToScreen(offset) : offset;
+        switch (side) {
+            case ISIDE_LEFT:
+                m.postTranslate(-surfaceOffset, 0);
+                frame.offset(-offset, 0);
+                break;
+            case ISIDE_TOP:
+                m.postTranslate(0, -surfaceOffset);
+                frame.offset(0, -offset);
+                break;
+            case ISIDE_RIGHT:
+                m.postTranslate(surfaceOffset, 0);
+                frame.offset(offset, 0);
+                break;
+            case ISIDE_BOTTOM:
+                m.postTranslate(0, surfaceOffset);
+                frame.offset(0, offset);
+                break;
+        }
+    }
+
+    private static void buildSideControlsMap(SparseIntArray typeSideMap,
+            SparseSetArray<InsetsSourceControl> sideControlsMap,
+            SparseArray<InsetsSourceControl> controls) {
+        for (int i = typeSideMap.size() - 1; i >= 0; i--) {
+            final int type = typeSideMap.keyAt(i);
+            final int side = typeSideMap.valueAt(i);
+            final InsetsSourceControl control = controls.get(type);
+            if (control == null) {
+                // If the types that we are controlling are less than the types that the system has,
+                // there can be some null controllers.
+                continue;
+            }
+            sideControlsMap.add(side, control);
+        }
+    }
+
+    private static void buildSideControlsMap(
+            SparseSetArray<InsetsSourceControl> sideControlsMap,
+            SparseArray<InsetsSourceControl> controls) {
+        for (int i = controls.size() - 1; i >= 0; i--) {
+            final InsetsSourceControl control  = controls.valueAt(i);
+            if (control == null) {
+                // control may be null if it got revoked.
+                continue;
+            }
+            @InternalInsetsSide int side = InsetsState.getInsetSide(control.getInsetsHint());
+            if (side == ISIDE_FLOATING && control.getType() == ITYPE_IME) {
+                side = ISIDE_BOTTOM;
+            }
+            sideControlsMap.add(side, control);
+        }
+    }
+}
diff --git a/android/view/InsetsAnimationControlRunner.java b/android/view/InsetsAnimationControlRunner.java
new file mode 100644
index 0000000..1cb00e3
--- /dev/null
+++ b/android/view/InsetsAnimationControlRunner.java
@@ -0,0 +1,86 @@
+/*
+ * 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.view;
+
+import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
+import android.view.InsetsController.AnimationType;
+import android.view.InsetsState.InternalInsetsType;
+import android.view.WindowInsets.Type.InsetsType;
+
+/**
+ * Interface representing a runner for an insets animation.
+ *
+ * @hide
+ */
+public interface InsetsAnimationControlRunner {
+
+    /**
+     * @return The {@link InsetsType} the animation of this runner controls.
+     */
+    @InsetsType int getTypes();
+
+    /**
+     * @return The {@link InsetsType} the animation of this runner is controlling. This can be
+     *         changed if a control is revoked.
+     */
+    @InsetsType int getControllingTypes();
+
+    /**
+     * Notifies {@link InsetsType types} of control are getting revoked.
+     */
+    void notifyControlRevoked(@InsetsType int types);
+
+    /**
+     * Updates the surface positions of the controls owned by this runner if there is any.
+     *
+     * @param controls An array of {@link InsetsSourceControl} that the caller newly receives.
+     */
+    void updateSurfacePosition(SparseArray<InsetsSourceControl> controls);
+
+    /**
+     * Cancels the animation.
+     */
+    void cancel();
+
+    /**
+     * @return The animation this runner is running.
+     */
+    WindowInsetsAnimation getAnimation();
+
+    /**
+     * @return Whether {@link #getTypes()} maps to a specific {@link InternalInsetsType}.
+     */
+    default boolean controlsInternalType(@InternalInsetsType int type) {
+        return InsetsState.toInternalType(getTypes()).contains(type);
+    }
+
+    /**
+     * @return The animation type this runner is running.
+     */
+    @AnimationType int getAnimationType();
+
+    /**
+     *
+     * Export the state of classes that implement this interface into a protocol buffer
+     * output stream.
+     *
+     * @param proto Stream to write the state to
+     * @param fieldId FieldId of the implementation class
+     */
+    void dumpDebug(ProtoOutputStream proto, long fieldId);
+}
diff --git a/android/view/InsetsAnimationThread.java b/android/view/InsetsAnimationThread.java
new file mode 100644
index 0000000..cdf9733
--- /dev/null
+++ b/android/view/InsetsAnimationThread.java
@@ -0,0 +1,70 @@
+/*
+ * 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.view;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Trace;
+
+/**
+ * Thread to be used for inset animations to be running off the main thread.
+ * @hide
+ */
+public class InsetsAnimationThread extends HandlerThread {
+
+    private static InsetsAnimationThread sInstance;
+    private static Handler sHandler;
+
+    private InsetsAnimationThread() {
+        // TODO: Should this use higher priority?
+        super("InsetsAnimations");
+    }
+
+    private static void ensureThreadLocked() {
+        if (sInstance == null) {
+            sInstance = new InsetsAnimationThread();
+            sInstance.start();
+            sInstance.getLooper().setTraceTag(Trace.TRACE_TAG_VIEW);
+            sHandler = new Handler(sInstance.getLooper());
+        }
+    }
+
+    public static void release() {
+        synchronized (InsetsAnimationThread.class) {
+            if (sInstance == null) {
+                return;
+            }
+            sInstance.getLooper().quitSafely();
+            sInstance = null;
+            sHandler = null;
+        }
+    }
+
+    public static InsetsAnimationThread get() {
+        synchronized (InsetsAnimationThread.class) {
+            ensureThreadLocked();
+            return sInstance;
+        }
+    }
+
+    public static Handler getHandler() {
+        synchronized (InsetsAnimationThread.class) {
+            ensureThreadLocked();
+            return sHandler;
+        }
+    }
+}
diff --git a/android/view/InsetsAnimationThreadControlRunner.java b/android/view/InsetsAnimationThreadControlRunner.java
new file mode 100644
index 0000000..691e638
--- /dev/null
+++ b/android/view/InsetsAnimationThreadControlRunner.java
@@ -0,0 +1,187 @@
+/*
+ * 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.view;
+
+import static android.view.InsetsController.DEBUG;
+import static android.view.SyncRtSurfaceTransactionApplier.applyParams;
+
+import android.annotation.Nullable;
+import android.annotation.UiThread;
+import android.content.res.CompatibilityInfo;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Trace;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
+import android.view.InsetsController.AnimationType;
+import android.view.InsetsController.LayoutInsetsDuringAnimation;
+import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
+import android.view.WindowInsets.Type.InsetsType;
+import android.view.WindowInsetsAnimation.Bounds;
+import android.view.animation.Interpolator;
+
+/**
+ * Insets animation runner that uses {@link InsetsAnimationThread} to run the animation off from the
+ * main thread.
+ *
+ * @hide
+ */
+public class InsetsAnimationThreadControlRunner implements InsetsAnimationControlRunner {
+
+    private static final String TAG = "InsetsAnimThreadRunner";
+    private final InsetsAnimationControlImpl mControl;
+    private final InsetsAnimationControlCallbacks mOuterCallbacks;
+    private final Handler mMainThreadHandler;
+    private final InsetsAnimationControlCallbacks mCallbacks =
+            new InsetsAnimationControlCallbacks() {
+
+        private final float[] mTmpFloat9 = new float[9];
+
+        @Override
+        @UiThread
+        public void startAnimation(InsetsAnimationControlImpl controller,
+                WindowInsetsAnimationControlListener listener, int types,
+                WindowInsetsAnimation animation, Bounds bounds) {
+            // Animation will be started in constructor already.
+        }
+
+        @Override
+        public void scheduleApplyChangeInsets(InsetsAnimationControlRunner runner) {
+            synchronized (mControl) {
+                // This reads the surface position on the animation thread, but the surface position
+                // would be updated on the UI thread, so we need this critical section.
+                // See: updateSurfacePosition.
+                mControl.applyChangeInsets(null /* outState */);
+            }
+        }
+
+        @Override
+        public void notifyFinished(InsetsAnimationControlRunner runner, boolean shown) {
+            Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW,
+                    "InsetsAsyncAnimation: " + WindowInsets.Type.toString(runner.getTypes()),
+                    runner.getTypes());
+            releaseControls(mControl.getControls());
+            mMainThreadHandler.post(() ->
+                    mOuterCallbacks.notifyFinished(InsetsAnimationThreadControlRunner.this, shown));
+        }
+
+        @Override
+        public void applySurfaceParams(SurfaceParams... params) {
+            if (DEBUG) Log.d(TAG, "applySurfaceParams");
+            SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            for (int i = params.length - 1; i >= 0; i--) {
+                SyncRtSurfaceTransactionApplier.SurfaceParams surfaceParams = params[i];
+                applyParams(t, surfaceParams, mTmpFloat9);
+            }
+            t.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
+            t.apply();
+            t.close();
+        }
+
+        @Override
+        public void releaseSurfaceControlFromRt(SurfaceControl sc) {
+            if (DEBUG) Log.d(TAG, "releaseSurfaceControlFromRt");
+            // Since we don't push the SurfaceParams to the RT we can release directly
+            sc.release();
+        }
+
+        @Override
+        public void reportPerceptible(int types, boolean perceptible) {
+            mMainThreadHandler.post(() -> mOuterCallbacks.reportPerceptible(types, perceptible));
+        }
+    };
+
+    @UiThread
+    public InsetsAnimationThreadControlRunner(SparseArray<InsetsSourceControl> controls,
+            @Nullable Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener,
+            @InsetsType int types, InsetsAnimationControlCallbacks controller, long durationMs,
+            Interpolator interpolator, @AnimationType int animationType,
+            @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
+            CompatibilityInfo.Translator translator, Handler mainThreadHandler) {
+        mMainThreadHandler = mainThreadHandler;
+        mOuterCallbacks = controller;
+        mControl = new InsetsAnimationControlImpl(controls, frame, state, listener, types,
+                mCallbacks, durationMs, interpolator, animationType, layoutInsetsDuringAnimation,
+                translator);
+        InsetsAnimationThread.getHandler().post(() -> {
+            if (mControl.isCancelled()) {
+                return;
+            }
+            Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW,
+                    "InsetsAsyncAnimation: " + WindowInsets.Type.toString(types), types);
+            listener.onReady(mControl, types);
+        });
+    }
+
+    private void releaseControls(SparseArray<InsetsSourceControl> controls) {
+        for (int i = controls.size() - 1; i >= 0; i--) {
+            controls.valueAt(i).release(SurfaceControl::release);
+        }
+    }
+
+    @Override
+    @UiThread
+    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+        mControl.dumpDebug(proto, fieldId);
+    }
+
+    @Override
+    @UiThread
+    public int getTypes() {
+        return mControl.getTypes();
+    }
+
+    @Override
+    @UiThread
+    public int getControllingTypes() {
+        return mControl.getControllingTypes();
+    }
+
+    @Override
+    @UiThread
+    public void notifyControlRevoked(@InsetsType int types) {
+        mControl.notifyControlRevoked(types);
+    }
+
+    @Override
+    @UiThread
+    public void updateSurfacePosition(SparseArray<InsetsSourceControl> controls) {
+        synchronized (mControl) {
+            // This is called from the UI thread, however, the surface position will be used on the
+            // animation thread, so we need this critical section. See: scheduleApplyChangeInsets.
+            mControl.updateSurfacePosition(controls);
+        }
+    }
+
+    @Override
+    @UiThread
+    public void cancel() {
+        InsetsAnimationThread.getHandler().post(mControl::cancel);
+    }
+
+    @Override
+    @UiThread
+    public WindowInsetsAnimation getAnimation() {
+        return mControl.getAnimation();
+    }
+
+    @Override
+    public int getAnimationType() {
+        return mControl.getAnimationType();
+    }
+}
diff --git a/android/view/InsetsController.java b/android/view/InsetsController.java
new file mode 100644
index 0000000..6f915c9
--- /dev/null
+++ b/android/view/InsetsController.java
@@ -0,0 +1,1632 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.os.Trace.TRACE_TAG_VIEW;
+import static android.view.InsetsControllerProto.CONTROL;
+import static android.view.InsetsControllerProto.STATE;
+import static android.view.InsetsState.ITYPE_CAPTION_BAR;
+import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsState.toInternalType;
+import static android.view.InsetsState.toPublicType;
+import static android.view.WindowInsets.Type.all;
+import static android.view.WindowInsets.Type.ime;
+
+import android.animation.AnimationHandler;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.TypeEvaluator;
+import android.animation.ValueAnimator;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.CompatibilityInfo;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Trace;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.util.imetracing.ImeTracing;
+import android.util.proto.ProtoOutputStream;
+import android.view.InsetsSourceConsumer.ShowResult;
+import android.view.InsetsState.InternalInsetsType;
+import android.view.SurfaceControl.Transaction;
+import android.view.WindowInsets.Type;
+import android.view.WindowInsets.Type.InsetsType;
+import android.view.WindowInsetsAnimation.Bounds;
+import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.PathInterpolator;
+import android.view.inputmethod.InputMethodManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.BiFunction;
+
+/**
+ * Implements {@link WindowInsetsController} on the client.
+ * @hide
+ */
+public class InsetsController implements WindowInsetsController, InsetsAnimationControlCallbacks {
+
+    private int mTypesBeingCancelled;
+
+    public interface Host {
+
+        Handler getHandler();
+
+        /**
+         * Notifies host that {@link InsetsController#getState()} has changed.
+         */
+        void notifyInsetsChanged();
+
+        void dispatchWindowInsetsAnimationPrepare(@NonNull WindowInsetsAnimation animation);
+        Bounds dispatchWindowInsetsAnimationStart(
+                @NonNull WindowInsetsAnimation animation, @NonNull Bounds bounds);
+        WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull WindowInsets insets,
+                @NonNull List<WindowInsetsAnimation> runningAnimations);
+        void dispatchWindowInsetsAnimationEnd(@NonNull WindowInsetsAnimation animation);
+
+        /**
+         * Requests host to apply surface params in synchronized manner.
+         */
+        void applySurfaceParams(final SyncRtSurfaceTransactionApplier.SurfaceParams... params);
+
+        /**
+         * @see ViewRootImpl#updateCompatSysUiVisibility(int, boolean, boolean)
+         */
+        void updateCompatSysUiVisibility(@InternalInsetsType int type, boolean visible,
+                boolean hasControl);
+
+        /**
+         * Called when insets have been modified by the client and should be reported back to WM.
+         */
+        void onInsetsModified(InsetsState insetsState);
+
+        /**
+         * @return Whether the host has any callbacks it wants to synchronize the animations with.
+         *         If there are no callbacks, the animation will be off-loaded to another thread and
+         *         slightly different animation curves are picked.
+         */
+        boolean hasAnimationCallbacks();
+
+        /**
+         * @see WindowInsetsController#setSystemBarsAppearance
+         */
+        void setSystemBarsAppearance(@Appearance int appearance, @Appearance int mask);
+
+        /**
+         * @see WindowInsetsController#getSystemBarsAppearance()
+         */
+        @Appearance int getSystemBarsAppearance();
+
+        default boolean isSystemBarsAppearanceControlled() {
+            return false;
+        }
+
+        /**
+         * @see WindowInsetsController#setSystemBarsBehavior
+         */
+        void setSystemBarsBehavior(@Behavior int behavior);
+
+        /**
+         * @see WindowInsetsController#getSystemBarsBehavior
+         */
+        @Behavior int getSystemBarsBehavior();
+
+        default boolean isSystemBarsBehaviorControlled() {
+            return false;
+        }
+
+        /**
+         * Releases a surface and ensure that this is done after {@link #applySurfaceParams} has
+         * finished applying params.
+         */
+        void releaseSurfaceControlFromRt(SurfaceControl surfaceControl);
+
+        /**
+         * If this host is a view hierarchy, adds a pre-draw runnable to ensure proper ordering as
+         * described in {@link WindowInsetsAnimation.Callback#onPrepare}.
+         *
+         * If this host isn't a view hierarchy, the runnable can be executed immediately.
+         */
+        void addOnPreDrawRunnable(Runnable r);
+
+        /**
+         * Adds a runnbale to be executed during {@link Choreographer#CALLBACK_INSETS_ANIMATION}
+         * phase.
+         */
+        void postInsetsAnimationCallback(Runnable r);
+
+        /**
+         * Obtains {@link InputMethodManager} instance from host.
+         */
+        InputMethodManager getInputMethodManager();
+
+        /**
+         * @return title of the rootView, if it has one.
+         * Note: this method is for debugging purposes only.
+         */
+        @Nullable
+        String getRootViewTitle();
+
+        /** @see ViewRootImpl#dipToPx */
+        int dipToPx(int dips);
+
+        /**
+         * @return token associated with the host, if it has one.
+         */
+        @Nullable
+        IBinder getWindowToken();
+
+        /**
+         * @return Translator associated with the host, if it has one.
+         */
+        @Nullable
+        default CompatibilityInfo.Translator getTranslator() {
+            return null;
+        }
+    }
+
+    private static final String TAG = "InsetsController";
+    private static final int ANIMATION_DURATION_MOVE_IN_MS = 275;
+    private static final int ANIMATION_DURATION_MOVE_OUT_MS = 340;
+    private static final int ANIMATION_DURATION_FADE_IN_MS = 500;
+    private static final int ANIMATION_DURATION_FADE_OUT_MS = 1500;
+
+    private static final int ANIMATION_DELAY_DIM_MS = 500;
+
+    private static final int ANIMATION_DURATION_SYNC_IME_MS = 285;
+    private static final int ANIMATION_DURATION_UNSYNC_IME_MS = 200;
+
+    private static final int PENDING_CONTROL_TIMEOUT_MS = 2000;
+
+    private static final Interpolator SYSTEM_BARS_INSETS_INTERPOLATOR =
+            new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+    private static final Interpolator SYSTEM_BARS_ALPHA_INTERPOLATOR =
+            new PathInterpolator(0.3f, 0f, 1f, 1f);
+    private static final Interpolator SYSTEM_BARS_DIM_INTERPOLATOR = alphaFraction -> {
+        // While playing dim animation, alphaFraction is changed from 1f to 0f. Here changes it to
+        // time-based fraction for computing delay and interpolation.
+        float fraction = 1 - alphaFraction;
+        final float fractionDelay = (float) ANIMATION_DELAY_DIM_MS / ANIMATION_DURATION_FADE_OUT_MS;
+        if (fraction <= fractionDelay) {
+            return 1f;
+        } else {
+            float innerFraction = (fraction - fractionDelay) / (1f - fractionDelay);
+            return 1f - SYSTEM_BARS_ALPHA_INTERPOLATOR.getInterpolation(innerFraction);
+        }
+    };
+    private static final Interpolator SYNC_IME_INTERPOLATOR =
+            new PathInterpolator(0.2f, 0f, 0f, 1f);
+    private static final Interpolator LINEAR_OUT_SLOW_IN_INTERPOLATOR =
+            new PathInterpolator(0, 0, 0.2f, 1f);
+    private static final Interpolator FAST_OUT_LINEAR_IN_INTERPOLATOR =
+            new PathInterpolator(0.4f, 0f, 1f, 1f);
+
+    /** The amount IME will move up/down when animating in floating mode. */
+    private static final int FLOATING_IME_BOTTOM_INSET_DP = -80;
+
+    static final boolean DEBUG = false;
+    static final boolean WARN = false;
+
+    /**
+     * Layout mode during insets animation: The views should be laid out as if the changing inset
+     * types are fully shown. Before starting the animation, {@link View#onApplyWindowInsets} will
+     * be called as if the changing insets types are shown, which will result in the views being
+     * laid out as if the insets are fully shown.
+     */
+    public static final int LAYOUT_INSETS_DURING_ANIMATION_SHOWN = 0;
+
+    /**
+     * Layout mode during insets animation: The views should be laid out as if the changing inset
+     * types are fully hidden. Before starting the animation, {@link View#onApplyWindowInsets} will
+     * be called as if the changing insets types are hidden, which will result in the views being
+     * laid out as if the insets are fully hidden.
+     */
+    public static final int LAYOUT_INSETS_DURING_ANIMATION_HIDDEN = 1;
+
+    /**
+     * Determines the behavior of how the views should be laid out during an insets animation that
+     * is controlled by the application by calling {@link #controlWindowInsetsAnimation}.
+     * <p>
+     * When the animation is system-initiated, the layout mode is always chosen such that the
+     * pre-animation layout will represent the opposite of the starting state, i.e. when insets
+     * are appearing, {@link #LAYOUT_INSETS_DURING_ANIMATION_SHOWN} will be used. When insets
+     * are disappearing, {@link #LAYOUT_INSETS_DURING_ANIMATION_HIDDEN} will be used.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {LAYOUT_INSETS_DURING_ANIMATION_SHOWN,
+            LAYOUT_INSETS_DURING_ANIMATION_HIDDEN})
+    @interface LayoutInsetsDuringAnimation {
+    }
+
+    /** Not running an animation. */
+    @VisibleForTesting
+    public static final int ANIMATION_TYPE_NONE = -1;
+
+    /** Running animation will show insets */
+    @VisibleForTesting
+    public static final int ANIMATION_TYPE_SHOW = 0;
+
+    /** Running animation will hide insets */
+    @VisibleForTesting
+    public static final int ANIMATION_TYPE_HIDE = 1;
+
+    /** Running animation is controlled by user via {@link #controlWindowInsetsAnimation} */
+    @VisibleForTesting
+    public static final int ANIMATION_TYPE_USER = 2;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {ANIMATION_TYPE_NONE, ANIMATION_TYPE_SHOW, ANIMATION_TYPE_HIDE,
+            ANIMATION_TYPE_USER})
+    @interface AnimationType {
+    }
+
+    /**
+     * Translation animation evaluator.
+     */
+    private static TypeEvaluator<Insets> sEvaluator = (fraction, startValue, endValue) -> Insets.of(
+            (int) (startValue.left + fraction * (endValue.left - startValue.left)),
+            (int) (startValue.top + fraction * (endValue.top - startValue.top)),
+            (int) (startValue.right + fraction * (endValue.right - startValue.right)),
+            (int) (startValue.bottom + fraction * (endValue.bottom - startValue.bottom)));
+
+    /**
+     * The default implementation of listener, to be used by InsetsController and InsetsPolicy to
+     * animate insets.
+     */
+    public static class InternalAnimationControlListener
+            implements WindowInsetsAnimationControlListener {
+
+        private WindowInsetsAnimationController mController;
+        private ValueAnimator mAnimator;
+        private final boolean mShow;
+        private final boolean mHasAnimationCallbacks;
+        private final @InsetsType int mRequestedTypes;
+        private final @Behavior int mBehavior;
+        private final long mDurationMs;
+        private final boolean mDisable;
+        private final int mFloatingImeBottomInset;
+
+        private ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal =
+                new ThreadLocal<AnimationHandler>() {
+            @Override
+            protected AnimationHandler initialValue() {
+                AnimationHandler handler = new AnimationHandler();
+                handler.setProvider(new SfVsyncFrameCallbackProvider());
+                return handler;
+            }
+        };
+
+        public InternalAnimationControlListener(boolean show, boolean hasAnimationCallbacks,
+                @InsetsType int requestedTypes, @Behavior int behavior, boolean disable,
+                int floatingImeBottomInset) {
+            mShow = show;
+            mHasAnimationCallbacks = hasAnimationCallbacks;
+            mRequestedTypes = requestedTypes;
+            mBehavior = behavior;
+            mDurationMs = calculateDurationMs();
+            mDisable = disable;
+            mFloatingImeBottomInset = floatingImeBottomInset;
+        }
+
+        @Override
+        public void onReady(WindowInsetsAnimationController controller, int types) {
+            mController = controller;
+            if (DEBUG) Log.d(TAG, "default animation onReady types: " + types);
+
+            if (mDisable) {
+                onAnimationFinish();
+                return;
+            }
+            mAnimator = ValueAnimator.ofFloat(0f, 1f);
+            mAnimator.setDuration(mDurationMs);
+            mAnimator.setInterpolator(new LinearInterpolator());
+            Insets hiddenInsets = controller.getHiddenStateInsets();
+            // IME with zero insets is a special case: it will animate-in from offscreen and end
+            // with final insets of zero and vice-versa.
+            hiddenInsets = controller.hasZeroInsetsIme()
+                    ? Insets.of(hiddenInsets.left, hiddenInsets.top, hiddenInsets.right,
+                            mFloatingImeBottomInset)
+                    : hiddenInsets;
+            Insets start = mShow
+                    ? hiddenInsets
+                    : controller.getShownStateInsets();
+            Insets end = mShow
+                    ? controller.getShownStateInsets()
+                    : hiddenInsets;
+            Interpolator insetsInterpolator = getInsetsInterpolator();
+            Interpolator alphaInterpolator = getAlphaInterpolator();
+            mAnimator.addUpdateListener(animation -> {
+                float rawFraction = animation.getAnimatedFraction();
+                float alphaFraction = mShow
+                        ? rawFraction
+                        : 1 - rawFraction;
+                float insetsFraction = insetsInterpolator.getInterpolation(rawFraction);
+                controller.setInsetsAndAlpha(
+                        sEvaluator.evaluate(insetsFraction, start, end),
+                        alphaInterpolator.getInterpolation(alphaFraction),
+                        rawFraction);
+                if (DEBUG) Log.d(TAG, "Default animation setInsetsAndAlpha fraction: "
+                        + insetsFraction);
+            });
+            mAnimator.addListener(new AnimatorListenerAdapter() {
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    onAnimationFinish();
+                }
+            });
+            if (!mHasAnimationCallbacks) {
+                mAnimator.setAnimationHandler(mSfAnimationHandlerThreadLocal.get());
+            }
+            mAnimator.start();
+        }
+
+        @Override
+        public void onFinished(WindowInsetsAnimationController controller) {
+            if (DEBUG) Log.d(TAG, "InternalAnimationControlListener onFinished types:"
+                    + Type.toString(mRequestedTypes));
+        }
+
+        @Override
+        public void onCancelled(WindowInsetsAnimationController controller) {
+            // Animator can be null when it is cancelled before onReady() completes.
+            if (mAnimator != null) {
+                mAnimator.cancel();
+            }
+            if (DEBUG) Log.d(TAG, "InternalAnimationControlListener onCancelled types:"
+                    + mRequestedTypes);
+        }
+
+        protected Interpolator getInsetsInterpolator() {
+            if ((mRequestedTypes & ime()) != 0) {
+                if (mHasAnimationCallbacks) {
+                    return SYNC_IME_INTERPOLATOR;
+                } else if (mShow) {
+                    return LINEAR_OUT_SLOW_IN_INTERPOLATOR;
+                } else {
+                    return FAST_OUT_LINEAR_IN_INTERPOLATOR;
+                }
+            } else {
+                if (mBehavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) {
+                    return SYSTEM_BARS_INSETS_INTERPOLATOR;
+                } else {
+                    // Makes insets stay at the shown position.
+                    return input -> mShow ? 1f : 0f;
+                }
+            }
+        }
+
+        Interpolator getAlphaInterpolator() {
+            if ((mRequestedTypes & ime()) != 0) {
+                if (mHasAnimationCallbacks) {
+                    return input -> 1f;
+                } else if (mShow) {
+
+                    // Alpha animation takes half the time with linear interpolation;
+                    return input -> Math.min(1f, 2 * input);
+                } else {
+                    return FAST_OUT_LINEAR_IN_INTERPOLATOR;
+                }
+            } else {
+                if (mBehavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) {
+                    return input -> 1f;
+                } else {
+                    if (mShow) {
+                        return SYSTEM_BARS_ALPHA_INTERPOLATOR;
+                    } else {
+                        return SYSTEM_BARS_DIM_INTERPOLATOR;
+                    }
+                }
+            }
+        }
+
+        protected void onAnimationFinish() {
+            mController.finish(mShow);
+            if (DEBUG) Log.d(TAG, "onAnimationFinish showOnFinish: " + mShow);
+        }
+
+        /**
+         * To get the animation duration in MS.
+         */
+        public long getDurationMs() {
+            return mDurationMs;
+        }
+
+        private long calculateDurationMs() {
+            if ((mRequestedTypes & ime()) != 0) {
+                if (mHasAnimationCallbacks) {
+                    return ANIMATION_DURATION_SYNC_IME_MS;
+                } else {
+                    return ANIMATION_DURATION_UNSYNC_IME_MS;
+                }
+            } else {
+                if (mBehavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) {
+                    return mShow ? ANIMATION_DURATION_MOVE_IN_MS : ANIMATION_DURATION_MOVE_OUT_MS;
+                } else {
+                    return mShow ? ANIMATION_DURATION_FADE_IN_MS : ANIMATION_DURATION_FADE_OUT_MS;
+                }
+            }
+        }
+    }
+
+    /**
+     * Represents a running animation
+     */
+    private static class RunningAnimation {
+
+        RunningAnimation(InsetsAnimationControlRunner runner, int type) {
+            this.runner = runner;
+            this.type = type;
+        }
+
+        final InsetsAnimationControlRunner runner;
+        final @AnimationType int type;
+
+        /**
+         * Whether {@link WindowInsetsAnimation.Callback#onStart(WindowInsetsAnimation, Bounds)} has
+         * been dispatched already for this animation.
+         */
+        boolean startDispatched;
+    }
+
+    /**
+     * Represents a control request that we had to defer because we are waiting for the IME to
+     * process our show request.
+     */
+    private static class PendingControlRequest {
+
+        PendingControlRequest(@InsetsType int types, WindowInsetsAnimationControlListener listener,
+                long durationMs, Interpolator interpolator, @AnimationType int animationType,
+                @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
+                CancellationSignal cancellationSignal, boolean useInsetsAnimationThread) {
+            this.types = types;
+            this.listener = listener;
+            this.durationMs = durationMs;
+            this.interpolator = interpolator;
+            this.animationType = animationType;
+            this.layoutInsetsDuringAnimation = layoutInsetsDuringAnimation;
+            this.cancellationSignal = cancellationSignal;
+            this.useInsetsAnimationThread = useInsetsAnimationThread;
+        }
+
+        final @InsetsType int types;
+        final WindowInsetsAnimationControlListener listener;
+        final long durationMs;
+        final Interpolator interpolator;
+        final @AnimationType int animationType;
+        final @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation;
+        final CancellationSignal cancellationSignal;
+        final boolean useInsetsAnimationThread;
+    }
+
+    /** The local state */
+    private final InsetsState mState = new InsetsState();
+
+    /** The state dispatched from server */
+    private final InsetsState mLastDispatchedState = new InsetsState();
+
+    // TODO: Use other class to represent the requested visibility of each type, because the
+    //       display frame and the frame in each source are not used.
+    /** The requested visibilities sent to server */
+    private final InsetsState mRequestedState = new InsetsState();
+
+    private final Rect mFrame = new Rect();
+    private final BiFunction<InsetsController, Integer, InsetsSourceConsumer> mConsumerCreator;
+    private final SparseArray<InsetsSourceConsumer> mSourceConsumers = new SparseArray<>();
+    private final Host mHost;
+    private final Handler mHandler;
+
+    private final SparseArray<InsetsSourceControl> mTmpControlArray = new SparseArray<>();
+    private final ArrayList<RunningAnimation> mRunningAnimations = new ArrayList<>();
+    private final ArrayList<InsetsAnimationControlImpl> mTmpFinishedControls = new ArrayList<>();
+    private final ArraySet<InsetsSourceConsumer> mRequestedVisibilityChanged = new ArraySet<>();
+    private WindowInsets mLastInsets;
+
+    private boolean mAnimCallbackScheduled;
+
+    private final Runnable mAnimCallback;
+
+    /** Pending control request that is waiting on IME to be ready to be shown */
+    private PendingControlRequest mPendingImeControlRequest;
+
+    private int mWindowType;
+    private int mLastLegacySoftInputMode;
+    private int mLastLegacyWindowFlags;
+    private int mLastLegacySystemUiFlags;
+    private int mLastWindowingMode;
+    private boolean mStartingAnimation;
+    private int mCaptionInsetsHeight = 0;
+    private boolean mAnimationsDisabled;
+
+    private Runnable mPendingControlTimeout = this::abortPendingImeControlRequest;
+    private final ArrayList<OnControllableInsetsChangedListener> mControllableInsetsChangedListeners
+            = new ArrayList<>();
+
+    /** Set of inset types for which an animation was started since last resetting this field */
+    private @InsetsType int mLastStartedAnimTypes;
+
+    /** Set of inset types which cannot be controlled by the user animation */
+    private @InsetsType int mDisabledUserAnimationInsetsTypes;
+
+    private Runnable mInvokeControllableInsetsChangedListeners =
+            this::invokeControllableInsetsChangedListeners;
+
+    public InsetsController(Host host) {
+        this(host, (controller, type) -> {
+            if (type == ITYPE_IME) {
+                return new ImeInsetsSourceConsumer(controller.mState, Transaction::new, controller);
+            } else {
+                return new InsetsSourceConsumer(type, controller.mState, Transaction::new,
+                        controller);
+            }
+        }, host.getHandler());
+    }
+
+    @VisibleForTesting
+    public InsetsController(Host host,
+            BiFunction<InsetsController, Integer, InsetsSourceConsumer> consumerCreator,
+            Handler handler) {
+        mHost = host;
+        mConsumerCreator = consumerCreator;
+        mHandler = handler;
+        mAnimCallback = () -> {
+            mAnimCallbackScheduled = false;
+            if (mRunningAnimations.isEmpty()) {
+                return;
+            }
+
+            final List<WindowInsetsAnimation> runningAnimations = new ArrayList<>();
+            final InsetsState state = new InsetsState(mState, true /* copySources */);
+            for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
+                RunningAnimation runningAnimation = mRunningAnimations.get(i);
+                if (DEBUG) Log.d(TAG, "Running animation type: " + runningAnimation.type);
+                InsetsAnimationControlRunner runner = runningAnimation.runner;
+                if (runner instanceof InsetsAnimationControlImpl) {
+                    InsetsAnimationControlImpl control = (InsetsAnimationControlImpl) runner;
+
+                    // Keep track of running animation to be dispatched. Aggregate it here such that
+                    // if it gets finished within applyChangeInsets we still dispatch it to
+                    // onProgress.
+                    if (runningAnimation.startDispatched) {
+                        runningAnimations.add(control.getAnimation());
+                    }
+
+                    if (control.applyChangeInsets(state)) {
+                        mTmpFinishedControls.add(control);
+                    }
+                }
+            }
+
+            WindowInsets insets = state.calculateInsets(mFrame, mState /* ignoringVisibilityState*/,
+                    mLastInsets.isRound(), mLastInsets.shouldAlwaysConsumeSystemBars(),
+                    mLastLegacySoftInputMode, mLastLegacyWindowFlags, mLastLegacySystemUiFlags,
+                    mWindowType, mLastWindowingMode, null /* typeSideMap */);
+            mHost.dispatchWindowInsetsAnimationProgress(insets,
+                    Collections.unmodifiableList(runningAnimations));
+            if (DEBUG) {
+                for (WindowInsetsAnimation anim : runningAnimations) {
+                    Log.d(TAG, String.format("Running animation type: %d, progress: %f",
+                            anim.getTypeMask(), anim.getInterpolatedFraction()));
+                }
+            }
+
+            for (int i = mTmpFinishedControls.size() - 1; i >= 0; i--) {
+                dispatchAnimationEnd(mTmpFinishedControls.get(i).getAnimation());
+            }
+            mTmpFinishedControls.clear();
+        };
+    }
+
+    @VisibleForTesting
+    public void onFrameChanged(Rect frame) {
+        if (mFrame.equals(frame)) {
+            return;
+        }
+        mHost.notifyInsetsChanged();
+        mFrame.set(frame);
+    }
+
+    @Override
+    public InsetsState getState() {
+        return mState;
+    }
+
+    @Override
+    public boolean isRequestedVisible(int type) {
+        return getSourceConsumer(type).isRequestedVisible();
+    }
+
+    public InsetsState getLastDispatchedState() {
+        return mLastDispatchedState;
+    }
+
+    @VisibleForTesting
+    public boolean onStateChanged(InsetsState state) {
+        boolean stateChanged = !mState.equals(state, true /* excludingCaptionInsets */,
+                        false /* excludeInvisibleIme */)
+                || !captionInsetsUnchanged();
+        if (!stateChanged && mLastDispatchedState.equals(state)) {
+            return false;
+        }
+        if (DEBUG) Log.d(TAG, "onStateChanged: " + state);
+        mLastDispatchedState.set(state, true /* copySources */);
+
+        final InsetsState lastState = new InsetsState(mState, true /* copySources */);
+        updateState(state);
+        applyLocalVisibilityOverride();
+
+        if (!mState.equals(lastState, false /* excludingCaptionInsets */,
+                true /* excludeInvisibleIme */)) {
+            if (DEBUG) Log.d(TAG, "onStateChanged, notifyInsetsChanged");
+            mHost.notifyInsetsChanged();
+        }
+        return true;
+    }
+
+    private void updateState(InsetsState newState) {
+        mState.setDisplayFrame(newState.getDisplayFrame());
+        mState.setDisplayCutout(newState.getDisplayCutout());
+        mState.setRoundedCorners(newState.getRoundedCorners());
+        mState.setPrivacyIndicatorBounds(newState.getPrivacyIndicatorBounds());
+        @InsetsType int disabledUserAnimationTypes = 0;
+        @InsetsType int[] cancelledUserAnimationTypes = {0};
+        for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
+            InsetsSource source = newState.peekSource(type);
+            if (source == null) continue;
+            @AnimationType int animationType = getAnimationType(type);
+            if (!source.isUserControllable()) {
+                @InsetsType int insetsType = toPublicType(type);
+                // The user animation is not allowed when visible frame is empty.
+                disabledUserAnimationTypes |= insetsType;
+                if (animationType == ANIMATION_TYPE_USER) {
+                    // Existing user animation needs to be cancelled.
+                    animationType = ANIMATION_TYPE_NONE;
+                    cancelledUserAnimationTypes[0] |= insetsType;
+                }
+            }
+            getSourceConsumer(type).updateSource(source, animationType);
+        }
+        for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
+            // Only update the server side insets here.
+            if (type == ITYPE_CAPTION_BAR) continue;
+            InsetsSource source = mState.peekSource(type);
+            if (source == null) continue;
+            if (newState.peekSource(type) == null) {
+                mState.removeSource(type);
+            }
+        }
+
+        updateDisabledUserAnimationTypes(disabledUserAnimationTypes);
+
+        if (cancelledUserAnimationTypes[0] != 0) {
+            mHandler.post(() -> show(cancelledUserAnimationTypes[0]));
+        }
+    }
+
+    private void updateDisabledUserAnimationTypes(@InsetsType int disabledUserAnimationTypes) {
+        @InsetsType int diff = mDisabledUserAnimationInsetsTypes ^ disabledUserAnimationTypes;
+        if (diff != 0) {
+            for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
+                InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
+                if (consumer.getControl() != null
+                        && (toPublicType(consumer.getType()) & diff) != 0) {
+                    mHandler.removeCallbacks(mInvokeControllableInsetsChangedListeners);
+                    mHandler.post(mInvokeControllableInsetsChangedListeners);
+                    break;
+                }
+            }
+            mDisabledUserAnimationInsetsTypes = disabledUserAnimationTypes;
+        }
+    }
+
+    private boolean captionInsetsUnchanged() {
+        if (mState.peekSource(ITYPE_CAPTION_BAR) == null
+                && mCaptionInsetsHeight == 0) {
+            return true;
+        }
+        if (mState.peekSource(ITYPE_CAPTION_BAR) != null
+                && mCaptionInsetsHeight
+                == mState.peekSource(ITYPE_CAPTION_BAR).getFrame().height()) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * @see InsetsState#calculateInsets
+     */
+    @VisibleForTesting
+    public WindowInsets calculateInsets(boolean isScreenRound, boolean alwaysConsumeSystemBars,
+            int windowType, int windowingMode, int legacySoftInputMode, int legacyWindowFlags,
+            int legacySystemUiFlags) {
+        mWindowType = windowType;
+        mLastWindowingMode = windowingMode;
+        mLastLegacySoftInputMode = legacySoftInputMode;
+        mLastLegacyWindowFlags = legacyWindowFlags;
+        mLastLegacySystemUiFlags = legacySystemUiFlags;
+        mLastInsets = mState.calculateInsets(mFrame, null /* ignoringVisibilityState*/,
+                isScreenRound, alwaysConsumeSystemBars, legacySoftInputMode, legacyWindowFlags,
+                legacySystemUiFlags, windowType, windowingMode, null /* typeSideMap */);
+        return mLastInsets;
+    }
+
+    /**
+     * @see InsetsState#calculateVisibleInsets(Rect, int)
+     */
+    public Rect calculateVisibleInsets(@SoftInputModeFlags int softInputMode) {
+        return mState.calculateVisibleInsets(mFrame, softInputMode);
+    }
+
+    /**
+     * Called when the server has dispatched us a new set of inset controls.
+     */
+    public void onControlsChanged(InsetsSourceControl[] activeControls) {
+        if (activeControls != null) {
+            for (InsetsSourceControl activeControl : activeControls) {
+                if (activeControl != null) {
+                    // TODO(b/122982984): Figure out why it can be null.
+                    mTmpControlArray.put(activeControl.getType(), activeControl);
+                }
+            }
+        }
+
+        boolean requestedStateStale = false;
+        final int[] showTypes = new int[1];
+        final int[] hideTypes = new int[1];
+
+        // Ensure to update all existing source consumers
+        for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
+            final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
+            final InsetsSourceControl control = mTmpControlArray.get(consumer.getType());
+
+            // control may be null, but we still need to update the control to null if it got
+            // revoked.
+            consumer.setControl(control, showTypes, hideTypes);
+        }
+
+        // Ensure to create source consumers if not available yet.
+        for (int i = mTmpControlArray.size() - 1; i >= 0; i--) {
+            final InsetsSourceControl control = mTmpControlArray.valueAt(i);
+            final @InternalInsetsType int type = control.getType();
+            final InsetsSourceConsumer consumer = getSourceConsumer(type);
+            consumer.setControl(control, showTypes, hideTypes);
+
+            if (!requestedStateStale) {
+                final boolean requestedVisible = consumer.isRequestedVisible();
+
+                // We might have changed our requested visibilities while we don't have the control,
+                // so we need to update our requested state once we have control. Otherwise, our
+                // requested state at the server side might be incorrect.
+                final boolean requestedVisibilityChanged =
+                        requestedVisible != mRequestedState.getSourceOrDefaultVisibility(type);
+
+                // The IME client visibility will be reset by insets source provider while updating
+                // control, so if IME is requested visible, we need to send the request to server.
+                final boolean imeRequestedVisible = type == ITYPE_IME && requestedVisible;
+
+                requestedStateStale = requestedVisibilityChanged || imeRequestedVisible;
+            }
+        }
+
+        if (mTmpControlArray.size() > 0) {
+            // Update surface positions for animations.
+            for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
+                mRunningAnimations.get(i).runner.updateSurfacePosition(mTmpControlArray);
+            }
+        }
+        mTmpControlArray.clear();
+
+        // Do not override any animations that the app started in the OnControllableInsetsChanged
+        // listeners.
+        int animatingTypes = invokeControllableInsetsChangedListeners();
+        showTypes[0] &= ~animatingTypes;
+        hideTypes[0] &= ~animatingTypes;
+
+        if (showTypes[0] != 0) {
+            applyAnimation(showTypes[0], true /* show */, false /* fromIme */);
+        }
+        if (hideTypes[0] != 0) {
+            applyAnimation(hideTypes[0], false /* show */, false /* fromIme */);
+        }
+
+        // InsetsSourceConsumer#setControl might change the requested visibility.
+        updateRequestedVisibility();
+    }
+
+    @Override
+    public void show(@InsetsType int types) {
+        show(types, false /* fromIme */);
+    }
+
+    @VisibleForTesting
+    public void show(@InsetsType int types, boolean fromIme) {
+        if ((types & ime()) != 0) {
+            Log.d(TAG, "show(ime(), fromIme=" + fromIme + ")");
+        }
+        if (fromIme) {
+            ImeTracing.getInstance().triggerClientDump("InsetsController#show",
+                    mHost.getInputMethodManager(), null /* icProto */);
+            Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApiToImeReady", 0);
+            Trace.asyncTraceBegin(TRACE_TAG_VIEW, "IC.showRequestFromIme", 0);
+        } else {
+            Trace.asyncTraceBegin(TRACE_TAG_VIEW, "IC.showRequestFromApi", 0);
+            Trace.asyncTraceBegin(TRACE_TAG_VIEW, "IC.showRequestFromApiToImeReady", 0);
+        }
+        // Handle pending request ready in case there was one set.
+        if (fromIme && mPendingImeControlRequest != null) {
+            PendingControlRequest pendingRequest = mPendingImeControlRequest;
+            mPendingImeControlRequest = null;
+            mHandler.removeCallbacks(mPendingControlTimeout);
+
+            // We are about to playing the default animation. Passing a null frame indicates the
+            // controlled types should be animated regardless of the frame.
+            controlAnimationUnchecked(
+                    pendingRequest.types, pendingRequest.cancellationSignal,
+                    pendingRequest.listener, null /* frame */,
+                    true /* fromIme */, pendingRequest.durationMs, pendingRequest.interpolator,
+                    pendingRequest.animationType,
+                    pendingRequest.layoutInsetsDuringAnimation,
+                    pendingRequest.useInsetsAnimationThread);
+            return;
+        }
+
+        // TODO: Support a ResultReceiver for IME.
+        // TODO(b/123718661): Make show() work for multi-session IME.
+        int typesReady = 0;
+        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
+        for (int i = internalTypes.size() - 1; i >= 0; i--) {
+            @InternalInsetsType int internalType = internalTypes.valueAt(i);
+            @AnimationType int animationType = getAnimationType(internalType);
+            InsetsSourceConsumer consumer = getSourceConsumer(internalType);
+            if (consumer.isRequestedVisible() && animationType == ANIMATION_TYPE_NONE
+                    || animationType == ANIMATION_TYPE_SHOW) {
+                // no-op: already shown or animating in (because window visibility is
+                // applied before starting animation).
+                if (DEBUG) Log.d(TAG, String.format(
+                        "show ignored for type: %d animType: %d requestedVisible: %s",
+                        consumer.getType(), animationType, consumer.isRequestedVisible()));
+                continue;
+            }
+            if (fromIme && animationType == ANIMATION_TYPE_USER) {
+                // App is already controlling the IME, don't cancel it.
+                continue;
+            }
+            typesReady |= InsetsState.toPublicType(consumer.getType());
+        }
+        if (DEBUG) Log.d(TAG, "show typesReady: " + typesReady);
+        applyAnimation(typesReady, true /* show */, fromIme);
+    }
+
+    @Override
+    public void hide(@InsetsType int types) {
+        hide(types, false /* fromIme */);
+    }
+
+    @VisibleForTesting
+    public void hide(@InsetsType int types, boolean fromIme) {
+        if (fromIme) {
+            ImeTracing.getInstance().triggerClientDump("InsetsController#hide",
+                    mHost.getInputMethodManager(), null /* icProto */);
+            Trace.asyncTraceBegin(TRACE_TAG_VIEW, "IC.hideRequestFromIme", 0);
+        } else {
+            Trace.asyncTraceBegin(TRACE_TAG_VIEW, "IC.hideRequestFromApi", 0);
+        }
+        int typesReady = 0;
+        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
+        for (int i = internalTypes.size() - 1; i >= 0; i--) {
+            @InternalInsetsType int internalType = internalTypes.valueAt(i);
+            @AnimationType int animationType = getAnimationType(internalType);
+            InsetsSourceConsumer consumer = getSourceConsumer(internalType);
+            if (!consumer.isRequestedVisible() && animationType == ANIMATION_TYPE_NONE
+                    || animationType == ANIMATION_TYPE_HIDE) {
+                // no-op: already hidden or animating out.
+                continue;
+            }
+            typesReady |= InsetsState.toPublicType(consumer.getType());
+        }
+        applyAnimation(typesReady, false /* show */, fromIme /* fromIme */);
+    }
+
+    @Override
+    public void controlWindowInsetsAnimation(@InsetsType int types, long durationMillis,
+            @Nullable Interpolator interpolator,
+            @Nullable CancellationSignal cancellationSignal,
+            @NonNull WindowInsetsAnimationControlListener listener) {
+        controlWindowInsetsAnimation(types, cancellationSignal, listener,
+                false /* fromIme */, durationMillis, interpolator, ANIMATION_TYPE_USER);
+    }
+
+    private void controlWindowInsetsAnimation(@InsetsType int types,
+            @Nullable CancellationSignal cancellationSignal,
+            WindowInsetsAnimationControlListener listener,
+            boolean fromIme, long durationMs, @Nullable Interpolator interpolator,
+            @AnimationType int animationType) {
+        if ((mState.calculateUncontrollableInsetsFromFrame(mFrame) & types) != 0) {
+            listener.onCancelled(null);
+            return;
+        }
+        if (fromIme) {
+            ImeTracing.getInstance().triggerClientDump(
+                    "InsetsController#controlWindowInsetsAnimation",
+                    mHost.getInputMethodManager(), null /* icProto */);
+        }
+
+        controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, durationMs,
+                interpolator, animationType, getLayoutInsetsDuringAnimationMode(types),
+                false /* useInsetsAnimationThread */);
+    }
+
+    private void controlAnimationUnchecked(@InsetsType int types,
+            @Nullable CancellationSignal cancellationSignal,
+            WindowInsetsAnimationControlListener listener, @Nullable Rect frame, boolean fromIme,
+            long durationMs, Interpolator interpolator,
+            @AnimationType int animationType,
+            @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
+            boolean useInsetsAnimationThread) {
+        if ((types & mTypesBeingCancelled) != 0) {
+            throw new IllegalStateException("Cannot start a new insets animation of "
+                    + Type.toString(types)
+                    + " while an existing " + Type.toString(mTypesBeingCancelled)
+                    + " is being cancelled.");
+        }
+        if (animationType == ANIMATION_TYPE_USER) {
+            final @InsetsType int disabledTypes = types & mDisabledUserAnimationInsetsTypes;
+            if (DEBUG) Log.d(TAG, "user animation disabled types: " + disabledTypes);
+            types &= ~mDisabledUserAnimationInsetsTypes;
+
+            if (fromIme && (disabledTypes & ime()) != 0
+                    && !mState.getSource(ITYPE_IME).isVisible()) {
+                // We've requested IMM to show IME, but the IME is not controllable. We need to
+                // cancel the request.
+                getSourceConsumer(ITYPE_IME).hide(true, animationType);
+            }
+        }
+        if (types == 0) {
+            // nothing to animate.
+            listener.onCancelled(null);
+            updateRequestedVisibility();
+            if (DEBUG) Log.d(TAG, "no types to animate in controlAnimationUnchecked");
+            return;
+        }
+        cancelExistingControllers(types);
+        if (DEBUG) Log.d(TAG, "controlAnimation types: " + types);
+        mLastStartedAnimTypes |= types;
+
+        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
+        final SparseArray<InsetsSourceControl> controls = new SparseArray<>();
+
+        Pair<Integer, Boolean> typesReadyPair = collectSourceControls(
+                fromIme, internalTypes, controls, animationType);
+        int typesReady = typesReadyPair.first;
+        boolean imeReady = typesReadyPair.second;
+        if (DEBUG) Log.d(TAG, String.format(
+                "controlAnimationUnchecked, typesReady: %s imeReady: %s", typesReady, imeReady));
+        if (!imeReady) {
+            // IME isn't ready, all requested types will be animated once IME is ready
+            abortPendingImeControlRequest();
+            final PendingControlRequest request = new PendingControlRequest(types,
+                    listener, durationMs,
+                    interpolator, animationType, layoutInsetsDuringAnimation, cancellationSignal,
+                    useInsetsAnimationThread);
+            mPendingImeControlRequest = request;
+            mHandler.postDelayed(mPendingControlTimeout, PENDING_CONTROL_TIMEOUT_MS);
+            if (DEBUG) Log.d(TAG, "Ime not ready. Create pending request");
+            if (cancellationSignal != null) {
+                cancellationSignal.setOnCancelListener(() -> {
+                    if (mPendingImeControlRequest == request) {
+                        if (DEBUG) Log.d(TAG,
+                                "Cancellation signal abortPendingImeControlRequest");
+                        abortPendingImeControlRequest();
+                    }
+                });
+            }
+            updateRequestedVisibility();
+            Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApi", 0);
+            return;
+        }
+
+        if (typesReady == 0) {
+            if (DEBUG) Log.d(TAG, "No types ready. onCancelled()");
+            listener.onCancelled(null);
+            updateRequestedVisibility();
+            return;
+        }
+
+
+        final InsetsAnimationControlRunner runner = useInsetsAnimationThread
+                ? new InsetsAnimationThreadControlRunner(controls,
+                        frame, mState, listener, typesReady, this, durationMs, interpolator,
+                        animationType, layoutInsetsDuringAnimation, mHost.getTranslator(),
+                        mHost.getHandler())
+                : new InsetsAnimationControlImpl(controls,
+                        frame, mState, listener, typesReady, this, durationMs, interpolator,
+                        animationType, layoutInsetsDuringAnimation, mHost.getTranslator());
+        if ((typesReady & WindowInsets.Type.ime()) != 0) {
+            ImeTracing.getInstance().triggerClientDump("InsetsAnimationControlImpl",
+                    mHost.getInputMethodManager(), null /* icProto */);
+        }
+        mRunningAnimations.add(new RunningAnimation(runner, animationType));
+        if (DEBUG) Log.d(TAG, "Animation added to runner. useInsetsAnimationThread: "
+                + useInsetsAnimationThread);
+        if (cancellationSignal != null) {
+            cancellationSignal.setOnCancelListener(() -> {
+                cancelAnimation(runner, true /* invokeCallback */);
+            });
+        } else {
+            Trace.asyncTraceBegin(TRACE_TAG_VIEW, "IC.pendingAnim", 0);
+        }
+        if (layoutInsetsDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN) {
+            showDirectly(types, fromIme);
+        } else {
+            hideDirectly(types, false /* animationFinished */, animationType, fromIme);
+        }
+        updateRequestedVisibility();
+    }
+
+    /**
+     * @return Pair of (types ready to animate, IME ready to animate).
+     */
+    private Pair<Integer, Boolean> collectSourceControls(boolean fromIme,
+            ArraySet<Integer> internalTypes, SparseArray<InsetsSourceControl> controls,
+            @AnimationType int animationType) {
+        int typesReady = 0;
+        boolean imeReady = true;
+        for (int i = internalTypes.size() - 1; i >= 0; i--) {
+            final InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
+            boolean show = animationType == ANIMATION_TYPE_SHOW
+                    || animationType == ANIMATION_TYPE_USER;
+            boolean canRun = false;
+            if (show) {
+                // Show request
+                if (fromIme) {
+                    ImeTracing.getInstance().triggerClientDump(
+                            "ImeInsetsSourceConsumer#requestShow", mHost.getInputMethodManager(),
+                            null /* icProto */);
+                }
+                switch(consumer.requestShow(fromIme)) {
+                    case ShowResult.SHOW_IMMEDIATELY:
+                        canRun = true;
+                        break;
+                    case ShowResult.IME_SHOW_DELAYED:
+                        imeReady = false;
+                        if (DEBUG) Log.d(TAG, "requestShow IME_SHOW_DELAYED");
+                        break;
+                    case ShowResult.IME_SHOW_FAILED:
+                        if (WARN) Log.w(TAG, "requestShow IME_SHOW_FAILED. fromIme: "
+                                + fromIme);
+                        // IME cannot be shown (since it didn't have focus), proceed
+                        // with animation of other types.
+                        break;
+                }
+            } else {
+                // Hide request
+                // TODO: Move notifyHidden() to beginning of the hide animation
+                // (when visibility actually changes using hideDirectly()).
+                if (!fromIme) {
+                    consumer.notifyHidden();
+                }
+                canRun = true;
+            }
+            if (!canRun) {
+                if (WARN) Log.w(TAG, String.format(
+                        "collectSourceControls can't continue show for type: %s fromIme: %b",
+                        InsetsState.typeToString(consumer.getType()), fromIme));
+                continue;
+            }
+            final InsetsSourceControl control = consumer.getControl();
+            if (control != null && control.getLeash() != null) {
+                controls.put(consumer.getType(), new InsetsSourceControl(control));
+                typesReady |= toPublicType(consumer.getType());
+            } else if (animationType == ANIMATION_TYPE_SHOW) {
+                if (DEBUG) Log.d(TAG, "collectSourceControls no control for show(). fromIme: "
+                        + fromIme);
+                // We don't have a control at the moment. However, we still want to update requested
+                // visibility state such that in case we get control, we can apply show animation.
+                if (fromIme) {
+                    ImeTracing.getInstance().triggerClientDump(
+                            "InsetsSourceConsumer#show", mHost.getInputMethodManager(),
+                            null /* icProto */);
+                }
+                consumer.show(fromIme);
+            } else if (animationType == ANIMATION_TYPE_HIDE) {
+                if (fromIme) {
+                    ImeTracing.getInstance().triggerClientDump(
+                            "InsetsSourceConsumer#hide", mHost.getInputMethodManager(),
+                            null /* icProto */);
+                }
+                consumer.hide();
+            }
+        }
+        return new Pair<>(typesReady, imeReady);
+    }
+
+    private @LayoutInsetsDuringAnimation int getLayoutInsetsDuringAnimationMode(
+            @InsetsType int types) {
+
+        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
+
+        // Generally, we want to layout the opposite of the current state. This is to make animation
+        // callbacks easy to use: The can capture the layout values and then treat that as end-state
+        // during the animation.
+        //
+        // However, if controlling multiple sources, we want to treat it as shown if any of the
+        // types is currently hidden.
+        for (int i = internalTypes.size() - 1; i >= 0; i--) {
+            InsetsSourceConsumer consumer = mSourceConsumers.get(internalTypes.valueAt(i));
+            if (consumer == null) {
+                continue;
+            }
+            if (!consumer.isRequestedVisible()) {
+                return LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
+            }
+        }
+        return LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
+    }
+
+    private void cancelExistingControllers(@InsetsType int types) {
+        final int originalmTypesBeingCancelled = mTypesBeingCancelled;
+        mTypesBeingCancelled |= types;
+        try {
+            for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
+                InsetsAnimationControlRunner control = mRunningAnimations.get(i).runner;
+                if ((control.getTypes() & types) != 0) {
+                    cancelAnimation(control, true /* invokeCallback */);
+                }
+            }
+            if ((types & ime()) != 0) {
+                abortPendingImeControlRequest();
+            }
+        } finally {
+            mTypesBeingCancelled = originalmTypesBeingCancelled;
+        }
+    }
+
+    private void abortPendingImeControlRequest() {
+        if (mPendingImeControlRequest != null) {
+            mPendingImeControlRequest.listener.onCancelled(null);
+            mPendingImeControlRequest = null;
+            mHandler.removeCallbacks(mPendingControlTimeout);
+            if (DEBUG) Log.d(TAG, "abortPendingImeControlRequest");
+        }
+    }
+
+    @VisibleForTesting
+    @Override
+    public void notifyFinished(InsetsAnimationControlRunner runner, boolean shown) {
+        cancelAnimation(runner, false /* invokeCallback */);
+        if (DEBUG) Log.d(TAG, "notifyFinished. shown: " + shown);
+        if (shown) {
+            showDirectly(runner.getTypes(), true /* fromIme */);
+        } else {
+            hideDirectly(runner.getTypes(), true /* animationFinished */,
+                    runner.getAnimationType(), true /* fromIme */);
+        }
+    }
+
+    @Override
+    public void applySurfaceParams(final SyncRtSurfaceTransactionApplier.SurfaceParams... params) {
+        mHost.applySurfaceParams(params);
+    }
+
+    void notifyControlRevoked(InsetsSourceConsumer consumer) {
+        final @InsetsType int types = toPublicType(consumer.getType());
+        for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
+            InsetsAnimationControlRunner control = mRunningAnimations.get(i).runner;
+            control.notifyControlRevoked(types);
+            if (control.getControllingTypes() == 0) {
+                cancelAnimation(control, true /* invokeCallback */);
+            }
+        }
+        if (consumer.getType() == ITYPE_IME) {
+            abortPendingImeControlRequest();
+        }
+    }
+
+    private void cancelAnimation(InsetsAnimationControlRunner control, boolean invokeCallback) {
+        if (DEBUG) Log.d(TAG, String.format("cancelAnimation of types: %d, animType: %d",
+                control.getTypes(), control.getAnimationType()));
+        if (invokeCallback) {
+            control.cancel();
+        }
+        boolean stateChanged = false;
+        for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
+            RunningAnimation runningAnimation = mRunningAnimations.get(i);
+            if (runningAnimation.runner == control) {
+                mRunningAnimations.remove(i);
+                ArraySet<Integer> types = toInternalType(control.getTypes());
+                for (int j = types.size() - 1; j >= 0; j--) {
+                    if (types.valueAt(j) == ITYPE_IME) {
+                        ImeTracing.getInstance().triggerClientDump(
+                                "InsetsSourceConsumer#notifyAnimationFinished",
+                                mHost.getInputMethodManager(), null /* icProto */);
+                    }
+                    stateChanged |= getSourceConsumer(types.valueAt(j)).notifyAnimationFinished();
+                }
+                if (invokeCallback) {
+                    dispatchAnimationEnd(runningAnimation.runner.getAnimation());
+                }
+                break;
+            }
+        }
+        if (stateChanged) {
+            mHost.notifyInsetsChanged();
+        }
+    }
+
+    private void applyLocalVisibilityOverride() {
+        for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
+            final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
+            consumer.applyLocalVisibilityOverride();
+        }
+    }
+
+    @VisibleForTesting
+    public @NonNull InsetsSourceConsumer getSourceConsumer(@InternalInsetsType int type) {
+        InsetsSourceConsumer controller = mSourceConsumers.get(type);
+        if (controller != null) {
+            return controller;
+        }
+        controller = mConsumerCreator.apply(this, type);
+        mSourceConsumers.put(type, controller);
+        return controller;
+    }
+
+    @VisibleForTesting
+    public void notifyVisibilityChanged() {
+        mHost.notifyInsetsChanged();
+    }
+
+    /**
+     * @see ViewRootImpl#updateCompatSysUiVisibility(int, boolean, boolean)
+     */
+    public void updateCompatSysUiVisibility(@InternalInsetsType int type, boolean visible,
+            boolean hasControl) {
+        mHost.updateCompatSysUiVisibility(type, visible, hasControl);
+    }
+
+    /**
+     * Called when current window gains focus.
+     */
+    public void onWindowFocusGained(boolean hasViewFocused) {
+        getSourceConsumer(ITYPE_IME).onWindowFocusGained(hasViewFocused);
+    }
+
+    /**
+     * Called when current window loses focus.
+     */
+    public void onWindowFocusLost() {
+        getSourceConsumer(ITYPE_IME).onWindowFocusLost();
+    }
+
+    @VisibleForTesting
+    public @AnimationType int getAnimationType(@InternalInsetsType int type) {
+        for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
+            InsetsAnimationControlRunner control = mRunningAnimations.get(i).runner;
+            if (control.controlsInternalType(type)) {
+                return mRunningAnimations.get(i).type;
+            }
+        }
+        return ANIMATION_TYPE_NONE;
+    }
+
+    @VisibleForTesting
+    public void onRequestedVisibilityChanged(InsetsSourceConsumer consumer) {
+        mRequestedVisibilityChanged.add(consumer);
+    }
+
+    /**
+     * Sends the requested visibilities to window manager if any of them is changed.
+     */
+    private void updateRequestedVisibility() {
+        boolean changed = false;
+        for (int i = mRequestedVisibilityChanged.size() - 1; i >= 0; i--) {
+            final InsetsSourceConsumer consumer = mRequestedVisibilityChanged.valueAt(i);
+            final @InternalInsetsType int type = consumer.getType();
+            if (type == ITYPE_CAPTION_BAR) {
+                continue;
+            }
+            final boolean requestedVisible = consumer.isRequestedVisible();
+            if (requestedVisible != mRequestedState.getSourceOrDefaultVisibility(type)) {
+                mRequestedState.getSource(type).setVisible(requestedVisible);
+                changed = true;
+            }
+        }
+        mRequestedVisibilityChanged.clear();
+        if (!changed) {
+            return;
+        }
+        mHost.onInsetsModified(mRequestedState);
+    }
+
+    InsetsState getRequestedVisibility() {
+        return mRequestedState;
+    }
+
+    @VisibleForTesting
+    public void applyAnimation(@InsetsType final int types, boolean show, boolean fromIme) {
+        // TODO(b/166736352): We should only skip the animation of specific types, not all types.
+        boolean skipAnim = false;
+        if ((types & ime()) != 0) {
+            final InsetsSourceConsumer consumer = mSourceConsumers.get(ITYPE_IME);
+            final InsetsSourceControl imeControl = consumer != null ? consumer.getControl() : null;
+            // Skip showing animation once that made by system for some reason.
+            // (e.g. starting window with IME snapshot)
+            if (imeControl != null) {
+                skipAnim = imeControl.getAndClearSkipAnimationOnce() && show
+                        && consumer.hasViewFocusWhenWindowFocusGain();
+            }
+        }
+        applyAnimation(types, show, fromIme, skipAnim);
+    }
+
+    @VisibleForTesting
+    public void applyAnimation(@InsetsType final int types, boolean show, boolean fromIme,
+            boolean skipAnim) {
+        if (types == 0) {
+            // nothing to animate.
+            if (DEBUG) Log.d(TAG, "applyAnimation, nothing to animate");
+            return;
+        }
+
+        boolean hasAnimationCallbacks = mHost.hasAnimationCallbacks();
+        final InternalAnimationControlListener listener = new InternalAnimationControlListener(
+                show, hasAnimationCallbacks, types, mHost.getSystemBarsBehavior(),
+                skipAnim || mAnimationsDisabled, mHost.dipToPx(FLOATING_IME_BOTTOM_INSET_DP));
+
+        // We are about to playing the default animation (show/hide). Passing a null frame indicates
+        // the controlled types should be animated regardless of the frame.
+        controlAnimationUnchecked(
+                types, null /* cancellationSignal */, listener, null /* frame */, fromIme,
+                listener.getDurationMs(), listener.getInsetsInterpolator(),
+                show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE,
+                show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN,
+                !hasAnimationCallbacks /* useInsetsAnimationThread */);
+    }
+
+    private void hideDirectly(
+            @InsetsType int types, boolean animationFinished, @AnimationType int animationType,
+            boolean fromIme) {
+        if ((types & ime()) != 0) {
+            ImeTracing.getInstance().triggerClientDump("InsetsController#hideDirectly",
+                    mHost.getInputMethodManager(), null /* icProto */);
+        }
+        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
+        for (int i = internalTypes.size() - 1; i >= 0; i--) {
+            getSourceConsumer(internalTypes.valueAt(i)).hide(animationFinished, animationType);
+        }
+        updateRequestedVisibility();
+
+        if (fromIme) {
+            Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.hideRequestFromIme", 0);
+        }
+    }
+
+    private void showDirectly(@InsetsType int types, boolean fromIme) {
+        if ((types & ime()) != 0) {
+            ImeTracing.getInstance().triggerClientDump("InsetsController#showDirectly",
+                    mHost.getInputMethodManager(), null /* icProto */);
+        }
+        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
+        for (int i = internalTypes.size() - 1; i >= 0; i--) {
+            getSourceConsumer(internalTypes.valueAt(i)).show(false /* fromIme */);
+        }
+        updateRequestedVisibility();
+
+        if (fromIme) {
+            Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromIme", 0);
+        }
+    }
+
+    /**
+     * Cancel on-going animation to show/hide {@link InsetsType}.
+     */
+    @VisibleForTesting
+    public void cancelExistingAnimations() {
+        cancelExistingControllers(all());
+    }
+
+    void dump(String prefix, PrintWriter pw) {
+        pw.print(prefix); pw.println("InsetsController:");
+        mState.dump(prefix + "  ", pw);
+    }
+
+    void dumpDebug(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        mState.dumpDebug(proto, STATE);
+        for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
+            InsetsAnimationControlRunner runner = mRunningAnimations.get(i).runner;
+            runner.dumpDebug(proto, CONTROL);
+        }
+        proto.end(token);
+    }
+
+    @VisibleForTesting
+    @Override
+    public void startAnimation(InsetsAnimationControlImpl controller,
+            WindowInsetsAnimationControlListener listener, int types,
+            WindowInsetsAnimation animation, Bounds bounds) {
+        mHost.dispatchWindowInsetsAnimationPrepare(animation);
+        mHost.addOnPreDrawRunnable(() -> {
+            if (controller.isCancelled()) {
+                if (WARN) Log.w(TAG, "startAnimation canceled before preDraw");
+                return;
+            }
+            Trace.asyncTraceBegin(TRACE_TAG_VIEW,
+                    "InsetsAnimation: " + WindowInsets.Type.toString(types), types);
+            for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
+                RunningAnimation runningAnimation = mRunningAnimations.get(i);
+                if (runningAnimation.runner == controller) {
+                    runningAnimation.startDispatched = true;
+                }
+            }
+            Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.pendingAnim", 0);
+            mHost.dispatchWindowInsetsAnimationStart(animation, bounds);
+            mStartingAnimation = true;
+            controller.mReadyDispatched = true;
+            listener.onReady(controller, types);
+            mStartingAnimation = false;
+        });
+    }
+
+    @VisibleForTesting
+    public void dispatchAnimationEnd(WindowInsetsAnimation animation) {
+        Trace.asyncTraceEnd(TRACE_TAG_VIEW,
+                "InsetsAnimation: " + WindowInsets.Type.toString(animation.getTypeMask()),
+                animation.getTypeMask());
+        mHost.dispatchWindowInsetsAnimationEnd(animation);
+    }
+
+    @VisibleForTesting
+    @Override
+    public void scheduleApplyChangeInsets(InsetsAnimationControlRunner runner) {
+        if (mStartingAnimation || runner.getAnimationType() == ANIMATION_TYPE_USER) {
+            mAnimCallback.run();
+            mAnimCallbackScheduled = false;
+            return;
+        }
+        if (!mAnimCallbackScheduled) {
+            mHost.postInsetsAnimationCallback(mAnimCallback);
+            mAnimCallbackScheduled = true;
+        }
+    }
+
+    @Override
+    public void setSystemBarsAppearance(@Appearance int appearance, @Appearance int mask) {
+        mHost.setSystemBarsAppearance(appearance, mask);
+    }
+
+    @Override
+    public @Appearance int getSystemBarsAppearance() {
+        if (!mHost.isSystemBarsAppearanceControlled()) {
+            // We only return the requested appearance, not the implied one.
+            return 0;
+        }
+        return mHost.getSystemBarsAppearance();
+    }
+
+    @Override
+    public void setCaptionInsetsHeight(int height) {
+        if (mCaptionInsetsHeight != height) {
+            mCaptionInsetsHeight = height;
+            if (mCaptionInsetsHeight != 0) {
+                mState.getSource(ITYPE_CAPTION_BAR).setFrame(new Rect(mFrame.left, mFrame.top,
+                        mFrame.right, mFrame.top + mCaptionInsetsHeight));
+            } else {
+                mState.removeSource(ITYPE_CAPTION_BAR);
+            }
+            mHost.notifyInsetsChanged();
+        }
+    }
+
+    @Override
+    public void setSystemBarsBehavior(@Behavior int behavior) {
+        mHost.setSystemBarsBehavior(behavior);
+    }
+
+    @Override
+    public @Behavior int getSystemBarsBehavior() {
+        if (!mHost.isSystemBarsBehaviorControlled()) {
+            // We only return the requested behavior, not the implied one.
+            return 0;
+        }
+        return mHost.getSystemBarsBehavior();
+    }
+
+    @Override
+    public void setAnimationsDisabled(boolean disable) {
+        mAnimationsDisabled = disable;
+    }
+
+    private @InsetsType int calculateControllableTypes() {
+        @InsetsType int result = 0;
+        for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
+            InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
+            InsetsSource source = mState.peekSource(consumer.mType);
+            if (consumer.getControl() != null && source != null && source.isUserControllable()) {
+                result |= toPublicType(consumer.mType);
+            }
+        }
+        return result & ~mState.calculateUncontrollableInsetsFromFrame(mFrame);
+    }
+
+    /**
+     * @return The types that are now animating due to a listener invoking control/show/hide
+     */
+    private @InsetsType int invokeControllableInsetsChangedListeners() {
+        mHandler.removeCallbacks(mInvokeControllableInsetsChangedListeners);
+        mLastStartedAnimTypes = 0;
+        @InsetsType int types = calculateControllableTypes();
+        int size = mControllableInsetsChangedListeners.size();
+        for (int i = 0; i < size; i++) {
+            mControllableInsetsChangedListeners.get(i).onControllableInsetsChanged(this, types);
+        }
+        return mLastStartedAnimTypes;
+    }
+
+    @Override
+    public void addOnControllableInsetsChangedListener(
+            OnControllableInsetsChangedListener listener) {
+        Objects.requireNonNull(listener);
+        mControllableInsetsChangedListeners.add(listener);
+        listener.onControllableInsetsChanged(this, calculateControllableTypes());
+    }
+
+    @Override
+    public void removeOnControllableInsetsChangedListener(
+            OnControllableInsetsChangedListener listener) {
+        Objects.requireNonNull(listener);
+        mControllableInsetsChangedListeners.remove(listener);
+    }
+
+    @Override
+    public void releaseSurfaceControlFromRt(SurfaceControl sc) {
+        mHost.releaseSurfaceControlFromRt(sc);
+    }
+
+    @Override
+    public void reportPerceptible(int types, boolean perceptible) {
+        final ArraySet<Integer> internalTypes = toInternalType(types);
+        final int size = mSourceConsumers.size();
+        for (int i = 0; i < size; i++) {
+            final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
+            if (internalTypes.contains(consumer.getType())) {
+                consumer.onPerceptible(perceptible);
+            }
+        }
+    }
+
+    Host getHost() {
+        return mHost;
+    }
+}
diff --git a/android/view/InsetsFlags.java b/android/view/InsetsFlags.java
new file mode 100644
index 0000000..3355252
--- /dev/null
+++ b/android/view/InsetsFlags.java
@@ -0,0 +1,82 @@
+/*
+ * 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.view;
+
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS;
+import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT;
+import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
+
+import android.view.WindowInsetsController.Appearance;
+import android.view.WindowInsetsController.Behavior;
+
+/**
+ * Contains the information about {@link Appearance} and {@link Behavior} of system windows which
+ * can produce insets. This is for carrying the request from a client to the system server.
+ * @hide
+ */
+public class InsetsFlags {
+
+    @ViewDebug.ExportedProperty(flagMapping = {
+            @ViewDebug.FlagToString(
+                    mask = APPEARANCE_OPAQUE_STATUS_BARS,
+                    equals = APPEARANCE_OPAQUE_STATUS_BARS,
+                    name = "OPAQUE_STATUS_BARS"),
+            @ViewDebug.FlagToString(
+                    mask = APPEARANCE_OPAQUE_NAVIGATION_BARS,
+                    equals = APPEARANCE_OPAQUE_NAVIGATION_BARS,
+                    name = "OPAQUE_NAVIGATION_BARS"),
+            @ViewDebug.FlagToString(
+                    mask = APPEARANCE_LOW_PROFILE_BARS,
+                    equals = APPEARANCE_LOW_PROFILE_BARS,
+                    name = "LOW_PROFILE_BARS"),
+            @ViewDebug.FlagToString(
+                    mask = APPEARANCE_LIGHT_STATUS_BARS,
+                    equals = APPEARANCE_LIGHT_STATUS_BARS,
+                    name = "LIGHT_STATUS_BARS"),
+            @ViewDebug.FlagToString(
+                    mask = APPEARANCE_LIGHT_NAVIGATION_BARS,
+                    equals = APPEARANCE_LIGHT_NAVIGATION_BARS,
+                    name = "LIGHT_NAVIGATION_BARS"),
+            @ViewDebug.FlagToString(
+                    mask = APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS,
+                    equals = APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS,
+                    name = "SEMI_TRANSPARENT_STATUS_BARS"),
+            @ViewDebug.FlagToString(
+                    mask = APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS,
+                    equals = APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS,
+                    name = "SEMI_TRANSPARENT_NAVIGATION_BARS")
+    })
+    public @Appearance int appearance;
+
+    @ViewDebug.ExportedProperty(flagMapping = {
+            @ViewDebug.FlagToString(
+                    mask = BEHAVIOR_DEFAULT,
+                    equals = BEHAVIOR_DEFAULT,
+                    name = "DEFAULT"),
+            @ViewDebug.FlagToString(
+                    mask = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
+                    equals = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
+                    name = "SHOW_TRANSIENT_BARS_BY_SWIPE")
+    })
+    public @Behavior int behavior = BEHAVIOR_DEFAULT;
+}
diff --git a/android/view/InsetsSource.java b/android/view/InsetsSource.java
new file mode 100644
index 0000000..e6cf683
--- /dev/null
+++ b/android/view/InsetsSource.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.view.InsetsSourceProto.FRAME;
+import static android.view.InsetsSourceProto.TYPE;
+import static android.view.InsetsSourceProto.VISIBLE;
+import static android.view.InsetsSourceProto.VISIBLE_FRAME;
+import static android.view.InsetsState.ITYPE_CAPTION_BAR;
+import static android.view.InsetsState.ITYPE_IME;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.proto.ProtoOutputStream;
+import android.view.InsetsState.InternalInsetsType;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+/**
+ * Represents the state of a single window generating insets for clients.
+ * @hide
+ */
+public class InsetsSource implements Parcelable {
+
+    private final @InternalInsetsType int mType;
+
+    /** Frame of the source in screen coordinate space */
+    private final Rect mFrame;
+    private @Nullable Rect mVisibleFrame;
+    private boolean mVisible;
+
+    private final Rect mTmpFrame = new Rect();
+
+    public InsetsSource(@InternalInsetsType int type) {
+        mType = type;
+        mFrame = new Rect();
+        mVisible = InsetsState.getDefaultVisibility(type);
+    }
+
+    public InsetsSource(InsetsSource other) {
+        mType = other.mType;
+        mFrame = new Rect(other.mFrame);
+        mVisible = other.mVisible;
+        mVisibleFrame = other.mVisibleFrame != null
+                ? new Rect(other.mVisibleFrame)
+                : null;
+    }
+
+    public void set(InsetsSource other) {
+        mFrame.set(other.mFrame);
+        mVisible = other.mVisible;
+        mVisibleFrame = other.mVisibleFrame != null
+                ? new Rect(other.mVisibleFrame)
+                : null;
+    }
+
+    public void setFrame(int left, int top, int right, int bottom) {
+        mFrame.set(left, top, right, bottom);
+    }
+
+    public void setFrame(Rect frame) {
+        mFrame.set(frame);
+    }
+
+    public void setVisibleFrame(@Nullable Rect visibleFrame) {
+        mVisibleFrame = visibleFrame != null ? new Rect(visibleFrame) : visibleFrame;
+    }
+
+    public void setVisible(boolean visible) {
+        mVisible = visible;
+    }
+
+    public @InternalInsetsType int getType() {
+        return mType;
+    }
+
+    public Rect getFrame() {
+        return mFrame;
+    }
+
+    public @Nullable Rect getVisibleFrame() {
+        return mVisibleFrame;
+    }
+
+    public boolean isVisible() {
+        return mVisible;
+    }
+
+    boolean isUserControllable() {
+        // If mVisibleFrame is null, it will be the same area as mFrame.
+        return mVisibleFrame == null || !mVisibleFrame.isEmpty();
+    }
+
+    /**
+     * Calculates the insets this source will cause to a client window.
+     *
+     * @param relativeFrame The frame to calculate the insets relative to.
+     * @param ignoreVisibility If true, always reports back insets even if source isn't visible.
+     * @return The resulting insets. The contract is that only one side will be occupied by a
+     *         source.
+     */
+    public Insets calculateInsets(Rect relativeFrame, boolean ignoreVisibility) {
+        return calculateInsets(relativeFrame, mFrame, ignoreVisibility);
+    }
+
+    /**
+     * Like {@link #calculateInsets(Rect, boolean)}, but will return visible insets.
+     */
+    public Insets calculateVisibleInsets(Rect relativeFrame) {
+        return calculateInsets(relativeFrame, mVisibleFrame != null ? mVisibleFrame : mFrame,
+                false /* ignoreVisibility */);
+    }
+
+    private Insets calculateInsets(Rect relativeFrame, Rect frame, boolean ignoreVisibility) {
+        if (!ignoreVisibility && !mVisible) {
+            return Insets.NONE;
+        }
+        // During drag-move and drag-resizing, the caption insets position may not get updated
+        // before the app frame get updated. To layout the app content correctly during drag events,
+        // we always return the insets with the corresponding height covering the top.
+        if (getType() == ITYPE_CAPTION_BAR) {
+            return Insets.of(0, frame.height(), 0, 0);
+        }
+        // Checks for whether there is shared edge with insets for 0-width/height window.
+        final boolean hasIntersection = relativeFrame.isEmpty()
+                ? getIntersection(frame, relativeFrame, mTmpFrame)
+                : mTmpFrame.setIntersect(frame, relativeFrame);
+        if (!hasIntersection) {
+            return Insets.NONE;
+        }
+
+        // TODO: Currently, non-floating IME always intersects at bottom due to issues with cutout.
+        // However, we should let the policy decide from the server.
+        if (getType() == ITYPE_IME) {
+            return Insets.of(0, 0, 0, mTmpFrame.height());
+        }
+
+        // Intersecting at top/bottom
+        if (mTmpFrame.width() == relativeFrame.width()) {
+            if (mTmpFrame.top == relativeFrame.top) {
+                return Insets.of(0, mTmpFrame.height(), 0, 0);
+            } else if (mTmpFrame.bottom == relativeFrame.bottom) {
+                return Insets.of(0, 0, 0, mTmpFrame.height());
+            }
+            // TODO: remove when insets are shell-customizable.
+            // This is a hack that says "if this is a top-inset (eg statusbar), always apply it
+            // to the top". It is used when adjusting primary split for IME.
+            if (mTmpFrame.top == 0) {
+                return Insets.of(0, mTmpFrame.height(), 0, 0);
+            }
+        }
+        // Intersecting at left/right
+        else if (mTmpFrame.height() == relativeFrame.height()) {
+            if (mTmpFrame.left == relativeFrame.left) {
+                return Insets.of(mTmpFrame.width(), 0, 0, 0);
+            } else if (mTmpFrame.right == relativeFrame.right) {
+                return Insets.of(0, 0, mTmpFrame.width(), 0);
+            }
+        }
+        return Insets.NONE;
+    }
+
+    /**
+     * Outputs the intersection of two rectangles. The shared edges will also be counted in the
+     * intersection.
+     *
+     * @param a The first rectangle being intersected with.
+     * @param b The second rectangle being intersected with.
+     * @param out The rectangle which represents the intersection.
+     * @return {@code true} if there is any intersection.
+     */
+    private static boolean getIntersection(@NonNull Rect a, @NonNull Rect b, @NonNull Rect out) {
+        if (a.left <= b.right && b.left <= a.right && a.top <= b.bottom && b.top <= a.bottom) {
+            out.left = Math.max(a.left, b.left);
+            out.top = Math.max(a.top, b.top);
+            out.right = Math.min(a.right, b.right);
+            out.bottom = Math.min(a.bottom, b.bottom);
+            return true;
+        }
+        out.setEmpty();
+        return false;
+    }
+
+    /**
+     * Export the state of {@link InsetsSource} into a protocol buffer output stream.
+     *
+     * @param proto   Stream to write the state to
+     * @param fieldId FieldId of InsetsSource as defined in the parent message
+     */
+    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(TYPE, InsetsState.typeToString(mType));
+        mFrame.dumpDebug(proto, FRAME);
+        if (mVisibleFrame != null) {
+            mVisibleFrame.dumpDebug(proto, VISIBLE_FRAME);
+        }
+        proto.write(VISIBLE, mVisible);
+        proto.end(token);
+    }
+
+    public void dump(String prefix, PrintWriter pw) {
+        pw.print(prefix);
+        pw.print("InsetsSource type="); pw.print(InsetsState.typeToString(mType));
+        pw.print(" frame="); pw.print(mFrame.toShortString());
+        if (mVisibleFrame != null) {
+            pw.print(" visibleFrame="); pw.print(mVisibleFrame.toShortString());
+        }
+        pw.print(" visible="); pw.print(mVisible);
+        pw.println();
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        return equals(o, false);
+    }
+
+    /**
+     * @param excludeInvisibleImeFrames If {@link InsetsState#ITYPE_IME} frames should be ignored
+     *                                  when IME is not visible.
+     */
+    public boolean equals(@Nullable Object o, boolean excludeInvisibleImeFrames) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        InsetsSource that = (InsetsSource) o;
+
+        if (mType != that.mType) return false;
+        if (mVisible != that.mVisible) return false;
+        if (excludeInvisibleImeFrames && !mVisible && mType == ITYPE_IME) return true;
+        if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false;
+        return mFrame.equals(that.mFrame);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mType;
+        result = 31 * result + mFrame.hashCode();
+        result = 31 * result + (mVisibleFrame != null ? mVisibleFrame.hashCode() : 0);
+        result = 31 * result + (mVisible ? 1 : 0);
+        return result;
+    }
+
+    public InsetsSource(Parcel in) {
+        mType = in.readInt();
+        mFrame = Rect.CREATOR.createFromParcel(in);
+        if (in.readInt() != 0) {
+            mVisibleFrame = Rect.CREATOR.createFromParcel(in);
+        } else {
+            mVisibleFrame = null;
+        }
+        mVisible = in.readBoolean();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mType);
+        mFrame.writeToParcel(dest, 0);
+        if (mVisibleFrame != null) {
+            dest.writeInt(1);
+            mVisibleFrame.writeToParcel(dest, 0);
+        } else {
+            dest.writeInt(0);
+        }
+        dest.writeBoolean(mVisible);
+    }
+
+    @Override
+    public String toString() {
+        return "InsetsSource: {"
+                + "mType=" + InsetsState.typeToString(mType)
+                + ", mFrame=" + mFrame.toShortString()
+                + ", mVisible=" + mVisible
+                + "}";
+    }
+
+    public static final @android.annotation.NonNull Creator<InsetsSource> CREATOR = new Creator<InsetsSource>() {
+
+        public InsetsSource createFromParcel(Parcel in) {
+            return new InsetsSource(in);
+        }
+
+        public InsetsSource[] newArray(int size) {
+            return new InsetsSource[size];
+        }
+    };
+}
diff --git a/android/view/InsetsSourceConsumer.java b/android/view/InsetsSourceConsumer.java
new file mode 100644
index 0000000..ee33541
--- /dev/null
+++ b/android/view/InsetsSourceConsumer.java
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.view;
+
+import static android.view.InsetsController.ANIMATION_TYPE_NONE;
+import static android.view.InsetsController.AnimationType;
+import static android.view.InsetsController.DEBUG;
+import static android.view.InsetsSourceConsumerProto.HAS_WINDOW_FOCUS;
+import static android.view.InsetsSourceConsumerProto.INTERNAL_INSETS_TYPE;
+import static android.view.InsetsSourceConsumerProto.IS_REQUESTED_VISIBLE;
+import static android.view.InsetsSourceConsumerProto.PENDING_FRAME;
+import static android.view.InsetsSourceConsumerProto.PENDING_VISIBLE_FRAME;
+import static android.view.InsetsSourceConsumerProto.SOURCE_CONTROL;
+import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsState.getDefaultVisibility;
+import static android.view.InsetsState.toPublicType;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.util.Log;
+import android.util.imetracing.ImeTracing;
+import android.util.proto.ProtoOutputStream;
+import android.view.InsetsState.InternalInsetsType;
+import android.view.SurfaceControl.Transaction;
+import android.view.WindowInsets.Type.InsetsType;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+import java.util.function.Supplier;
+
+/**
+ * Controls the visibility and animations of a single window insets source.
+ * @hide
+ */
+public class InsetsSourceConsumer {
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {ShowResult.SHOW_IMMEDIATELY, ShowResult.IME_SHOW_DELAYED, ShowResult.IME_SHOW_FAILED})
+    @interface ShowResult {
+        /**
+         * Window type is ready to be shown, will be shown immidiately.
+         */
+        int SHOW_IMMEDIATELY = 0;
+        /**
+         * Result will be delayed. Window needs to be prepared or request is not from controller.
+         * Request will be delegated to controller and may or may not be shown.
+         */
+        int IME_SHOW_DELAYED = 1;
+        /**
+         * Window will not be shown because one of the conditions couldn't be met.
+         * (e.g. in IME's case, when no editor is focused.)
+         */
+        int IME_SHOW_FAILED = 2;
+    }
+
+    protected final InsetsController mController;
+    protected boolean mRequestedVisible;
+    protected final InsetsState mState;
+    protected final @InternalInsetsType int mType;
+
+    private static final String TAG = "InsetsSourceConsumer";
+    private final Supplier<Transaction> mTransactionSupplier;
+    private @Nullable InsetsSourceControl mSourceControl;
+    private boolean mHasWindowFocus;
+
+    /**
+     * Whether the view has focus returned by {@link #onWindowFocusGained(boolean)}.
+     */
+    private boolean mHasViewFocusWhenWindowFocusGain;
+    private Rect mPendingFrame;
+    private Rect mPendingVisibleFrame;
+
+    /**
+     * Indicates if we have the pending animation. When we have the control, we need to play the
+     * animation if the requested visibility is different from the current state. But if we haven't
+     * had a leash yet, we will set this flag, and play the animation once we get the leash.
+     */
+    private boolean mIsAnimationPending;
+
+    public InsetsSourceConsumer(@InternalInsetsType int type, InsetsState state,
+            Supplier<Transaction> transactionSupplier, InsetsController controller) {
+        mType = type;
+        mState = state;
+        mTransactionSupplier = transactionSupplier;
+        mController = controller;
+        mRequestedVisible = getDefaultVisibility(type);
+    }
+
+    /**
+     * Updates the control delivered from the server.
+
+     * @param showTypes An integer array with a single entry that determines which types a show
+     *                  animation should be run after setting the control.
+     * @param hideTypes An integer array with a single entry that determines which types a hide
+     *                  animation should be run after setting the control.
+     */
+    public void setControl(@Nullable InsetsSourceControl control,
+            @InsetsType int[] showTypes, @InsetsType int[] hideTypes) {
+        if (mType == ITYPE_IME) {
+            ImeTracing.getInstance().triggerClientDump("InsetsSourceConsumer#setControl",
+                    mController.getHost().getInputMethodManager(), null /* icProto */);
+        }
+        if (Objects.equals(mSourceControl, control)) {
+            if (mSourceControl != null && mSourceControl != control) {
+                mSourceControl.release(SurfaceControl::release);
+                mSourceControl = control;
+            }
+            return;
+        }
+        SurfaceControl oldLeash = mSourceControl != null ? mSourceControl.getLeash() : null;
+
+        final InsetsSourceControl lastControl = mSourceControl;
+        mSourceControl = control;
+        if (control != null) {
+            if (DEBUG) Log.d(TAG, String.format("setControl -> %s on %s",
+                    InsetsState.typeToString(control.getType()),
+                    mController.getHost().getRootViewTitle()));
+        }
+        if (mSourceControl == null) {
+            // We are loosing control
+            mController.notifyControlRevoked(this);
+
+            // Check if we need to restore server visibility.
+            final InsetsSource source = mState.getSource(mType);
+            final boolean serverVisibility =
+                    mController.getLastDispatchedState().getSourceOrDefaultVisibility(mType);
+            if (source.isVisible() != serverVisibility) {
+                source.setVisible(serverVisibility);
+                mController.notifyVisibilityChanged();
+            }
+
+            // For updateCompatSysUiVisibility
+            applyLocalVisibilityOverride();
+        } else {
+            // We are gaining control, and need to run an animation since previous state
+            // didn't match
+            final boolean requestedVisible = isRequestedVisibleAwaitingControl();
+            final boolean needAnimation = requestedVisible != mState.getSource(mType).isVisible();
+            if (control.getLeash() != null && (needAnimation || mIsAnimationPending)) {
+                if (DEBUG) Log.d(TAG, String.format("Gaining control in %s, requestedVisible: %b",
+                        mController.getHost().getRootViewTitle(), requestedVisible));
+                if (requestedVisible) {
+                    showTypes[0] |= toPublicType(getType());
+                } else {
+                    hideTypes[0] |= toPublicType(getType());
+                }
+                mIsAnimationPending = false;
+            } else {
+                if (needAnimation) {
+                    // We need animation but we haven't had a leash yet. Set this flag that when we
+                    // get the leash we can play the deferred animation.
+                    mIsAnimationPending = true;
+                }
+                // We are gaining control, but don't need to run an animation.
+                // However make sure that the leash visibility is still up to date.
+                if (applyLocalVisibilityOverride()) {
+                    mController.notifyVisibilityChanged();
+                }
+
+                // If we have a new leash, make sure visibility is up-to-date, even though we
+                // didn't want to run an animation above.
+                SurfaceControl newLeash = mSourceControl.getLeash();
+                if (oldLeash == null || newLeash == null || !oldLeash.isSameSurface(newLeash)) {
+                    applyHiddenToControl();
+                }
+
+                // Remove the surface that owned by last control when it lost.
+                if (!requestedVisible && !mIsAnimationPending && lastControl == null) {
+                    removeSurface();
+                }
+            }
+        }
+        if (lastControl != null) {
+            lastControl.release(SurfaceControl::release);
+        }
+    }
+
+    @VisibleForTesting
+    public InsetsSourceControl getControl() {
+        return mSourceControl;
+    }
+
+    /**
+     * Determines if the consumer will be shown after control is available.
+     * Note: for system bars this method is same as {@link #isRequestedVisible()}.
+     *
+     * @return {@code true} if consumer has a pending show.
+     */
+    protected boolean isRequestedVisibleAwaitingControl() {
+        return isRequestedVisible();
+    }
+
+    int getType() {
+        return mType;
+    }
+
+    @VisibleForTesting
+    public void show(boolean fromIme) {
+        if (DEBUG) Log.d(TAG, String.format("Call show() for type: %s fromIme: %b ",
+                InsetsState.typeToString(mType), fromIme));
+        setRequestedVisible(true);
+    }
+
+    @VisibleForTesting
+    public void hide() {
+        if (DEBUG) Log.d(TAG, String.format("Call hide for %s on %s",
+                InsetsState.typeToString(mType), mController.getHost().getRootViewTitle()));
+        setRequestedVisible(false);
+    }
+
+    void hide(boolean animationFinished, @AnimationType int animationType) {
+        hide();
+    }
+
+    /**
+     * Called when current window gains focus
+     */
+    public void onWindowFocusGained(boolean hasViewFocus) {
+        mHasWindowFocus = true;
+        mHasViewFocusWhenWindowFocusGain = hasViewFocus;
+    }
+
+    /**
+     * Called when current window loses focus.
+     */
+    public void onWindowFocusLost() {
+        mHasWindowFocus = false;
+    }
+
+    boolean hasViewFocusWhenWindowFocusGain() {
+        return mHasViewFocusWhenWindowFocusGain;
+    }
+
+    boolean applyLocalVisibilityOverride() {
+        final InsetsSource source = mState.peekSource(mType);
+        final boolean isVisible = source != null ? source.isVisible() : getDefaultVisibility(mType);
+        final boolean hasControl = mSourceControl != null;
+
+        if (mType == ITYPE_IME) {
+            ImeTracing.getInstance().triggerClientDump(
+                    "InsetsSourceConsumer#applyLocalVisibilityOverride",
+                    mController.getHost().getInputMethodManager(), null /* icProto */);
+        }
+
+        // We still need to let the legacy app know the visibility change even if we don't have the
+        // control. If we don't have the source, we don't change the requested visibility for making
+        // the callback behavior compatible.
+        mController.updateCompatSysUiVisibility(
+                mType, (hasControl || source == null) ? mRequestedVisible : isVisible, hasControl);
+
+        // If we don't have control, we are not able to change the visibility.
+        if (!hasControl) {
+            if (DEBUG) Log.d(TAG, "applyLocalVisibilityOverride: No control in "
+                    + mController.getHost().getRootViewTitle()
+                    + " requestedVisible " + mRequestedVisible);
+            return false;
+        }
+        if (isVisible == mRequestedVisible) {
+            return false;
+        }
+        if (DEBUG) Log.d(TAG, String.format("applyLocalVisibilityOverride: %s requestedVisible: %b",
+                mController.getHost().getRootViewTitle(), mRequestedVisible));
+        mState.getSource(mType).setVisible(mRequestedVisible);
+        return true;
+    }
+
+    @VisibleForTesting
+    public boolean isRequestedVisible() {
+        return mRequestedVisible;
+    }
+
+    /**
+     * Request to show current window type.
+     *
+     * @param fromController {@code true} if request is coming from controller.
+     *                       (e.g. in IME case, controller is
+     *                       {@link android.inputmethodservice.InputMethodService}).
+     * @return @see {@link ShowResult}.
+     */
+    @VisibleForTesting
+    public @ShowResult int requestShow(boolean fromController) {
+        return ShowResult.SHOW_IMMEDIATELY;
+    }
+
+    /**
+     * Reports that this source's perceptibility has changed
+     *
+     * @param perceptible true if the source is perceptible, false otherwise.
+     * @see InsetsAnimationControlCallbacks#reportPerceptible
+     */
+    public void onPerceptible(boolean perceptible) {
+    }
+
+    /**
+     * Notify listeners that window is now hidden.
+     */
+    void notifyHidden() {
+        // no-op for types that always return ShowResult#SHOW_IMMEDIATELY.
+    }
+
+    /**
+     * Remove surface on which this consumer type is drawn.
+     */
+    public void removeSurface() {
+        // no-op for types that always return ShowResult#SHOW_IMMEDIATELY.
+    }
+
+    @VisibleForTesting(visibility = PACKAGE)
+    public void updateSource(InsetsSource newSource, @AnimationType int animationType) {
+        InsetsSource source = mState.peekSource(mType);
+        if (source == null || animationType == ANIMATION_TYPE_NONE
+                || source.getFrame().equals(newSource.getFrame())) {
+            mPendingFrame = null;
+            mPendingVisibleFrame = null;
+            mState.addSource(newSource);
+            return;
+        }
+
+        // Frame is changing while animating. Keep note of the new frame but keep existing frame
+        // until animation is finished.
+        newSource = new InsetsSource(newSource);
+        mPendingFrame = new Rect(newSource.getFrame());
+        mPendingVisibleFrame = newSource.getVisibleFrame() != null
+                ? new Rect(newSource.getVisibleFrame())
+                : null;
+        newSource.setFrame(source.getFrame());
+        newSource.setVisibleFrame(source.getVisibleFrame());
+        mState.addSource(newSource);
+        if (DEBUG) Log.d(TAG, "updateSource: " + newSource);
+    }
+
+    @VisibleForTesting(visibility = PACKAGE)
+    public boolean notifyAnimationFinished() {
+        if (mPendingFrame != null) {
+            InsetsSource source = mState.getSource(mType);
+            source.setFrame(mPendingFrame);
+            source.setVisibleFrame(mPendingVisibleFrame);
+            mPendingFrame = null;
+            mPendingVisibleFrame = null;
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Sets requested visibility from the client, regardless of whether we are able to control it at
+     * the moment.
+     */
+    protected void setRequestedVisible(boolean requestedVisible) {
+        if (mRequestedVisible != requestedVisible) {
+            mRequestedVisible = requestedVisible;
+
+            // We need an animation later if the leash of a real control (which has an insets hint)
+            // is not ready. The !mIsAnimationPending check is in case that the requested visibility
+            // is changed twice before playing the animation -- we don't need an animation in this
+            // case.
+            mIsAnimationPending = !mIsAnimationPending
+                    && mSourceControl != null
+                    && mSourceControl.getLeash() == null
+                    && !Insets.NONE.equals(mSourceControl.getInsetsHint());
+
+            mController.onRequestedVisibilityChanged(this);
+            if (DEBUG) Log.d(TAG, "setRequestedVisible: " + requestedVisible);
+        }
+        if (applyLocalVisibilityOverride()) {
+            mController.notifyVisibilityChanged();
+        }
+    }
+
+    private void applyHiddenToControl() {
+        if (mSourceControl == null || mSourceControl.getLeash() == null) {
+            return;
+        }
+
+        final Transaction t = mTransactionSupplier.get();
+        if (DEBUG) Log.d(TAG, "applyHiddenToControl: " + mRequestedVisible);
+        if (mRequestedVisible) {
+            t.show(mSourceControl.getLeash());
+        } else {
+            t.hide(mSourceControl.getLeash());
+        }
+        t.apply();
+        onPerceptible(mRequestedVisible);
+    }
+
+    void dumpDebug(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(INTERNAL_INSETS_TYPE, InsetsState.typeToString(mType));
+        proto.write(HAS_WINDOW_FOCUS, mHasWindowFocus);
+        proto.write(IS_REQUESTED_VISIBLE, mRequestedVisible);
+        if (mSourceControl != null) {
+            mSourceControl.dumpDebug(proto, SOURCE_CONTROL);
+        }
+        if (mPendingFrame != null) {
+            mPendingFrame.dumpDebug(proto, PENDING_FRAME);
+        }
+        if (mPendingVisibleFrame != null) {
+            mPendingVisibleFrame.dumpDebug(proto, PENDING_VISIBLE_FRAME);
+        }
+        proto.end(token);
+    }
+}
diff --git a/android/view/InsetsSourceControl.java b/android/view/InsetsSourceControl.java
new file mode 100644
index 0000000..1506ee4
--- /dev/null
+++ b/android/view/InsetsSourceControl.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.graphics.PointProto.X;
+import static android.graphics.PointProto.Y;
+import static android.view.InsetsSourceControlProto.LEASH;
+import static android.view.InsetsSourceControlProto.POSITION;
+import static android.view.InsetsSourceControlProto.TYPE;
+
+import android.annotation.Nullable;
+import android.graphics.Insets;
+import android.graphics.Point;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.proto.ProtoOutputStream;
+import android.view.InsetsState.InternalInsetsType;
+
+import java.io.PrintWriter;
+import java.util.function.Consumer;
+
+/**
+ * Represents a parcelable object to allow controlling a single {@link InsetsSource}.
+ * @hide
+ */
+public class InsetsSourceControl implements Parcelable {
+
+    private final @InternalInsetsType int mType;
+    private final @Nullable SurfaceControl mLeash;
+    private final Point mSurfacePosition;
+
+    // This is used while playing an insets animation regardless of the relative frame. This would
+    // be the insets received by the bounds of its source window.
+    private Insets mInsetsHint;
+
+    private boolean mSkipAnimationOnce;
+    private int mParcelableFlags;
+
+    public InsetsSourceControl(@InternalInsetsType int type, @Nullable SurfaceControl leash,
+            Point surfacePosition, Insets insetsHint) {
+        mType = type;
+        mLeash = leash;
+        mSurfacePosition = surfacePosition;
+        mInsetsHint = insetsHint;
+    }
+
+    public InsetsSourceControl(InsetsSourceControl other) {
+        mType = other.mType;
+        if (other.mLeash != null) {
+            mLeash = new SurfaceControl(other.mLeash, "InsetsSourceControl");
+        } else {
+            mLeash = null;
+        }
+        mSurfacePosition = new Point(other.mSurfacePosition);
+        mInsetsHint = other.mInsetsHint;
+        mSkipAnimationOnce = other.getAndClearSkipAnimationOnce();
+    }
+
+    public InsetsSourceControl(Parcel in) {
+        mType = in.readInt();
+        mLeash = in.readTypedObject(SurfaceControl.CREATOR);
+        mSurfacePosition = in.readTypedObject(Point.CREATOR);
+        mInsetsHint = in.readTypedObject(Insets.CREATOR);
+        mSkipAnimationOnce = in.readBoolean();
+    }
+
+    public int getType() {
+        return mType;
+    }
+
+    /**
+     * Gets the leash for controlling insets source. If the system is controlling the insets source,
+     * for example, transient bars, the client will receive fake controls without leash in it.
+     *
+     * @return the leash.
+     */
+    public @Nullable SurfaceControl getLeash() {
+        return mLeash;
+    }
+
+    public boolean setSurfacePosition(int left, int top) {
+        if (mSurfacePosition.equals(left, top)) {
+            return false;
+        }
+        mSurfacePosition.set(left, top);
+        return true;
+    }
+
+    public Point getSurfacePosition() {
+        return mSurfacePosition;
+    }
+
+    public void setInsetsHint(Insets insets) {
+        mInsetsHint = insets;
+    }
+
+    public void setInsetsHint(int left, int top, int right, int bottom) {
+        mInsetsHint = Insets.of(left, top, right, bottom);
+    }
+
+    public Insets getInsetsHint() {
+        return mInsetsHint;
+    }
+
+    public void setSkipAnimationOnce(boolean skipAnimation) {
+        mSkipAnimationOnce = skipAnimation;
+    }
+
+    /**
+     * Get the state whether the current control needs to skip animation or not.
+     *
+     * Note that this is a one-time check that the state is only valid and can be called when
+     * {@link InsetsController#applyAnimation} to check if the current control can skip animation
+     * at this time, and then will clear the state value.
+     */
+    public boolean getAndClearSkipAnimationOnce() {
+        final boolean result = mSkipAnimationOnce;
+        mSkipAnimationOnce = false;
+        return result;
+    }
+
+    public void setParcelableFlags(int parcelableFlags) {
+        mParcelableFlags = parcelableFlags;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mType);
+        dest.writeTypedObject(mLeash, mParcelableFlags);
+        dest.writeTypedObject(mSurfacePosition, mParcelableFlags);
+        dest.writeTypedObject(mInsetsHint, mParcelableFlags);
+        dest.writeBoolean(mSkipAnimationOnce);
+    }
+
+    public void release(Consumer<SurfaceControl> surfaceReleaseConsumer) {
+        if (mLeash != null) {
+            surfaceReleaseConsumer.accept(mLeash);
+        }
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final InsetsSourceControl that = (InsetsSourceControl) o;
+        final SurfaceControl thatLeash = that.mLeash;
+        return mType == that.mType
+                && ((mLeash == thatLeash)
+                        || (mLeash != null && thatLeash != null && mLeash.isSameSurface(thatLeash)))
+                && mSurfacePosition.equals(that.mSurfacePosition)
+                && mInsetsHint.equals(that.mInsetsHint)
+                && mSkipAnimationOnce == that.mSkipAnimationOnce;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mType;
+        result = 31 * result + (mLeash != null ? mLeash.hashCode() : 0);
+        result = 31 * result + mSurfacePosition.hashCode();
+        result = 31 * result + mInsetsHint.hashCode();
+        result = 31 * result + (mSkipAnimationOnce ? 1 : 0);
+        return result;
+    }
+
+    public void dump(String prefix, PrintWriter pw) {
+        pw.print(prefix);
+        pw.print("InsetsSourceControl type="); pw.print(InsetsState.typeToString(mType));
+        pw.print(" mLeash="); pw.print(mLeash);
+        pw.print(" mSurfacePosition="); pw.print(mSurfacePosition);
+        pw.print(" mInsetsHint="); pw.print(mInsetsHint);
+        pw.print(" mSkipAnimationOnce="); pw.print(mSkipAnimationOnce);
+        pw.println();
+    }
+
+    public static final @android.annotation.NonNull Creator<InsetsSourceControl> CREATOR
+            = new Creator<InsetsSourceControl>() {
+        public InsetsSourceControl createFromParcel(Parcel in) {
+            return new InsetsSourceControl(in);
+        }
+
+        public InsetsSourceControl[] newArray(int size) {
+            return new InsetsSourceControl[size];
+        }
+    };
+
+    /**
+     * Export the state of {@link InsetsSourceControl} into a protocol buffer output stream.
+     *
+     * @param proto   Stream to write the state to
+     * @param fieldId FieldId of InsetsSource as defined in the parent message
+     */
+    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(TYPE, InsetsState.typeToString(mType));
+
+        final long surfaceToken = proto.start(POSITION);
+        proto.write(X, mSurfacePosition.x);
+        proto.write(Y, mSurfacePosition.y);
+        proto.end(surfaceToken);
+
+        if (mLeash != null) {
+            mLeash.dumpDebug(proto, LEASH);
+        }
+        proto.end(token);
+    }
+}
diff --git a/android/view/InsetsState.java b/android/view/InsetsState.java
new file mode 100644
index 0000000..37101b7
--- /dev/null
+++ b/android/view/InsetsState.java
@@ -0,0 +1,893 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.view.InsetsStateProto.DISPLAY_CUTOUT;
+import static android.view.InsetsStateProto.DISPLAY_FRAME;
+import static android.view.InsetsStateProto.SOURCES;
+import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
+import static android.view.WindowInsets.Type.displayCutout;
+import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.indexOf;
+import static android.view.WindowInsets.Type.isVisibleInsetsType;
+import static android.view.WindowInsets.Type.statusBars;
+import static android.view.WindowInsets.Type.systemBars;
+import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.WindowConfiguration;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArraySet;
+import android.util.SparseIntArray;
+import android.util.proto.ProtoOutputStream;
+import android.view.WindowInsets.Type;
+import android.view.WindowInsets.Type.InsetsType;
+import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.StringJoiner;
+
+/**
+ * Holder for state of system windows that cause window insets for all other windows in the system.
+ * @hide
+ */
+public class InsetsState implements Parcelable {
+
+    /**
+     * Internal representation of inset source types. This is different from the public API in
+     * {@link WindowInsets.Type} as one type from the public API might indicate multiple windows
+     * at the same time.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "ITYPE", value = {
+            ITYPE_STATUS_BAR,
+            ITYPE_NAVIGATION_BAR,
+            ITYPE_CAPTION_BAR,
+            ITYPE_TOP_GESTURES,
+            ITYPE_BOTTOM_GESTURES,
+            ITYPE_LEFT_GESTURES,
+            ITYPE_RIGHT_GESTURES,
+            ITYPE_TOP_MANDATORY_GESTURES,
+            ITYPE_BOTTOM_MANDATORY_GESTURES,
+            ITYPE_LEFT_MANDATORY_GESTURES,
+            ITYPE_RIGHT_MANDATORY_GESTURES,
+            ITYPE_TOP_TAPPABLE_ELEMENT,
+            ITYPE_BOTTOM_TAPPABLE_ELEMENT,
+            ITYPE_LEFT_DISPLAY_CUTOUT,
+            ITYPE_TOP_DISPLAY_CUTOUT,
+            ITYPE_RIGHT_DISPLAY_CUTOUT,
+            ITYPE_BOTTOM_DISPLAY_CUTOUT,
+            ITYPE_IME,
+            ITYPE_CLIMATE_BAR,
+            ITYPE_EXTRA_NAVIGATION_BAR
+    })
+    public @interface InternalInsetsType {}
+
+    /**
+     * Special value to be used to by methods returning an {@link InternalInsetsType} to indicate
+     * that the objects/parameters aren't associated with an {@link InternalInsetsType}
+     */
+    public static final int ITYPE_INVALID = -1;
+
+    static final int FIRST_TYPE = 0;
+
+    public static final int ITYPE_STATUS_BAR = FIRST_TYPE;
+    public static final int ITYPE_NAVIGATION_BAR = 1;
+    public static final int ITYPE_CAPTION_BAR = 2;
+
+    public static final int ITYPE_TOP_GESTURES = 3;
+    public static final int ITYPE_BOTTOM_GESTURES = 4;
+    public static final int ITYPE_LEFT_GESTURES = 5;
+    public static final int ITYPE_RIGHT_GESTURES = 6;
+
+    public static final int ITYPE_TOP_MANDATORY_GESTURES = 7;
+    public static final int ITYPE_BOTTOM_MANDATORY_GESTURES = 8;
+    public static final int ITYPE_LEFT_MANDATORY_GESTURES = 9;
+    public static final int ITYPE_RIGHT_MANDATORY_GESTURES = 10;
+
+    public static final int ITYPE_LEFT_DISPLAY_CUTOUT = 11;
+    public static final int ITYPE_TOP_DISPLAY_CUTOUT = 12;
+    public static final int ITYPE_RIGHT_DISPLAY_CUTOUT = 13;
+    public static final int ITYPE_BOTTOM_DISPLAY_CUTOUT = 14;
+
+    public static final int ITYPE_LEFT_TAPPABLE_ELEMENT = 15;
+    public static final int ITYPE_TOP_TAPPABLE_ELEMENT = 16;
+    public static final int ITYPE_RIGHT_TAPPABLE_ELEMENT = 17;
+    public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT = 18;
+
+    /** Input method window. */
+    public static final int ITYPE_IME = 19;
+
+    /** Additional system decorations inset type. */
+    public static final int ITYPE_CLIMATE_BAR = 20;
+    public static final int ITYPE_EXTRA_NAVIGATION_BAR = 21;
+
+    static final int LAST_TYPE = ITYPE_EXTRA_NAVIGATION_BAR;
+    public static final int SIZE = LAST_TYPE + 1;
+
+    // Derived types
+
+    /** A shelf is the same as the navigation bar. */
+    public static final int ITYPE_SHELF = ITYPE_NAVIGATION_BAR;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "IINSETS_SIDE", value = {
+            ISIDE_LEFT,
+            ISIDE_TOP,
+            ISIDE_RIGHT,
+            ISIDE_BOTTOM,
+            ISIDE_FLOATING,
+            ISIDE_UNKNOWN
+    })
+    public @interface InternalInsetsSide {}
+    static final int ISIDE_LEFT = 0;
+    static final int ISIDE_TOP = 1;
+    static final int ISIDE_RIGHT = 2;
+    static final int ISIDE_BOTTOM = 3;
+    static final int ISIDE_FLOATING = 4;
+    static final int ISIDE_UNKNOWN = 5;
+
+    private final InsetsSource[] mSources = new InsetsSource[SIZE];
+
+    /**
+     * The frame of the display these sources are relative to.
+     */
+    private final Rect mDisplayFrame = new Rect();
+
+    /** The area cut from the display. */
+    private final DisplayCutout.ParcelableWrapper mDisplayCutout =
+            new DisplayCutout.ParcelableWrapper();
+
+    /** The rounded corners on the display */
+    private RoundedCorners mRoundedCorners = RoundedCorners.NO_ROUNDED_CORNERS;
+
+    /** The bounds of the Privacy Indicator */
+    private PrivacyIndicatorBounds mPrivacyIndicatorBounds =
+            new PrivacyIndicatorBounds();
+
+    public InsetsState() {
+    }
+
+    public InsetsState(InsetsState copy) {
+        set(copy);
+    }
+
+    public InsetsState(InsetsState copy, boolean copySources) {
+        set(copy, copySources);
+    }
+
+    /**
+     * Calculates {@link WindowInsets} based on the current source configuration.
+     *
+     * @param frame The frame to calculate the insets relative to.
+     * @param ignoringVisibilityState {@link InsetsState} used to calculate
+     *        {@link WindowInsets#getInsetsIgnoringVisibility(int)} information, or pass
+     *        {@code null} to use this state to calculate that information.
+     * @return The calculated insets.
+     */
+    public WindowInsets calculateInsets(Rect frame, @Nullable InsetsState ignoringVisibilityState,
+            boolean isScreenRound, boolean alwaysConsumeSystemBars,
+            int legacySoftInputMode, int legacyWindowFlags, int legacySystemUiFlags,
+            int windowType, @WindowConfiguration.WindowingMode int windowingMode,
+            @Nullable @InternalInsetsSide SparseIntArray typeSideMap) {
+        Insets[] typeInsetsMap = new Insets[Type.SIZE];
+        Insets[] typeMaxInsetsMap = new Insets[Type.SIZE];
+        boolean[] typeVisibilityMap = new boolean[SIZE];
+        final Rect relativeFrame = new Rect(frame);
+        final Rect relativeFrameMax = new Rect(frame);
+        for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
+            InsetsSource source = mSources[type];
+            if (source == null) {
+                int index = indexOf(toPublicType(type));
+                if (typeInsetsMap[index] == null) {
+                    typeInsetsMap[index] = Insets.NONE;
+                }
+                continue;
+            }
+
+            processSource(source, relativeFrame, false /* ignoreVisibility */, typeInsetsMap,
+                    typeSideMap, typeVisibilityMap);
+
+            // IME won't be reported in max insets as the size depends on the EditorInfo of the IME
+            // target.
+            if (source.getType() != ITYPE_IME) {
+                InsetsSource ignoringVisibilitySource = ignoringVisibilityState != null
+                        ? ignoringVisibilityState.getSource(type)
+                        : source;
+                if (ignoringVisibilitySource == null) {
+                    continue;
+                }
+                processSource(ignoringVisibilitySource, relativeFrameMax,
+                        true /* ignoreVisibility */, typeMaxInsetsMap, null /* typeSideMap */,
+                        null /* typeVisibilityMap */);
+            }
+        }
+        final int softInputAdjustMode = legacySoftInputMode & SOFT_INPUT_MASK_ADJUST;
+
+        @InsetsType int compatInsetsTypes = systemBars() | displayCutout();
+        if (softInputAdjustMode == SOFT_INPUT_ADJUST_RESIZE) {
+            compatInsetsTypes |= ime();
+        }
+        if ((legacyWindowFlags & FLAG_FULLSCREEN) != 0) {
+            compatInsetsTypes &= ~statusBars();
+        }
+        if (clearCompatInsets(windowType, legacyWindowFlags, windowingMode)) {
+            compatInsetsTypes = 0;
+        }
+
+        return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound,
+                alwaysConsumeSystemBars, calculateRelativeCutout(frame),
+                calculateRelativeRoundedCorners(frame),
+                calculateRelativePrivacyIndicatorBounds(frame),
+                compatInsetsTypes, (legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0);
+    }
+
+    private DisplayCutout calculateRelativeCutout(Rect frame) {
+        final DisplayCutout raw = mDisplayCutout.get();
+        if (mDisplayFrame.equals(frame)) {
+            return raw;
+        }
+        if (frame == null) {
+            return DisplayCutout.NO_CUTOUT;
+        }
+        final int insetLeft = frame.left - mDisplayFrame.left;
+        final int insetTop = frame.top - mDisplayFrame.top;
+        final int insetRight = mDisplayFrame.right - frame.right;
+        final int insetBottom = mDisplayFrame.bottom - frame.bottom;
+        if (insetLeft >= raw.getSafeInsetLeft()
+                && insetTop >= raw.getSafeInsetTop()
+                && insetRight >= raw.getSafeInsetRight()
+                && insetBottom >= raw.getSafeInsetBottom()) {
+            return DisplayCutout.NO_CUTOUT;
+        }
+        return raw.inset(insetLeft, insetTop, insetRight, insetBottom);
+    }
+
+    private RoundedCorners calculateRelativeRoundedCorners(Rect frame) {
+        if (mDisplayFrame.equals(frame)) {
+            return mRoundedCorners;
+        }
+        if (frame == null) {
+            return RoundedCorners.NO_ROUNDED_CORNERS;
+        }
+        final int insetLeft = frame.left - mDisplayFrame.left;
+        final int insetTop = frame.top - mDisplayFrame.top;
+        final int insetRight = mDisplayFrame.right - frame.right;
+        final int insetBottom = mDisplayFrame.bottom - frame.bottom;
+        return mRoundedCorners.inset(insetLeft, insetTop, insetRight, insetBottom);
+    }
+
+    private PrivacyIndicatorBounds calculateRelativePrivacyIndicatorBounds(Rect frame) {
+        if (mDisplayFrame.equals(frame)) {
+            return mPrivacyIndicatorBounds;
+        }
+        if (frame == null) {
+            return null;
+        }
+        final int insetLeft = frame.left - mDisplayFrame.left;
+        final int insetTop = frame.top - mDisplayFrame.top;
+        final int insetRight = mDisplayFrame.right - frame.right;
+        final int insetBottom = mDisplayFrame.bottom - frame.bottom;
+        return mPrivacyIndicatorBounds.inset(insetLeft, insetTop, insetRight, insetBottom);
+    }
+
+    public Rect calculateInsets(Rect frame, @InsetsType int types, boolean ignoreVisibility) {
+        Insets insets = Insets.NONE;
+        for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
+            InsetsSource source = mSources[type];
+            if (source == null) {
+                continue;
+            }
+            int publicType = InsetsState.toPublicType(type);
+            if ((publicType & types) == 0) {
+                continue;
+            }
+            insets = Insets.max(source.calculateInsets(frame, ignoreVisibility), insets);
+        }
+        return insets.toRect();
+    }
+
+    public Rect calculateVisibleInsets(Rect frame, @SoftInputModeFlags int softInputMode) {
+        Insets insets = Insets.NONE;
+        for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
+            InsetsSource source = mSources[type];
+            if (source == null) {
+                continue;
+            }
+
+            // Ignore everything that's not a system bar or IME.
+            int publicType = InsetsState.toPublicType(type);
+            if (!isVisibleInsetsType(publicType, softInputMode)) {
+                continue;
+            }
+            insets = Insets.max(source.calculateVisibleInsets(frame), insets);
+        }
+        return insets.toRect();
+    }
+
+    /**
+     * Calculate which insets *cannot* be controlled, because the frame does not cover the
+     * respective side of the inset.
+     *
+     * If the frame of our window doesn't cover the entire inset, the control API makes very
+     * little sense, as we don't deal with negative insets.
+     */
+    @InsetsType
+    public int calculateUncontrollableInsetsFromFrame(Rect frame) {
+        int blocked = 0;
+        for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
+            InsetsSource source = mSources[type];
+            if (source == null) {
+                continue;
+            }
+            if (!canControlSide(frame, getInsetSide(
+                    source.calculateInsets(frame, true /* ignoreVisibility */)))) {
+                blocked |= toPublicType(type);
+            }
+        }
+        return blocked;
+    }
+
+    private boolean canControlSide(Rect frame, int side) {
+        switch (side) {
+            case ISIDE_LEFT:
+            case ISIDE_RIGHT:
+                return frame.left == mDisplayFrame.left && frame.right == mDisplayFrame.right;
+            case ISIDE_TOP:
+            case ISIDE_BOTTOM:
+                return frame.top == mDisplayFrame.top && frame.bottom == mDisplayFrame.bottom;
+            case ISIDE_FLOATING:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility,
+            Insets[] typeInsetsMap, @Nullable @InternalInsetsSide SparseIntArray typeSideMap,
+            @Nullable boolean[] typeVisibilityMap) {
+        Insets insets = source.calculateInsets(relativeFrame, ignoreVisibility);
+
+        int type = toPublicType(source.getType());
+        processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
+                insets, type);
+
+        if (type == Type.MANDATORY_SYSTEM_GESTURES) {
+            // Mandatory system gestures are also system gestures.
+            // TODO: find a way to express this more generally. One option would be to define
+            //       Type.systemGestureInsets() as NORMAL | MANDATORY, but then we lose the
+            //       ability to set systemGestureInsets() independently from
+            //       mandatorySystemGestureInsets() in the Builder.
+            processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
+                    insets, Type.SYSTEM_GESTURES);
+        }
+    }
+
+    private void processSourceAsPublicType(InsetsSource source, Insets[] typeInsetsMap,
+            @InternalInsetsSide @Nullable SparseIntArray typeSideMap,
+            @Nullable boolean[] typeVisibilityMap, Insets insets, int type) {
+        int index = indexOf(type);
+        Insets existing = typeInsetsMap[index];
+        if (existing == null) {
+            typeInsetsMap[index] = insets;
+        } else {
+            typeInsetsMap[index] = Insets.max(existing, insets);
+        }
+
+        if (typeVisibilityMap != null) {
+            typeVisibilityMap[index] = source.isVisible();
+        }
+
+        if (typeSideMap != null) {
+            @InternalInsetsSide int insetSide = getInsetSide(insets);
+            if (insetSide != ISIDE_UNKNOWN) {
+                typeSideMap.put(source.getType(), insetSide);
+            }
+        }
+    }
+
+    /**
+     * Retrieves the side for a certain {@code insets}. It is required that only one field l/t/r/b
+     * is set in order that this method returns a meaningful result.
+     */
+    static @InternalInsetsSide int getInsetSide(Insets insets) {
+        if (Insets.NONE.equals(insets)) {
+            return ISIDE_FLOATING;
+        }
+        if (insets.left != 0) {
+            return ISIDE_LEFT;
+        }
+        if (insets.top != 0) {
+            return ISIDE_TOP;
+        }
+        if (insets.right != 0) {
+            return ISIDE_RIGHT;
+        }
+        if (insets.bottom != 0) {
+            return ISIDE_BOTTOM;
+        }
+        return ISIDE_UNKNOWN;
+    }
+
+    public InsetsSource getSource(@InternalInsetsType int type) {
+        InsetsSource source = mSources[type];
+        if (source != null) {
+            return source;
+        }
+        source = new InsetsSource(type);
+        mSources[type] = source;
+        return source;
+    }
+
+    public @Nullable InsetsSource peekSource(@InternalInsetsType int type) {
+        return mSources[type];
+    }
+
+    /**
+     * Returns the source visibility or the default visibility if the source doesn't exist. This is
+     * useful if when treating this object as a request.
+     *
+     * @param type The {@link InternalInsetsType} to query.
+     * @return {@code true} if the source is visible or the type is default visible and the source
+     *         doesn't exist.
+     */
+    public boolean getSourceOrDefaultVisibility(@InternalInsetsType int type) {
+        final InsetsSource source = mSources[type];
+        return source != null ? source.isVisible() : getDefaultVisibility(type);
+    }
+
+    public void setDisplayFrame(Rect frame) {
+        mDisplayFrame.set(frame);
+    }
+
+    public Rect getDisplayFrame() {
+        return mDisplayFrame;
+    }
+
+    public void setDisplayCutout(DisplayCutout cutout) {
+        mDisplayCutout.set(cutout);
+    }
+
+    public DisplayCutout getDisplayCutout() {
+        return mDisplayCutout.get();
+    }
+
+    public void setRoundedCorners(RoundedCorners roundedCorners) {
+        mRoundedCorners = roundedCorners;
+    }
+
+    public RoundedCorners getRoundedCorners() {
+        return mRoundedCorners;
+    }
+
+    public void setPrivacyIndicatorBounds(PrivacyIndicatorBounds bounds) {
+        mPrivacyIndicatorBounds = bounds;
+    }
+
+    public PrivacyIndicatorBounds getPrivacyIndicatorBounds() {
+        return mPrivacyIndicatorBounds;
+    }
+
+    /**
+     * Modifies the state of this class to exclude a certain type to make it ready for dispatching
+     * to the client.
+     *
+     * @param type The {@link InternalInsetsType} of the source to remove
+     * @return {@code true} if this InsetsState was modified; {@code false} otherwise.
+     */
+    public boolean removeSource(@InternalInsetsType int type) {
+        if (mSources[type] == null) {
+            return false;
+        }
+        mSources[type] = null;
+        return true;
+    }
+
+    /**
+     * A shortcut for setting the visibility of the source.
+     *
+     * @param type The {@link InternalInsetsType} of the source to set the visibility
+     * @param visible {@code true} for visible
+     */
+    public void setSourceVisible(@InternalInsetsType int type, boolean visible) {
+        InsetsSource source = mSources[type];
+        if (source != null) {
+            source.setVisible(visible);
+        }
+    }
+
+    /**
+     * Scales the frame and the visible frame (if there is one) of each source.
+     *
+     * @param scale the scale to be applied
+     */
+    public void scale(float scale) {
+        mDisplayFrame.scale(scale);
+        mDisplayCutout.scale(scale);
+        mRoundedCorners = mRoundedCorners.scale(scale);
+        mPrivacyIndicatorBounds = mPrivacyIndicatorBounds.scale(scale);
+        for (int i = 0; i < SIZE; i++) {
+            final InsetsSource source = mSources[i];
+            if (source != null) {
+                source.getFrame().scale(scale);
+                final Rect visibleFrame = source.getVisibleFrame();
+                if (visibleFrame != null) {
+                    visibleFrame.scale(scale);
+                }
+            }
+        }
+    }
+
+    public void set(InsetsState other) {
+        set(other, false /* copySources */);
+    }
+
+    public void set(InsetsState other, boolean copySources) {
+        mDisplayFrame.set(other.mDisplayFrame);
+        mDisplayCutout.set(other.mDisplayCutout);
+        mRoundedCorners = other.getRoundedCorners();
+        mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds();
+        if (copySources) {
+            for (int i = 0; i < SIZE; i++) {
+                InsetsSource source = other.mSources[i];
+                mSources[i] = source != null ? new InsetsSource(source) : null;
+            }
+        } else {
+            for (int i = 0; i < SIZE; i++) {
+                mSources[i] = other.mSources[i];
+            }
+        }
+    }
+
+    /**
+     * Sets the values from the other InsetsState. But for sources, only specific types of source
+     * would be set.
+     *
+     * @param other the other InsetsState.
+     * @param types the only types of sources would be set.
+     */
+    public void set(InsetsState other, @InsetsType int types) {
+        mDisplayFrame.set(other.mDisplayFrame);
+        mDisplayCutout.set(other.mDisplayCutout);
+        mRoundedCorners = other.getRoundedCorners();
+        mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds();
+        final ArraySet<Integer> t = toInternalType(types);
+        for (int i = t.size() - 1; i >= 0; i--) {
+            final int type = t.valueAt(i);
+            mSources[type] = other.mSources[type];
+        }
+    }
+
+    public void addSource(InsetsSource source) {
+        mSources[source.getType()] = source;
+    }
+
+    public static boolean clearCompatInsets(int windowType, int windowFlags, int windowingMode) {
+        return (windowFlags & FLAG_LAYOUT_NO_LIMITS) != 0
+                && windowType != TYPE_WALLPAPER && windowType != TYPE_SYSTEM_ERROR
+                && !WindowConfiguration.inMultiWindowMode(windowingMode);
+    }
+
+    public static @InternalInsetsType ArraySet<Integer> toInternalType(@InsetsType int types) {
+        final ArraySet<Integer> result = new ArraySet<>();
+        if ((types & Type.STATUS_BARS) != 0) {
+            result.add(ITYPE_STATUS_BAR);
+            result.add(ITYPE_CLIMATE_BAR);
+        }
+        if ((types & Type.NAVIGATION_BARS) != 0) {
+            result.add(ITYPE_NAVIGATION_BAR);
+            result.add(ITYPE_EXTRA_NAVIGATION_BAR);
+        }
+        if ((types & Type.CAPTION_BAR) != 0) {
+            result.add(ITYPE_CAPTION_BAR);
+        }
+        if ((types & Type.SYSTEM_GESTURES) != 0) {
+            result.add(ITYPE_LEFT_GESTURES);
+            result.add(ITYPE_TOP_GESTURES);
+            result.add(ITYPE_RIGHT_GESTURES);
+            result.add(ITYPE_BOTTOM_GESTURES);
+        }
+        if ((types & Type.MANDATORY_SYSTEM_GESTURES) != 0) {
+            result.add(ITYPE_LEFT_MANDATORY_GESTURES);
+            result.add(ITYPE_TOP_MANDATORY_GESTURES);
+            result.add(ITYPE_RIGHT_MANDATORY_GESTURES);
+            result.add(ITYPE_BOTTOM_MANDATORY_GESTURES);
+        }
+        if ((types & Type.DISPLAY_CUTOUT) != 0) {
+            result.add(ITYPE_LEFT_DISPLAY_CUTOUT);
+            result.add(ITYPE_TOP_DISPLAY_CUTOUT);
+            result.add(ITYPE_RIGHT_DISPLAY_CUTOUT);
+            result.add(ITYPE_BOTTOM_DISPLAY_CUTOUT);
+        }
+        if ((types & Type.IME) != 0) {
+            result.add(ITYPE_IME);
+        }
+        return result;
+    }
+
+    /**
+     * Converting a internal type to the public type.
+     * @param type internal insets type, {@code InternalInsetsType}.
+     * @return public insets type, {@code Type.InsetsType}.
+     */
+    public static @Type.InsetsType int toPublicType(@InternalInsetsType int type) {
+        switch (type) {
+            case ITYPE_STATUS_BAR:
+            case ITYPE_CLIMATE_BAR:
+                return Type.STATUS_BARS;
+            case ITYPE_NAVIGATION_BAR:
+            case ITYPE_EXTRA_NAVIGATION_BAR:
+                return Type.NAVIGATION_BARS;
+            case ITYPE_CAPTION_BAR:
+                return Type.CAPTION_BAR;
+            case ITYPE_IME:
+                return Type.IME;
+            case ITYPE_TOP_MANDATORY_GESTURES:
+            case ITYPE_BOTTOM_MANDATORY_GESTURES:
+            case ITYPE_LEFT_MANDATORY_GESTURES:
+            case ITYPE_RIGHT_MANDATORY_GESTURES:
+                return Type.MANDATORY_SYSTEM_GESTURES;
+            case ITYPE_TOP_GESTURES:
+            case ITYPE_BOTTOM_GESTURES:
+            case ITYPE_LEFT_GESTURES:
+            case ITYPE_RIGHT_GESTURES:
+                return Type.SYSTEM_GESTURES;
+            case ITYPE_LEFT_TAPPABLE_ELEMENT:
+            case ITYPE_TOP_TAPPABLE_ELEMENT:
+            case ITYPE_RIGHT_TAPPABLE_ELEMENT:
+            case ITYPE_BOTTOM_TAPPABLE_ELEMENT:
+                return Type.TAPPABLE_ELEMENT;
+            case ITYPE_LEFT_DISPLAY_CUTOUT:
+            case ITYPE_TOP_DISPLAY_CUTOUT:
+            case ITYPE_RIGHT_DISPLAY_CUTOUT:
+            case ITYPE_BOTTOM_DISPLAY_CUTOUT:
+                return Type.DISPLAY_CUTOUT;
+            default:
+                throw new IllegalArgumentException("Unknown type: " + type);
+        }
+    }
+
+    public static boolean getDefaultVisibility(@InternalInsetsType int type) {
+        return type != ITYPE_IME;
+    }
+
+    public static boolean containsType(@InternalInsetsType int[] types,
+            @InternalInsetsType int type) {
+        if (types == null) {
+            return false;
+        }
+        for (int t : types) {
+            if (t == type) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void dump(String prefix, PrintWriter pw) {
+        final String newPrefix = prefix + "  ";
+        pw.println(prefix + "InsetsState");
+        pw.println(newPrefix + "mDisplayFrame=" + mDisplayFrame);
+        pw.println(newPrefix + "mDisplayCutout=" + mDisplayCutout.get());
+        pw.println(newPrefix + "mRoundedCorners=" + mRoundedCorners);
+        pw.println(newPrefix + "mPrivacyIndicatorBounds=" + mPrivacyIndicatorBounds);
+        for (int i = 0; i < SIZE; i++) {
+            InsetsSource source = mSources[i];
+            if (source == null) continue;
+            source.dump(newPrefix + "  ", pw);
+        }
+    }
+
+    void dumpDebug(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        InsetsSource source = mSources[ITYPE_IME];
+        if (source != null) {
+            source.dumpDebug(proto, SOURCES);
+        }
+        mDisplayFrame.dumpDebug(proto, DISPLAY_FRAME);
+        mDisplayCutout.get().dumpDebug(proto, DISPLAY_CUTOUT);
+        proto.end(token);
+    }
+
+    public static String typeToString(@InternalInsetsType int type) {
+        switch (type) {
+            case ITYPE_STATUS_BAR:
+                return "ITYPE_STATUS_BAR";
+            case ITYPE_NAVIGATION_BAR:
+                return "ITYPE_NAVIGATION_BAR";
+            case ITYPE_CAPTION_BAR:
+                return "ITYPE_CAPTION_BAR";
+            case ITYPE_TOP_GESTURES:
+                return "ITYPE_TOP_GESTURES";
+            case ITYPE_BOTTOM_GESTURES:
+                return "ITYPE_BOTTOM_GESTURES";
+            case ITYPE_LEFT_GESTURES:
+                return "ITYPE_LEFT_GESTURES";
+            case ITYPE_RIGHT_GESTURES:
+                return "ITYPE_RIGHT_GESTURES";
+            case ITYPE_TOP_MANDATORY_GESTURES:
+                return "ITYPE_TOP_MANDATORY_GESTURES";
+            case ITYPE_BOTTOM_MANDATORY_GESTURES:
+                return "ITYPE_BOTTOM_MANDATORY_GESTURES";
+            case ITYPE_LEFT_MANDATORY_GESTURES:
+                return "ITYPE_LEFT_MANDATORY_GESTURES";
+            case ITYPE_RIGHT_MANDATORY_GESTURES:
+                return "ITYPE_RIGHT_MANDATORY_GESTURES";
+            case ITYPE_LEFT_TAPPABLE_ELEMENT:
+                return "ITYPE_LEFT_TAPPABLE_ELEMENT";
+            case ITYPE_TOP_TAPPABLE_ELEMENT:
+                return "ITYPE_TOP_TAPPABLE_ELEMENT";
+            case ITYPE_RIGHT_TAPPABLE_ELEMENT:
+                return "ITYPE_RIGHT_TAPPABLE_ELEMENT";
+            case ITYPE_BOTTOM_TAPPABLE_ELEMENT:
+                return "ITYPE_BOTTOM_TAPPABLE_ELEMENT";
+            case ITYPE_LEFT_DISPLAY_CUTOUT:
+                return "ITYPE_LEFT_DISPLAY_CUTOUT";
+            case ITYPE_TOP_DISPLAY_CUTOUT:
+                return "ITYPE_TOP_DISPLAY_CUTOUT";
+            case ITYPE_RIGHT_DISPLAY_CUTOUT:
+                return "ITYPE_RIGHT_DISPLAY_CUTOUT";
+            case ITYPE_BOTTOM_DISPLAY_CUTOUT:
+                return "ITYPE_BOTTOM_DISPLAY_CUTOUT";
+            case ITYPE_IME:
+                return "ITYPE_IME";
+            case ITYPE_CLIMATE_BAR:
+                return "ITYPE_CLIMATE_BAR";
+            case ITYPE_EXTRA_NAVIGATION_BAR:
+                return "ITYPE_EXTRA_NAVIGATION_BAR";
+            default:
+                return "ITYPE_UNKNOWN_" + type;
+        }
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        return equals(o, false, false);
+    }
+
+    /**
+     * An equals method can exclude the caption insets. This is useful because we assemble the
+     * caption insets information on the client side, and when we communicate with server, it's
+     * excluded.
+     * @param excludingCaptionInsets {@code true} if we want to compare two InsetsState objects but
+     *                                           ignore the caption insets source value.
+     * @param excludeInvisibleImeFrames If {@link #ITYPE_IME} frames should be ignored when IME is
+     *                                  not visible.
+     * @return {@code true} if the two InsetsState objects are equal, {@code false} otherwise.
+     */
+    @VisibleForTesting
+    public boolean equals(@Nullable Object o, boolean excludingCaptionInsets,
+            boolean excludeInvisibleImeFrames) {
+        if (this == o) { return true; }
+        if (o == null || getClass() != o.getClass()) { return false; }
+
+        InsetsState state = (InsetsState) o;
+
+        if (!mDisplayFrame.equals(state.mDisplayFrame)
+                || !mDisplayCutout.equals(state.mDisplayCutout)
+                || !mRoundedCorners.equals(state.mRoundedCorners)
+                || !mPrivacyIndicatorBounds.equals(state.mPrivacyIndicatorBounds)) {
+            return false;
+        }
+        for (int i = 0; i < SIZE; i++) {
+            if (excludingCaptionInsets) {
+                if (i == ITYPE_CAPTION_BAR) continue;
+            }
+            InsetsSource source = mSources[i];
+            InsetsSource otherSource = state.mSources[i];
+            if (source == null && otherSource == null) {
+                continue;
+            }
+            if (source == null || otherSource == null) {
+                return false;
+            }
+            if (!otherSource.equals(source, excludeInvisibleImeFrames)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mDisplayFrame, mDisplayCutout, Arrays.hashCode(mSources),
+                mRoundedCorners, mPrivacyIndicatorBounds);
+    }
+
+    public InsetsState(Parcel in) {
+        readFromParcel(in);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        mDisplayFrame.writeToParcel(dest, flags);
+        mDisplayCutout.writeToParcel(dest, flags);
+        dest.writeTypedArray(mSources, 0 /* parcelableFlags */);
+        dest.writeTypedObject(mRoundedCorners, flags);
+        dest.writeTypedObject(mPrivacyIndicatorBounds, flags);
+    }
+
+    public static final @NonNull Creator<InsetsState> CREATOR = new Creator<InsetsState>() {
+
+        public InsetsState createFromParcel(Parcel in) {
+            return new InsetsState(in);
+        }
+
+        public InsetsState[] newArray(int size) {
+            return new InsetsState[size];
+        }
+    };
+
+    public void readFromParcel(Parcel in) {
+        mDisplayFrame.readFromParcel(in);
+        mDisplayCutout.readFromParcel(in);
+        in.readTypedArray(mSources, InsetsSource.CREATOR);
+        mRoundedCorners = in.readTypedObject(RoundedCorners.CREATOR);
+        mPrivacyIndicatorBounds = in.readTypedObject(PrivacyIndicatorBounds.CREATOR);
+    }
+
+    @Override
+    public String toString() {
+        StringJoiner joiner = new StringJoiner(", ");
+        for (int i = 0; i < SIZE; i++) {
+            InsetsSource source = mSources[i];
+            if (source != null) {
+                joiner.add(source.toString());
+            }
+        }
+        return "InsetsState: {"
+                + "mDisplayFrame=" + mDisplayFrame
+                + ", mDisplayCutout=" + mDisplayCutout
+                + ", mRoundedCorners=" + mRoundedCorners
+                + ", mPrivacyIndicatorBounds=" + mPrivacyIndicatorBounds
+                + ", mSources= { " + joiner
+                + " }";
+    }
+
+    public @NonNull String toSourceVisibilityString() {
+        StringJoiner joiner = new StringJoiner(", ");
+        for (int i = 0; i < SIZE; i++) {
+            InsetsSource source = mSources[i];
+            if (source != null) {
+                joiner.add(typeToString(i) + ": " + (source.isVisible() ? "visible" : "invisible"));
+            }
+        }
+        return joiner.toString();
+    }
+}
+
diff --git a/android/view/KeyCharacterMap.java b/android/view/KeyCharacterMap.java
new file mode 100644
index 0000000..aa1acc1
--- /dev/null
+++ b/android/view/KeyCharacterMap.java
@@ -0,0 +1,818 @@
+/*
+ * 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.view;
+
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.hardware.input.InputManager;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.method.MetaKeyKeyListener;
+import android.util.AndroidRuntimeException;
+import android.util.SparseIntArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.text.Normalizer;
+
+/**
+ * Describes the keys provided by a keyboard device and their associated labels.
+ */
+public class KeyCharacterMap implements Parcelable {
+    /**
+     * The id of the device's primary built in keyboard is always 0.
+     *
+     * @deprecated This constant should no longer be used because there is no
+     * guarantee that a device has a built-in keyboard that can be used for
+     * typing text.  There might not be a built-in keyboard, the built-in keyboard
+     * might be a {@link #NUMERIC} or {@link #SPECIAL_FUNCTION} keyboard, or there
+     * might be multiple keyboards installed including external keyboards.
+     * When interpreting key presses received from the framework, applications should
+     * use the device id specified in the {@link KeyEvent} received.
+     * When synthesizing key presses for delivery elsewhere or when translating key presses
+     * from unknown keyboards, applications should use the special {@link #VIRTUAL_KEYBOARD}
+     * device id.
+     */
+    @Deprecated
+    public static final int BUILT_IN_KEYBOARD = 0;
+
+    /**
+     * The id of a generic virtual keyboard with a full layout that can be used to
+     * synthesize key events.  Typically used with {@link #getEvents}.
+     */
+    public static final int VIRTUAL_KEYBOARD = -1;
+
+    /**
+     * A numeric (12-key) keyboard.
+     * <p>
+     * A numeric keyboard supports text entry using a multi-tap approach.
+     * It may be necessary to tap a key multiple times to generate the desired letter
+     * or symbol.
+     * </p><p>
+     * This type of keyboard is generally designed for thumb typing.
+     * </p>
+     */
+    public static final int NUMERIC = 1;
+
+    /**
+     * A keyboard with all the letters, but with more than one letter per key.
+     * <p>
+     * This type of keyboard is generally designed for thumb typing.
+     * </p>
+     */
+    public static final int PREDICTIVE = 2;
+
+    /**
+     * A keyboard with all the letters, and maybe some numbers.
+     * <p>
+     * An alphabetic keyboard supports text entry directly but may have a condensed
+     * layout with a small form factor.  In contrast to a {@link #FULL full keyboard}, some
+     * symbols may only be accessible using special on-screen character pickers.
+     * In addition, to improve typing speed and accuracy, the framework provides
+     * special affordances for alphabetic keyboards such as auto-capitalization
+     * and toggled / locked shift and alt keys.
+     * </p><p>
+     * This type of keyboard is generally designed for thumb typing.
+     * </p>
+     */
+    public static final int ALPHA = 3;
+
+    /**
+     * A full PC-style keyboard.
+     * <p>
+     * A full keyboard behaves like a PC keyboard.  All symbols are accessed directly
+     * by pressing keys on the keyboard without on-screen support or affordances such
+     * as auto-capitalization.
+     * </p><p>
+     * This type of keyboard is generally designed for full two hand typing.
+     * </p>
+     */
+    public static final int FULL = 4;
+
+    /**
+     * A keyboard that is only used to control special functions rather than for typing.
+     * <p>
+     * A special function keyboard consists only of non-printing keys such as
+     * HOME and POWER that are not actually used for typing.
+     * </p>
+     */
+    public static final int SPECIAL_FUNCTION = 5;
+
+    /**
+     * This private-use character is used to trigger Unicode character
+     * input by hex digits.
+     */
+    public static final char HEX_INPUT = '\uEF00';
+
+    /**
+     * This private-use character is used to bring up a character picker for
+     * miscellaneous symbols.
+     */
+    public static final char PICKER_DIALOG_INPUT = '\uEF01';
+
+    /**
+     * Modifier keys may be chorded with character keys.
+     *
+     * @see #getModifierBehavior()
+     */
+    public static final int MODIFIER_BEHAVIOR_CHORDED = 0;
+
+    /**
+     * Modifier keys may be chorded with character keys or they may toggle
+     * into latched or locked states when pressed independently.
+     *
+     * @see #getModifierBehavior()
+     */
+    public static final int MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED = 1;
+
+    /*
+     * This bit will be set in the return value of {@link #get(int, int)} if the
+     * key is a "dead key."
+     */
+    public static final int COMBINING_ACCENT = 0x80000000;
+
+    /**
+     * Mask the return value from {@link #get(int, int)} with this value to get
+     * a printable representation of the accent character of a "dead key."
+     */
+    public static final int COMBINING_ACCENT_MASK = 0x7FFFFFFF;
+
+    /* Characters used to display placeholders for dead keys. */
+    private static final int ACCENT_ACUTE = '\u00B4';
+    private static final int ACCENT_BREVE = '\u02D8';
+    private static final int ACCENT_CARON = '\u02C7';
+    private static final int ACCENT_CEDILLA = '\u00B8';
+    private static final int ACCENT_CIRCUMFLEX = '\u02C6';
+    private static final int ACCENT_COMMA_ABOVE = '\u1FBD';
+    private static final int ACCENT_COMMA_ABOVE_RIGHT = '\u02BC';
+    private static final int ACCENT_DOT_ABOVE = '\u02D9';
+    private static final int ACCENT_DOT_BELOW = '.'; // approximate
+    private static final int ACCENT_DOUBLE_ACUTE = '\u02DD';
+    private static final int ACCENT_GRAVE = '\u02CB';
+    private static final int ACCENT_HOOK_ABOVE = '\u02C0';
+    private static final int ACCENT_HORN = '\''; // approximate
+    private static final int ACCENT_MACRON = '\u00AF';
+    private static final int ACCENT_MACRON_BELOW = '\u02CD';
+    private static final int ACCENT_OGONEK = '\u02DB';
+    private static final int ACCENT_REVERSED_COMMA_ABOVE = '\u02BD';
+    private static final int ACCENT_RING_ABOVE = '\u02DA';
+    private static final int ACCENT_STROKE = '-'; // approximate
+    private static final int ACCENT_TILDE = '\u02DC';
+    private static final int ACCENT_TURNED_COMMA_ABOVE = '\u02BB';
+    private static final int ACCENT_UMLAUT = '\u00A8';
+    private static final int ACCENT_VERTICAL_LINE_ABOVE = '\u02C8';
+    private static final int ACCENT_VERTICAL_LINE_BELOW = '\u02CC';
+
+    /* Legacy dead key display characters used in previous versions of the API.
+     * We still support these characters by mapping them to their non-legacy version. */
+    private static final int ACCENT_GRAVE_LEGACY = '`';
+    private static final int ACCENT_CIRCUMFLEX_LEGACY = '^';
+    private static final int ACCENT_TILDE_LEGACY = '~';
+
+    private static final int CHAR_SPACE = ' ';
+
+    /**
+     * Maps Unicode combining diacritical to display-form dead key.
+     */
+    private static final SparseIntArray sCombiningToAccent = new SparseIntArray();
+    private static final SparseIntArray sAccentToCombining = new SparseIntArray();
+    static {
+        addCombining('\u0300', ACCENT_GRAVE);
+        addCombining('\u0301', ACCENT_ACUTE);
+        addCombining('\u0302', ACCENT_CIRCUMFLEX);
+        addCombining('\u0303', ACCENT_TILDE);
+        addCombining('\u0304', ACCENT_MACRON);
+        addCombining('\u0306', ACCENT_BREVE);
+        addCombining('\u0307', ACCENT_DOT_ABOVE);
+        addCombining('\u0308', ACCENT_UMLAUT);
+        addCombining('\u0309', ACCENT_HOOK_ABOVE);
+        addCombining('\u030A', ACCENT_RING_ABOVE);
+        addCombining('\u030B', ACCENT_DOUBLE_ACUTE);
+        addCombining('\u030C', ACCENT_CARON);
+        addCombining('\u030D', ACCENT_VERTICAL_LINE_ABOVE);
+        //addCombining('\u030E', ACCENT_DOUBLE_VERTICAL_LINE_ABOVE);
+        //addCombining('\u030F', ACCENT_DOUBLE_GRAVE);
+        //addCombining('\u0310', ACCENT_CANDRABINDU);
+        //addCombining('\u0311', ACCENT_INVERTED_BREVE);
+        addCombining('\u0312', ACCENT_TURNED_COMMA_ABOVE);
+        addCombining('\u0313', ACCENT_COMMA_ABOVE);
+        addCombining('\u0314', ACCENT_REVERSED_COMMA_ABOVE);
+        addCombining('\u0315', ACCENT_COMMA_ABOVE_RIGHT);
+        addCombining('\u031B', ACCENT_HORN);
+        addCombining('\u0323', ACCENT_DOT_BELOW);
+        //addCombining('\u0326', ACCENT_COMMA_BELOW);
+        addCombining('\u0327', ACCENT_CEDILLA);
+        addCombining('\u0328', ACCENT_OGONEK);
+        addCombining('\u0329', ACCENT_VERTICAL_LINE_BELOW);
+        addCombining('\u0331', ACCENT_MACRON_BELOW);
+        addCombining('\u0335', ACCENT_STROKE);
+        //addCombining('\u0342', ACCENT_PERISPOMENI);
+        //addCombining('\u0344', ACCENT_DIALYTIKA_TONOS);
+        //addCombining('\u0345', ACCENT_YPOGEGRAMMENI);
+
+        // One-way mappings to equivalent preferred accents.
+        sCombiningToAccent.append('\u0340', ACCENT_GRAVE);
+        sCombiningToAccent.append('\u0341', ACCENT_ACUTE);
+        sCombiningToAccent.append('\u0343', ACCENT_COMMA_ABOVE);
+
+        // One-way legacy mappings to preserve compatibility with older applications.
+        sAccentToCombining.append(ACCENT_GRAVE_LEGACY, '\u0300');
+        sAccentToCombining.append(ACCENT_CIRCUMFLEX_LEGACY, '\u0302');
+        sAccentToCombining.append(ACCENT_TILDE_LEGACY, '\u0303');
+    }
+
+    private static void addCombining(int combining, int accent) {
+        sCombiningToAccent.append(combining, accent);
+        sAccentToCombining.append(accent, combining);
+    }
+
+    /**
+     * Maps combinations of (display-form) combining key and second character
+     * to combined output character.
+     * These mappings are derived from the Unicode NFC tables as needed.
+     */
+    private static final SparseIntArray sDeadKeyCache = new SparseIntArray();
+    private static final StringBuilder sDeadKeyBuilder = new StringBuilder();
+    static {
+        // Non-standard decompositions.
+        // Stroke modifier for Finnish multilingual keyboard and others.
+        addDeadKey(ACCENT_STROKE, 'D', '\u0110');
+        addDeadKey(ACCENT_STROKE, 'G', '\u01e4');
+        addDeadKey(ACCENT_STROKE, 'H', '\u0126');
+        addDeadKey(ACCENT_STROKE, 'I', '\u0197');
+        addDeadKey(ACCENT_STROKE, 'L', '\u0141');
+        addDeadKey(ACCENT_STROKE, 'O', '\u00d8');
+        addDeadKey(ACCENT_STROKE, 'T', '\u0166');
+        addDeadKey(ACCENT_STROKE, 'd', '\u0111');
+        addDeadKey(ACCENT_STROKE, 'g', '\u01e5');
+        addDeadKey(ACCENT_STROKE, 'h', '\u0127');
+        addDeadKey(ACCENT_STROKE, 'i', '\u0268');
+        addDeadKey(ACCENT_STROKE, 'l', '\u0142');
+        addDeadKey(ACCENT_STROKE, 'o', '\u00f8');
+        addDeadKey(ACCENT_STROKE, 't', '\u0167');
+    }
+
+    private static void addDeadKey(int accent, int c, int result) {
+        final int combining = sAccentToCombining.get(accent);
+        if (combining == 0) {
+            throw new IllegalStateException("Invalid dead key declaration.");
+        }
+        final int combination = (combining << 16) | c;
+        sDeadKeyCache.put(combination, result);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<KeyCharacterMap> CREATOR =
+            new Parcelable.Creator<KeyCharacterMap>() {
+        public KeyCharacterMap createFromParcel(Parcel in) {
+            return new KeyCharacterMap(in);
+        }
+        public KeyCharacterMap[] newArray(int size) {
+            return new KeyCharacterMap[size];
+        }
+    };
+
+    private long mPtr;
+
+    private static native long nativeReadFromParcel(Parcel in);
+    private static native void nativeWriteToParcel(long ptr, Parcel out);
+    private static native void nativeDispose(long ptr);
+
+    private static native char nativeGetCharacter(long ptr, int keyCode, int metaState);
+    private static native boolean nativeGetFallbackAction(long ptr, int keyCode, int metaState,
+            FallbackAction outFallbackAction);
+    private static native char nativeGetNumber(long ptr, int keyCode);
+    private static native char nativeGetMatch(long ptr, int keyCode, char[] chars, int metaState);
+    private static native char nativeGetDisplayLabel(long ptr, int keyCode);
+    private static native int nativeGetKeyboardType(long ptr);
+    private static native KeyEvent[] nativeGetEvents(long ptr, char[] chars);
+    private static native KeyCharacterMap nativeObtainEmptyKeyCharacterMap(int deviceId);
+    private static native boolean nativeEquals(long ptr1, long ptr2);
+
+    private KeyCharacterMap(Parcel in) {
+        if (in == null) {
+            throw new IllegalArgumentException("parcel must not be null");
+        }
+        mPtr = nativeReadFromParcel(in);
+        if (mPtr == 0) {
+            throw new RuntimeException("Could not read KeyCharacterMap from parcel.");
+        }
+    }
+
+    // Called from native
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private KeyCharacterMap(long ptr) {
+        mPtr = ptr;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        if (mPtr != 0) {
+            nativeDispose(mPtr);
+            mPtr = 0;
+        }
+    }
+
+    /**
+     * Obtain empty key character map
+     * @param deviceId The input device ID
+     * @return The KeyCharacterMap object
+     * @hide
+     */
+    @VisibleForTesting
+    @Nullable
+    public static KeyCharacterMap obtainEmptyMap(int deviceId) {
+        return nativeObtainEmptyKeyCharacterMap(deviceId);
+    }
+
+    /**
+     * Loads the key character maps for the keyboard with the specified device id.
+     *
+     * @param deviceId The device id of the keyboard.
+     * @return The associated key character map.
+     * @throws {@link UnavailableException} if the key character map
+     * could not be loaded because it was malformed or the default key character map
+     * is missing from the system.
+     */
+    public static KeyCharacterMap load(int deviceId) {
+        final InputManager im = InputManager.getInstance();
+        InputDevice inputDevice = im.getInputDevice(deviceId);
+        if (inputDevice == null) {
+            inputDevice = im.getInputDevice(VIRTUAL_KEYBOARD);
+            if (inputDevice == null) {
+                throw new UnavailableException(
+                        "Could not load key character map for device " + deviceId);
+            }
+        }
+        return inputDevice.getKeyCharacterMap();
+    }
+
+    /**
+     * Gets the Unicode character generated by the specified key and meta
+     * key state combination.
+     * <p>
+     * Returns the Unicode character that the specified key would produce
+     * when the specified meta bits (see {@link MetaKeyKeyListener})
+     * were active.
+     * </p><p>
+     * Returns 0 if the key is not one that is used to type Unicode
+     * characters.
+     * </p><p>
+     * If the return value has bit {@link #COMBINING_ACCENT} set, the
+     * key is a "dead key" that should be combined with another to
+     * actually produce a character -- see {@link #getDeadChar} --
+     * after masking with {@link #COMBINING_ACCENT_MASK}.
+     * </p>
+     *
+     * @param keyCode The key code.
+     * @param metaState The meta key modifier state.
+     * @return The associated character or combining accent, or 0 if none.
+     */
+    public int get(int keyCode, int metaState) {
+        metaState = KeyEvent.normalizeMetaState(metaState);
+        char ch = nativeGetCharacter(mPtr, keyCode, metaState);
+
+        int map = sCombiningToAccent.get(ch);
+        if (map != 0) {
+            return map | COMBINING_ACCENT;
+        } else {
+            return ch;
+        }
+    }
+
+    /**
+     * Gets the fallback action to perform if the application does not
+     * handle the specified key.
+     * <p>
+     * When an application does not handle a particular key, the system may
+     * translate the key to an alternate fallback key (specified in the
+     * fallback action) and dispatch it to the application.
+     * The event containing the fallback key is flagged
+     * with {@link KeyEvent#FLAG_FALLBACK}.
+     * </p>
+     *
+     * @param keyCode The key code.
+     * @param metaState The meta key modifier state.
+     * @return The fallback action, or null if none.  Remember to recycle the fallback action.
+     *
+     * @hide
+     */
+    public FallbackAction getFallbackAction(int keyCode, int metaState) {
+        FallbackAction action = FallbackAction.obtain();
+        metaState = KeyEvent.normalizeMetaState(metaState);
+        if (nativeGetFallbackAction(mPtr, keyCode, metaState, action)) {
+            action.metaState = KeyEvent.normalizeMetaState(action.metaState);
+            return action;
+        }
+        action.recycle();
+        return null;
+    }
+
+    /**
+     * Gets the number or symbol associated with the key.
+     * <p>
+     * The character value is returned, not the numeric value.
+     * If the key is not a number, but is a symbol, the symbol is retuned.
+     * </p><p>
+     * This method is intended to to support dial pads and other numeric or
+     * symbolic entry on keyboards where certain keys serve dual function
+     * as alphabetic and symbolic keys.  This method returns the number
+     * or symbol associated with the key independent of whether the user
+     * has pressed the required modifier.
+     * </p><p>
+     * For example, on one particular keyboard the keys on the top QWERTY row generate
+     * numbers when ALT is pressed such that ALT-Q maps to '1'.  So for that keyboard
+     * when {@link #getNumber} is called with {@link KeyEvent#KEYCODE_Q} it returns '1'
+     * so that the user can type numbers without pressing ALT when it makes sense.
+     * </p>
+     *
+     * @param keyCode The key code.
+     * @return The associated numeric or symbolic character, or 0 if none.
+     */
+    public char getNumber(int keyCode) {
+        return nativeGetNumber(mPtr, keyCode);
+    }
+
+    /**
+     * Gets the first character in the character array that can be generated
+     * by the specified key code.
+     * <p>
+     * This is a convenience function that returns the same value as
+     * {@link #getMatch(int,char[],int) getMatch(keyCode, chars, 0)}.
+     * </p>
+     *
+     * @param keyCode The keycode.
+     * @param chars The array of matching characters to consider.
+     * @return The matching associated character, or 0 if none.
+     * @throws {@link IllegalArgumentException} if the passed array of characters is null.
+     */
+    public char getMatch(int keyCode, char[] chars) {
+        return getMatch(keyCode, chars, 0);
+    }
+
+    /**
+     * Gets the first character in the character array that can be generated
+     * by the specified key code.  If there are multiple choices, prefers
+     * the one that would be generated with the specified meta key modifier state.
+     *
+     * @param keyCode The key code.
+     * @param chars The array of matching characters to consider.
+     * @param metaState The preferred meta key modifier state.
+     * @return The matching associated character, or 0 if none.
+     * @throws {@link IllegalArgumentException} if the passed array of characters is null.
+     */
+    public char getMatch(int keyCode, char[] chars, int metaState) {
+        if (chars == null) {
+            throw new IllegalArgumentException("chars must not be null.");
+        }
+
+        metaState = KeyEvent.normalizeMetaState(metaState);
+        return nativeGetMatch(mPtr, keyCode, chars, metaState);
+    }
+
+    /**
+     * Gets the primary character for this key.
+     * In other words, the label that is physically printed on it.
+     *
+     * @param keyCode The key code.
+     * @return The display label character, or 0 if none (eg. for non-printing keys).
+     */
+    public char getDisplayLabel(int keyCode) {
+        return nativeGetDisplayLabel(mPtr, keyCode);
+    }
+
+    /**
+     * Get the character that is produced by combining the dead key producing accent
+     * with the key producing character c.
+     * For example, getDeadChar('`', 'e') returns &egrave;.
+     * getDeadChar('^', ' ') returns '^' and getDeadChar('^', '^') returns '^'.
+     *
+     * @param accent The accent character.  eg. '`'
+     * @param c The basic character.
+     * @return The combined character, or 0 if the characters cannot be combined.
+     */
+    public static int getDeadChar(int accent, int c) {
+        if (c == accent || CHAR_SPACE == c) {
+            // The same dead character typed twice or a dead character followed by a
+            // space should both produce the non-combining version of the combining char.
+            // In this case we don't even need to compute the combining character.
+            return accent;
+        }
+
+        int combining = sAccentToCombining.get(accent);
+        if (combining == 0) {
+            return 0;
+        }
+
+        final int combination = (combining << 16) | c;
+        int combined;
+        synchronized (sDeadKeyCache) {
+            combined = sDeadKeyCache.get(combination, -1);
+            if (combined == -1) {
+                sDeadKeyBuilder.setLength(0);
+                sDeadKeyBuilder.append((char)c);
+                sDeadKeyBuilder.append((char)combining);
+                String result = Normalizer.normalize(sDeadKeyBuilder, Normalizer.Form.NFC);
+                combined = result.codePointCount(0, result.length()) == 1
+                        ? result.codePointAt(0) : 0;
+                sDeadKeyCache.put(combination, combined);
+            }
+        }
+        return combined;
+    }
+
+    /**
+     * Describes the character mappings associated with a key.
+     *
+     * @deprecated instead use {@link KeyCharacterMap#getDisplayLabel(int)},
+     * {@link KeyCharacterMap#getNumber(int)} and {@link KeyCharacterMap#get(int, int)}.
+     */
+    @Deprecated
+    public static class KeyData {
+        public static final int META_LENGTH = 4;
+
+        /**
+         * The display label (see {@link #getDisplayLabel}).
+         */
+        public char displayLabel;
+        /**
+         * The "number" value (see {@link #getNumber}).
+         */
+        public char number;
+        /**
+         * The character that will be generated in various meta states
+         * (the same ones used for {@link #get} and defined as
+         * {@link KeyEvent#META_SHIFT_ON} and {@link KeyEvent#META_ALT_ON}).
+         *      <table>
+         *          <tr><th>Index</th><th align="left">Value</th></tr>
+         *          <tr><td>0</td><td>no modifiers</td></tr>
+         *          <tr><td>1</td><td>caps</td></tr>
+         *          <tr><td>2</td><td>alt</td></tr>
+         *          <tr><td>3</td><td>caps + alt</td></tr>
+         *      </table>
+         */
+        public char[] meta = new char[META_LENGTH];
+    }
+
+    /**
+     * Get the character conversion data for a given key code.
+     *
+     * @param keyCode The keyCode to query.
+     * @param results A {@link KeyData} instance that will be filled with the results.
+     * @return True if the key was mapped.  If the key was not mapped, results is not modified.
+     *
+     * @deprecated instead use {@link KeyCharacterMap#getDisplayLabel(int)},
+     * {@link KeyCharacterMap#getNumber(int)} or {@link KeyCharacterMap#get(int, int)}.
+     */
+    @Deprecated
+    public boolean getKeyData(int keyCode, KeyData results) {
+        if (results.meta.length < KeyData.META_LENGTH) {
+            throw new IndexOutOfBoundsException(
+                    "results.meta.length must be >= " + KeyData.META_LENGTH);
+        }
+
+        char displayLabel = nativeGetDisplayLabel(mPtr, keyCode);
+        if (displayLabel == 0) {
+            return false;
+        }
+
+        results.displayLabel = displayLabel;
+        results.number = nativeGetNumber(mPtr, keyCode);
+        results.meta[0] = nativeGetCharacter(mPtr, keyCode, 0);
+        results.meta[1] = nativeGetCharacter(mPtr, keyCode, KeyEvent.META_SHIFT_ON);
+        results.meta[2] = nativeGetCharacter(mPtr, keyCode, KeyEvent.META_ALT_ON);
+        results.meta[3] = nativeGetCharacter(mPtr, keyCode,
+                KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON);
+        return true;
+    }
+
+    /**
+     * Get an array of KeyEvent objects that if put into the input stream
+     * could plausibly generate the provided sequence of characters.  It is
+     * not guaranteed that the sequence is the only way to generate these
+     * events or that it is optimal.
+     * <p>
+     * This function is primarily offered for instrumentation and testing purposes.
+     * It may fail to map characters to key codes.  In particular, the key character
+     * map for the {@link #BUILT_IN_KEYBOARD built-in keyboard} device id may be empty.
+     * Consider using the key character map associated with the
+     * {@link #VIRTUAL_KEYBOARD virtual keyboard} device id instead.
+     * </p><p>
+     * For robust text entry, do not use this function.  Instead construct a
+     * {@link KeyEvent} with action code {@link KeyEvent#ACTION_MULTIPLE} that contains
+     * the desired string using {@link KeyEvent#KeyEvent(long, String, int, int)}.
+     * </p>
+     *
+     * @param chars The sequence of characters to generate.
+     * @return An array of {@link KeyEvent} objects, or null if the given char array
+     *         can not be generated using the current key character map.
+     * @throws {@link IllegalArgumentException} if the passed array of characters is null.
+     */
+    public KeyEvent[] getEvents(char[] chars) {
+        if (chars == null) {
+            throw new IllegalArgumentException("chars must not be null.");
+        }
+        return nativeGetEvents(mPtr, chars);
+    }
+
+    /**
+     * Returns true if the specified key produces a glyph.
+     *
+     * @param keyCode The key code.
+     * @return True if the key is a printing key.
+     */
+    public boolean isPrintingKey(int keyCode) {
+        int type = Character.getType(nativeGetDisplayLabel(mPtr, keyCode));
+
+        switch (type)
+        {
+            case Character.SPACE_SEPARATOR:
+            case Character.LINE_SEPARATOR:
+            case Character.PARAGRAPH_SEPARATOR:
+            case Character.CONTROL:
+            case Character.FORMAT:
+                return false;
+            default:
+                return true;
+        }
+    }
+
+    /**
+     * Gets the keyboard type.
+     * Returns {@link #NUMERIC}, {@link #PREDICTIVE}, {@link #ALPHA}, {@link #FULL}
+     * or {@link #SPECIAL_FUNCTION}.
+     * <p>
+     * Different keyboard types have different semantics.  Refer to the documentation
+     * associated with the keyboard type constants for details.
+     * </p>
+     *
+     * @return The keyboard type.
+     */
+    public int getKeyboardType() {
+        return nativeGetKeyboardType(mPtr);
+    }
+
+    /**
+     * Gets a constant that describes the behavior of this keyboard's modifier keys
+     * such as {@link KeyEvent#KEYCODE_SHIFT_LEFT}.
+     * <p>
+     * Currently there are two behaviors that may be combined:
+     * </p>
+     * <ul>
+     * <li>Chorded behavior: When the modifier key is pressed together with one or more
+     * character keys, the keyboard inserts the modified keys and
+     * then resets the modifier state when the modifier key is released.</li>
+     * <li>Toggled behavior: When the modifier key is pressed and released on its own
+     * it first toggles into a latched state.  When latched, the modifier will apply
+     * to next character key that is pressed and will then reset itself to the initial state.
+     * If the modifier is already latched and the modifier key is pressed and release on
+     * its own again, then it toggles into a locked state.  When locked, the modifier will
+     * apply to all subsequent character keys that are pressed until unlocked by pressing
+     * the modifier key on its own one more time to reset it to the initial state.
+     * Toggled behavior is useful for small profile keyboards designed for thumb typing.
+     * </ul>
+     * <p>
+     * This function currently returns {@link #MODIFIER_BEHAVIOR_CHORDED} when the
+     * {@link #getKeyboardType() keyboard type} is {@link #FULL} or {@link #SPECIAL_FUNCTION} and
+     * {@link #MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED} otherwise.
+     * In the future, the function may also take into account global keyboard
+     * accessibility settings, other user preferences, or new device capabilities.
+     * </p>
+     *
+     * @return The modifier behavior for this keyboard.
+     *
+     * @see #MODIFIER_BEHAVIOR_CHORDED
+     * @see #MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED
+     */
+    public int getModifierBehavior() {
+        switch (getKeyboardType()) {
+            case FULL:
+            case SPECIAL_FUNCTION:
+                return MODIFIER_BEHAVIOR_CHORDED;
+            default:
+                return MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED;
+        }
+    }
+
+    /**
+     * Queries the framework about whether any physical keys exist on the
+     * any keyboard attached to the device that are capable of producing the given key code.
+     *
+     * @param keyCode The key code to query.
+     * @return True if at least one attached keyboard supports the specified key code.
+     */
+    public static boolean deviceHasKey(int keyCode) {
+        return InputManager.getInstance().deviceHasKeys(new int[] { keyCode })[0];
+    }
+
+    /**
+     * Queries the framework about whether any physical keys exist on the
+     * any keyboard attached to the device that are capable of producing the given
+     * array of key codes.
+     *
+     * @param keyCodes The array of key codes to query.
+     * @return A new array of the same size as the key codes array whose elements
+     * are set to true if at least one attached keyboard supports the corresponding key code
+     * at the same index in the key codes array.
+     */
+    public static boolean[] deviceHasKeys(int[] keyCodes) {
+        return InputManager.getInstance().deviceHasKeys(keyCodes);
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        if (out == null) {
+            throw new IllegalArgumentException("parcel must not be null");
+        }
+        nativeWriteToParcel(mPtr, out);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || !(obj instanceof KeyCharacterMap)) {
+            return false;
+        }
+        KeyCharacterMap peer = (KeyCharacterMap) obj;
+        if (mPtr == 0 || peer.mPtr == 0) {
+            return mPtr == peer.mPtr;
+        }
+        return nativeEquals(mPtr, peer.mPtr);
+    }
+
+    /**
+     * Thrown by {@link KeyCharacterMap#load} when a key character map could not be loaded.
+     */
+    public static class UnavailableException extends AndroidRuntimeException {
+        public UnavailableException(String msg) {
+            super(msg);
+        }
+    }
+
+    /**
+     * Specifies a substitute key code and meta state as a fallback action
+     * for an unhandled key.
+     * @hide
+     */
+    public static final class FallbackAction {
+        private static final int MAX_RECYCLED = 10;
+        private static final Object sRecycleLock = new Object();
+        private static FallbackAction sRecycleBin;
+        private static int sRecycledCount;
+
+        private FallbackAction next;
+
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        public int keyCode;
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        public int metaState;
+
+        private FallbackAction() {
+        }
+
+        public static FallbackAction obtain() {
+            final FallbackAction target;
+            synchronized (sRecycleLock) {
+                if (sRecycleBin == null) {
+                    target = new FallbackAction();
+                } else {
+                    target = sRecycleBin;
+                    sRecycleBin = target.next;
+                    sRecycledCount--;
+                    target.next = null;
+                }
+            }
+            return target;
+        }
+
+        public void recycle() {
+            synchronized (sRecycleLock) {
+                if (sRecycledCount < MAX_RECYCLED) {
+                    next = sRecycleBin;
+                    sRecycleBin = this;
+                    sRecycledCount += 1;
+                } else {
+                    next = null;
+                }
+            }
+        }
+    }
+}
diff --git a/android/view/KeyEvent.java b/android/view/KeyEvent.java
new file mode 100644
index 0000000..cda9b23
--- /dev/null
+++ b/android/view/KeyEvent.java
@@ -0,0 +1,3178 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.os.IInputConstants.INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT;
+import static android.view.Display.INVALID_DISPLAY;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.method.MetaKeyKeyListener;
+import android.util.Log;
+import android.util.SparseIntArray;
+import android.view.KeyCharacterMap.KeyData;
+
+/**
+ * Object used to report key and button events.
+ * <p>
+ * Each key press is described by a sequence of key events.  A key press
+ * starts with a key event with {@link #ACTION_DOWN}.  If the key is held
+ * sufficiently long that it repeats, then the initial down is followed
+ * additional key events with {@link #ACTION_DOWN} and a non-zero value for
+ * {@link #getRepeatCount()}.  The last key event is a {@link #ACTION_UP}
+ * for the key up.  If the key press is canceled, the key up event will have the
+ * {@link #FLAG_CANCELED} flag set.
+ * </p><p>
+ * Key events are generally accompanied by a key code ({@link #getKeyCode()}),
+ * scan code ({@link #getScanCode()}) and meta state ({@link #getMetaState()}).
+ * Key code constants are defined in this class.  Scan code constants are raw
+ * device-specific codes obtained from the OS and so are not generally meaningful
+ * to applications unless interpreted using the {@link KeyCharacterMap}.
+ * Meta states describe the pressed state of key modifiers
+ * such as {@link #META_SHIFT_ON} or {@link #META_ALT_ON}.
+ * </p><p>
+ * Key codes typically correspond one-to-one with individual keys on an input device.
+ * Many keys and key combinations serve quite different functions on different
+ * input devices so care must be taken when interpreting them.  Always use the
+ * {@link KeyCharacterMap} associated with the input device when mapping keys
+ * to characters.  Be aware that there may be multiple key input devices active
+ * at the same time and each will have its own key character map.
+ * </p><p>
+ * As soft input methods can use multiple and inventive ways of inputting text,
+ * there is no guarantee that any key press on a soft keyboard will generate a key
+ * event: this is left to the IME's discretion, and in fact sending such events is
+ * discouraged.  You should never rely on receiving KeyEvents for any key on a soft
+ * input method.  In particular, the default software keyboard will never send any
+ * key event to any application targetting Jelly Bean or later, and will only send
+ * events for some presses of the delete and return keys to applications targetting
+ * Ice Cream Sandwich or earlier.  Be aware that other software input methods may
+ * never send key events regardless of the version.  Consider using editor actions
+ * like {@link android.view.inputmethod.EditorInfo#IME_ACTION_DONE} if you need
+ * specific interaction with the software keyboard, as it gives more visibility to
+ * the user as to how your application will react to key presses.
+ * </p><p>
+ * When interacting with an IME, the framework may deliver key events
+ * with the special action {@link #ACTION_MULTIPLE} that either specifies
+ * that single repeated key code or a sequence of characters to insert.
+ * </p><p>
+ * In general, the framework cannot guarantee that the key events it delivers
+ * to a view always constitute complete key sequences since some events may be dropped
+ * or modified by containing views before they are delivered.  The view implementation
+ * should be prepared to handle {@link #FLAG_CANCELED} and should tolerate anomalous
+ * situations such as receiving a new {@link #ACTION_DOWN} without first having
+ * received an {@link #ACTION_UP} for the prior key press.
+ * </p><p>
+ * Refer to {@link InputDevice} for more information about how different kinds of
+ * input devices and sources represent keys and buttons.
+ * </p>
+ */
+public class KeyEvent extends InputEvent implements Parcelable {
+    /** Key code constant: Unknown key code. */
+    public static final int KEYCODE_UNKNOWN         = 0;
+    /** Key code constant: Soft Left key.
+     * Usually situated below the display on phones and used as a multi-function
+     * feature key for selecting a software defined function shown on the bottom left
+     * of the display. */
+    public static final int KEYCODE_SOFT_LEFT       = 1;
+    /** Key code constant: Soft Right key.
+     * Usually situated below the display on phones and used as a multi-function
+     * feature key for selecting a software defined function shown on the bottom right
+     * of the display. */
+    public static final int KEYCODE_SOFT_RIGHT      = 2;
+    /** Key code constant: Home key.
+     * This key is handled by the framework and is never delivered to applications. */
+    public static final int KEYCODE_HOME            = 3;
+    /** Key code constant: Back key. */
+    public static final int KEYCODE_BACK            = 4;
+    /** Key code constant: Call key. */
+    public static final int KEYCODE_CALL            = 5;
+    /** Key code constant: End Call key. */
+    public static final int KEYCODE_ENDCALL         = 6;
+    /** Key code constant: '0' key. */
+    public static final int KEYCODE_0               = 7;
+    /** Key code constant: '1' key. */
+    public static final int KEYCODE_1               = 8;
+    /** Key code constant: '2' key. */
+    public static final int KEYCODE_2               = 9;
+    /** Key code constant: '3' key. */
+    public static final int KEYCODE_3               = 10;
+    /** Key code constant: '4' key. */
+    public static final int KEYCODE_4               = 11;
+    /** Key code constant: '5' key. */
+    public static final int KEYCODE_5               = 12;
+    /** Key code constant: '6' key. */
+    public static final int KEYCODE_6               = 13;
+    /** Key code constant: '7' key. */
+    public static final int KEYCODE_7               = 14;
+    /** Key code constant: '8' key. */
+    public static final int KEYCODE_8               = 15;
+    /** Key code constant: '9' key. */
+    public static final int KEYCODE_9               = 16;
+    /** Key code constant: '*' key. */
+    public static final int KEYCODE_STAR            = 17;
+    /** Key code constant: '#' key. */
+    public static final int KEYCODE_POUND           = 18;
+    /** Key code constant: Directional Pad Up key.
+     * May also be synthesized from trackball motions. */
+    public static final int KEYCODE_DPAD_UP         = 19;
+    /** Key code constant: Directional Pad Down key.
+     * May also be synthesized from trackball motions. */
+    public static final int KEYCODE_DPAD_DOWN       = 20;
+    /** Key code constant: Directional Pad Left key.
+     * May also be synthesized from trackball motions. */
+    public static final int KEYCODE_DPAD_LEFT       = 21;
+    /** Key code constant: Directional Pad Right key.
+     * May also be synthesized from trackball motions. */
+    public static final int KEYCODE_DPAD_RIGHT      = 22;
+    /** Key code constant: Directional Pad Center key.
+     * May also be synthesized from trackball motions. */
+    public static final int KEYCODE_DPAD_CENTER     = 23;
+    /** Key code constant: Volume Up key.
+     * Adjusts the speaker volume up. */
+    public static final int KEYCODE_VOLUME_UP       = 24;
+    /** Key code constant: Volume Down key.
+     * Adjusts the speaker volume down. */
+    public static final int KEYCODE_VOLUME_DOWN     = 25;
+    /** Key code constant: Power key. */
+    public static final int KEYCODE_POWER           = 26;
+    /** Key code constant: Camera key.
+     * Used to launch a camera application or take pictures. */
+    public static final int KEYCODE_CAMERA          = 27;
+    /** Key code constant: Clear key. */
+    public static final int KEYCODE_CLEAR           = 28;
+    /** Key code constant: 'A' key. */
+    public static final int KEYCODE_A               = 29;
+    /** Key code constant: 'B' key. */
+    public static final int KEYCODE_B               = 30;
+    /** Key code constant: 'C' key. */
+    public static final int KEYCODE_C               = 31;
+    /** Key code constant: 'D' key. */
+    public static final int KEYCODE_D               = 32;
+    /** Key code constant: 'E' key. */
+    public static final int KEYCODE_E               = 33;
+    /** Key code constant: 'F' key. */
+    public static final int KEYCODE_F               = 34;
+    /** Key code constant: 'G' key. */
+    public static final int KEYCODE_G               = 35;
+    /** Key code constant: 'H' key. */
+    public static final int KEYCODE_H               = 36;
+    /** Key code constant: 'I' key. */
+    public static final int KEYCODE_I               = 37;
+    /** Key code constant: 'J' key. */
+    public static final int KEYCODE_J               = 38;
+    /** Key code constant: 'K' key. */
+    public static final int KEYCODE_K               = 39;
+    /** Key code constant: 'L' key. */
+    public static final int KEYCODE_L               = 40;
+    /** Key code constant: 'M' key. */
+    public static final int KEYCODE_M               = 41;
+    /** Key code constant: 'N' key. */
+    public static final int KEYCODE_N               = 42;
+    /** Key code constant: 'O' key. */
+    public static final int KEYCODE_O               = 43;
+    /** Key code constant: 'P' key. */
+    public static final int KEYCODE_P               = 44;
+    /** Key code constant: 'Q' key. */
+    public static final int KEYCODE_Q               = 45;
+    /** Key code constant: 'R' key. */
+    public static final int KEYCODE_R               = 46;
+    /** Key code constant: 'S' key. */
+    public static final int KEYCODE_S               = 47;
+    /** Key code constant: 'T' key. */
+    public static final int KEYCODE_T               = 48;
+    /** Key code constant: 'U' key. */
+    public static final int KEYCODE_U               = 49;
+    /** Key code constant: 'V' key. */
+    public static final int KEYCODE_V               = 50;
+    /** Key code constant: 'W' key. */
+    public static final int KEYCODE_W               = 51;
+    /** Key code constant: 'X' key. */
+    public static final int KEYCODE_X               = 52;
+    /** Key code constant: 'Y' key. */
+    public static final int KEYCODE_Y               = 53;
+    /** Key code constant: 'Z' key. */
+    public static final int KEYCODE_Z               = 54;
+    /** Key code constant: ',' key. */
+    public static final int KEYCODE_COMMA           = 55;
+    /** Key code constant: '.' key. */
+    public static final int KEYCODE_PERIOD          = 56;
+    /** Key code constant: Left Alt modifier key. */
+    public static final int KEYCODE_ALT_LEFT        = 57;
+    /** Key code constant: Right Alt modifier key. */
+    public static final int KEYCODE_ALT_RIGHT       = 58;
+    /** Key code constant: Left Shift modifier key. */
+    public static final int KEYCODE_SHIFT_LEFT      = 59;
+    /** Key code constant: Right Shift modifier key. */
+    public static final int KEYCODE_SHIFT_RIGHT     = 60;
+    /** Key code constant: Tab key. */
+    public static final int KEYCODE_TAB             = 61;
+    /** Key code constant: Space key. */
+    public static final int KEYCODE_SPACE           = 62;
+    /** Key code constant: Symbol modifier key.
+     * Used to enter alternate symbols. */
+    public static final int KEYCODE_SYM             = 63;
+    /** Key code constant: Explorer special function key.
+     * Used to launch a browser application. */
+    public static final int KEYCODE_EXPLORER        = 64;
+    /** Key code constant: Envelope special function key.
+     * Used to launch a mail application. */
+    public static final int KEYCODE_ENVELOPE        = 65;
+    /** Key code constant: Enter key. */
+    public static final int KEYCODE_ENTER           = 66;
+    /** Key code constant: Backspace key.
+     * Deletes characters before the insertion point, unlike {@link #KEYCODE_FORWARD_DEL}. */
+    public static final int KEYCODE_DEL             = 67;
+    /** Key code constant: '`' (backtick) key. */
+    public static final int KEYCODE_GRAVE           = 68;
+    /** Key code constant: '-'. */
+    public static final int KEYCODE_MINUS           = 69;
+    /** Key code constant: '=' key. */
+    public static final int KEYCODE_EQUALS          = 70;
+    /** Key code constant: '[' key. */
+    public static final int KEYCODE_LEFT_BRACKET    = 71;
+    /** Key code constant: ']' key. */
+    public static final int KEYCODE_RIGHT_BRACKET   = 72;
+    /** Key code constant: '\' key. */
+    public static final int KEYCODE_BACKSLASH       = 73;
+    /** Key code constant: ';' key. */
+    public static final int KEYCODE_SEMICOLON       = 74;
+    /** Key code constant: ''' (apostrophe) key. */
+    public static final int KEYCODE_APOSTROPHE      = 75;
+    /** Key code constant: '/' key. */
+    public static final int KEYCODE_SLASH           = 76;
+    /** Key code constant: '@' key. */
+    public static final int KEYCODE_AT              = 77;
+    /** Key code constant: Number modifier key.
+     * Used to enter numeric symbols.
+     * This key is not Num Lock; it is more like {@link #KEYCODE_ALT_LEFT} and is
+     * interpreted as an ALT key by {@link android.text.method.MetaKeyKeyListener}. */
+    public static final int KEYCODE_NUM             = 78;
+    /** Key code constant: Headset Hook key.
+     * Used to hang up calls and stop media. */
+    public static final int KEYCODE_HEADSETHOOK     = 79;
+    /** Key code constant: Camera Focus key.
+     * Used to focus the camera. */
+    public static final int KEYCODE_FOCUS           = 80;   // *Camera* focus
+    /** Key code constant: '+' key. */
+    public static final int KEYCODE_PLUS            = 81;
+    /** Key code constant: Menu key. */
+    public static final int KEYCODE_MENU            = 82;
+    /** Key code constant: Notification key. */
+    public static final int KEYCODE_NOTIFICATION    = 83;
+    /** Key code constant: Search key. */
+    public static final int KEYCODE_SEARCH          = 84;
+    /** Key code constant: Play/Pause media key. */
+    public static final int KEYCODE_MEDIA_PLAY_PAUSE= 85;
+    /** Key code constant: Stop media key. */
+    public static final int KEYCODE_MEDIA_STOP      = 86;
+    /** Key code constant: Play Next media key. */
+    public static final int KEYCODE_MEDIA_NEXT      = 87;
+    /** Key code constant: Play Previous media key. */
+    public static final int KEYCODE_MEDIA_PREVIOUS  = 88;
+    /** Key code constant: Rewind media key. */
+    public static final int KEYCODE_MEDIA_REWIND    = 89;
+    /** Key code constant: Fast Forward media key. */
+    public static final int KEYCODE_MEDIA_FAST_FORWARD = 90;
+    /** Key code constant: Mute key.
+     * Mutes the microphone, unlike {@link #KEYCODE_VOLUME_MUTE}. */
+    public static final int KEYCODE_MUTE            = 91;
+    /** Key code constant: Page Up key. */
+    public static final int KEYCODE_PAGE_UP         = 92;
+    /** Key code constant: Page Down key. */
+    public static final int KEYCODE_PAGE_DOWN       = 93;
+    /** Key code constant: Picture Symbols modifier key.
+     * Used to switch symbol sets (Emoji, Kao-moji). */
+    public static final int KEYCODE_PICTSYMBOLS     = 94;   // switch symbol-sets (Emoji,Kao-moji)
+    /** Key code constant: Switch Charset modifier key.
+     * Used to switch character sets (Kanji, Katakana). */
+    public static final int KEYCODE_SWITCH_CHARSET  = 95;   // switch char-sets (Kanji,Katakana)
+    /** Key code constant: A Button key.
+     * On a game controller, the A button should be either the button labeled A
+     * or the first button on the bottom row of controller buttons. */
+    public static final int KEYCODE_BUTTON_A        = 96;
+    /** Key code constant: B Button key.
+     * On a game controller, the B button should be either the button labeled B
+     * or the second button on the bottom row of controller buttons. */
+    public static final int KEYCODE_BUTTON_B        = 97;
+    /** Key code constant: C Button key.
+     * On a game controller, the C button should be either the button labeled C
+     * or the third button on the bottom row of controller buttons. */
+    public static final int KEYCODE_BUTTON_C        = 98;
+    /** Key code constant: X Button key.
+     * On a game controller, the X button should be either the button labeled X
+     * or the first button on the upper row of controller buttons. */
+    public static final int KEYCODE_BUTTON_X        = 99;
+    /** Key code constant: Y Button key.
+     * On a game controller, the Y button should be either the button labeled Y
+     * or the second button on the upper row of controller buttons. */
+    public static final int KEYCODE_BUTTON_Y        = 100;
+    /** Key code constant: Z Button key.
+     * On a game controller, the Z button should be either the button labeled Z
+     * or the third button on the upper row of controller buttons. */
+    public static final int KEYCODE_BUTTON_Z        = 101;
+    /** Key code constant: L1 Button key.
+     * On a game controller, the L1 button should be either the button labeled L1 (or L)
+     * or the top left trigger button. */
+    public static final int KEYCODE_BUTTON_L1       = 102;
+    /** Key code constant: R1 Button key.
+     * On a game controller, the R1 button should be either the button labeled R1 (or R)
+     * or the top right trigger button. */
+    public static final int KEYCODE_BUTTON_R1       = 103;
+    /** Key code constant: L2 Button key.
+     * On a game controller, the L2 button should be either the button labeled L2
+     * or the bottom left trigger button. */
+    public static final int KEYCODE_BUTTON_L2       = 104;
+    /** Key code constant: R2 Button key.
+     * On a game controller, the R2 button should be either the button labeled R2
+     * or the bottom right trigger button. */
+    public static final int KEYCODE_BUTTON_R2       = 105;
+    /** Key code constant: Left Thumb Button key.
+     * On a game controller, the left thumb button indicates that the left (or only)
+     * joystick is pressed. */
+    public static final int KEYCODE_BUTTON_THUMBL   = 106;
+    /** Key code constant: Right Thumb Button key.
+     * On a game controller, the right thumb button indicates that the right
+     * joystick is pressed. */
+    public static final int KEYCODE_BUTTON_THUMBR   = 107;
+    /** Key code constant: Start Button key.
+     * On a game controller, the button labeled Start. */
+    public static final int KEYCODE_BUTTON_START    = 108;
+    /** Key code constant: Select Button key.
+     * On a game controller, the button labeled Select. */
+    public static final int KEYCODE_BUTTON_SELECT   = 109;
+    /** Key code constant: Mode Button key.
+     * On a game controller, the button labeled Mode. */
+    public static final int KEYCODE_BUTTON_MODE     = 110;
+    /** Key code constant: Escape key. */
+    public static final int KEYCODE_ESCAPE          = 111;
+    /** Key code constant: Forward Delete key.
+     * Deletes characters ahead of the insertion point, unlike {@link #KEYCODE_DEL}. */
+    public static final int KEYCODE_FORWARD_DEL     = 112;
+    /** Key code constant: Left Control modifier key. */
+    public static final int KEYCODE_CTRL_LEFT       = 113;
+    /** Key code constant: Right Control modifier key. */
+    public static final int KEYCODE_CTRL_RIGHT      = 114;
+    /** Key code constant: Caps Lock key. */
+    public static final int KEYCODE_CAPS_LOCK       = 115;
+    /** Key code constant: Scroll Lock key. */
+    public static final int KEYCODE_SCROLL_LOCK     = 116;
+    /** Key code constant: Left Meta modifier key. */
+    public static final int KEYCODE_META_LEFT       = 117;
+    /** Key code constant: Right Meta modifier key. */
+    public static final int KEYCODE_META_RIGHT      = 118;
+    /** Key code constant: Function modifier key. */
+    public static final int KEYCODE_FUNCTION        = 119;
+    /** Key code constant: System Request / Print Screen key. */
+    public static final int KEYCODE_SYSRQ           = 120;
+    /** Key code constant: Break / Pause key. */
+    public static final int KEYCODE_BREAK           = 121;
+    /** Key code constant: Home Movement key.
+     * Used for scrolling or moving the cursor around to the start of a line
+     * or to the top of a list. */
+    public static final int KEYCODE_MOVE_HOME       = 122;
+    /** Key code constant: End Movement key.
+     * Used for scrolling or moving the cursor around to the end of a line
+     * or to the bottom of a list. */
+    public static final int KEYCODE_MOVE_END        = 123;
+    /** Key code constant: Insert key.
+     * Toggles insert / overwrite edit mode. */
+    public static final int KEYCODE_INSERT          = 124;
+    /** Key code constant: Forward key.
+     * Navigates forward in the history stack.  Complement of {@link #KEYCODE_BACK}. */
+    public static final int KEYCODE_FORWARD         = 125;
+    /** Key code constant: Play media key. */
+    public static final int KEYCODE_MEDIA_PLAY      = 126;
+    /** Key code constant: Pause media key. */
+    public static final int KEYCODE_MEDIA_PAUSE     = 127;
+    /** Key code constant: Close media key.
+     * May be used to close a CD tray, for example. */
+    public static final int KEYCODE_MEDIA_CLOSE     = 128;
+    /** Key code constant: Eject media key.
+     * May be used to eject a CD tray, for example. */
+    public static final int KEYCODE_MEDIA_EJECT     = 129;
+    /** Key code constant: Record media key. */
+    public static final int KEYCODE_MEDIA_RECORD    = 130;
+    /** Key code constant: F1 key. */
+    public static final int KEYCODE_F1              = 131;
+    /** Key code constant: F2 key. */
+    public static final int KEYCODE_F2              = 132;
+    /** Key code constant: F3 key. */
+    public static final int KEYCODE_F3              = 133;
+    /** Key code constant: F4 key. */
+    public static final int KEYCODE_F4              = 134;
+    /** Key code constant: F5 key. */
+    public static final int KEYCODE_F5              = 135;
+    /** Key code constant: F6 key. */
+    public static final int KEYCODE_F6              = 136;
+    /** Key code constant: F7 key. */
+    public static final int KEYCODE_F7              = 137;
+    /** Key code constant: F8 key. */
+    public static final int KEYCODE_F8              = 138;
+    /** Key code constant: F9 key. */
+    public static final int KEYCODE_F9              = 139;
+    /** Key code constant: F10 key. */
+    public static final int KEYCODE_F10             = 140;
+    /** Key code constant: F11 key. */
+    public static final int KEYCODE_F11             = 141;
+    /** Key code constant: F12 key. */
+    public static final int KEYCODE_F12             = 142;
+    /** Key code constant: Num Lock key.
+     * This is the Num Lock key; it is different from {@link #KEYCODE_NUM}.
+     * This key alters the behavior of other keys on the numeric keypad. */
+    public static final int KEYCODE_NUM_LOCK        = 143;
+    /** Key code constant: Numeric keypad '0' key. */
+    public static final int KEYCODE_NUMPAD_0        = 144;
+    /** Key code constant: Numeric keypad '1' key. */
+    public static final int KEYCODE_NUMPAD_1        = 145;
+    /** Key code constant: Numeric keypad '2' key. */
+    public static final int KEYCODE_NUMPAD_2        = 146;
+    /** Key code constant: Numeric keypad '3' key. */
+    public static final int KEYCODE_NUMPAD_3        = 147;
+    /** Key code constant: Numeric keypad '4' key. */
+    public static final int KEYCODE_NUMPAD_4        = 148;
+    /** Key code constant: Numeric keypad '5' key. */
+    public static final int KEYCODE_NUMPAD_5        = 149;
+    /** Key code constant: Numeric keypad '6' key. */
+    public static final int KEYCODE_NUMPAD_6        = 150;
+    /** Key code constant: Numeric keypad '7' key. */
+    public static final int KEYCODE_NUMPAD_7        = 151;
+    /** Key code constant: Numeric keypad '8' key. */
+    public static final int KEYCODE_NUMPAD_8        = 152;
+    /** Key code constant: Numeric keypad '9' key. */
+    public static final int KEYCODE_NUMPAD_9        = 153;
+    /** Key code constant: Numeric keypad '/' key (for division). */
+    public static final int KEYCODE_NUMPAD_DIVIDE   = 154;
+    /** Key code constant: Numeric keypad '*' key (for multiplication). */
+    public static final int KEYCODE_NUMPAD_MULTIPLY = 155;
+    /** Key code constant: Numeric keypad '-' key (for subtraction). */
+    public static final int KEYCODE_NUMPAD_SUBTRACT = 156;
+    /** Key code constant: Numeric keypad '+' key (for addition). */
+    public static final int KEYCODE_NUMPAD_ADD      = 157;
+    /** Key code constant: Numeric keypad '.' key (for decimals or digit grouping). */
+    public static final int KEYCODE_NUMPAD_DOT      = 158;
+    /** Key code constant: Numeric keypad ',' key (for decimals or digit grouping). */
+    public static final int KEYCODE_NUMPAD_COMMA    = 159;
+    /** Key code constant: Numeric keypad Enter key. */
+    public static final int KEYCODE_NUMPAD_ENTER    = 160;
+    /** Key code constant: Numeric keypad '=' key. */
+    public static final int KEYCODE_NUMPAD_EQUALS   = 161;
+    /** Key code constant: Numeric keypad '(' key. */
+    public static final int KEYCODE_NUMPAD_LEFT_PAREN = 162;
+    /** Key code constant: Numeric keypad ')' key. */
+    public static final int KEYCODE_NUMPAD_RIGHT_PAREN = 163;
+    /** Key code constant: Volume Mute key.
+     * Mutes the speaker, unlike {@link #KEYCODE_MUTE}.
+     * This key should normally be implemented as a toggle such that the first press
+     * mutes the speaker and the second press restores the original volume. */
+    public static final int KEYCODE_VOLUME_MUTE     = 164;
+    /** Key code constant: Info key.
+     * Common on TV remotes to show additional information related to what is
+     * currently being viewed. */
+    public static final int KEYCODE_INFO            = 165;
+    /** Key code constant: Channel up key.
+     * On TV remotes, increments the television channel. */
+    public static final int KEYCODE_CHANNEL_UP      = 166;
+    /** Key code constant: Channel down key.
+     * On TV remotes, decrements the television channel. */
+    public static final int KEYCODE_CHANNEL_DOWN    = 167;
+    /** Key code constant: Zoom in key. */
+    public static final int KEYCODE_ZOOM_IN         = 168;
+    /** Key code constant: Zoom out key. */
+    public static final int KEYCODE_ZOOM_OUT        = 169;
+    /** Key code constant: TV key.
+     * On TV remotes, switches to viewing live TV. */
+    public static final int KEYCODE_TV              = 170;
+    /** Key code constant: Window key.
+     * On TV remotes, toggles picture-in-picture mode or other windowing functions.
+     * On Android Wear devices, triggers a display offset. */
+    public static final int KEYCODE_WINDOW          = 171;
+    /** Key code constant: Guide key.
+     * On TV remotes, shows a programming guide. */
+    public static final int KEYCODE_GUIDE           = 172;
+    /** Key code constant: DVR key.
+     * On some TV remotes, switches to a DVR mode for recorded shows. */
+    public static final int KEYCODE_DVR             = 173;
+    /** Key code constant: Bookmark key.
+     * On some TV remotes, bookmarks content or web pages. */
+    public static final int KEYCODE_BOOKMARK        = 174;
+    /** Key code constant: Toggle captions key.
+     * Switches the mode for closed-captioning text, for example during television shows. */
+    public static final int KEYCODE_CAPTIONS        = 175;
+    /** Key code constant: Settings key.
+     * Starts the system settings activity. */
+    public static final int KEYCODE_SETTINGS        = 176;
+    /**
+     * Key code constant: TV power key.
+     * On HDMI TV panel devices and Android TV devices that don't support HDMI, toggles the power
+     * state of the device.
+     * On HDMI source devices, toggles the power state of the HDMI-connected TV via HDMI-CEC and
+     * makes the source device follow this power state.
+     */
+    public static final int KEYCODE_TV_POWER        = 177;
+    /** Key code constant: TV input key.
+     * On TV remotes, switches the input on a television screen. */
+    public static final int KEYCODE_TV_INPUT        = 178;
+    /** Key code constant: Set-top-box power key.
+     * On TV remotes, toggles the power on an external Set-top-box. */
+    public static final int KEYCODE_STB_POWER       = 179;
+    /** Key code constant: Set-top-box input key.
+     * On TV remotes, switches the input mode on an external Set-top-box. */
+    public static final int KEYCODE_STB_INPUT       = 180;
+    /** Key code constant: A/V Receiver power key.
+     * On TV remotes, toggles the power on an external A/V Receiver. */
+    public static final int KEYCODE_AVR_POWER       = 181;
+    /** Key code constant: A/V Receiver input key.
+     * On TV remotes, switches the input mode on an external A/V Receiver. */
+    public static final int KEYCODE_AVR_INPUT       = 182;
+    /** Key code constant: Red "programmable" key.
+     * On TV remotes, acts as a contextual/programmable key. */
+    public static final int KEYCODE_PROG_RED        = 183;
+    /** Key code constant: Green "programmable" key.
+     * On TV remotes, actsas a contextual/programmable key. */
+    public static final int KEYCODE_PROG_GREEN      = 184;
+    /** Key code constant: Yellow "programmable" key.
+     * On TV remotes, acts as a contextual/programmable key. */
+    public static final int KEYCODE_PROG_YELLOW     = 185;
+    /** Key code constant: Blue "programmable" key.
+     * On TV remotes, acts as a contextual/programmable key. */
+    public static final int KEYCODE_PROG_BLUE       = 186;
+    /** Key code constant: App switch key.
+     * Should bring up the application switcher dialog. */
+    public static final int KEYCODE_APP_SWITCH      = 187;
+    /** Key code constant: Generic Game Pad Button #1.*/
+    public static final int KEYCODE_BUTTON_1        = 188;
+    /** Key code constant: Generic Game Pad Button #2.*/
+    public static final int KEYCODE_BUTTON_2        = 189;
+    /** Key code constant: Generic Game Pad Button #3.*/
+    public static final int KEYCODE_BUTTON_3        = 190;
+    /** Key code constant: Generic Game Pad Button #4.*/
+    public static final int KEYCODE_BUTTON_4        = 191;
+    /** Key code constant: Generic Game Pad Button #5.*/
+    public static final int KEYCODE_BUTTON_5        = 192;
+    /** Key code constant: Generic Game Pad Button #6.*/
+    public static final int KEYCODE_BUTTON_6        = 193;
+    /** Key code constant: Generic Game Pad Button #7.*/
+    public static final int KEYCODE_BUTTON_7        = 194;
+    /** Key code constant: Generic Game Pad Button #8.*/
+    public static final int KEYCODE_BUTTON_8        = 195;
+    /** Key code constant: Generic Game Pad Button #9.*/
+    public static final int KEYCODE_BUTTON_9        = 196;
+    /** Key code constant: Generic Game Pad Button #10.*/
+    public static final int KEYCODE_BUTTON_10       = 197;
+    /** Key code constant: Generic Game Pad Button #11.*/
+    public static final int KEYCODE_BUTTON_11       = 198;
+    /** Key code constant: Generic Game Pad Button #12.*/
+    public static final int KEYCODE_BUTTON_12       = 199;
+    /** Key code constant: Generic Game Pad Button #13.*/
+    public static final int KEYCODE_BUTTON_13       = 200;
+    /** Key code constant: Generic Game Pad Button #14.*/
+    public static final int KEYCODE_BUTTON_14       = 201;
+    /** Key code constant: Generic Game Pad Button #15.*/
+    public static final int KEYCODE_BUTTON_15       = 202;
+    /** Key code constant: Generic Game Pad Button #16.*/
+    public static final int KEYCODE_BUTTON_16       = 203;
+    /** Key code constant: Language Switch key.
+     * Toggles the current input language such as switching between English and Japanese on
+     * a QWERTY keyboard.  On some devices, the same function may be performed by
+     * pressing Shift+Spacebar. */
+    public static final int KEYCODE_LANGUAGE_SWITCH = 204;
+    /** Key code constant: Manner Mode key.
+     * Toggles silent or vibrate mode on and off to make the device behave more politely
+     * in certain settings such as on a crowded train.  On some devices, the key may only
+     * operate when long-pressed. */
+    public static final int KEYCODE_MANNER_MODE     = 205;
+    /** Key code constant: 3D Mode key.
+     * Toggles the display between 2D and 3D mode. */
+    public static final int KEYCODE_3D_MODE         = 206;
+    /** Key code constant: Contacts special function key.
+     * Used to launch an address book application. */
+    public static final int KEYCODE_CONTACTS        = 207;
+    /** Key code constant: Calendar special function key.
+     * Used to launch a calendar application. */
+    public static final int KEYCODE_CALENDAR        = 208;
+    /** Key code constant: Music special function key.
+     * Used to launch a music player application. */
+    public static final int KEYCODE_MUSIC           = 209;
+    /** Key code constant: Calculator special function key.
+     * Used to launch a calculator application. */
+    public static final int KEYCODE_CALCULATOR      = 210;
+    /** Key code constant: Japanese full-width / half-width key. */
+    public static final int KEYCODE_ZENKAKU_HANKAKU = 211;
+    /** Key code constant: Japanese alphanumeric key. */
+    public static final int KEYCODE_EISU            = 212;
+    /** Key code constant: Japanese non-conversion key. */
+    public static final int KEYCODE_MUHENKAN        = 213;
+    /** Key code constant: Japanese conversion key. */
+    public static final int KEYCODE_HENKAN          = 214;
+    /** Key code constant: Japanese katakana / hiragana key. */
+    public static final int KEYCODE_KATAKANA_HIRAGANA = 215;
+    /** Key code constant: Japanese Yen key. */
+    public static final int KEYCODE_YEN             = 216;
+    /** Key code constant: Japanese Ro key. */
+    public static final int KEYCODE_RO              = 217;
+    /** Key code constant: Japanese kana key. */
+    public static final int KEYCODE_KANA            = 218;
+    /** Key code constant: Assist key.
+     * Launches the global assist activity.  Not delivered to applications. */
+    public static final int KEYCODE_ASSIST          = 219;
+    /** Key code constant: Brightness Down key.
+     * Adjusts the screen brightness down. */
+    public static final int KEYCODE_BRIGHTNESS_DOWN = 220;
+    /** Key code constant: Brightness Up key.
+     * Adjusts the screen brightness up. */
+    public static final int KEYCODE_BRIGHTNESS_UP   = 221;
+    /** Key code constant: Audio Track key.
+     * Switches the audio tracks. */
+    public static final int KEYCODE_MEDIA_AUDIO_TRACK = 222;
+    /** Key code constant: Sleep key.
+     * Puts the device to sleep.  Behaves somewhat like {@link #KEYCODE_POWER} but it
+     * has no effect if the device is already asleep. */
+    public static final int KEYCODE_SLEEP           = 223;
+    /** Key code constant: Wakeup key.
+     * Wakes up the device.  Behaves somewhat like {@link #KEYCODE_POWER} but it
+     * has no effect if the device is already awake. */
+    public static final int KEYCODE_WAKEUP          = 224;
+    /** Key code constant: Pairing key.
+     * Initiates peripheral pairing mode. Useful for pairing remote control
+     * devices or game controllers, especially if no other input mode is
+     * available. */
+    public static final int KEYCODE_PAIRING         = 225;
+    /** Key code constant: Media Top Menu key.
+     * Goes to the top of media menu. */
+    public static final int KEYCODE_MEDIA_TOP_MENU  = 226;
+    /** Key code constant: '11' key. */
+    public static final int KEYCODE_11              = 227;
+    /** Key code constant: '12' key. */
+    public static final int KEYCODE_12              = 228;
+    /** Key code constant: Last Channel key.
+     * Goes to the last viewed channel. */
+    public static final int KEYCODE_LAST_CHANNEL    = 229;
+    /** Key code constant: TV data service key.
+     * Displays data services like weather, sports. */
+    public static final int KEYCODE_TV_DATA_SERVICE = 230;
+    /** Key code constant: Voice Assist key.
+     * Launches the global voice assist activity. Not delivered to applications. */
+    public static final int KEYCODE_VOICE_ASSIST = 231;
+    /** Key code constant: Radio key.
+     * Toggles TV service / Radio service. */
+    public static final int KEYCODE_TV_RADIO_SERVICE = 232;
+    /** Key code constant: Teletext key.
+     * Displays Teletext service. */
+    public static final int KEYCODE_TV_TELETEXT = 233;
+    /** Key code constant: Number entry key.
+     * Initiates to enter multi-digit channel nubmber when each digit key is assigned
+     * for selecting separate channel. Corresponds to Number Entry Mode (0x1D) of CEC
+     * User Control Code. */
+    public static final int KEYCODE_TV_NUMBER_ENTRY = 234;
+    /** Key code constant: Analog Terrestrial key.
+     * Switches to analog terrestrial broadcast service. */
+    public static final int KEYCODE_TV_TERRESTRIAL_ANALOG = 235;
+    /** Key code constant: Digital Terrestrial key.
+     * Switches to digital terrestrial broadcast service. */
+    public static final int KEYCODE_TV_TERRESTRIAL_DIGITAL = 236;
+    /** Key code constant: Satellite key.
+     * Switches to digital satellite broadcast service. */
+    public static final int KEYCODE_TV_SATELLITE = 237;
+    /** Key code constant: BS key.
+     * Switches to BS digital satellite broadcasting service available in Japan. */
+    public static final int KEYCODE_TV_SATELLITE_BS = 238;
+    /** Key code constant: CS key.
+     * Switches to CS digital satellite broadcasting service available in Japan. */
+    public static final int KEYCODE_TV_SATELLITE_CS = 239;
+    /** Key code constant: BS/CS key.
+     * Toggles between BS and CS digital satellite services. */
+    public static final int KEYCODE_TV_SATELLITE_SERVICE = 240;
+    /** Key code constant: Toggle Network key.
+     * Toggles selecting broacast services. */
+    public static final int KEYCODE_TV_NETWORK = 241;
+    /** Key code constant: Antenna/Cable key.
+     * Toggles broadcast input source between antenna and cable. */
+    public static final int KEYCODE_TV_ANTENNA_CABLE = 242;
+    /** Key code constant: HDMI #1 key.
+     * Switches to HDMI input #1. */
+    public static final int KEYCODE_TV_INPUT_HDMI_1 = 243;
+    /** Key code constant: HDMI #2 key.
+     * Switches to HDMI input #2. */
+    public static final int KEYCODE_TV_INPUT_HDMI_2 = 244;
+    /** Key code constant: HDMI #3 key.
+     * Switches to HDMI input #3. */
+    public static final int KEYCODE_TV_INPUT_HDMI_3 = 245;
+    /** Key code constant: HDMI #4 key.
+     * Switches to HDMI input #4. */
+    public static final int KEYCODE_TV_INPUT_HDMI_4 = 246;
+    /** Key code constant: Composite #1 key.
+     * Switches to composite video input #1. */
+    public static final int KEYCODE_TV_INPUT_COMPOSITE_1 = 247;
+    /** Key code constant: Composite #2 key.
+     * Switches to composite video input #2. */
+    public static final int KEYCODE_TV_INPUT_COMPOSITE_2 = 248;
+    /** Key code constant: Component #1 key.
+     * Switches to component video input #1. */
+    public static final int KEYCODE_TV_INPUT_COMPONENT_1 = 249;
+    /** Key code constant: Component #2 key.
+     * Switches to component video input #2. */
+    public static final int KEYCODE_TV_INPUT_COMPONENT_2 = 250;
+    /** Key code constant: VGA #1 key.
+     * Switches to VGA (analog RGB) input #1. */
+    public static final int KEYCODE_TV_INPUT_VGA_1 = 251;
+    /** Key code constant: Audio description key.
+     * Toggles audio description off / on. */
+    public static final int KEYCODE_TV_AUDIO_DESCRIPTION = 252;
+    /** Key code constant: Audio description mixing volume up key.
+     * Louden audio description volume as compared with normal audio volume. */
+    public static final int KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP = 253;
+    /** Key code constant: Audio description mixing volume down key.
+     * Lessen audio description volume as compared with normal audio volume. */
+    public static final int KEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN = 254;
+    /** Key code constant: Zoom mode key.
+     * Changes Zoom mode (Normal, Full, Zoom, Wide-zoom, etc.) */
+    public static final int KEYCODE_TV_ZOOM_MODE = 255;
+    /** Key code constant: Contents menu key.
+     * Goes to the title list. Corresponds to Contents Menu (0x0B) of CEC User Control
+     * Code */
+    public static final int KEYCODE_TV_CONTENTS_MENU = 256;
+    /** Key code constant: Media context menu key.
+     * Goes to the context menu of media contents. Corresponds to Media Context-sensitive
+     * Menu (0x11) of CEC User Control Code. */
+    public static final int KEYCODE_TV_MEDIA_CONTEXT_MENU = 257;
+    /** Key code constant: Timer programming key.
+     * Goes to the timer recording menu. Corresponds to Timer Programming (0x54) of
+     * CEC User Control Code. */
+    public static final int KEYCODE_TV_TIMER_PROGRAMMING = 258;
+    /** Key code constant: Help key. */
+    public static final int KEYCODE_HELP = 259;
+    /** Key code constant: Navigate to previous key.
+     * Goes backward by one item in an ordered collection of items. */
+    public static final int KEYCODE_NAVIGATE_PREVIOUS = 260;
+    /** Key code constant: Navigate to next key.
+     * Advances to the next item in an ordered collection of items. */
+    public static final int KEYCODE_NAVIGATE_NEXT   = 261;
+    /** Key code constant: Navigate in key.
+     * Activates the item that currently has focus or expands to the next level of a navigation
+     * hierarchy. */
+    public static final int KEYCODE_NAVIGATE_IN     = 262;
+    /** Key code constant: Navigate out key.
+     * Backs out one level of a navigation hierarchy or collapses the item that currently has
+     * focus. */
+    public static final int KEYCODE_NAVIGATE_OUT    = 263;
+    /** Key code constant: Primary stem key for Wear
+     * Main power/reset button on watch. */
+    public static final int KEYCODE_STEM_PRIMARY = 264;
+    /** Key code constant: Generic stem key 1 for Wear */
+    public static final int KEYCODE_STEM_1 = 265;
+    /** Key code constant: Generic stem key 2 for Wear */
+    public static final int KEYCODE_STEM_2 = 266;
+    /** Key code constant: Generic stem key 3 for Wear */
+    public static final int KEYCODE_STEM_3 = 267;
+    /** Key code constant: Directional Pad Up-Left */
+    public static final int KEYCODE_DPAD_UP_LEFT    = 268;
+    /** Key code constant: Directional Pad Down-Left */
+    public static final int KEYCODE_DPAD_DOWN_LEFT  = 269;
+    /** Key code constant: Directional Pad Up-Right */
+    public static final int KEYCODE_DPAD_UP_RIGHT   = 270;
+    /** Key code constant: Directional Pad Down-Right */
+    public static final int KEYCODE_DPAD_DOWN_RIGHT = 271;
+    /** Key code constant: Skip forward media key. */
+    public static final int KEYCODE_MEDIA_SKIP_FORWARD = 272;
+    /** Key code constant: Skip backward media key. */
+    public static final int KEYCODE_MEDIA_SKIP_BACKWARD = 273;
+    /** Key code constant: Step forward media key.
+     * Steps media forward, one frame at a time. */
+    public static final int KEYCODE_MEDIA_STEP_FORWARD = 274;
+    /** Key code constant: Step backward media key.
+     * Steps media backward, one frame at a time. */
+    public static final int KEYCODE_MEDIA_STEP_BACKWARD = 275;
+    /** Key code constant: put device to sleep unless a wakelock is held. */
+    public static final int KEYCODE_SOFT_SLEEP = 276;
+    /** Key code constant: Cut key. */
+    public static final int KEYCODE_CUT = 277;
+    /** Key code constant: Copy key. */
+    public static final int KEYCODE_COPY = 278;
+    /** Key code constant: Paste key. */
+    public static final int KEYCODE_PASTE = 279;
+    /** Key code constant: Consumed by the system for navigation up */
+    public static final int KEYCODE_SYSTEM_NAVIGATION_UP = 280;
+    /** Key code constant: Consumed by the system for navigation down */
+    public static final int KEYCODE_SYSTEM_NAVIGATION_DOWN = 281;
+    /** Key code constant: Consumed by the system for navigation left*/
+    public static final int KEYCODE_SYSTEM_NAVIGATION_LEFT = 282;
+    /** Key code constant: Consumed by the system for navigation right */
+    public static final int KEYCODE_SYSTEM_NAVIGATION_RIGHT = 283;
+    /** Key code constant: Show all apps */
+    public static final int KEYCODE_ALL_APPS = 284;
+    /** Key code constant: Refresh key. */
+    public static final int KEYCODE_REFRESH = 285;
+    /** Key code constant: Thumbs up key. Apps can use this to let user upvote content. */
+    public static final int KEYCODE_THUMBS_UP = 286;
+    /** Key code constant: Thumbs down key. Apps can use this to let user downvote content. */
+    public static final int KEYCODE_THUMBS_DOWN = 287;
+    /**
+     * Key code constant: Used to switch current {@link android.accounts.Account} that is
+     * consuming content. May be consumed by system to set account globally.
+     */
+    public static final int KEYCODE_PROFILE_SWITCH = 288;
+
+    /**
+     * Integer value of the last KEYCODE. Increases as new keycodes are added to KeyEvent.
+     * @hide
+     */
+    @TestApi
+    public static final int LAST_KEYCODE = KEYCODE_PROFILE_SWITCH;
+
+    // NOTE: If you add a new keycode here you must also add it to:
+    //  isSystem()
+    //  isWakeKey()
+    //  frameworks/native/include/android/keycodes.h
+    //  frameworks/native/include/input/InputEventLabels.h
+    //  frameworks/base/core/res/res/values/attrs.xml
+    //  emulator?
+    //  LAST_KEYCODE
+    //
+    //  Also Android currently does not reserve code ranges for vendor-
+    //  specific key codes.  If you have new key codes to have, you
+    //  MUST contribute a patch to the open source project to define
+    //  those new codes.  This is intended to maintain a consistent
+    //  set of key code definitions across all Android devices.
+
+    // Symbolic names of all metakeys in bit order from least significant to most significant.
+    // Accordingly there are exactly 32 values in this table.
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private static final String[] META_SYMBOLIC_NAMES = new String[] {
+        "META_SHIFT_ON",
+        "META_ALT_ON",
+        "META_SYM_ON",
+        "META_FUNCTION_ON",
+        "META_ALT_LEFT_ON",
+        "META_ALT_RIGHT_ON",
+        "META_SHIFT_LEFT_ON",
+        "META_SHIFT_RIGHT_ON",
+        "META_CAP_LOCKED",
+        "META_ALT_LOCKED",
+        "META_SYM_LOCKED",
+        "0x00000800",
+        "META_CTRL_ON",
+        "META_CTRL_LEFT_ON",
+        "META_CTRL_RIGHT_ON",
+        "0x00008000",
+        "META_META_ON",
+        "META_META_LEFT_ON",
+        "META_META_RIGHT_ON",
+        "0x00080000",
+        "META_CAPS_LOCK_ON",
+        "META_NUM_LOCK_ON",
+        "META_SCROLL_LOCK_ON",
+        "0x00800000",
+        "0x01000000",
+        "0x02000000",
+        "0x04000000",
+        "0x08000000",
+        "0x10000000",
+        "0x20000000",
+        "0x40000000",
+        "0x80000000",
+    };
+
+    private static final String LABEL_PREFIX = "KEYCODE_";
+
+    /**
+     * @deprecated There are now more than MAX_KEYCODE keycodes.
+     * Use {@link #getMaxKeyCode()} instead.
+     */
+    @Deprecated
+    public static final int MAX_KEYCODE             = 84;
+
+    /**
+     * {@link #getAction} value: the key has been pressed down.
+     */
+    public static final int ACTION_DOWN             = 0;
+    /**
+     * {@link #getAction} value: the key has been released.
+     */
+    public static final int ACTION_UP               = 1;
+    /**
+     * @deprecated No longer used by the input system.
+     * {@link #getAction} value: multiple duplicate key events have
+     * occurred in a row, or a complex string is being delivered.  If the
+     * key code is not {@link #KEYCODE_UNKNOWN} then the
+     * {@link #getRepeatCount()} method returns the number of times
+     * the given key code should be executed.
+     * Otherwise, if the key code is {@link #KEYCODE_UNKNOWN}, then
+     * this is a sequence of characters as returned by {@link #getCharacters}.
+     */
+    @Deprecated
+    public static final int ACTION_MULTIPLE         = 2;
+
+    /**
+     * SHIFT key locked in CAPS mode.
+     * Reserved for use by {@link MetaKeyKeyListener} for a published constant in its API.
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public static final int META_CAP_LOCKED = 0x100;
+
+    /**
+     * ALT key locked.
+     * Reserved for use by {@link MetaKeyKeyListener} for a published constant in its API.
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public static final int META_ALT_LOCKED = 0x200;
+
+    /**
+     * SYM key locked.
+     * Reserved for use by {@link MetaKeyKeyListener} for a published constant in its API.
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public static final int META_SYM_LOCKED = 0x400;
+
+    /**
+     * Text is in selection mode.
+     * Reserved for use by {@link MetaKeyKeyListener} for a private unpublished constant
+     * in its API that is currently being retained for legacy reasons.
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public static final int META_SELECTING = 0x800;
+
+    /**
+     * <p>This mask is used to check whether one of the ALT meta keys is pressed.</p>
+     *
+     * @see #isAltPressed()
+     * @see #getMetaState()
+     * @see #KEYCODE_ALT_LEFT
+     * @see #KEYCODE_ALT_RIGHT
+     */
+    public static final int META_ALT_ON = 0x02;
+
+    /**
+     * <p>This mask is used to check whether the left ALT meta key is pressed.</p>
+     *
+     * @see #isAltPressed()
+     * @see #getMetaState()
+     * @see #KEYCODE_ALT_LEFT
+     */
+    public static final int META_ALT_LEFT_ON = 0x10;
+
+    /**
+     * <p>This mask is used to check whether the right the ALT meta key is pressed.</p>
+     *
+     * @see #isAltPressed()
+     * @see #getMetaState()
+     * @see #KEYCODE_ALT_RIGHT
+     */
+    public static final int META_ALT_RIGHT_ON = 0x20;
+
+    /**
+     * <p>This mask is used to check whether one of the SHIFT meta keys is pressed.</p>
+     *
+     * @see #isShiftPressed()
+     * @see #getMetaState()
+     * @see #KEYCODE_SHIFT_LEFT
+     * @see #KEYCODE_SHIFT_RIGHT
+     */
+    public static final int META_SHIFT_ON = 0x1;
+
+    /**
+     * <p>This mask is used to check whether the left SHIFT meta key is pressed.</p>
+     *
+     * @see #isShiftPressed()
+     * @see #getMetaState()
+     * @see #KEYCODE_SHIFT_LEFT
+     */
+    public static final int META_SHIFT_LEFT_ON = 0x40;
+
+    /**
+     * <p>This mask is used to check whether the right SHIFT meta key is pressed.</p>
+     *
+     * @see #isShiftPressed()
+     * @see #getMetaState()
+     * @see #KEYCODE_SHIFT_RIGHT
+     */
+    public static final int META_SHIFT_RIGHT_ON = 0x80;
+
+    /**
+     * <p>This mask is used to check whether the SYM meta key is pressed.</p>
+     *
+     * @see #isSymPressed()
+     * @see #getMetaState()
+     */
+    public static final int META_SYM_ON = 0x4;
+
+    /**
+     * <p>This mask is used to check whether the FUNCTION meta key is pressed.</p>
+     *
+     * @see #isFunctionPressed()
+     * @see #getMetaState()
+     */
+    public static final int META_FUNCTION_ON = 0x8;
+
+    /**
+     * <p>This mask is used to check whether one of the CTRL meta keys is pressed.</p>
+     *
+     * @see #isCtrlPressed()
+     * @see #getMetaState()
+     * @see #KEYCODE_CTRL_LEFT
+     * @see #KEYCODE_CTRL_RIGHT
+     */
+    public static final int META_CTRL_ON = 0x1000;
+
+    /**
+     * <p>This mask is used to check whether the left CTRL meta key is pressed.</p>
+     *
+     * @see #isCtrlPressed()
+     * @see #getMetaState()
+     * @see #KEYCODE_CTRL_LEFT
+     */
+    public static final int META_CTRL_LEFT_ON = 0x2000;
+
+    /**
+     * <p>This mask is used to check whether the right CTRL meta key is pressed.</p>
+     *
+     * @see #isCtrlPressed()
+     * @see #getMetaState()
+     * @see #KEYCODE_CTRL_RIGHT
+     */
+    public static final int META_CTRL_RIGHT_ON = 0x4000;
+
+    /**
+     * <p>This mask is used to check whether one of the META meta keys is pressed.</p>
+     *
+     * @see #isMetaPressed()
+     * @see #getMetaState()
+     * @see #KEYCODE_META_LEFT
+     * @see #KEYCODE_META_RIGHT
+     */
+    public static final int META_META_ON = 0x10000;
+
+    /**
+     * <p>This mask is used to check whether the left META meta key is pressed.</p>
+     *
+     * @see #isMetaPressed()
+     * @see #getMetaState()
+     * @see #KEYCODE_META_LEFT
+     */
+    public static final int META_META_LEFT_ON = 0x20000;
+
+    /**
+     * <p>This mask is used to check whether the right META meta key is pressed.</p>
+     *
+     * @see #isMetaPressed()
+     * @see #getMetaState()
+     * @see #KEYCODE_META_RIGHT
+     */
+    public static final int META_META_RIGHT_ON = 0x40000;
+
+    /**
+     * <p>This mask is used to check whether the CAPS LOCK meta key is on.</p>
+     *
+     * @see #isCapsLockOn()
+     * @see #getMetaState()
+     * @see #KEYCODE_CAPS_LOCK
+     */
+    public static final int META_CAPS_LOCK_ON = 0x100000;
+
+    /**
+     * <p>This mask is used to check whether the NUM LOCK meta key is on.</p>
+     *
+     * @see #isNumLockOn()
+     * @see #getMetaState()
+     * @see #KEYCODE_NUM_LOCK
+     */
+    public static final int META_NUM_LOCK_ON = 0x200000;
+
+    /**
+     * <p>This mask is used to check whether the SCROLL LOCK meta key is on.</p>
+     *
+     * @see #isScrollLockOn()
+     * @see #getMetaState()
+     * @see #KEYCODE_SCROLL_LOCK
+     */
+    public static final int META_SCROLL_LOCK_ON = 0x400000;
+
+    /**
+     * This mask is a combination of {@link #META_SHIFT_ON}, {@link #META_SHIFT_LEFT_ON}
+     * and {@link #META_SHIFT_RIGHT_ON}.
+     */
+    public static final int META_SHIFT_MASK = META_SHIFT_ON
+            | META_SHIFT_LEFT_ON | META_SHIFT_RIGHT_ON;
+
+    /**
+     * This mask is a combination of {@link #META_ALT_ON}, {@link #META_ALT_LEFT_ON}
+     * and {@link #META_ALT_RIGHT_ON}.
+     */
+    public static final int META_ALT_MASK = META_ALT_ON
+            | META_ALT_LEFT_ON | META_ALT_RIGHT_ON;
+
+    /**
+     * This mask is a combination of {@link #META_CTRL_ON}, {@link #META_CTRL_LEFT_ON}
+     * and {@link #META_CTRL_RIGHT_ON}.
+     */
+    public static final int META_CTRL_MASK = META_CTRL_ON
+            | META_CTRL_LEFT_ON | META_CTRL_RIGHT_ON;
+
+    /**
+     * This mask is a combination of {@link #META_META_ON}, {@link #META_META_LEFT_ON}
+     * and {@link #META_META_RIGHT_ON}.
+     */
+    public static final int META_META_MASK = META_META_ON
+            | META_META_LEFT_ON | META_META_RIGHT_ON;
+
+    /**
+     * This mask is set if the device woke because of this key event.
+     *
+     * @deprecated This flag will never be set by the system since the system
+     * consumes all wake keys itself.
+     */
+    @Deprecated
+    public static final int FLAG_WOKE_HERE = 0x1;
+
+    /**
+     * This mask is set if the key event was generated by a software keyboard.
+     */
+    public static final int FLAG_SOFT_KEYBOARD = 0x2;
+
+    /**
+     * This mask is set if we don't want the key event to cause us to leave
+     * touch mode.
+     */
+    public static final int FLAG_KEEP_TOUCH_MODE = 0x4;
+
+    /**
+     * This mask is set if an event was known to come from a trusted part
+     * of the system.  That is, the event is known to come from the user,
+     * and could not have been spoofed by a third party component.
+     */
+    public static final int FLAG_FROM_SYSTEM = 0x8;
+
+    /**
+     * This mask is used for compatibility, to identify enter keys that are
+     * coming from an IME whose enter key has been auto-labelled "next" or
+     * "done".  This allows TextView to dispatch these as normal enter keys
+     * for old applications, but still do the appropriate action when
+     * receiving them.
+     */
+    public static final int FLAG_EDITOR_ACTION = 0x10;
+
+    /**
+     * When associated with up key events, this indicates that the key press
+     * has been canceled.  Typically this is used with virtual touch screen
+     * keys, where the user can slide from the virtual key area on to the
+     * display: in that case, the application will receive a canceled up
+     * event and should not perform the action normally associated with the
+     * key.  Note that for this to work, the application can not perform an
+     * action for a key until it receives an up or the long press timeout has
+     * expired.
+     */
+    public static final int FLAG_CANCELED = 0x20;
+
+    /**
+     * This key event was generated by a virtual (on-screen) hard key area.
+     * Typically this is an area of the touchscreen, outside of the regular
+     * display, dedicated to "hardware" buttons.
+     */
+    public static final int FLAG_VIRTUAL_HARD_KEY = 0x40;
+
+    /**
+     * This flag is set for the first key repeat that occurs after the
+     * long press timeout.
+     */
+    public static final int FLAG_LONG_PRESS = 0x80;
+
+    /**
+     * Set when a key event has {@link #FLAG_CANCELED} set because a long
+     * press action was executed while it was down.
+     */
+    public static final int FLAG_CANCELED_LONG_PRESS = 0x100;
+
+    /**
+     * Set for {@link #ACTION_UP} when this event's key code is still being
+     * tracked from its initial down.  That is, somebody requested that tracking
+     * started on the key down and a long press has not caused
+     * the tracking to be canceled.
+     */
+    public static final int FLAG_TRACKING = 0x200;
+
+    /**
+     * Set when a key event has been synthesized to implement default behavior
+     * for an event that the application did not handle.
+     * Fallback key events are generated by unhandled trackball motions
+     * (to emulate a directional keypad) and by certain unhandled key presses
+     * that are declared in the key map (such as special function numeric keypad
+     * keys when numlock is off).
+     */
+    public static final int FLAG_FALLBACK = 0x400;
+
+    /**
+     * This flag indicates that this event was modified by or generated from an accessibility
+     * service. Value = 0x800
+     * @hide
+     */
+    @TestApi
+    public static final int FLAG_IS_ACCESSIBILITY_EVENT = INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT;
+
+    /**
+     * Signifies that the key is being predispatched.
+     * @hide
+     */
+    public static final int FLAG_PREDISPATCH = 0x20000000;
+
+    /**
+     * Private control to determine when an app is tracking a key sequence.
+     * @hide
+     */
+    public static final int FLAG_START_TRACKING = 0x40000000;
+
+    /**
+     * Private flag that indicates when the system has detected that this key event
+     * may be inconsistent with respect to the sequence of previously delivered key events,
+     * such as when a key up event is sent but the key was not down.
+     *
+     * @hide
+     * @see #isTainted
+     * @see #setTainted
+     */
+    public static final int FLAG_TAINTED = 0x80000000;
+
+    /**
+     * Returns the maximum keycode.
+     */
+    public static int getMaxKeyCode() {
+        return LAST_KEYCODE;
+    }
+
+    /**
+     * Get the character that is produced by putting accent on the character
+     * c.
+     * For example, getDeadChar('`', 'e') returns &egrave;.
+     */
+    public static int getDeadChar(int accent, int c) {
+        return KeyCharacterMap.getDeadChar(accent, c);
+    }
+
+    static final boolean DEBUG = false;
+    static final String TAG = "KeyEvent";
+
+    private static final int MAX_RECYCLED = 10;
+    private static final Object gRecyclerLock = new Object();
+    private static int gRecyclerUsed;
+    private static KeyEvent gRecyclerTop;
+
+    private KeyEvent mNext;
+
+    private int mId;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private int mDeviceId;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    private int mSource;
+    private int mDisplayId;
+    private @Nullable byte[] mHmac;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private int mMetaState;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private int mAction;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private int mKeyCode;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private int mScanCode;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private int mRepeatCount;
+    @UnsupportedAppUsage
+    private int mFlags;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private long mDownTime;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private long mEventTime;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private String mCharacters;
+
+    public interface Callback {
+        /**
+         * Called when a key down event has occurred.  If you return true,
+         * you can first call {@link KeyEvent#startTracking()
+         * KeyEvent.startTracking()} to have the framework track the event
+         * through its {@link #onKeyUp(int, KeyEvent)} and also call your
+         * {@link #onKeyLongPress(int, KeyEvent)} if it occurs.
+         *
+         * @param keyCode The value in event.getKeyCode().
+         * @param event Description of the key event.
+         *
+         * @return If you handled the event, return true.  If you want to allow
+         *         the event to be handled by the next receiver, return false.
+         */
+        boolean onKeyDown(int keyCode, KeyEvent event);
+
+        /**
+         * Called when a long press has occurred.  If you return true,
+         * the final key up will have {@link KeyEvent#FLAG_CANCELED} and
+         * {@link KeyEvent#FLAG_CANCELED_LONG_PRESS} set.  Note that in
+         * order to receive this callback, someone in the event change
+         * <em>must</em> return true from {@link #onKeyDown} <em>and</em>
+         * call {@link KeyEvent#startTracking()} on the event.
+         *
+         * @param keyCode The value in event.getKeyCode().
+         * @param event Description of the key event.
+         *
+         * @return If you handled the event, return true.  If you want to allow
+         *         the event to be handled by the next receiver, return false.
+         */
+        boolean onKeyLongPress(int keyCode, KeyEvent event);
+
+        /**
+         * Called when a key up event has occurred.
+         *
+         * @param keyCode The value in event.getKeyCode().
+         * @param event Description of the key event.
+         *
+         * @return If you handled the event, return true.  If you want to allow
+         *         the event to be handled by the next receiver, return false.
+         */
+        boolean onKeyUp(int keyCode, KeyEvent event);
+
+        /**
+         * Called when a user's interaction with an analog control, such as
+         * flinging a trackball, generates simulated down/up events for the same
+         * key multiple times in quick succession.
+         *
+         * @param keyCode The value in event.getKeyCode().
+         * @param count Number of pairs as returned by event.getRepeatCount().
+         * @param event Description of the key event.
+         *
+         * @return If you handled the event, return true.  If you want to allow
+         *         the event to be handled by the next receiver, return false.
+         */
+        boolean onKeyMultiple(int keyCode, int count, KeyEvent event);
+    }
+
+    private static native String nativeKeyCodeToString(int keyCode);
+    private static native int nativeKeyCodeFromString(String keyCode);
+    private static native int nativeNextId();
+
+    private KeyEvent() {}
+
+    /**
+     * Create a new key event.
+     *
+     * @param action Action code: either {@link #ACTION_DOWN},
+     * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
+     * @param code The key code.
+     */
+    public KeyEvent(int action, int code) {
+        mId = nativeNextId();
+        mAction = action;
+        mKeyCode = code;
+        mRepeatCount = 0;
+        mDeviceId = KeyCharacterMap.VIRTUAL_KEYBOARD;
+    }
+
+    /**
+     * Create a new key event.
+     *
+     * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
+     * at which this key code originally went down.
+     * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
+     * at which this event happened.
+     * @param action Action code: either {@link #ACTION_DOWN},
+     * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
+     * @param code The key code.
+     * @param repeat A repeat count for down events (> 0 if this is after the
+     * initial down) or event count for multiple events.
+     */
+    public KeyEvent(long downTime, long eventTime, int action,
+                    int code, int repeat) {
+        mId = nativeNextId();
+        mDownTime = downTime;
+        mEventTime = eventTime;
+        mAction = action;
+        mKeyCode = code;
+        mRepeatCount = repeat;
+        mDeviceId = KeyCharacterMap.VIRTUAL_KEYBOARD;
+    }
+
+    /**
+     * Create a new key event.
+     *
+     * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
+     * at which this key code originally went down.
+     * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
+     * at which this event happened.
+     * @param action Action code: either {@link #ACTION_DOWN},
+     * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
+     * @param code The key code.
+     * @param repeat A repeat count for down events (> 0 if this is after the
+     * initial down) or event count for multiple events.
+     * @param metaState Flags indicating which meta keys are currently pressed.
+     */
+    public KeyEvent(long downTime, long eventTime, int action,
+                    int code, int repeat, int metaState) {
+        mId = nativeNextId();
+        mDownTime = downTime;
+        mEventTime = eventTime;
+        mAction = action;
+        mKeyCode = code;
+        mRepeatCount = repeat;
+        mMetaState = metaState;
+        mDeviceId = KeyCharacterMap.VIRTUAL_KEYBOARD;
+    }
+
+    /**
+     * Create a new key event.
+     *
+     * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
+     * at which this key code originally went down.
+     * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
+     * at which this event happened.
+     * @param action Action code: either {@link #ACTION_DOWN},
+     * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
+     * @param code The key code.
+     * @param repeat A repeat count for down events (> 0 if this is after the
+     * initial down) or event count for multiple events.
+     * @param metaState Flags indicating which meta keys are currently pressed.
+     * @param deviceId The device ID that generated the key event.
+     * @param scancode Raw device scan code of the event.
+     */
+    public KeyEvent(long downTime, long eventTime, int action,
+                    int code, int repeat, int metaState,
+                    int deviceId, int scancode) {
+        mId = nativeNextId();
+        mDownTime = downTime;
+        mEventTime = eventTime;
+        mAction = action;
+        mKeyCode = code;
+        mRepeatCount = repeat;
+        mMetaState = metaState;
+        mDeviceId = deviceId;
+        mScanCode = scancode;
+    }
+
+    /**
+     * Create a new key event.
+     *
+     * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
+     * at which this key code originally went down.
+     * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
+     * at which this event happened.
+     * @param action Action code: either {@link #ACTION_DOWN},
+     * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
+     * @param code The key code.
+     * @param repeat A repeat count for down events (> 0 if this is after the
+     * initial down) or event count for multiple events.
+     * @param metaState Flags indicating which meta keys are currently pressed.
+     * @param deviceId The device ID that generated the key event.
+     * @param scancode Raw device scan code of the event.
+     * @param flags The flags for this key event
+     */
+    public KeyEvent(long downTime, long eventTime, int action,
+                    int code, int repeat, int metaState,
+                    int deviceId, int scancode, int flags) {
+        mId = nativeNextId();
+        mDownTime = downTime;
+        mEventTime = eventTime;
+        mAction = action;
+        mKeyCode = code;
+        mRepeatCount = repeat;
+        mMetaState = metaState;
+        mDeviceId = deviceId;
+        mScanCode = scancode;
+        mFlags = flags;
+    }
+
+    /**
+     * Create a new key event.
+     *
+     * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
+     * at which this key code originally went down.
+     * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
+     * at which this event happened.
+     * @param action Action code: either {@link #ACTION_DOWN},
+     * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
+     * @param code The key code.
+     * @param repeat A repeat count for down events (> 0 if this is after the
+     * initial down) or event count for multiple events.
+     * @param metaState Flags indicating which meta keys are currently pressed.
+     * @param deviceId The device ID that generated the key event.
+     * @param scancode Raw device scan code of the event.
+     * @param flags The flags for this key event
+     * @param source The input source such as {@link InputDevice#SOURCE_KEYBOARD}.
+     */
+    public KeyEvent(long downTime, long eventTime, int action,
+                    int code, int repeat, int metaState,
+                    int deviceId, int scancode, int flags, int source) {
+        mId = nativeNextId();
+        mDownTime = downTime;
+        mEventTime = eventTime;
+        mAction = action;
+        mKeyCode = code;
+        mRepeatCount = repeat;
+        mMetaState = metaState;
+        mDeviceId = deviceId;
+        mScanCode = scancode;
+        mFlags = flags;
+        mSource = source;
+        mDisplayId = INVALID_DISPLAY;
+    }
+
+    /**
+     * Create a new key event for a string of characters.  The key code,
+     * action, repeat count and source will automatically be set to
+     * {@link #KEYCODE_UNKNOWN}, {@link #ACTION_MULTIPLE}, 0, and
+     * {@link InputDevice#SOURCE_KEYBOARD} for you.
+     *
+     * @param time The time (in {@link android.os.SystemClock#uptimeMillis})
+     * at which this event occured.
+     * @param characters The string of characters.
+     * @param deviceId The device ID that generated the key event.
+     * @param flags The flags for this key event
+     */
+    public KeyEvent(long time, String characters, int deviceId, int flags) {
+        mId = nativeNextId();
+        mDownTime = time;
+        mEventTime = time;
+        mCharacters = characters;
+        mAction = ACTION_MULTIPLE;
+        mKeyCode = KEYCODE_UNKNOWN;
+        mRepeatCount = 0;
+        mDeviceId = deviceId;
+        mFlags = flags;
+        mSource = InputDevice.SOURCE_KEYBOARD;
+        mDisplayId = INVALID_DISPLAY;
+    }
+
+    /**
+     * Make an exact copy of an existing key event.
+     */
+    public KeyEvent(KeyEvent origEvent) {
+        mId = origEvent.mId;
+        mDownTime = origEvent.mDownTime;
+        mEventTime = origEvent.mEventTime;
+        mAction = origEvent.mAction;
+        mKeyCode = origEvent.mKeyCode;
+        mRepeatCount = origEvent.mRepeatCount;
+        mMetaState = origEvent.mMetaState;
+        mDeviceId = origEvent.mDeviceId;
+        mSource = origEvent.mSource;
+        mDisplayId = origEvent.mDisplayId;
+        mHmac = origEvent.mHmac == null ? null : origEvent.mHmac.clone();
+        mScanCode = origEvent.mScanCode;
+        mFlags = origEvent.mFlags;
+        mCharacters = origEvent.mCharacters;
+    }
+
+    /**
+     * Copy an existing key event, modifying its time and repeat count.
+     *
+     * @deprecated Use {@link #changeTimeRepeat(KeyEvent, long, int)}
+     * instead.
+     *
+     * @param origEvent The existing event to be copied.
+     * @param eventTime The new event time
+     * (in {@link android.os.SystemClock#uptimeMillis}) of the event.
+     * @param newRepeat The new repeat count of the event.
+     */
+    @Deprecated
+    public KeyEvent(KeyEvent origEvent, long eventTime, int newRepeat) {
+        mId = nativeNextId();  // Not an exact copy so assign a new ID.
+        mDownTime = origEvent.mDownTime;
+        mEventTime = eventTime;
+        mAction = origEvent.mAction;
+        mKeyCode = origEvent.mKeyCode;
+        mRepeatCount = newRepeat;
+        mMetaState = origEvent.mMetaState;
+        mDeviceId = origEvent.mDeviceId;
+        mSource = origEvent.mSource;
+        mDisplayId = origEvent.mDisplayId;
+        mHmac = null; // Don't copy HMAC, it will be invalid because eventTime is changing
+        mScanCode = origEvent.mScanCode;
+        mFlags = origEvent.mFlags;
+        mCharacters = origEvent.mCharacters;
+    }
+
+    private static KeyEvent obtain() {
+        final KeyEvent ev;
+        synchronized (gRecyclerLock) {
+            ev = gRecyclerTop;
+            if (ev == null) {
+                return new KeyEvent();
+            }
+            gRecyclerTop = ev.mNext;
+            gRecyclerUsed -= 1;
+        }
+        ev.mNext = null;
+        ev.prepareForReuse();
+        return ev;
+    }
+
+    /**
+     * Obtains a (potentially recycled) key event. Used by native code to create a Java object.
+     *
+     * @hide
+     */
+    public static KeyEvent obtain(int id, long downTime, long eventTime, int action,
+            int code, int repeat, int metaState,
+            int deviceId, int scancode, int flags, int source, int displayId, @Nullable byte[] hmac,
+            String characters) {
+        KeyEvent ev = obtain();
+        ev.mId = id;
+        ev.mDownTime = downTime;
+        ev.mEventTime = eventTime;
+        ev.mAction = action;
+        ev.mKeyCode = code;
+        ev.mRepeatCount = repeat;
+        ev.mMetaState = metaState;
+        ev.mDeviceId = deviceId;
+        ev.mScanCode = scancode;
+        ev.mFlags = flags;
+        ev.mSource = source;
+        ev.mDisplayId = displayId;
+        ev.mHmac = hmac;
+        ev.mCharacters = characters;
+        return ev;
+    }
+
+    /**
+     * Obtains a (potentially recycled) key event.
+     *
+     * @hide
+     */
+    public static KeyEvent obtain(long downTime, long eventTime, int action,
+            int code, int repeat, int metaState,
+            int deviceId, int scanCode, int flags, int source, int displayId, String characters) {
+        return obtain(nativeNextId(), downTime, eventTime, action, code, repeat, metaState,
+                deviceId, scanCode, flags, source, displayId, null /* hmac */, characters);
+    }
+
+    /**
+     * Obtains a (potentially recycled) key event.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public static KeyEvent obtain(long downTime, long eventTime, int action,
+            int code, int repeat, int metaState,
+            int deviceId, int scancode, int flags, int source, String characters) {
+        return obtain(downTime, eventTime, action, code, repeat, metaState, deviceId, scancode,
+                flags, source, INVALID_DISPLAY, characters);
+    }
+
+    /**
+
+    /**
+     * Obtains a (potentially recycled) copy of another key event.
+     *
+     * @hide
+     */
+    public static KeyEvent obtain(KeyEvent other) {
+        KeyEvent ev = obtain();
+        ev.mId = other.mId;
+        ev.mDownTime = other.mDownTime;
+        ev.mEventTime = other.mEventTime;
+        ev.mAction = other.mAction;
+        ev.mKeyCode = other.mKeyCode;
+        ev.mRepeatCount = other.mRepeatCount;
+        ev.mMetaState = other.mMetaState;
+        ev.mDeviceId = other.mDeviceId;
+        ev.mScanCode = other.mScanCode;
+        ev.mFlags = other.mFlags;
+        ev.mSource = other.mSource;
+        ev.mDisplayId = other.mDisplayId;
+        ev.mHmac = other.mHmac == null ? null : other.mHmac.clone();
+        ev.mCharacters = other.mCharacters;
+        return ev;
+    }
+
+    /** @hide */
+    @Override
+    public KeyEvent copy() {
+        return obtain(this);
+    }
+
+    /**
+     * Recycles a key event.
+     * Key events should only be recycled if they are owned by the system since user
+     * code expects them to be essentially immutable, "tracking" notwithstanding.
+     *
+     * @hide
+     */
+    @Override
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    public final void recycle() {
+        super.recycle();
+        mCharacters = null;
+
+        synchronized (gRecyclerLock) {
+            if (gRecyclerUsed < MAX_RECYCLED) {
+                gRecyclerUsed++;
+                mNext = gRecyclerTop;
+                gRecyclerTop = this;
+            }
+        }
+    }
+
+    /** @hide */
+    @Override
+    public final void recycleIfNeededAfterDispatch() {
+        // Do nothing.
+    }
+
+    /** @hide */
+    @Override
+    public int getId() {
+        return mId;
+    }
+
+    /**
+     * Create a new key event that is the same as the given one, but whose
+     * event time and repeat count are replaced with the given value.
+     *
+     * @param event The existing event to be copied.  This is not modified.
+     * @param eventTime The new event time
+     * (in {@link android.os.SystemClock#uptimeMillis}) of the event.
+     * @param newRepeat The new repeat count of the event.
+     */
+    public static KeyEvent changeTimeRepeat(KeyEvent event, long eventTime,
+            int newRepeat) {
+        return new KeyEvent(event, eventTime, newRepeat);
+    }
+
+    /**
+     * Create a new key event that is the same as the given one, but whose
+     * event time and repeat count are replaced with the given value.
+     *
+     * @param event The existing event to be copied.  This is not modified.
+     * @param eventTime The new event time
+     * (in {@link android.os.SystemClock#uptimeMillis}) of the event.
+     * @param newRepeat The new repeat count of the event.
+     * @param newFlags New flags for the event, replacing the entire value
+     * in the original event.
+     */
+    public static KeyEvent changeTimeRepeat(KeyEvent event, long eventTime,
+            int newRepeat, int newFlags) {
+        KeyEvent ret = new KeyEvent(event);
+        ret.mId = nativeNextId();  // Not an exact copy so assign a new ID.
+        ret.mEventTime = eventTime;
+        ret.mRepeatCount = newRepeat;
+        ret.mFlags = newFlags;
+        return ret;
+    }
+
+    /**
+     * Copy an existing key event, modifying its action.
+     *
+     * @param origEvent The existing event to be copied.
+     * @param action The new action code of the event.
+     */
+    private KeyEvent(KeyEvent origEvent, int action) {
+        mId = nativeNextId();  // Not an exact copy so assign a new ID.
+        mDownTime = origEvent.mDownTime;
+        mEventTime = origEvent.mEventTime;
+        mAction = action;
+        mKeyCode = origEvent.mKeyCode;
+        mRepeatCount = origEvent.mRepeatCount;
+        mMetaState = origEvent.mMetaState;
+        mDeviceId = origEvent.mDeviceId;
+        mSource = origEvent.mSource;
+        mDisplayId = origEvent.mDisplayId;
+        mHmac = null; // Don't copy the hmac, it will be invalid since action is changing
+        mScanCode = origEvent.mScanCode;
+        mFlags = origEvent.mFlags;
+        // Don't copy mCharacters, since one way or the other we'll lose it
+        // when changing the action.
+    }
+
+    /**
+     * Create a new key event that is the same as the given one, but whose
+     * action is replaced with the given value.
+     *
+     * @param event The existing event to be copied.  This is not modified.
+     * @param action The new action code of the event.
+     */
+    public static KeyEvent changeAction(KeyEvent event, int action) {
+        return new KeyEvent(event, action);
+    }
+
+    /**
+     * Create a new key event that is the same as the given one, but whose
+     * flags are replaced with the given value.
+     *
+     * @param event The existing event to be copied.  This is not modified.
+     * @param flags The new flags constant.
+     */
+    public static KeyEvent changeFlags(KeyEvent event, int flags) {
+        event = new KeyEvent(event);
+        event.mId = nativeNextId();  // Not an exact copy so assign a new ID.
+        event.mFlags = flags;
+        return event;
+    }
+
+    /** @hide */
+    @Override
+    public final boolean isTainted() {
+        return (mFlags & FLAG_TAINTED) != 0;
+    }
+
+    /** @hide */
+    @Override
+    public final void setTainted(boolean tainted) {
+        mFlags = tainted ? mFlags | FLAG_TAINTED : mFlags & ~FLAG_TAINTED;
+    }
+
+    /**
+     * Don't use in new code, instead explicitly check
+     * {@link #getAction()}.
+     *
+     * @return If the action is ACTION_DOWN, returns true; else false.
+     *
+     * @deprecated
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @Deprecated public final boolean isDown() {
+        return mAction == ACTION_DOWN;
+    }
+
+    /** Is this a system key?  System keys can not be used for menu shortcuts.
+     */
+    public final boolean isSystem() {
+        return isSystemKey(mKeyCode);
+    }
+
+    /** @hide */
+    public final boolean isWakeKey() {
+        return isWakeKey(mKeyCode);
+    }
+
+    /**
+     * Returns true if the specified keycode is a gamepad button.
+     * @return True if the keycode is a gamepad button, such as {@link #KEYCODE_BUTTON_A}.
+     */
+    public static final boolean isGamepadButton(int keyCode) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_BUTTON_A:
+            case KeyEvent.KEYCODE_BUTTON_B:
+            case KeyEvent.KEYCODE_BUTTON_C:
+            case KeyEvent.KEYCODE_BUTTON_X:
+            case KeyEvent.KEYCODE_BUTTON_Y:
+            case KeyEvent.KEYCODE_BUTTON_Z:
+            case KeyEvent.KEYCODE_BUTTON_L1:
+            case KeyEvent.KEYCODE_BUTTON_R1:
+            case KeyEvent.KEYCODE_BUTTON_L2:
+            case KeyEvent.KEYCODE_BUTTON_R2:
+            case KeyEvent.KEYCODE_BUTTON_THUMBL:
+            case KeyEvent.KEYCODE_BUTTON_THUMBR:
+            case KeyEvent.KEYCODE_BUTTON_START:
+            case KeyEvent.KEYCODE_BUTTON_SELECT:
+            case KeyEvent.KEYCODE_BUTTON_MODE:
+            case KeyEvent.KEYCODE_BUTTON_1:
+            case KeyEvent.KEYCODE_BUTTON_2:
+            case KeyEvent.KEYCODE_BUTTON_3:
+            case KeyEvent.KEYCODE_BUTTON_4:
+            case KeyEvent.KEYCODE_BUTTON_5:
+            case KeyEvent.KEYCODE_BUTTON_6:
+            case KeyEvent.KEYCODE_BUTTON_7:
+            case KeyEvent.KEYCODE_BUTTON_8:
+            case KeyEvent.KEYCODE_BUTTON_9:
+            case KeyEvent.KEYCODE_BUTTON_10:
+            case KeyEvent.KEYCODE_BUTTON_11:
+            case KeyEvent.KEYCODE_BUTTON_12:
+            case KeyEvent.KEYCODE_BUTTON_13:
+            case KeyEvent.KEYCODE_BUTTON_14:
+            case KeyEvent.KEYCODE_BUTTON_15:
+            case KeyEvent.KEYCODE_BUTTON_16:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /** Whether key will, by default, trigger a click on the focused view.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final boolean isConfirmKey(int keyCode) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+            case KeyEvent.KEYCODE_ENTER:
+            case KeyEvent.KEYCODE_SPACE:
+            case KeyEvent.KEYCODE_NUMPAD_ENTER:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Returns whether this key will be sent to the
+     * {@link android.media.session.MediaSession.Callback} if not handled.
+     */
+    public static final boolean isMediaSessionKey(int keyCode) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_MEDIA_PLAY:
+            case KeyEvent.KEYCODE_MEDIA_PAUSE:
+            case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+            case KeyEvent.KEYCODE_MUTE:
+            case KeyEvent.KEYCODE_HEADSETHOOK:
+            case KeyEvent.KEYCODE_MEDIA_STOP:
+            case KeyEvent.KEYCODE_MEDIA_NEXT:
+            case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+            case KeyEvent.KEYCODE_MEDIA_REWIND:
+            case KeyEvent.KEYCODE_MEDIA_RECORD:
+            case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+                return true;
+        }
+        return false;
+    }
+
+    /** Is this a system key? System keys can not be used for menu shortcuts.
+     * @hide
+     */
+    public static final boolean isSystemKey(int keyCode) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_MENU:
+            case KeyEvent.KEYCODE_SOFT_RIGHT:
+            case KeyEvent.KEYCODE_HOME:
+            case KeyEvent.KEYCODE_BACK:
+            case KeyEvent.KEYCODE_CALL:
+            case KeyEvent.KEYCODE_ENDCALL:
+            case KeyEvent.KEYCODE_VOLUME_UP:
+            case KeyEvent.KEYCODE_VOLUME_DOWN:
+            case KeyEvent.KEYCODE_VOLUME_MUTE:
+            case KeyEvent.KEYCODE_MUTE:
+            case KeyEvent.KEYCODE_POWER:
+            case KeyEvent.KEYCODE_HEADSETHOOK:
+            case KeyEvent.KEYCODE_MEDIA_PLAY:
+            case KeyEvent.KEYCODE_MEDIA_PAUSE:
+            case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+            case KeyEvent.KEYCODE_MEDIA_STOP:
+            case KeyEvent.KEYCODE_MEDIA_NEXT:
+            case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+            case KeyEvent.KEYCODE_MEDIA_REWIND:
+            case KeyEvent.KEYCODE_MEDIA_RECORD:
+            case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+            case KeyEvent.KEYCODE_CAMERA:
+            case KeyEvent.KEYCODE_FOCUS:
+            case KeyEvent.KEYCODE_SEARCH:
+            case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
+            case KeyEvent.KEYCODE_BRIGHTNESS_UP:
+            case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK:
+            case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP:
+            case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN:
+            case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT:
+            case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT:
+                return true;
+        }
+
+        return false;
+    }
+
+    /** @hide */
+    public static final boolean isWakeKey(int keyCode) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_CAMERA:
+            case KeyEvent.KEYCODE_MENU:
+            case KeyEvent.KEYCODE_PAIRING:
+            case KeyEvent.KEYCODE_STEM_1:
+            case KeyEvent.KEYCODE_STEM_2:
+            case KeyEvent.KEYCODE_STEM_3:
+            case KeyEvent.KEYCODE_WAKEUP:
+                return true;
+        }
+        return false;
+    }
+
+    /** @hide */
+    public static final boolean isMetaKey(int keyCode) {
+        return keyCode == KeyEvent.KEYCODE_META_LEFT || keyCode == KeyEvent.KEYCODE_META_RIGHT;
+    }
+
+    /** @hide */
+    public static final boolean isAltKey(int keyCode) {
+        return keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final int getDeviceId() {
+        return mDeviceId;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final int getSource() {
+        return mSource;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final void setSource(int source) {
+        mSource = source;
+    }
+
+    /** @hide */
+    @Override
+    public final int getDisplayId() {
+        return mDisplayId;
+    }
+
+    /** @hide */
+    @TestApi
+    @Override
+    public final void setDisplayId(int displayId) {
+        mDisplayId = displayId;
+    }
+
+    /**
+     * <p>Returns the state of the meta keys.</p>
+     *
+     * @return an integer in which each bit set to 1 represents a pressed
+     *         meta key
+     *
+     * @see #isAltPressed()
+     * @see #isShiftPressed()
+     * @see #isSymPressed()
+     * @see #isCtrlPressed()
+     * @see #isMetaPressed()
+     * @see #isFunctionPressed()
+     * @see #isCapsLockOn()
+     * @see #isNumLockOn()
+     * @see #isScrollLockOn()
+     * @see #META_ALT_ON
+     * @see #META_ALT_LEFT_ON
+     * @see #META_ALT_RIGHT_ON
+     * @see #META_SHIFT_ON
+     * @see #META_SHIFT_LEFT_ON
+     * @see #META_SHIFT_RIGHT_ON
+     * @see #META_SYM_ON
+     * @see #META_FUNCTION_ON
+     * @see #META_CTRL_ON
+     * @see #META_CTRL_LEFT_ON
+     * @see #META_CTRL_RIGHT_ON
+     * @see #META_META_ON
+     * @see #META_META_LEFT_ON
+     * @see #META_META_RIGHT_ON
+     * @see #META_CAPS_LOCK_ON
+     * @see #META_NUM_LOCK_ON
+     * @see #META_SCROLL_LOCK_ON
+     * @see #getModifiers
+     */
+    public final int getMetaState() {
+        return mMetaState;
+    }
+
+    /**
+     * Returns the state of the modifier keys.
+     * <p>
+     * For the purposes of this function, {@link #KEYCODE_CAPS_LOCK},
+     * {@link #KEYCODE_SCROLL_LOCK}, and {@link #KEYCODE_NUM_LOCK} are
+     * not considered modifier keys.  Consequently, this function specifically masks out
+     * {@link #META_CAPS_LOCK_ON}, {@link #META_SCROLL_LOCK_ON} and {@link #META_NUM_LOCK_ON}.
+     * </p><p>
+     * The value returned consists of the meta state (from {@link #getMetaState})
+     * normalized using {@link #normalizeMetaState(int)} and then masked with
+     * {@link #getModifierMetaStateMask} so that only valid modifier bits are retained.
+     * </p>
+     *
+     * @return An integer in which each bit set to 1 represents a pressed modifier key.
+     * @see #getMetaState
+     */
+    public final int getModifiers() {
+        return normalizeMetaState(mMetaState) & META_MODIFIER_MASK;
+    }
+
+    /**
+     * Modifies the flags of the event.
+     *
+     * @param newFlags New flags for the event, replacing the entire value.
+     * @hide
+     */
+    public final void setFlags(int newFlags) {
+        mFlags = newFlags;
+    }
+
+    /**
+     * Returns the flags for this key event.
+     *
+     * @see #FLAG_WOKE_HERE
+     */
+    public final int getFlags() {
+        return mFlags;
+    }
+
+    // Mask of all modifier key meta states.  Specifically excludes locked keys like caps lock.
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private static final int META_MODIFIER_MASK =
+            META_SHIFT_ON | META_SHIFT_LEFT_ON | META_SHIFT_RIGHT_ON
+            | META_ALT_ON | META_ALT_LEFT_ON | META_ALT_RIGHT_ON
+            | META_CTRL_ON | META_CTRL_LEFT_ON | META_CTRL_RIGHT_ON
+            | META_META_ON | META_META_LEFT_ON | META_META_RIGHT_ON
+            | META_SYM_ON | META_FUNCTION_ON;
+
+    // Mask of all lock key meta states.
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private static final int META_LOCK_MASK =
+            META_CAPS_LOCK_ON | META_NUM_LOCK_ON | META_SCROLL_LOCK_ON;
+
+    // Mask of all valid meta states.
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private static final int META_ALL_MASK = META_MODIFIER_MASK | META_LOCK_MASK;
+
+    // Mask of all synthetic meta states that are reserved for API compatibility with
+    // historical uses in MetaKeyKeyListener.
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private static final int META_SYNTHETIC_MASK =
+            META_CAP_LOCKED | META_ALT_LOCKED | META_SYM_LOCKED | META_SELECTING;
+
+    // Mask of all meta states that are not valid use in specifying a modifier key.
+    // These bits are known to be used for purposes other than specifying modifiers.
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private static final int META_INVALID_MODIFIER_MASK =
+            META_LOCK_MASK | META_SYNTHETIC_MASK;
+
+    /**
+     * Gets a mask that includes all valid modifier key meta state bits.
+     * <p>
+     * For the purposes of this function, {@link #KEYCODE_CAPS_LOCK},
+     * {@link #KEYCODE_SCROLL_LOCK}, and {@link #KEYCODE_NUM_LOCK} are
+     * not considered modifier keys.  Consequently, the mask specifically excludes
+     * {@link #META_CAPS_LOCK_ON}, {@link #META_SCROLL_LOCK_ON} and {@link #META_NUM_LOCK_ON}.
+     * </p>
+     *
+     * @return The modifier meta state mask which is a combination of
+     * {@link #META_SHIFT_ON}, {@link #META_SHIFT_LEFT_ON}, {@link #META_SHIFT_RIGHT_ON},
+     * {@link #META_ALT_ON}, {@link #META_ALT_LEFT_ON}, {@link #META_ALT_RIGHT_ON},
+     * {@link #META_CTRL_ON}, {@link #META_CTRL_LEFT_ON}, {@link #META_CTRL_RIGHT_ON},
+     * {@link #META_META_ON}, {@link #META_META_LEFT_ON}, {@link #META_META_RIGHT_ON},
+     * {@link #META_SYM_ON}, {@link #META_FUNCTION_ON}.
+     */
+    public static int getModifierMetaStateMask() {
+        return META_MODIFIER_MASK;
+    }
+
+    /**
+     * Returns true if this key code is a modifier key.
+     * <p>
+     * For the purposes of this function, {@link #KEYCODE_CAPS_LOCK},
+     * {@link #KEYCODE_SCROLL_LOCK}, and {@link #KEYCODE_NUM_LOCK} are
+     * not considered modifier keys.  Consequently, this function return false
+     * for those keys.
+     * </p>
+     *
+     * @return True if the key code is one of
+     * {@link #KEYCODE_SHIFT_LEFT} {@link #KEYCODE_SHIFT_RIGHT},
+     * {@link #KEYCODE_ALT_LEFT}, {@link #KEYCODE_ALT_RIGHT},
+     * {@link #KEYCODE_CTRL_LEFT}, {@link #KEYCODE_CTRL_RIGHT},
+     * {@link #KEYCODE_META_LEFT}, or {@link #KEYCODE_META_RIGHT},
+     * {@link #KEYCODE_SYM}, {@link #KEYCODE_NUM}, {@link #KEYCODE_FUNCTION}.
+     */
+    public static boolean isModifierKey(int keyCode) {
+        switch (keyCode) {
+            case KEYCODE_SHIFT_LEFT:
+            case KEYCODE_SHIFT_RIGHT:
+            case KEYCODE_ALT_LEFT:
+            case KEYCODE_ALT_RIGHT:
+            case KEYCODE_CTRL_LEFT:
+            case KEYCODE_CTRL_RIGHT:
+            case KEYCODE_META_LEFT:
+            case KEYCODE_META_RIGHT:
+            case KEYCODE_SYM:
+            case KEYCODE_NUM:
+            case KEYCODE_FUNCTION:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Normalizes the specified meta state.
+     * <p>
+     * The meta state is normalized such that if either the left or right modifier meta state
+     * bits are set then the result will also include the universal bit for that modifier.
+     * </p><p>
+     * If the specified meta state contains {@link #META_ALT_LEFT_ON} then
+     * the result will also contain {@link #META_ALT_ON} in addition to {@link #META_ALT_LEFT_ON}
+     * and the other bits that were specified in the input.  The same is process is
+     * performed for shift, control and meta.
+     * </p><p>
+     * If the specified meta state contains synthetic meta states defined by
+     * {@link MetaKeyKeyListener}, then those states are translated here and the original
+     * synthetic meta states are removed from the result.
+     * {@link MetaKeyKeyListener#META_CAP_LOCKED} is translated to {@link #META_CAPS_LOCK_ON}.
+     * {@link MetaKeyKeyListener#META_ALT_LOCKED} is translated to {@link #META_ALT_ON}.
+     * {@link MetaKeyKeyListener#META_SYM_LOCKED} is translated to {@link #META_SYM_ON}.
+     * </p><p>
+     * Undefined meta state bits are removed.
+     * </p>
+     *
+     * @param metaState The meta state.
+     * @return The normalized meta state.
+     */
+    public static int normalizeMetaState(int metaState) {
+        if ((metaState & (META_SHIFT_LEFT_ON | META_SHIFT_RIGHT_ON)) != 0) {
+            metaState |= META_SHIFT_ON;
+        }
+        if ((metaState & (META_ALT_LEFT_ON | META_ALT_RIGHT_ON)) != 0) {
+            metaState |= META_ALT_ON;
+        }
+        if ((metaState & (META_CTRL_LEFT_ON | META_CTRL_RIGHT_ON)) != 0) {
+            metaState |= META_CTRL_ON;
+        }
+        if ((metaState & (META_META_LEFT_ON | META_META_RIGHT_ON)) != 0) {
+            metaState |= META_META_ON;
+        }
+        if ((metaState & MetaKeyKeyListener.META_CAP_LOCKED) != 0) {
+            metaState |= META_CAPS_LOCK_ON;
+        }
+        if ((metaState & MetaKeyKeyListener.META_ALT_LOCKED) != 0) {
+            metaState |= META_ALT_ON;
+        }
+        if ((metaState & MetaKeyKeyListener.META_SYM_LOCKED) != 0) {
+            metaState |= META_SYM_ON;
+        }
+        return metaState & META_ALL_MASK;
+    }
+
+    /**
+     * Returns true if no modifiers keys are pressed according to the specified meta state.
+     * <p>
+     * For the purposes of this function, {@link #KEYCODE_CAPS_LOCK},
+     * {@link #KEYCODE_SCROLL_LOCK}, and {@link #KEYCODE_NUM_LOCK} are
+     * not considered modifier keys.  Consequently, this function ignores
+     * {@link #META_CAPS_LOCK_ON}, {@link #META_SCROLL_LOCK_ON} and {@link #META_NUM_LOCK_ON}.
+     * </p><p>
+     * The meta state is normalized prior to comparison using {@link #normalizeMetaState(int)}.
+     * </p>
+     *
+     * @param metaState The meta state to consider.
+     * @return True if no modifier keys are pressed.
+     * @see #hasNoModifiers()
+     */
+    public static boolean metaStateHasNoModifiers(int metaState) {
+        return (normalizeMetaState(metaState) & META_MODIFIER_MASK) == 0;
+    }
+
+    /**
+     * Returns true if only the specified modifier keys are pressed according to
+     * the specified meta state.  Returns false if a different combination of modifier
+     * keys are pressed.
+     * <p>
+     * For the purposes of this function, {@link #KEYCODE_CAPS_LOCK},
+     * {@link #KEYCODE_SCROLL_LOCK}, and {@link #KEYCODE_NUM_LOCK} are
+     * not considered modifier keys.  Consequently, this function ignores
+     * {@link #META_CAPS_LOCK_ON}, {@link #META_SCROLL_LOCK_ON} and {@link #META_NUM_LOCK_ON}.
+     * </p><p>
+     * If the specified modifier mask includes directional modifiers, such as
+     * {@link #META_SHIFT_LEFT_ON}, then this method ensures that the
+     * modifier is pressed on that side.
+     * If the specified modifier mask includes non-directional modifiers, such as
+     * {@link #META_SHIFT_ON}, then this method ensures that the modifier
+     * is pressed on either side.
+     * If the specified modifier mask includes both directional and non-directional modifiers
+     * for the same type of key, such as {@link #META_SHIFT_ON} and {@link #META_SHIFT_LEFT_ON},
+     * then this method throws an illegal argument exception.
+     * </p>
+     *
+     * @param metaState The meta state to consider.
+     * @param modifiers The meta state of the modifier keys to check.  May be a combination
+     * of modifier meta states as defined by {@link #getModifierMetaStateMask()}.  May be 0 to
+     * ensure that no modifier keys are pressed.
+     * @return True if only the specified modifier keys are pressed.
+     * @throws IllegalArgumentException if the modifiers parameter contains invalid modifiers
+     * @see #hasModifiers
+     */
+    public static boolean metaStateHasModifiers(int metaState, int modifiers) {
+        // Note: For forward compatibility, we allow the parameter to contain meta states
+        //       that we do not recognize but we explicitly disallow meta states that
+        //       are not valid modifiers.
+        if ((modifiers & META_INVALID_MODIFIER_MASK) != 0) {
+            throw new IllegalArgumentException("modifiers must not contain "
+                    + "META_CAPS_LOCK_ON, META_NUM_LOCK_ON, META_SCROLL_LOCK_ON, "
+                    + "META_CAP_LOCKED, META_ALT_LOCKED, META_SYM_LOCKED, "
+                    + "or META_SELECTING");
+        }
+
+        metaState = normalizeMetaState(metaState) & META_MODIFIER_MASK;
+        metaState = metaStateFilterDirectionalModifiers(metaState, modifiers,
+                META_SHIFT_ON, META_SHIFT_LEFT_ON, META_SHIFT_RIGHT_ON);
+        metaState = metaStateFilterDirectionalModifiers(metaState, modifiers,
+                META_ALT_ON, META_ALT_LEFT_ON, META_ALT_RIGHT_ON);
+        metaState = metaStateFilterDirectionalModifiers(metaState, modifiers,
+                META_CTRL_ON, META_CTRL_LEFT_ON, META_CTRL_RIGHT_ON);
+        metaState = metaStateFilterDirectionalModifiers(metaState, modifiers,
+                META_META_ON, META_META_LEFT_ON, META_META_RIGHT_ON);
+        return metaState == modifiers;
+    }
+
+    private static int metaStateFilterDirectionalModifiers(int metaState,
+            int modifiers, int basic, int left, int right) {
+        final boolean wantBasic = (modifiers & basic) != 0;
+        final int directional = left | right;
+        final boolean wantLeftOrRight = (modifiers & directional) != 0;
+
+        if (wantBasic) {
+            if (wantLeftOrRight) {
+                throw new IllegalArgumentException("modifiers must not contain "
+                        + metaStateToString(basic) + " combined with "
+                        + metaStateToString(left) + " or " + metaStateToString(right));
+            }
+            return metaState & ~directional;
+        } else if (wantLeftOrRight) {
+            return metaState & ~basic;
+        } else {
+            return metaState;
+        }
+    }
+
+    /**
+     * Returns true if no modifier keys are pressed.
+     * <p>
+     * For the purposes of this function, {@link #KEYCODE_CAPS_LOCK},
+     * {@link #KEYCODE_SCROLL_LOCK}, and {@link #KEYCODE_NUM_LOCK} are
+     * not considered modifier keys.  Consequently, this function ignores
+     * {@link #META_CAPS_LOCK_ON}, {@link #META_SCROLL_LOCK_ON} and {@link #META_NUM_LOCK_ON}.
+     * </p><p>
+     * The meta state is normalized prior to comparison using {@link #normalizeMetaState(int)}.
+     * </p>
+     *
+     * @return True if no modifier keys are pressed.
+     * @see #metaStateHasNoModifiers
+     */
+    public final boolean hasNoModifiers() {
+        return metaStateHasNoModifiers(mMetaState);
+    }
+
+    /**
+     * Returns true if only the specified modifiers keys are pressed.
+     * Returns false if a different combination of modifier keys are pressed.
+     * <p>
+     * For the purposes of this function, {@link #KEYCODE_CAPS_LOCK},
+     * {@link #KEYCODE_SCROLL_LOCK}, and {@link #KEYCODE_NUM_LOCK} are
+     * not considered modifier keys.  Consequently, this function ignores
+     * {@link #META_CAPS_LOCK_ON}, {@link #META_SCROLL_LOCK_ON} and {@link #META_NUM_LOCK_ON}.
+     * </p><p>
+     * If the specified modifier mask includes directional modifiers, such as
+     * {@link #META_SHIFT_LEFT_ON}, then this method ensures that the
+     * modifier is pressed on that side.
+     * If the specified modifier mask includes non-directional modifiers, such as
+     * {@link #META_SHIFT_ON}, then this method ensures that the modifier
+     * is pressed on either side.
+     * If the specified modifier mask includes both directional and non-directional modifiers
+     * for the same type of key, such as {@link #META_SHIFT_ON} and {@link #META_SHIFT_LEFT_ON},
+     * then this method throws an illegal argument exception.
+     * </p>
+     *
+     * @param modifiers The meta state of the modifier keys to check.  May be a combination
+     * of modifier meta states as defined by {@link #getModifierMetaStateMask()}.  May be 0 to
+     * ensure that no modifier keys are pressed.
+     * @return True if only the specified modifier keys are pressed.
+     * @throws IllegalArgumentException if the modifiers parameter contains invalid modifiers
+     * @see #metaStateHasModifiers
+     */
+    public final boolean hasModifiers(int modifiers) {
+        return metaStateHasModifiers(mMetaState, modifiers);
+    }
+
+    /**
+     * <p>Returns the pressed state of the ALT meta key.</p>
+     *
+     * @return true if the ALT key is pressed, false otherwise
+     *
+     * @see #KEYCODE_ALT_LEFT
+     * @see #KEYCODE_ALT_RIGHT
+     * @see #META_ALT_ON
+     */
+    public final boolean isAltPressed() {
+        return (mMetaState & META_ALT_ON) != 0;
+    }
+
+    /**
+     * <p>Returns the pressed state of the SHIFT meta key.</p>
+     *
+     * @return true if the SHIFT key is pressed, false otherwise
+     *
+     * @see #KEYCODE_SHIFT_LEFT
+     * @see #KEYCODE_SHIFT_RIGHT
+     * @see #META_SHIFT_ON
+     */
+    public final boolean isShiftPressed() {
+        return (mMetaState & META_SHIFT_ON) != 0;
+    }
+
+    /**
+     * <p>Returns the pressed state of the SYM meta key.</p>
+     *
+     * @return true if the SYM key is pressed, false otherwise
+     *
+     * @see #KEYCODE_SYM
+     * @see #META_SYM_ON
+     */
+    public final boolean isSymPressed() {
+        return (mMetaState & META_SYM_ON) != 0;
+    }
+
+    /**
+     * <p>Returns the pressed state of the CTRL meta key.</p>
+     *
+     * @return true if the CTRL key is pressed, false otherwise
+     *
+     * @see #KEYCODE_CTRL_LEFT
+     * @see #KEYCODE_CTRL_RIGHT
+     * @see #META_CTRL_ON
+     */
+    public final boolean isCtrlPressed() {
+        return (mMetaState & META_CTRL_ON) != 0;
+    }
+
+    /**
+     * <p>Returns the pressed state of the META meta key.</p>
+     *
+     * @return true if the META key is pressed, false otherwise
+     *
+     * @see #KEYCODE_META_LEFT
+     * @see #KEYCODE_META_RIGHT
+     * @see #META_META_ON
+     */
+    public final boolean isMetaPressed() {
+        return (mMetaState & META_META_ON) != 0;
+    }
+
+    /**
+     * <p>Returns the pressed state of the FUNCTION meta key.</p>
+     *
+     * @return true if the FUNCTION key is pressed, false otherwise
+     *
+     * @see #KEYCODE_FUNCTION
+     * @see #META_FUNCTION_ON
+     */
+    public final boolean isFunctionPressed() {
+        return (mMetaState & META_FUNCTION_ON) != 0;
+    }
+
+    /**
+     * <p>Returns the locked state of the CAPS LOCK meta key.</p>
+     *
+     * @return true if the CAPS LOCK key is on, false otherwise
+     *
+     * @see #KEYCODE_CAPS_LOCK
+     * @see #META_CAPS_LOCK_ON
+     */
+    public final boolean isCapsLockOn() {
+        return (mMetaState & META_CAPS_LOCK_ON) != 0;
+    }
+
+    /**
+     * <p>Returns the locked state of the NUM LOCK meta key.</p>
+     *
+     * @return true if the NUM LOCK key is on, false otherwise
+     *
+     * @see #KEYCODE_NUM_LOCK
+     * @see #META_NUM_LOCK_ON
+     */
+    public final boolean isNumLockOn() {
+        return (mMetaState & META_NUM_LOCK_ON) != 0;
+    }
+
+    /**
+     * <p>Returns the locked state of the SCROLL LOCK meta key.</p>
+     *
+     * @return true if the SCROLL LOCK key is on, false otherwise
+     *
+     * @see #KEYCODE_SCROLL_LOCK
+     * @see #META_SCROLL_LOCK_ON
+     */
+    public final boolean isScrollLockOn() {
+        return (mMetaState & META_SCROLL_LOCK_ON) != 0;
+    }
+
+    /**
+     * Retrieve the action of this key event.  May be either
+     * {@link #ACTION_DOWN}, {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
+     *
+     * @return The event action: ACTION_DOWN, ACTION_UP, or ACTION_MULTIPLE.
+     */
+    public final int getAction() {
+        return mAction;
+    }
+
+    /**
+     * For {@link #ACTION_UP} events, indicates that the event has been
+     * canceled as per {@link #FLAG_CANCELED}.
+     */
+    public final boolean isCanceled() {
+        return (mFlags&FLAG_CANCELED) != 0;
+    }
+
+    /**
+     * Set {@link #FLAG_CANCELED} flag for the key event.
+     *
+     * @hide
+     */
+    @Override
+    public final void cancel() {
+        mFlags |= FLAG_CANCELED;
+    }
+
+    /**
+     * Call this during {@link Callback#onKeyDown} to have the system track
+     * the key through its final up (possibly including a long press).  Note
+     * that only one key can be tracked at a time -- if another key down
+     * event is received while a previous one is being tracked, tracking is
+     * stopped on the previous event.
+     */
+    public final void startTracking() {
+        mFlags |= FLAG_START_TRACKING;
+    }
+
+    /**
+     * For {@link #ACTION_UP} events, indicates that the event is still being
+     * tracked from its initial down event as per
+     * {@link #FLAG_TRACKING}.
+     */
+    public final boolean isTracking() {
+        return (mFlags&FLAG_TRACKING) != 0;
+    }
+
+    /**
+     * For {@link #ACTION_DOWN} events, indicates that the event has been
+     * canceled as per {@link #FLAG_LONG_PRESS}.
+     */
+    public final boolean isLongPress() {
+        return (mFlags&FLAG_LONG_PRESS) != 0;
+    }
+
+    /**
+     * Retrieve the key code of the key event.  This is the physical key that
+     * was pressed, <em>not</em> the Unicode character.
+     *
+     * @return The key code of the event.
+     */
+    public final int getKeyCode() {
+        return mKeyCode;
+    }
+
+    /**
+     * For the special case of a {@link #ACTION_MULTIPLE} event with key
+     * code of {@link #KEYCODE_UNKNOWN}, this is a raw string of characters
+     * associated with the event.  In all other cases it is null.
+     *
+     * @return Returns a String of 1 or more characters associated with
+     * the event.
+     *
+     * @deprecated no longer used by the input system.
+     */
+    @Deprecated
+    public final String getCharacters() {
+        return mCharacters;
+    }
+
+    /**
+     * Retrieve the hardware key id of this key event.  These values are not
+     * reliable and vary from device to device.
+     *
+     * {@more}
+     * Mostly this is here for debugging purposes.
+     */
+    public final int getScanCode() {
+        return mScanCode;
+    }
+
+    /**
+     * Retrieve the repeat count of the event.  For key down events,
+     * this is the number of times the key has repeated with the first
+     * down starting at 0 and counting up from there.  For key up events,
+     * this is always equal to zero. For multiple key events,
+     * this is the number of down/up pairs that have occurred.
+     *
+     * @return The number of times the key has repeated.
+     */
+    public final int getRepeatCount() {
+        return mRepeatCount;
+    }
+
+    /**
+     * Modifies the down time and the event time of the event.
+     *
+     * @param downTime The new down time (in {@link android.os.SystemClock#uptimeMillis}) of the
+     *                 event.
+     * @param eventTime The new event time (in {@link android.os.SystemClock#uptimeMillis}) of the
+     *                  event.
+     * @hide
+     */
+    public final void setTime(long downTime, long eventTime) {
+        mDownTime = downTime;
+        mEventTime = eventTime;
+    }
+
+    /**
+     * Retrieve the time of the most recent key down event,
+     * in the {@link android.os.SystemClock#uptimeMillis} time base.  If this
+     * is a down event, this will be the same as {@link #getEventTime()}.
+     * Note that when chording keys, this value is the down time of the
+     * most recently pressed key, which may <em>not</em> be the same physical
+     * key of this event.
+     *
+     * @return Returns the most recent key down time, in the
+     * {@link android.os.SystemClock#uptimeMillis} time base
+     */
+    public final long getDownTime() {
+        return mDownTime;
+    }
+
+    /**
+     * Retrieve the time this event occurred,
+     * in the {@link android.os.SystemClock#uptimeMillis} time base.
+     *
+     * @return Returns the time this event occurred,
+     * in the {@link android.os.SystemClock#uptimeMillis} time base.
+     */
+    @Override
+    public final long getEventTime() {
+        return mEventTime;
+    }
+
+    /**
+     * Retrieve the time this event occurred,
+     * in the {@link android.os.SystemClock#uptimeMillis} time base but with
+     * nanosecond (instead of millisecond) precision.
+     * <p>
+     * The value is in nanosecond precision but it may not have nanosecond accuracy.
+     * </p>
+     *
+     * @return Returns the time this event occurred,
+     * in the {@link android.os.SystemClock#uptimeMillis} time base but with
+     * nanosecond (instead of millisecond) precision.
+     *
+     * @hide
+     */
+    @Override
+    public final long getEventTimeNano() {
+        return mEventTime * 1000000L;
+    }
+
+    /**
+     * Renamed to {@link #getDeviceId}.
+     *
+     * @hide
+     * @deprecated use {@link #getDeviceId()} instead.
+     */
+    @Deprecated
+    public final int getKeyboardDevice() {
+        return mDeviceId;
+    }
+
+    /**
+     * Gets the {@link KeyCharacterMap} associated with the keyboard device.
+     *
+     * @return The associated key character map.
+     * @throws {@link KeyCharacterMap.UnavailableException} if the key character map
+     * could not be loaded because it was malformed or the default key character map
+     * is missing from the system.
+     *
+     * @see KeyCharacterMap#load
+     */
+    public final KeyCharacterMap getKeyCharacterMap() {
+        return KeyCharacterMap.load(mDeviceId);
+    }
+
+    /**
+     * Gets the primary character for this key.
+     * In other words, the label that is physically printed on it.
+     *
+     * @return The display label character, or 0 if none (eg. for non-printing keys).
+     */
+    public char getDisplayLabel() {
+        return getKeyCharacterMap().getDisplayLabel(mKeyCode);
+    }
+
+    /**
+     * Gets the Unicode character generated by the specified key and meta
+     * key state combination.
+     * <p>
+     * Returns the Unicode character that the specified key would produce
+     * when the specified meta bits (see {@link MetaKeyKeyListener})
+     * were active.
+     * </p><p>
+     * Returns 0 if the key is not one that is used to type Unicode
+     * characters.
+     * </p><p>
+     * If the return value has bit {@link KeyCharacterMap#COMBINING_ACCENT} set, the
+     * key is a "dead key" that should be combined with another to
+     * actually produce a character -- see {@link KeyCharacterMap#getDeadChar} --
+     * after masking with {@link KeyCharacterMap#COMBINING_ACCENT_MASK}.
+     * </p>
+     *
+     * @return The associated character or combining accent, or 0 if none.
+     */
+    public int getUnicodeChar() {
+        return getUnicodeChar(mMetaState);
+    }
+
+    /**
+     * Gets the Unicode character generated by the specified key and meta
+     * key state combination.
+     * <p>
+     * Returns the Unicode character that the specified key would produce
+     * when the specified meta bits (see {@link MetaKeyKeyListener})
+     * were active.
+     * </p><p>
+     * Returns 0 if the key is not one that is used to type Unicode
+     * characters.
+     * </p><p>
+     * If the return value has bit {@link KeyCharacterMap#COMBINING_ACCENT} set, the
+     * key is a "dead key" that should be combined with another to
+     * actually produce a character -- see {@link KeyCharacterMap#getDeadChar} --
+     * after masking with {@link KeyCharacterMap#COMBINING_ACCENT_MASK}.
+     * </p>
+     *
+     * @param metaState The meta key modifier state.
+     * @return The associated character or combining accent, or 0 if none.
+     */
+    public int getUnicodeChar(int metaState) {
+        return getKeyCharacterMap().get(mKeyCode, metaState);
+    }
+
+    /**
+     * Get the character conversion data for a given key code.
+     *
+     * @param results A {@link KeyCharacterMap.KeyData} instance that will be
+     * filled with the results.
+     * @return True if the key was mapped.  If the key was not mapped, results is not modified.
+     *
+     * @deprecated instead use {@link #getDisplayLabel()},
+     * {@link #getNumber()} or {@link #getUnicodeChar(int)}.
+     */
+    @Deprecated
+    public boolean getKeyData(KeyData results) {
+        return getKeyCharacterMap().getKeyData(mKeyCode, results);
+    }
+
+    /**
+     * Gets the first character in the character array that can be generated
+     * by the specified key code.
+     * <p>
+     * This is a convenience function that returns the same value as
+     * {@link #getMatch(char[],int) getMatch(chars, 0)}.
+     * </p>
+     *
+     * @param chars The array of matching characters to consider.
+     * @return The matching associated character, or 0 if none.
+     */
+    public char getMatch(char[] chars) {
+        return getMatch(chars, 0);
+    }
+
+    /**
+     * Gets the first character in the character array that can be generated
+     * by the specified key code.  If there are multiple choices, prefers
+     * the one that would be generated with the specified meta key modifier state.
+     *
+     * @param chars The array of matching characters to consider.
+     * @param metaState The preferred meta key modifier state.
+     * @return The matching associated character, or 0 if none.
+     */
+    public char getMatch(char[] chars, int metaState) {
+        return getKeyCharacterMap().getMatch(mKeyCode, chars, metaState);
+    }
+
+    /**
+     * Gets the number or symbol associated with the key.
+     * <p>
+     * The character value is returned, not the numeric value.
+     * If the key is not a number, but is a symbol, the symbol is retuned.
+     * </p><p>
+     * This method is intended to to support dial pads and other numeric or
+     * symbolic entry on keyboards where certain keys serve dual function
+     * as alphabetic and symbolic keys.  This method returns the number
+     * or symbol associated with the key independent of whether the user
+     * has pressed the required modifier.
+     * </p><p>
+     * For example, on one particular keyboard the keys on the top QWERTY row generate
+     * numbers when ALT is pressed such that ALT-Q maps to '1'.  So for that keyboard
+     * when {@link #getNumber} is called with {@link KeyEvent#KEYCODE_Q} it returns '1'
+     * so that the user can type numbers without pressing ALT when it makes sense.
+     * </p>
+     *
+     * @return The associated numeric or symbolic character, or 0 if none.
+     */
+    public char getNumber() {
+        return getKeyCharacterMap().getNumber(mKeyCode);
+    }
+
+    /**
+     * Returns true if this key produces a glyph.
+     *
+     * @return True if the key is a printing key.
+     */
+    public boolean isPrintingKey() {
+        return getKeyCharacterMap().isPrintingKey(mKeyCode);
+    }
+
+    /**
+     * @deprecated Use {@link #dispatch(Callback, DispatcherState, Object)} instead.
+     */
+    @Deprecated
+    public final boolean dispatch(Callback receiver) {
+        return dispatch(receiver, null, null);
+    }
+
+    /**
+     * Deliver this key event to a {@link Callback} interface.  If this is
+     * an ACTION_MULTIPLE event and it is not handled, then an attempt will
+     * be made to deliver a single normal event.
+     *
+     * @param receiver The Callback that will be given the event.
+     * @param state State information retained across events.
+     * @param target The target of the dispatch, for use in tracking.
+     *
+     * @return The return value from the Callback method that was called.
+     */
+    public final boolean dispatch(Callback receiver, DispatcherState state,
+            Object target) {
+        switch (mAction) {
+            case ACTION_DOWN: {
+                mFlags &= ~FLAG_START_TRACKING;
+                if (DEBUG) Log.v(TAG, "Key down to " + target + " in " + state
+                        + ": " + this);
+                boolean res = receiver.onKeyDown(mKeyCode, this);
+                if (state != null) {
+                    if (res && mRepeatCount == 0 && (mFlags&FLAG_START_TRACKING) != 0) {
+                        if (DEBUG) Log.v(TAG, "  Start tracking!");
+                        state.startTracking(this, target);
+                    } else if (isLongPress() && state.isTracking(this)) {
+                        try {
+                            if (receiver.onKeyLongPress(mKeyCode, this)) {
+                                if (DEBUG) Log.v(TAG, "  Clear from long press!");
+                                state.performedLongPress(this);
+                                res = true;
+                            }
+                        } catch (AbstractMethodError e) {
+                        }
+                    }
+                }
+                return res;
+            }
+            case ACTION_UP:
+                if (DEBUG) Log.v(TAG, "Key up to " + target + " in " + state
+                        + ": " + this);
+                if (state != null) {
+                    state.handleUpEvent(this);
+                }
+                return receiver.onKeyUp(mKeyCode, this);
+            case ACTION_MULTIPLE:
+                final int count = mRepeatCount;
+                final int code = mKeyCode;
+                if (receiver.onKeyMultiple(code, count, this)) {
+                    return true;
+                }
+                if (code != KeyEvent.KEYCODE_UNKNOWN) {
+                    mAction = ACTION_DOWN;
+                    mRepeatCount = 0;
+                    boolean handled = receiver.onKeyDown(code, this);
+                    if (handled) {
+                        mAction = ACTION_UP;
+                        receiver.onKeyUp(code, this);
+                    }
+                    mAction = ACTION_MULTIPLE;
+                    mRepeatCount = count;
+                    return handled;
+                }
+                return false;
+        }
+        return false;
+    }
+
+    /**
+     * Use with {@link KeyEvent#dispatch(Callback, DispatcherState, Object)}
+     * for more advanced key dispatching, such as long presses.
+     */
+    public static class DispatcherState {
+        int mDownKeyCode;
+        Object mDownTarget;
+        SparseIntArray mActiveLongPresses = new SparseIntArray();
+
+        /**
+         * Reset back to initial state.
+         */
+        public void reset() {
+            if (DEBUG) Log.v(TAG, "Reset: " + this);
+            mDownKeyCode = 0;
+            mDownTarget = null;
+            mActiveLongPresses.clear();
+        }
+
+        /**
+         * Stop any tracking associated with this target.
+         */
+        public void reset(Object target) {
+            if (mDownTarget == target) {
+                if (DEBUG) Log.v(TAG, "Reset in " + target + ": " + this);
+                mDownKeyCode = 0;
+                mDownTarget = null;
+            }
+        }
+
+        /**
+         * Start tracking the key code associated with the given event.  This
+         * can only be called on a key down.  It will allow you to see any
+         * long press associated with the key, and will result in
+         * {@link KeyEvent#isTracking} return true on the long press and up
+         * events.
+         *
+         * <p>This is only needed if you are directly dispatching events, rather
+         * than handling them in {@link Callback#onKeyDown}.
+         */
+        public void startTracking(KeyEvent event, Object target) {
+            if (event.getAction() != ACTION_DOWN) {
+                throw new IllegalArgumentException(
+                        "Can only start tracking on a down event");
+            }
+            if (DEBUG) Log.v(TAG, "Start trackingt in " + target + ": " + this);
+            mDownKeyCode = event.getKeyCode();
+            mDownTarget = target;
+        }
+
+        /**
+         * Return true if the key event is for a key code that is currently
+         * being tracked by the dispatcher.
+         */
+        public boolean isTracking(KeyEvent event) {
+            return mDownKeyCode == event.getKeyCode();
+        }
+
+        /**
+         * Keep track of the given event's key code as having performed an
+         * action with a long press, so no action should occur on the up.
+         * <p>This is only needed if you are directly dispatching events, rather
+         * than handling them in {@link Callback#onKeyLongPress}.
+         */
+        public void performedLongPress(KeyEvent event) {
+            mActiveLongPresses.put(event.getKeyCode(), 1);
+        }
+
+        /**
+         * Handle key up event to stop tracking.  This resets the dispatcher state,
+         * and updates the key event state based on it.
+         * <p>This is only needed if you are directly dispatching events, rather
+         * than handling them in {@link Callback#onKeyUp}.
+         */
+        public void handleUpEvent(KeyEvent event) {
+            final int keyCode = event.getKeyCode();
+            if (DEBUG) Log.v(TAG, "Handle key up " + event + ": " + this);
+            int index = mActiveLongPresses.indexOfKey(keyCode);
+            if (index >= 0) {
+                if (DEBUG) Log.v(TAG, "  Index: " + index);
+                event.mFlags |= FLAG_CANCELED | FLAG_CANCELED_LONG_PRESS;
+                mActiveLongPresses.removeAt(index);
+            }
+            if (mDownKeyCode == keyCode) {
+                if (DEBUG) Log.v(TAG, "  Tracking!");
+                event.mFlags |= FLAG_TRACKING;
+                mDownKeyCode = 0;
+                mDownTarget = null;
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder msg = new StringBuilder();
+        msg.append("KeyEvent { action=").append(actionToString(mAction));
+        msg.append(", keyCode=").append(keyCodeToString(mKeyCode));
+        msg.append(", scanCode=").append(mScanCode);
+        if (mCharacters != null) {
+            msg.append(", characters=\"").append(mCharacters).append("\"");
+        }
+        msg.append(", metaState=").append(metaStateToString(mMetaState));
+        msg.append(", flags=0x").append(Integer.toHexString(mFlags));
+        msg.append(", repeatCount=").append(mRepeatCount);
+        msg.append(", eventTime=").append(mEventTime);
+        msg.append(", downTime=").append(mDownTime);
+        msg.append(", deviceId=").append(mDeviceId);
+        msg.append(", source=0x").append(Integer.toHexString(mSource));
+        msg.append(", displayId=").append(mDisplayId);
+        msg.append(" }");
+        return msg.toString();
+    }
+
+    /**
+     * Returns a string that represents the symbolic name of the specified action
+     * such as "ACTION_DOWN", or an equivalent numeric constant such as "35" if unknown.
+     *
+     * @param action The action.
+     * @return The symbolic name of the specified action.
+     * @hide
+     */
+    @TestApi
+    public static String actionToString(int action) {
+        switch (action) {
+            case ACTION_DOWN:
+                return "ACTION_DOWN";
+            case ACTION_UP:
+                return "ACTION_UP";
+            case ACTION_MULTIPLE:
+                return "ACTION_MULTIPLE";
+            default:
+                return Integer.toString(action);
+        }
+    }
+
+    /**
+     * Returns a string that represents the symbolic name of the specified keycode
+     * such as "KEYCODE_A", "KEYCODE_DPAD_UP", or an equivalent numeric constant
+     * such as "1001" if unknown.
+     *
+     * This function is intended to be used mostly for debugging, logging, and testing. It is not
+     * locale-specific and is not intended to be used in a user-facing manner.
+     *
+     * @param keyCode The key code.
+     * @return The symbolic name of the specified keycode.
+     *
+     * @see KeyCharacterMap#getDisplayLabel
+     */
+    public static String keyCodeToString(int keyCode) {
+        String symbolicName = nativeKeyCodeToString(keyCode);
+        return symbolicName != null ? LABEL_PREFIX + symbolicName : Integer.toString(keyCode);
+    }
+
+    /**
+     * Gets a keycode by its symbolic name such as "KEYCODE_A" or an equivalent
+     * numeric constant such as "29". For symbolic names,
+     * starting in {@link android.os.Build.VERSION_CODES#Q} the prefix "KEYCODE_" is optional.
+     *
+     * @param symbolicName The symbolic name of the keycode.
+     * @return The keycode or {@link #KEYCODE_UNKNOWN} if not found.
+     * @see #keyCodeToString(int)
+     */
+    public static int keyCodeFromString(@NonNull String symbolicName) {
+        try {
+            int keyCode = Integer.parseInt(symbolicName);
+            if (keyCodeIsValid(keyCode)) {
+                return keyCode;
+            }
+        } catch (NumberFormatException ex) {
+        }
+
+        if (symbolicName.startsWith(LABEL_PREFIX)) {
+            symbolicName = symbolicName.substring(LABEL_PREFIX.length());
+        }
+        int keyCode = nativeKeyCodeFromString(symbolicName);
+        if (keyCodeIsValid(keyCode)) {
+            return keyCode;
+        }
+        return KEYCODE_UNKNOWN;
+    }
+
+    private static boolean keyCodeIsValid(int keyCode) {
+        return keyCode >= KEYCODE_UNKNOWN && keyCode <= LAST_KEYCODE;
+    }
+
+    /**
+     * Returns a string that represents the symbolic name of the specified combined meta
+     * key modifier state flags such as "0", "META_SHIFT_ON",
+     * "META_ALT_ON|META_SHIFT_ON" or an equivalent numeric constant such as "0x10000000"
+     * if unknown.
+     *
+     * @param metaState The meta state.
+     * @return The symbolic name of the specified combined meta state flags.
+     * @hide
+     */
+    public static String metaStateToString(int metaState) {
+        if (metaState == 0) {
+            return "0";
+        }
+        StringBuilder result = null;
+        int i = 0;
+        while (metaState != 0) {
+            final boolean isSet = (metaState & 1) != 0;
+            metaState >>>= 1; // unsigned shift!
+            if (isSet) {
+                final String name = META_SYMBOLIC_NAMES[i];
+                if (result == null) {
+                    if (metaState == 0) {
+                        return name;
+                    }
+                    result = new StringBuilder(name);
+                } else {
+                    result.append('|');
+                    result.append(name);
+                }
+            }
+            i += 1;
+        }
+        return result.toString();
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<KeyEvent> CREATOR
+            = new Parcelable.Creator<KeyEvent>() {
+        @Override
+        public KeyEvent createFromParcel(Parcel in) {
+            in.readInt(); // skip token, we already know this is a KeyEvent
+            return KeyEvent.createFromParcelBody(in);
+        }
+
+        @Override
+        public KeyEvent[] newArray(int size) {
+            return new KeyEvent[size];
+        }
+    };
+
+    /** @hide */
+    public static KeyEvent createFromParcelBody(Parcel in) {
+        return new KeyEvent(in);
+    }
+
+    private KeyEvent(Parcel in) {
+        mId = in.readInt();
+        mDeviceId = in.readInt();
+        mSource = in.readInt();
+        mDisplayId = in.readInt();
+        mHmac = in.createByteArray();
+        mAction = in.readInt();
+        mKeyCode = in.readInt();
+        mRepeatCount = in.readInt();
+        mMetaState = in.readInt();
+        mScanCode = in.readInt();
+        mFlags = in.readInt();
+        mDownTime = in.readLong();
+        mEventTime = in.readLong();
+        mCharacters = in.readString();
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(PARCEL_TOKEN_KEY_EVENT);
+
+        out.writeInt(mId);
+        out.writeInt(mDeviceId);
+        out.writeInt(mSource);
+        out.writeInt(mDisplayId);
+        out.writeByteArray(mHmac);
+        out.writeInt(mAction);
+        out.writeInt(mKeyCode);
+        out.writeInt(mRepeatCount);
+        out.writeInt(mMetaState);
+        out.writeInt(mScanCode);
+        out.writeInt(mFlags);
+        out.writeLong(mDownTime);
+        out.writeLong(mEventTime);
+        out.writeString(mCharacters);
+    }
+}
diff --git a/android/view/KeyboardShortcutGroup.java b/android/view/KeyboardShortcutGroup.java
new file mode 100644
index 0000000..763ca26
--- /dev/null
+++ b/android/view/KeyboardShortcutGroup.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A group of {@link KeyboardShortcutInfo}.
+ */
+public final class KeyboardShortcutGroup implements Parcelable {
+    private final CharSequence mLabel;
+    private final List<KeyboardShortcutInfo> mItems;
+    // The system group looks different UI wise.
+    private boolean mSystemGroup;
+
+    /**
+     * @param label The title to be used for this group, or null if there is none.
+     * @param items The set of items to be included.
+     */
+    public KeyboardShortcutGroup(@Nullable CharSequence label,
+            @NonNull List<KeyboardShortcutInfo> items) {
+        mLabel = label;
+        mItems = new ArrayList<>(checkNotNull(items));
+    }
+
+    /**
+     * @param label The title to be used for this group, or null if there is none.
+     */
+    public KeyboardShortcutGroup(@Nullable CharSequence label) {
+        this(label, Collections.<KeyboardShortcutInfo>emptyList());
+    }
+
+    /**
+     * @param label The title to be used for this group, or null if there is none.
+     * @param items The set of items to be included.
+     * @param isSystemGroup Set this to {@code true} if this is s system group.
+     * @hide
+     */
+    @TestApi
+    public KeyboardShortcutGroup(@Nullable CharSequence label,
+            @NonNull List<KeyboardShortcutInfo> items, boolean isSystemGroup) {
+        mLabel = label;
+        mItems = new ArrayList<>(checkNotNull(items));
+        mSystemGroup = isSystemGroup;
+    }
+
+    /**
+     * @param label The title to be used for this group, or null if there is none.
+     * @param isSystemGroup Set this to {@code true} if this is s system group.
+     * @hide
+     */
+    @TestApi
+    public KeyboardShortcutGroup(@Nullable CharSequence label, boolean isSystemGroup) {
+        this(label, Collections.<KeyboardShortcutInfo>emptyList(), isSystemGroup);
+    }
+
+    private KeyboardShortcutGroup(Parcel source) {
+        mItems = new ArrayList<>();
+        mLabel = source.readCharSequence();
+        source.readTypedList(mItems, KeyboardShortcutInfo.CREATOR);
+        mSystemGroup = source.readInt() == 1;
+    }
+
+    /**
+     * Returns the label to be used to describe this group.
+     */
+    public CharSequence getLabel() {
+        return mLabel;
+    }
+
+    /**
+     * Returns the list of items included in this group.
+     */
+    public List<KeyboardShortcutInfo> getItems() {
+        return mItems;
+    }
+
+    /** @hide **/
+    @TestApi
+    public boolean isSystemGroup() {
+        return mSystemGroup;
+    }
+
+    /**
+     * Adds an item to the existing list.
+     *
+     * @param item The item to be added.
+     */
+    public void addItem(KeyboardShortcutInfo item) {
+        mItems.add(item);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeCharSequence(mLabel);
+        dest.writeTypedList(mItems);
+        dest.writeInt(mSystemGroup ? 1 : 0);
+    }
+
+    public static final @android.annotation.NonNull Creator<KeyboardShortcutGroup> CREATOR =
+            new Creator<KeyboardShortcutGroup>() {
+                public KeyboardShortcutGroup createFromParcel(Parcel source) {
+                    return new KeyboardShortcutGroup(source);
+                }
+                public KeyboardShortcutGroup[] newArray(int size) {
+                    return new KeyboardShortcutGroup[size];
+                }
+            };
+}
diff --git a/android/view/KeyboardShortcutInfo.java b/android/view/KeyboardShortcutInfo.java
new file mode 100644
index 0000000..2660e74
--- /dev/null
+++ b/android/view/KeyboardShortcutInfo.java
@@ -0,0 +1,168 @@
+/*
+ * 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.view;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import static java.lang.Character.MIN_VALUE;
+
+import android.annotation.Nullable;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Information about a Keyboard Shortcut.
+ */
+public final class KeyboardShortcutInfo implements Parcelable {
+    private final CharSequence mLabel;
+    private final Icon mIcon;
+    private final char mBaseCharacter;
+    private final int mKeycode;
+    private final int mModifiers;
+
+    /**
+     * @param label The label that identifies the action performed by this shortcut.
+     * @param icon An icon that identifies the action performed by this shortcut.
+     * @param keycode The keycode that triggers the shortcut. This should be a valid constant
+     *     defined in {@link KeyEvent}.
+     * @param modifiers The set of modifiers that, combined with the key, trigger the shortcut.
+     *     These should be a combination of {@link KeyEvent#META_CTRL_ON},
+     *     {@link KeyEvent#META_SHIFT_ON}, {@link KeyEvent#META_META_ON},
+     *     {@link KeyEvent#META_ALT_ON}, {@link KeyEvent#META_FUNCTION_ON} and
+     *     {@link KeyEvent#META_SYM_ON}.
+     *
+     * @hide
+     */
+    public KeyboardShortcutInfo(
+            @Nullable CharSequence label, @Nullable Icon icon, int keycode, int modifiers) {
+        mLabel = label;
+        mIcon = icon;
+        mBaseCharacter = MIN_VALUE;
+        checkArgument(keycode >= KeyEvent.KEYCODE_UNKNOWN && keycode <= KeyEvent.getMaxKeyCode());
+        mKeycode = keycode;
+        mModifiers = modifiers;
+    }
+
+    /**
+     * @param label The label that identifies the action performed by this shortcut.
+     * @param keycode The keycode that triggers the shortcut. This should be a valid constant
+     *     defined in {@link KeyEvent}.
+     * @param modifiers The set of modifiers that, combined with the key, trigger the shortcut.
+     *     These should be a combination of {@link KeyEvent#META_CTRL_ON},
+     *     {@link KeyEvent#META_SHIFT_ON}, {@link KeyEvent#META_META_ON},
+     *     {@link KeyEvent#META_ALT_ON}, {@link KeyEvent#META_FUNCTION_ON} and
+     *     {@link KeyEvent#META_SYM_ON}.
+     */
+    public KeyboardShortcutInfo(CharSequence label, int keycode, int modifiers) {
+        this(label, null, keycode, modifiers);
+    }
+
+    /**
+     * @param label The label that identifies the action performed by this shortcut.
+     * @param baseCharacter The character that triggers the shortcut.
+     * @param modifiers The set of modifiers that, combined with the key, trigger the shortcut.
+     *     These should be a combination of {@link KeyEvent#META_CTRL_ON},
+     *     {@link KeyEvent#META_SHIFT_ON}, {@link KeyEvent#META_META_ON},
+     *     {@link KeyEvent#META_ALT_ON}, {@link KeyEvent#META_FUNCTION_ON} and
+     *     {@link KeyEvent#META_SYM_ON}.
+     */
+    public KeyboardShortcutInfo(CharSequence label, char baseCharacter, int modifiers) {
+        mLabel = label;
+        checkArgument(baseCharacter != MIN_VALUE);
+        mBaseCharacter = baseCharacter;
+        mKeycode = KeyEvent.KEYCODE_UNKNOWN;
+        mModifiers = modifiers;
+        mIcon = null;
+    }
+
+    private KeyboardShortcutInfo(Parcel source) {
+        mLabel = source.readCharSequence();
+        mIcon = source.readParcelable(null);
+        mBaseCharacter = (char) source.readInt();
+        mKeycode = source.readInt();
+        mModifiers = source.readInt();
+    }
+
+    /**
+     * Returns the label to be used to describe this shortcut.
+     */
+    @Nullable
+    public CharSequence getLabel() {
+        return mLabel;
+    }
+
+    /**
+     * Returns the icon to be used to describe this shortcut.
+     *
+     * @hide
+     */
+    @Nullable
+    public Icon getIcon() {
+        return mIcon;
+    }
+
+    /**
+     * Returns the base keycode that, combined with the modifiers, triggers this shortcut. If the
+     * base character was set instead, returns {@link KeyEvent#KEYCODE_UNKNOWN}. Valid keycodes are
+     * defined as constants in {@link KeyEvent}.
+     */
+    public int getKeycode() {
+        return mKeycode;
+    }
+
+    /**
+     * Returns the base character that, combined with the modifiers, triggers this shortcut. If the
+     * keycode was set instead, returns {@link Character#MIN_VALUE}.
+     */
+    public char getBaseCharacter() {
+        return mBaseCharacter;
+    }
+
+    /**
+     * Returns the set of modifiers that, combined with the key, trigger this shortcut. These can
+     * be a combination of {@link KeyEvent#META_CTRL_ON}, {@link KeyEvent#META_SHIFT_ON},
+     * {@link KeyEvent#META_META_ON}, {@link KeyEvent#META_ALT_ON},
+     * {@link KeyEvent#META_FUNCTION_ON} and {@link KeyEvent#META_SYM_ON}.
+     */
+    public int getModifiers() {
+        return mModifiers;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeCharSequence(mLabel);
+        dest.writeParcelable(mIcon, 0);
+        dest.writeInt(mBaseCharacter);
+        dest.writeInt(mKeycode);
+        dest.writeInt(mModifiers);
+    }
+
+    public static final @android.annotation.NonNull Creator<KeyboardShortcutInfo> CREATOR =
+            new Creator<KeyboardShortcutInfo>() {
+        public KeyboardShortcutInfo createFromParcel(Parcel source) {
+            return new KeyboardShortcutInfo(source);
+        }
+        public KeyboardShortcutInfo[] newArray(int size) {
+            return new KeyboardShortcutInfo[size];
+        }
+    };
+}
\ No newline at end of file
diff --git a/android/view/LayoutInflater.java b/android/view/LayoutInflater.java
new file mode 100644
index 0000000..df78827
--- /dev/null
+++ b/android/view/LayoutInflater.java
@@ -0,0 +1,1366 @@
+/*
+ * 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.view;
+
+import android.annotation.LayoutRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.annotation.UiContext;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.Canvas;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Message;
+import android.os.StrictMode;
+import android.os.Trace;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.util.Xml;
+import android.widget.FrameLayout;
+
+import com.android.internal.R;
+
+import dalvik.system.PathClassLoader;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Objects;
+
+/**
+ * Instantiates a layout XML file into its corresponding {@link android.view.View}
+ * objects. It is never used directly. Instead, use
+ * {@link android.app.Activity#getLayoutInflater()} or
+ * {@link Context#getSystemService} to retrieve a standard LayoutInflater instance
+ * that is already hooked up to the current context and correctly configured
+ * for the device you are running on.
+ * <p>
+ * To create a new LayoutInflater with an additional {@link Factory} for your
+ * own views, you can use {@link #cloneInContext} to clone an existing
+ * ViewFactory, and then call {@link #setFactory} on it to include your
+ * Factory.
+ * <p>
+ * For performance reasons, view inflation relies heavily on pre-processing of
+ * XML files that is done at build time. Therefore, it is not currently possible
+ * to use LayoutInflater with an XmlPullParser over a plain XML file at runtime;
+ * it only works with an XmlPullParser returned from a compiled resource
+ * (R.<em>something</em> file.)
+ * <p>
+ * <strong>Note:</strong> This class is <strong>not</strong> thread-safe and a given
+ * instance should only be accessed by a single thread.
+ */
+@SystemService(Context.LAYOUT_INFLATER_SERVICE)
+public abstract class LayoutInflater {
+
+    private static final String TAG = LayoutInflater.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private static final String COMPILED_VIEW_DEX_FILE_NAME = "/compiled_view.dex";
+    /**
+     * Whether or not we use the precompiled layout.
+     */
+    private static final String USE_PRECOMPILED_LAYOUT = "view.precompiled_layout_enabled";
+
+    /** Empty stack trace used to avoid log spam in re-throw exceptions. */
+    private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0];
+
+    /**
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    // TODO(b/182007470): Use @ConfigurationContext instead
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    @UiContext
+    protected final Context mContext;
+
+    // these are optional, set by the caller
+    /**
+     * If any developer has desire to change this value, they should instead use
+     * {@link #cloneInContext(Context)} and set the new factory in thew newly-created
+     * LayoutInflater.
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    private boolean mFactorySet;
+    @UnsupportedAppUsage
+    private Factory mFactory;
+    @UnsupportedAppUsage
+    private Factory2 mFactory2;
+    @UnsupportedAppUsage
+    private Factory2 mPrivateFactory;
+    private Filter mFilter;
+
+    // Indicates whether we should try to inflate layouts using a precompiled layout instead of
+    // inflating from the XML resource.
+    private boolean mUseCompiledView;
+    // This variable holds the classloader that will be used to look for precompiled layouts. The
+    // The classloader includes the generated compiled_view.dex file.
+    private ClassLoader mPrecompiledClassLoader;
+
+    /**
+     * This is not a public API. Two APIs are now available to alleviate the need to access
+     * this directly: {@link #createView(Context, String, String, AttributeSet)} and
+     * {@link #onCreateView(Context, View, String, AttributeSet)}.
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    final Object[] mConstructorArgs = new Object[2];
+
+    @UnsupportedAppUsage
+    static final Class<?>[] mConstructorSignature = new Class[] {
+            Context.class, AttributeSet.class};
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769490)
+    private static final HashMap<String, Constructor<? extends View>> sConstructorMap =
+            new HashMap<String, Constructor<? extends View>>();
+
+    private HashMap<String, Boolean> mFilterMap;
+
+    private TypedValue mTempValue;
+
+    private static final String TAG_MERGE = "merge";
+    private static final String TAG_INCLUDE = "include";
+    private static final String TAG_1995 = "blink";
+    private static final String TAG_REQUEST_FOCUS = "requestFocus";
+    private static final String TAG_TAG = "tag";
+
+    private static final String ATTR_LAYOUT = "layout";
+
+    @UnsupportedAppUsage
+    private static final int[] ATTRS_THEME = new int[] {
+            com.android.internal.R.attr.theme };
+
+    /**
+     * Hook to allow clients of the LayoutInflater to restrict the set of Views that are allowed
+     * to be inflated.
+     *
+     */
+    public interface Filter {
+        /**
+         * Hook to allow clients of the LayoutInflater to restrict the set of Views
+         * that are allowed to be inflated.
+         *
+         * @param clazz The class object for the View that is about to be inflated
+         *
+         * @return True if this class is allowed to be inflated, or false otherwise
+         */
+        @SuppressWarnings("unchecked")
+        boolean onLoadClass(Class clazz);
+    }
+
+    public interface Factory {
+        /**
+         * Hook you can supply that is called when inflating from a LayoutInflater.
+         * You can use this to customize the tag names available in your XML
+         * layout files.
+         *
+         * <p>
+         * Note that it is good practice to prefix these custom names with your
+         * package (i.e., com.coolcompany.apps) to avoid conflicts with system
+         * names.
+         *
+         * @param name Tag name to be inflated.
+         * @param context The context the view is being created in.
+         * @param attrs Inflation attributes as specified in XML file.
+         *
+         * @return View Newly created view. Return null for the default
+         *         behavior.
+         */
+        @Nullable
+        View onCreateView(@NonNull String name, @NonNull Context context,
+                @NonNull AttributeSet attrs);
+    }
+
+    public interface Factory2 extends Factory {
+        /**
+         * Version of {@link #onCreateView(String, Context, AttributeSet)}
+         * that also supplies the parent that the view created view will be
+         * placed in.
+         *
+         * @param parent The parent that the created view will be placed
+         * in; <em>note that this may be null</em>.
+         * @param name Tag name to be inflated.
+         * @param context The context the view is being created in.
+         * @param attrs Inflation attributes as specified in XML file.
+         *
+         * @return View Newly created view. Return null for the default
+         *         behavior.
+         */
+        @Nullable
+        View onCreateView(@Nullable View parent, @NonNull String name,
+                @NonNull Context context, @NonNull AttributeSet attrs);
+    }
+
+    private static class FactoryMerger implements Factory2 {
+        private final Factory mF1, mF2;
+        private final Factory2 mF12, mF22;
+
+        FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) {
+            mF1 = f1;
+            mF2 = f2;
+            mF12 = f12;
+            mF22 = f22;
+        }
+
+        @Nullable
+        public View onCreateView(@NonNull String name, @NonNull Context context,
+                @NonNull AttributeSet attrs) {
+            View v = mF1.onCreateView(name, context, attrs);
+            if (v != null) return v;
+            return mF2.onCreateView(name, context, attrs);
+        }
+
+        @Nullable
+        public View onCreateView(@Nullable View parent, @NonNull String name,
+                @NonNull Context context, @NonNull AttributeSet attrs) {
+            View v = mF12 != null ? mF12.onCreateView(parent, name, context, attrs)
+                    : mF1.onCreateView(name, context, attrs);
+            if (v != null) return v;
+            return mF22 != null ? mF22.onCreateView(parent, name, context, attrs)
+                    : mF2.onCreateView(name, context, attrs);
+        }
+    }
+
+    /**
+     * Create a new LayoutInflater instance associated with a particular Context.
+     * Applications will almost always want to use
+     * {@link Context#getSystemService Context.getSystemService()} to retrieve
+     * the standard {@link Context#LAYOUT_INFLATER_SERVICE Context.INFLATER_SERVICE}.
+     *
+     * @param context The Context in which this LayoutInflater will create its
+     * Views; most importantly, this supplies the theme from which the default
+     * values for their attributes are retrieved.
+     */
+    protected LayoutInflater(Context context) {
+        StrictMode.assertConfigurationContext(context, "LayoutInflater");
+        mContext = context;
+        initPrecompiledViews();
+    }
+
+    /**
+     * Create a new LayoutInflater instance that is a copy of an existing
+     * LayoutInflater, optionally with its Context changed.  For use in
+     * implementing {@link #cloneInContext}.
+     *
+     * @param original The original LayoutInflater to copy.
+     * @param newContext The new Context to use.
+     */
+    protected LayoutInflater(LayoutInflater original, Context newContext) {
+        StrictMode.assertConfigurationContext(newContext, "LayoutInflater");
+        mContext = newContext;
+        mFactory = original.mFactory;
+        mFactory2 = original.mFactory2;
+        mPrivateFactory = original.mPrivateFactory;
+        setFilter(original.mFilter);
+        initPrecompiledViews();
+    }
+
+    /**
+     * Obtains the LayoutInflater from the given context.
+     */
+    public static LayoutInflater from(@UiContext Context context) {
+        LayoutInflater LayoutInflater =
+                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        if (LayoutInflater == null) {
+            throw new AssertionError("LayoutInflater not found.");
+        }
+        return LayoutInflater;
+    }
+
+    /**
+     * Create a copy of the existing LayoutInflater object, with the copy
+     * pointing to a different Context than the original.  This is used by
+     * {@link ContextThemeWrapper} to create a new LayoutInflater to go along
+     * with the new Context theme.
+     *
+     * @param newContext The new Context to associate with the new LayoutInflater.
+     * May be the same as the original Context if desired.
+     *
+     * @return Returns a brand spanking new LayoutInflater object associated with
+     * the given Context.
+     */
+    public abstract LayoutInflater cloneInContext(Context newContext);
+
+    /**
+     * Return the context we are running in, for access to resources, class
+     * loader, etc.
+     */
+    public Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * Return the current {@link Factory} (or null). This is called on each element
+     * name. If the factory returns a View, add that to the hierarchy. If it
+     * returns null, proceed to call onCreateView(name).
+     */
+    public final Factory getFactory() {
+        return mFactory;
+    }
+
+    /**
+     * Return the current {@link Factory2}.  Returns null if no factory is set
+     * or the set factory does not implement the {@link Factory2} interface.
+     * This is called on each element
+     * name. If the factory returns a View, add that to the hierarchy. If it
+     * returns null, proceed to call onCreateView(name).
+     */
+    public final Factory2 getFactory2() {
+        return mFactory2;
+    }
+
+    /**
+     * Attach a custom Factory interface for creating views while using
+     * this LayoutInflater.  This must not be null, and can only be set once;
+     * after setting, you can not change the factory.  This is
+     * called on each element name as the xml is parsed. If the factory returns
+     * a View, that is added to the hierarchy. If it returns null, the next
+     * factory default {@link #onCreateView} method is called.
+     *
+     * <p>If you have an existing
+     * LayoutInflater and want to add your own factory to it, use
+     * {@link #cloneInContext} to clone the existing instance and then you
+     * can use this function (once) on the returned new instance.  This will
+     * merge your own factory with whatever factory the original instance is
+     * using.
+     */
+    public void setFactory(Factory factory) {
+        if (mFactorySet) {
+            throw new IllegalStateException("A factory has already been set on this LayoutInflater");
+        }
+        if (factory == null) {
+            throw new NullPointerException("Given factory can not be null");
+        }
+        mFactorySet = true;
+        if (mFactory == null) {
+            mFactory = factory;
+        } else {
+            mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
+        }
+    }
+
+    /**
+     * Like {@link #setFactory}, but allows you to set a {@link Factory2}
+     * interface.
+     */
+    public void setFactory2(Factory2 factory) {
+        if (mFactorySet) {
+            throw new IllegalStateException("A factory has already been set on this LayoutInflater");
+        }
+        if (factory == null) {
+            throw new NullPointerException("Given factory can not be null");
+        }
+        mFactorySet = true;
+        if (mFactory == null) {
+            mFactory = mFactory2 = factory;
+        } else {
+            mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
+        }
+    }
+
+    /**
+     * @hide for use by framework
+     */
+    @UnsupportedAppUsage
+    public void setPrivateFactory(Factory2 factory) {
+        if (mPrivateFactory == null) {
+            mPrivateFactory = factory;
+        } else {
+            mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
+        }
+    }
+
+    /**
+     * @return The {@link Filter} currently used by this LayoutInflater to restrict the set of Views
+     * that are allowed to be inflated.
+     */
+    public Filter getFilter() {
+        return mFilter;
+    }
+
+    /**
+     * Sets the {@link Filter} to by this LayoutInflater. If a view is attempted to be inflated
+     * which is not allowed by the {@link Filter}, the {@link #inflate(int, ViewGroup)} call will
+     * throw an {@link InflateException}. This filter will replace any previous filter set on this
+     * LayoutInflater.
+     *
+     * @param filter The Filter which restricts the set of Views that are allowed to be inflated.
+     *        This filter will replace any previous filter set on this LayoutInflater.
+     */
+    public void setFilter(Filter filter) {
+        mFilter = filter;
+        if (filter != null) {
+            mFilterMap = new HashMap<String, Boolean>();
+        }
+    }
+
+    private void initPrecompiledViews() {
+        // Precompiled layouts are not supported in this release.
+        boolean enabled = false;
+        initPrecompiledViews(enabled);
+    }
+
+    private void initPrecompiledViews(boolean enablePrecompiledViews) {
+        mUseCompiledView = enablePrecompiledViews;
+
+        if (!mUseCompiledView) {
+            mPrecompiledClassLoader = null;
+            return;
+        }
+
+        // Make sure the application allows code generation
+        ApplicationInfo appInfo = mContext.getApplicationInfo();
+        if (appInfo.isEmbeddedDexUsed() || appInfo.isPrivilegedApp()) {
+            mUseCompiledView = false;
+            return;
+        }
+
+        // Try to load the precompiled layout file.
+        try {
+            mPrecompiledClassLoader = mContext.getClassLoader();
+            String dexFile = mContext.getCodeCacheDir() + COMPILED_VIEW_DEX_FILE_NAME;
+            if (new File(dexFile).exists()) {
+                mPrecompiledClassLoader = new PathClassLoader(dexFile, mPrecompiledClassLoader);
+            } else {
+                // If the precompiled layout file doesn't exist, then disable precompiled
+                // layouts.
+                mUseCompiledView = false;
+            }
+        } catch (Throwable e) {
+            if (DEBUG) {
+                Log.e(TAG, "Failed to initialized precompiled views layouts", e);
+            }
+            mUseCompiledView = false;
+        }
+        if (!mUseCompiledView) {
+            mPrecompiledClassLoader = null;
+        }
+    }
+
+    /**
+     * @hide for use by CTS tests
+     */
+    @TestApi
+    public void setPrecompiledLayoutsEnabledForTesting(boolean enablePrecompiledLayouts) {
+        initPrecompiledViews(enablePrecompiledLayouts);
+    }
+
+    /**
+     * Inflate a new view hierarchy from the specified xml resource. Throws
+     * {@link InflateException} if there is an error.
+     *
+     * @param resource ID for an XML layout resource to load (e.g.,
+     *        <code>R.layout.main_page</code>)
+     * @param root Optional view to be the parent of the generated hierarchy.
+     * @return The root View of the inflated hierarchy. If root was supplied,
+     *         this is the root View; otherwise it is the root of the inflated
+     *         XML file.
+     */
+    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
+        return inflate(resource, root, root != null);
+    }
+
+    /**
+     * Inflate a new view hierarchy from the specified xml node. Throws
+     * {@link InflateException} if there is an error. *
+     * <p>
+     * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
+     * reasons, view inflation relies heavily on pre-processing of XML files
+     * that is done at build time. Therefore, it is not currently possible to
+     * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
+     *
+     * @param parser XML dom node containing the description of the view
+     *        hierarchy.
+     * @param root Optional view to be the parent of the generated hierarchy.
+     * @return The root View of the inflated hierarchy. If root was supplied,
+     *         this is the root View; otherwise it is the root of the inflated
+     *         XML file.
+     */
+    public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
+        return inflate(parser, root, root != null);
+    }
+
+    /**
+     * Inflate a new view hierarchy from the specified xml resource. Throws
+     * {@link InflateException} if there is an error.
+     *
+     * @param resource ID for an XML layout resource to load (e.g.,
+     *        <code>R.layout.main_page</code>)
+     * @param root Optional view to be the parent of the generated hierarchy (if
+     *        <em>attachToRoot</em> is true), or else simply an object that
+     *        provides a set of LayoutParams values for root of the returned
+     *        hierarchy (if <em>attachToRoot</em> is false.)
+     * @param attachToRoot Whether the inflated hierarchy should be attached to
+     *        the root parameter? If false, root is only used to create the
+     *        correct subclass of LayoutParams for the root view in the XML.
+     * @return The root View of the inflated hierarchy. If root was supplied and
+     *         attachToRoot is true, this is root; otherwise it is the root of
+     *         the inflated XML file.
+     */
+    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
+        final Resources res = getContext().getResources();
+        if (DEBUG) {
+            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+                  + Integer.toHexString(resource) + ")");
+        }
+
+        View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
+        if (view != null) {
+            return view;
+        }
+        XmlResourceParser parser = res.getLayout(resource);
+        try {
+            return inflate(parser, root, attachToRoot);
+        } finally {
+            parser.close();
+        }
+    }
+
+    private @Nullable
+    View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root,
+        boolean attachToRoot) {
+        if (!mUseCompiledView) {
+            return null;
+        }
+
+        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate (precompiled)");
+
+        // Try to inflate using a precompiled layout.
+        String pkg = res.getResourcePackageName(resource);
+        String layout = res.getResourceEntryName(resource);
+
+        try {
+            Class clazz = Class.forName("" + pkg + ".CompiledView", false, mPrecompiledClassLoader);
+            Method inflater = clazz.getMethod(layout, Context.class, int.class);
+            View view = (View) inflater.invoke(null, mContext, resource);
+
+            if (view != null && root != null) {
+                // We were able to use the precompiled inflater, but now we need to do some work to
+                // attach the view to the root correctly.
+                XmlResourceParser parser = res.getLayout(resource);
+                try {
+                    AttributeSet attrs = Xml.asAttributeSet(parser);
+                    advanceToRootNode(parser);
+                    ViewGroup.LayoutParams params = root.generateLayoutParams(attrs);
+
+                    if (attachToRoot) {
+                        root.addView(view, params);
+                    } else {
+                        view.setLayoutParams(params);
+                    }
+                } finally {
+                    parser.close();
+                }
+            }
+
+            return view;
+        } catch (Throwable e) {
+            if (DEBUG) {
+                Log.e(TAG, "Failed to use precompiled view", e);
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        }
+        return null;
+    }
+
+    /**
+     * Advances the given parser to the first START_TAG. Throws InflateException if no start tag is
+     * found.
+     */
+    private void advanceToRootNode(XmlPullParser parser)
+        throws InflateException, IOException, XmlPullParserException {
+        // Look for the root node.
+        int type;
+        while ((type = parser.next()) != XmlPullParser.START_TAG &&
+            type != XmlPullParser.END_DOCUMENT) {
+            // Empty
+        }
+
+        if (type != XmlPullParser.START_TAG) {
+            throw new InflateException(parser.getPositionDescription()
+                + ": No start tag found!");
+        }
+    }
+
+    /**
+     * Inflate a new view hierarchy from the specified XML node. Throws
+     * {@link InflateException} if there is an error.
+     * <p>
+     * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
+     * reasons, view inflation relies heavily on pre-processing of XML files
+     * that is done at build time. Therefore, it is not currently possible to
+     * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
+     *
+     * @param parser XML dom node containing the description of the view
+     *        hierarchy.
+     * @param root Optional view to be the parent of the generated hierarchy (if
+     *        <em>attachToRoot</em> is true), or else simply an object that
+     *        provides a set of LayoutParams values for root of the returned
+     *        hierarchy (if <em>attachToRoot</em> is false.)
+     * @param attachToRoot Whether the inflated hierarchy should be attached to
+     *        the root parameter? If false, root is only used to create the
+     *        correct subclass of LayoutParams for the root view in the XML.
+     * @return The root View of the inflated hierarchy. If root was supplied and
+     *         attachToRoot is true, this is root; otherwise it is the root of
+     *         the inflated XML file.
+     */
+    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
+        synchronized (mConstructorArgs) {
+            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
+
+            final Context inflaterContext = mContext;
+            final AttributeSet attrs = Xml.asAttributeSet(parser);
+            Context lastContext = (Context) mConstructorArgs[0];
+            mConstructorArgs[0] = inflaterContext;
+            View result = root;
+
+            try {
+                advanceToRootNode(parser);
+                final String name = parser.getName();
+
+                if (DEBUG) {
+                    System.out.println("**************************");
+                    System.out.println("Creating root view: "
+                            + name);
+                    System.out.println("**************************");
+                }
+
+                if (TAG_MERGE.equals(name)) {
+                    if (root == null || !attachToRoot) {
+                        throw new InflateException("<merge /> can be used only with a valid "
+                                + "ViewGroup root and attachToRoot=true");
+                    }
+
+                    rInflate(parser, root, inflaterContext, attrs, false);
+                } else {
+                    // Temp is the root view that was found in the xml
+                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
+
+                    ViewGroup.LayoutParams params = null;
+
+                    if (root != null) {
+                        if (DEBUG) {
+                            System.out.println("Creating params from root: " +
+                                    root);
+                        }
+                        // Create layout params that match root, if supplied
+                        params = root.generateLayoutParams(attrs);
+                        if (!attachToRoot) {
+                            // Set the layout params for temp if we are not
+                            // attaching. (If we are, we use addView, below)
+                            temp.setLayoutParams(params);
+                        }
+                    }
+
+                    if (DEBUG) {
+                        System.out.println("-----> start inflating children");
+                    }
+
+                    // Inflate all children under temp against its context.
+                    rInflateChildren(parser, temp, attrs, true);
+
+                    if (DEBUG) {
+                        System.out.println("-----> done inflating children");
+                    }
+
+                    // We are supposed to attach all the views we found (int temp)
+                    // to root. Do that now.
+                    if (root != null && attachToRoot) {
+                        root.addView(temp, params);
+                    }
+
+                    // Decide whether to return the root that was passed in or the
+                    // top view found in xml.
+                    if (root == null || !attachToRoot) {
+                        result = temp;
+                    }
+                }
+
+            } catch (XmlPullParserException e) {
+                final InflateException ie = new InflateException(e.getMessage(), e);
+                ie.setStackTrace(EMPTY_STACK_TRACE);
+                throw ie;
+            } catch (Exception e) {
+                final InflateException ie = new InflateException(
+                        getParserStateDescription(inflaterContext, attrs)
+                        + ": " + e.getMessage(), e);
+                ie.setStackTrace(EMPTY_STACK_TRACE);
+                throw ie;
+            } finally {
+                // Don't retain static reference on context.
+                mConstructorArgs[0] = lastContext;
+                mConstructorArgs[1] = null;
+
+                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            }
+
+            return result;
+        }
+    }
+
+    private static String getParserStateDescription(Context context, AttributeSet attrs) {
+        int sourceResId = Resources.getAttributeSetSourceResId(attrs);
+        if (sourceResId == Resources.ID_NULL) {
+            return attrs.getPositionDescription();
+        } else {
+            return attrs.getPositionDescription() + " in "
+                    + context.getResources().getResourceName(sourceResId);
+        }
+    }
+
+    private static final ClassLoader BOOT_CLASS_LOADER = LayoutInflater.class.getClassLoader();
+
+    private final boolean verifyClassLoader(Constructor<? extends View> constructor) {
+        final ClassLoader constructorLoader = constructor.getDeclaringClass().getClassLoader();
+        if (constructorLoader == BOOT_CLASS_LOADER) {
+            // fast path for boot class loader (most common case?) - always ok
+            return true;
+        }
+        // in all normal cases (no dynamic code loading), we will exit the following loop on the
+        // first iteration (i.e. when the declaring classloader is the contexts class loader).
+        ClassLoader cl = mContext.getClassLoader();
+        do {
+            if (constructorLoader == cl) {
+                return true;
+            }
+            cl = cl.getParent();
+        } while (cl != null);
+        return false;
+    }
+    /**
+     * Low-level function for instantiating a view by name. This attempts to
+     * instantiate a view class of the given <var>name</var> found in this
+     * LayoutInflater's ClassLoader. To use an explicit Context in the View
+     * constructor, use {@link #createView(Context, String, String, AttributeSet)} instead.
+     *
+     * <p>
+     * There are two things that can happen in an error case: either the
+     * exception describing the error will be thrown, or a null will be
+     * returned. You must deal with both possibilities -- the former will happen
+     * the first time createView() is called for a class of a particular name,
+     * the latter every time there-after for that class name.
+     *
+     * @param name The full name of the class to be instantiated.
+     * @param attrs The XML attributes supplied for this instance.
+     *
+     * @return View The newly instantiated view, or null.
+     */
+    public final View createView(String name, String prefix, AttributeSet attrs)
+            throws ClassNotFoundException, InflateException {
+        Context context = (Context) mConstructorArgs[0];
+        if (context == null) {
+            context = mContext;
+        }
+        return createView(context, name, prefix, attrs);
+    }
+
+    /**
+     * Low-level function for instantiating a view by name. This attempts to
+     * instantiate a view class of the given <var>name</var> found in this
+     * LayoutInflater's ClassLoader.
+     *
+     * <p>
+     * There are two things that can happen in an error case: either the
+     * exception describing the error will be thrown, or a null will be
+     * returned. You must deal with both possibilities -- the former will happen
+     * the first time createView() is called for a class of a particular name,
+     * the latter every time there-after for that class name.
+     *
+     * @param viewContext The context used as the context parameter of the View constructor
+     * @param name The full name of the class to be instantiated.
+     * @param attrs The XML attributes supplied for this instance.
+     *
+     * @return View The newly instantiated view, or null.
+     */
+    @Nullable
+    public final View createView(@NonNull Context viewContext, @NonNull String name,
+            @Nullable String prefix, @Nullable AttributeSet attrs)
+            throws ClassNotFoundException, InflateException {
+        Objects.requireNonNull(viewContext);
+        Objects.requireNonNull(name);
+        Constructor<? extends View> constructor = sConstructorMap.get(name);
+        if (constructor != null && !verifyClassLoader(constructor)) {
+            constructor = null;
+            sConstructorMap.remove(name);
+        }
+        Class<? extends View> clazz = null;
+
+        try {
+            Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
+
+            if (constructor == null) {
+                // Class not found in the cache, see if it's real, and try to add it
+                clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
+                        mContext.getClassLoader()).asSubclass(View.class);
+
+                if (mFilter != null && clazz != null) {
+                    boolean allowed = mFilter.onLoadClass(clazz);
+                    if (!allowed) {
+                        failNotAllowed(name, prefix, viewContext, attrs);
+                    }
+                }
+                constructor = clazz.getConstructor(mConstructorSignature);
+                constructor.setAccessible(true);
+                sConstructorMap.put(name, constructor);
+            } else {
+                // If we have a filter, apply it to cached constructor
+                if (mFilter != null) {
+                    // Have we seen this name before?
+                    Boolean allowedState = mFilterMap.get(name);
+                    if (allowedState == null) {
+                        // New class -- remember whether it is allowed
+                        clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
+                                mContext.getClassLoader()).asSubclass(View.class);
+
+                        boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
+                        mFilterMap.put(name, allowed);
+                        if (!allowed) {
+                            failNotAllowed(name, prefix, viewContext, attrs);
+                        }
+                    } else if (allowedState.equals(Boolean.FALSE)) {
+                        failNotAllowed(name, prefix, viewContext, attrs);
+                    }
+                }
+            }
+
+            Object lastContext = mConstructorArgs[0];
+            mConstructorArgs[0] = viewContext;
+            Object[] args = mConstructorArgs;
+            args[1] = attrs;
+
+            try {
+                final View view = constructor.newInstance(args);
+                if (view instanceof ViewStub) {
+                    // Use the same context when inflating ViewStub later.
+                    final ViewStub viewStub = (ViewStub) view;
+                    viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
+                }
+                return view;
+            } finally {
+                mConstructorArgs[0] = lastContext;
+            }
+        } catch (NoSuchMethodException e) {
+            final InflateException ie = new InflateException(
+                    getParserStateDescription(viewContext, attrs)
+                    + ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);
+            ie.setStackTrace(EMPTY_STACK_TRACE);
+            throw ie;
+
+        } catch (ClassCastException e) {
+            // If loaded class is not a View subclass
+            final InflateException ie = new InflateException(
+                    getParserStateDescription(viewContext, attrs)
+                    + ": Class is not a View " + (prefix != null ? (prefix + name) : name), e);
+            ie.setStackTrace(EMPTY_STACK_TRACE);
+            throw ie;
+        } catch (ClassNotFoundException e) {
+            // If loadClass fails, we should propagate the exception.
+            throw e;
+        } catch (Exception e) {
+            final InflateException ie = new InflateException(
+                    getParserStateDescription(viewContext, attrs) + ": Error inflating class "
+                            + (clazz == null ? "<unknown>" : clazz.getName()), e);
+            ie.setStackTrace(EMPTY_STACK_TRACE);
+            throw ie;
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        }
+    }
+
+    /**
+     * Throw an exception because the specified class is not allowed to be inflated.
+     */
+    private void failNotAllowed(String name, String prefix, Context context, AttributeSet attrs) {
+        throw new InflateException(getParserStateDescription(context, attrs)
+                + ": Class not allowed to be inflated "+ (prefix != null ? (prefix + name) : name));
+    }
+
+    /**
+     * This routine is responsible for creating the correct subclass of View
+     * given the xml element name. Override it to handle custom view objects. If
+     * you override this in your subclass be sure to call through to
+     * super.onCreateView(name) for names you do not recognize.
+     *
+     * @param name The fully qualified class name of the View to be create.
+     * @param attrs An AttributeSet of attributes to apply to the View.
+     *
+     * @return View The View created.
+     */
+    protected View onCreateView(String name, AttributeSet attrs)
+            throws ClassNotFoundException {
+        return createView(name, "android.view.", attrs);
+    }
+
+    /**
+     * Version of {@link #onCreateView(String, AttributeSet)} that also
+     * takes the future parent of the view being constructed.  The default
+     * implementation simply calls {@link #onCreateView(String, AttributeSet)}.
+     *
+     * @param parent The future parent of the returned view.  <em>Note that
+     * this may be null.</em>
+     * @param name The fully qualified class name of the View to be create.
+     * @param attrs An AttributeSet of attributes to apply to the View.
+     *
+     * @return View The View created.
+     */
+    protected View onCreateView(View parent, String name, AttributeSet attrs)
+            throws ClassNotFoundException {
+        return onCreateView(name, attrs);
+    }
+
+    /**
+     * Version of {@link #onCreateView(View, String, AttributeSet)} that also
+     * takes the inflation context.  The default
+     * implementation simply calls {@link #onCreateView(View, String, AttributeSet)}.
+     *
+     * @param viewContext The Context to be used as a constructor parameter for the View
+     * @param parent The future parent of the returned view.  <em>Note that
+     * this may be null.</em>
+     * @param name The fully qualified class name of the View to be create.
+     * @param attrs An AttributeSet of attributes to apply to the View.
+     *
+     * @return View The View created.
+     */
+    @Nullable
+    public View onCreateView(@NonNull Context viewContext, @Nullable View parent,
+            @NonNull String name, @Nullable AttributeSet attrs)
+            throws ClassNotFoundException {
+        return onCreateView(parent, name, attrs);
+    }
+
+    /**
+     * Convenience method for calling through to the five-arg createViewFromTag
+     * method. This method passes {@code false} for the {@code ignoreThemeAttr}
+     * argument and should be used for everything except {@code &gt;include>}
+     * tag parsing.
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
+        return createViewFromTag(parent, name, context, attrs, false);
+    }
+
+    /**
+     * Creates a view from a tag name using the supplied attribute set.
+     * <p>
+     * <strong>Note:</strong> Default visibility so the BridgeInflater can
+     * override it.
+     *
+     * @param parent the parent view, used to inflate layout params
+     * @param name the name of the XML tag used to define the view
+     * @param context the inflation context for the view, typically the
+     *                {@code parent} or base layout inflater context
+     * @param attrs the attribute set for the XML tag used to define the view
+     * @param ignoreThemeAttr {@code true} to ignore the {@code android:theme}
+     *                        attribute (if set) for the view being inflated,
+     *                        {@code false} otherwise
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
+            boolean ignoreThemeAttr) {
+        if (name.equals("view")) {
+            name = attrs.getAttributeValue(null, "class");
+        }
+
+        // Apply a theme wrapper, if allowed and one is specified.
+        if (!ignoreThemeAttr) {
+            final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
+            final int themeResId = ta.getResourceId(0, 0);
+            if (themeResId != 0) {
+                context = new ContextThemeWrapper(context, themeResId);
+            }
+            ta.recycle();
+        }
+
+        try {
+            View view = tryCreateView(parent, name, context, attrs);
+
+            if (view == null) {
+                final Object lastContext = mConstructorArgs[0];
+                mConstructorArgs[0] = context;
+                try {
+                    if (-1 == name.indexOf('.')) {
+                        view = onCreateView(context, parent, name, attrs);
+                    } else {
+                        view = createView(context, name, null, attrs);
+                    }
+                } finally {
+                    mConstructorArgs[0] = lastContext;
+                }
+            }
+
+            return view;
+        } catch (InflateException e) {
+            throw e;
+
+        } catch (ClassNotFoundException e) {
+            final InflateException ie = new InflateException(
+                    getParserStateDescription(context, attrs)
+                    + ": Error inflating class " + name, e);
+            ie.setStackTrace(EMPTY_STACK_TRACE);
+            throw ie;
+
+        } catch (Exception e) {
+            final InflateException ie = new InflateException(
+                    getParserStateDescription(context, attrs)
+                    + ": Error inflating class " + name, e);
+            ie.setStackTrace(EMPTY_STACK_TRACE);
+            throw ie;
+        }
+    }
+
+    /**
+     * Tries to create a view from a tag name using the supplied attribute set.
+     *
+     * This method gives the factory provided by {@link LayoutInflater#setFactory} and
+     * {@link LayoutInflater#setFactory2} a chance to create a view. However, it does not apply all
+     * of the general view creation logic, and thus may return {@code null} for some tags. This
+     * method is used by {@link LayoutInflater#inflate} in creating {@code View} objects.
+     *
+     * @hide for use by precompiled layouts.
+     *
+     * @param parent the parent view, used to inflate layout params
+     * @param name the name of the XML tag used to define the view
+     * @param context the inflation context for the view, typically the
+     *                {@code parent} or base layout inflater context
+     * @param attrs the attribute set for the XML tag used to define the view
+     */
+    @UnsupportedAppUsage(trackingBug = 122360734)
+    @Nullable
+    public final View tryCreateView(@Nullable View parent, @NonNull String name,
+        @NonNull Context context,
+        @NonNull AttributeSet attrs) {
+        if (name.equals(TAG_1995)) {
+            // Let's party like it's 1995!
+            return new BlinkLayout(context, attrs);
+        }
+
+        View view;
+        if (mFactory2 != null) {
+            view = mFactory2.onCreateView(parent, name, context, attrs);
+        } else if (mFactory != null) {
+            view = mFactory.onCreateView(name, context, attrs);
+        } else {
+            view = null;
+        }
+
+        if (view == null && mPrivateFactory != null) {
+            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
+        }
+
+        return view;
+    }
+
+    /**
+     * Recursive method used to inflate internal (non-root) children. This
+     * method calls through to {@link #rInflate} using the parent context as
+     * the inflation context.
+     * <strong>Note:</strong> Default visibility so the BridgeInflater can
+     * call it.
+     */
+    final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
+            boolean finishInflate) throws XmlPullParserException, IOException {
+        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
+    }
+
+    /**
+     * Recursive method used to descend down the xml hierarchy and instantiate
+     * views, instantiate their children, and then call onFinishInflate().
+     * <p>
+     * <strong>Note:</strong> Default visibility so the BridgeInflater can
+     * override it.
+     */
+    void rInflate(XmlPullParser parser, View parent, Context context,
+            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
+
+        final int depth = parser.getDepth();
+        int type;
+        boolean pendingRequestFocus = false;
+
+        while (((type = parser.next()) != XmlPullParser.END_TAG ||
+                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
+
+            if (type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            final String name = parser.getName();
+
+            if (TAG_REQUEST_FOCUS.equals(name)) {
+                pendingRequestFocus = true;
+                consumeChildElements(parser);
+            } else if (TAG_TAG.equals(name)) {
+                parseViewTag(parser, parent, attrs);
+            } else if (TAG_INCLUDE.equals(name)) {
+                if (parser.getDepth() == 0) {
+                    throw new InflateException("<include /> cannot be the root element");
+                }
+                parseInclude(parser, context, parent, attrs);
+            } else if (TAG_MERGE.equals(name)) {
+                throw new InflateException("<merge /> must be the root element");
+            } else {
+                final View view = createViewFromTag(parent, name, context, attrs);
+                final ViewGroup viewGroup = (ViewGroup) parent;
+                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
+                rInflateChildren(parser, view, attrs, true);
+                viewGroup.addView(view, params);
+            }
+        }
+
+        if (pendingRequestFocus) {
+            parent.restoreDefaultFocus();
+        }
+
+        if (finishInflate) {
+            parent.onFinishInflate();
+        }
+    }
+
+    /**
+     * Parses a <code>&lt;tag&gt;</code> element and sets a keyed tag on the
+     * containing View.
+     */
+    private void parseViewTag(XmlPullParser parser, View view, AttributeSet attrs)
+            throws XmlPullParserException, IOException {
+        final Context context = view.getContext();
+        final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ViewTag);
+        final int key = ta.getResourceId(R.styleable.ViewTag_id, 0);
+        final CharSequence value = ta.getText(R.styleable.ViewTag_value);
+        view.setTag(key, value);
+        ta.recycle();
+
+        consumeChildElements(parser);
+    }
+
+    @UnsupportedAppUsage
+    private void parseInclude(XmlPullParser parser, Context context, View parent,
+            AttributeSet attrs) throws XmlPullParserException, IOException {
+        int type;
+
+        if (!(parent instanceof ViewGroup)) {
+            throw new InflateException("<include /> can only be used inside of a ViewGroup");
+        }
+
+        // Apply a theme wrapper, if requested. This is sort of a weird
+        // edge case, since developers think the <include> overwrites
+        // values in the AttributeSet of the included View. So, if the
+        // included View has a theme attribute, we'll need to ignore it.
+        final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
+        final int themeResId = ta.getResourceId(0, 0);
+        final boolean hasThemeOverride = themeResId != 0;
+        if (hasThemeOverride) {
+            context = new ContextThemeWrapper(context, themeResId);
+        }
+        ta.recycle();
+
+        // If the layout is pointing to a theme attribute, we have to
+        // massage the value to get a resource identifier out of it.
+        int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
+        if (layout == 0) {
+            final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
+            if (value == null || value.length() <= 0) {
+                throw new InflateException("You must specify a layout in the"
+                    + " include tag: <include layout=\"@layout/layoutID\" />");
+            }
+
+            // Attempt to resolve the "?attr/name" string to an attribute
+            // within the default (e.g. application) package.
+            layout = context.getResources().getIdentifier(
+                value.substring(1), "attr", context.getPackageName());
+
+        }
+
+        // The layout might be referencing a theme attribute.
+        if (mTempValue == null) {
+            mTempValue = new TypedValue();
+        }
+        if (layout != 0 && context.getTheme().resolveAttribute(layout, mTempValue, true)) {
+            layout = mTempValue.resourceId;
+        }
+
+        if (layout == 0) {
+            final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
+            throw new InflateException("You must specify a valid layout "
+                + "reference. The layout ID " + value + " is not valid.");
+        }
+
+        final View precompiled = tryInflatePrecompiled(layout, context.getResources(),
+            (ViewGroup) parent, /*attachToRoot=*/true);
+        if (precompiled == null) {
+            final XmlResourceParser childParser = context.getResources().getLayout(layout);
+
+            try {
+                final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
+
+                while ((type = childParser.next()) != XmlPullParser.START_TAG &&
+                    type != XmlPullParser.END_DOCUMENT) {
+                    // Empty.
+                }
+
+                if (type != XmlPullParser.START_TAG) {
+                    throw new InflateException(getParserStateDescription(context, childAttrs)
+                            + ": No start tag found!");
+                }
+
+                final String childName = childParser.getName();
+
+                if (TAG_MERGE.equals(childName)) {
+                    // The <merge> tag doesn't support android:theme, so
+                    // nothing special to do here.
+                    rInflate(childParser, parent, context, childAttrs, false);
+                } else {
+                    final View view = createViewFromTag(parent, childName,
+                        context, childAttrs, hasThemeOverride);
+                    final ViewGroup group = (ViewGroup) parent;
+
+                    final TypedArray a = context.obtainStyledAttributes(
+                        attrs, R.styleable.Include);
+                    final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
+                    final int visibility = a.getInt(R.styleable.Include_visibility, -1);
+                    a.recycle();
+
+                    // We try to load the layout params set in the <include /> tag.
+                    // If the parent can't generate layout params (ex. missing width
+                    // or height for the framework ViewGroups, though this is not
+                    // necessarily true of all ViewGroups) then we expect it to throw
+                    // a runtime exception.
+                    // We catch this exception and set localParams accordingly: true
+                    // means we successfully loaded layout params from the <include>
+                    // tag, false means we need to rely on the included layout params.
+                    ViewGroup.LayoutParams params = null;
+                    try {
+                        params = group.generateLayoutParams(attrs);
+                    } catch (RuntimeException e) {
+                        // Ignore, just fail over to child attrs.
+                    }
+                    if (params == null) {
+                        params = group.generateLayoutParams(childAttrs);
+                    }
+                    view.setLayoutParams(params);
+
+                    // Inflate all children.
+                    rInflateChildren(childParser, view, childAttrs, true);
+
+                    if (id != View.NO_ID) {
+                        view.setId(id);
+                    }
+
+                    switch (visibility) {
+                        case 0:
+                            view.setVisibility(View.VISIBLE);
+                            break;
+                        case 1:
+                            view.setVisibility(View.INVISIBLE);
+                            break;
+                        case 2:
+                            view.setVisibility(View.GONE);
+                            break;
+                    }
+
+                    group.addView(view);
+                }
+            } finally {
+                childParser.close();
+            }
+        }
+        LayoutInflater.consumeChildElements(parser);
+    }
+
+    /**
+     * <strong>Note:</strong> default visibility so that
+     * LayoutInflater_Delegate can call it.
+     */
+    final static void consumeChildElements(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        int type;
+        final int currentDepth = parser.getDepth();
+        while (((type = parser.next()) != XmlPullParser.END_TAG ||
+                parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
+            // Empty
+        }
+    }
+
+    private static class BlinkLayout extends FrameLayout {
+        private static final int MESSAGE_BLINK = 0x42;
+        private static final int BLINK_DELAY = 500;
+
+        private boolean mBlink;
+        private boolean mBlinkState;
+        private final Handler mHandler;
+
+        public BlinkLayout(Context context, AttributeSet attrs) {
+            super(context, attrs);
+            mHandler = new Handler(new Handler.Callback() {
+                @Override
+                public boolean handleMessage(Message msg) {
+                    if (msg.what == MESSAGE_BLINK) {
+                        if (mBlink) {
+                            mBlinkState = !mBlinkState;
+                            makeBlink();
+                        }
+                        invalidate();
+                        return true;
+                    }
+                    return false;
+                }
+            });
+        }
+
+        private void makeBlink() {
+            Message message = mHandler.obtainMessage(MESSAGE_BLINK);
+            mHandler.sendMessageDelayed(message, BLINK_DELAY);
+        }
+
+        @Override
+        protected void onAttachedToWindow() {
+            super.onAttachedToWindow();
+
+            mBlink = true;
+            mBlinkState = true;
+
+            makeBlink();
+        }
+
+        @Override
+        protected void onDetachedFromWindow() {
+            super.onDetachedFromWindow();
+
+            mBlink = false;
+            mBlinkState = true;
+
+            mHandler.removeMessages(MESSAGE_BLINK);
+        }
+
+        @Override
+        protected void dispatchDraw(Canvas canvas) {
+            if (mBlinkState) {
+                super.dispatchDraw(canvas);
+            }
+        }
+    }
+}
diff --git a/android/view/LayoutInflater_Delegate.java b/android/view/LayoutInflater_Delegate.java
new file mode 100644
index 0000000..cb446e7
--- /dev/null
+++ b/android/view/LayoutInflater_Delegate.java
@@ -0,0 +1,247 @@
+/*
+ * 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.view;
+
+import com.android.ide.common.rendering.api.ILayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.util.Xml;
+
+import java.io.IOException;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link LayoutInflater}
+ *
+ * Through the layoutlib_create tool, the original  methods of LayoutInflater have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class LayoutInflater_Delegate {
+    private static final String TAG_MERGE = "merge";
+
+    private static final String ATTR_LAYOUT = "layout";
+
+    private static final int[] ATTRS_THEME = new int[] {
+            com.android.internal.R.attr.theme };
+
+    public static boolean sIsInInclude = false;
+
+    /**
+     * Recursive method used to descend down the xml hierarchy and instantiate
+     * views, instantiate their children, and then call onFinishInflate().
+     *
+     * This implementation just records the merge status before calling the default implementation.
+     */
+    @LayoutlibDelegate
+    /* package */ static void rInflate(LayoutInflater thisInflater, XmlPullParser parser,
+            View parent, Context context, AttributeSet attrs, boolean finishInflate)
+            throws XmlPullParserException, IOException {
+
+        if (finishInflate == false) {
+            // this is a merge rInflate!
+            if (thisInflater instanceof BridgeInflater) {
+                ((BridgeInflater) thisInflater).setIsInMerge(true);
+            }
+        }
+
+        // ---- START DEFAULT IMPLEMENTATION.
+
+        thisInflater.rInflate_Original(parser, parent, context, attrs, finishInflate);
+
+        // ---- END DEFAULT IMPLEMENTATION.
+
+        if (finishInflate == false) {
+            // this is a merge rInflate!
+            if (thisInflater instanceof BridgeInflater) {
+                ((BridgeInflater) thisInflater).setIsInMerge(false);
+            }
+        }
+    }
+
+    @LayoutlibDelegate
+    public static void parseInclude(LayoutInflater thisInflater, XmlPullParser parser,
+            Context context, View parent, AttributeSet attrs)
+            throws XmlPullParserException, IOException {
+        int type;
+
+        if (parent instanceof ViewGroup) {
+            // Apply a theme wrapper, if requested. This is sort of a weird
+            // edge case, since developers think the <include> overwrites
+            // values in the AttributeSet of the included View. So, if the
+            // included View has a theme attribute, we'll need to ignore it.
+            final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
+            final int themeResId = ta.getResourceId(0, 0);
+            final boolean hasThemeOverride = themeResId != 0;
+            if (hasThemeOverride) {
+                context = new ContextThemeWrapper(context, themeResId);
+            }
+            ta.recycle();
+
+            // If the layout is pointing to a theme attribute, we have to
+            // massage the value to get a resource identifier out of it.
+            int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
+            if (layout == 0) {
+                final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
+                if (value == null || value.length() <= 0) {
+                    Bridge.getLog().error(ILayoutLog.TAG_BROKEN, "You must specify a layout in the"
+                            + " include tag: <include layout=\"@layout/layoutID\" />", null, null);
+                    LayoutInflater.consumeChildElements(parser);
+                    return;
+                }
+
+                // Attempt to resolve the "?attr/name" string to an identifier.
+                layout = context.getResources().getIdentifier(value.substring(1), null, null);
+            }
+
+            // The layout might be referencing a theme attribute.
+            // ---- START CHANGES
+            if (layout != 0) {
+                final TypedValue tempValue = new TypedValue();
+                if (context.getTheme().resolveAttribute(layout, tempValue, true)) {
+                    layout = tempValue.resourceId;
+                }
+            }
+            // ---- END CHANGES
+
+            if (layout == 0) {
+                final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
+                if (value == null) {
+                    Bridge.getLog().error(ILayoutLog.TAG_BROKEN, "You must specify a layout in the"
+                            + " include tag: <include layout=\"@layout/layoutID\" />", null, null);
+                } else {
+                    Bridge.getLog().error(ILayoutLog.TAG_BROKEN, "You must specify a valid layout "
+                            + "reference. The layout ID " + value + " is not valid.", null, null);
+                }
+            } else {
+                final XmlResourceParser childParser =
+                    thisInflater.getContext().getResources().getLayout(layout);
+
+                try {
+                    final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
+
+                    while ((type = childParser.next()) != XmlPullParser.START_TAG &&
+                            type != XmlPullParser.END_DOCUMENT) {
+                        // Empty.
+                    }
+
+                    if (type != XmlPullParser.START_TAG) {
+                        Bridge.getLog().error(ILayoutLog.TAG_BROKEN,
+                                childParser.getPositionDescription() + ": No start tag found!",
+                                null, null);
+                        LayoutInflater.consumeChildElements(parser);
+                        return;
+                    }
+
+                    final String childName = childParser.getName();
+
+                    if (TAG_MERGE.equals(childName)) {
+                        // Inflate all children.
+                        thisInflater.rInflate(childParser, parent, context, childAttrs, false);
+                    } else {
+                        final View view = thisInflater.createViewFromTag(parent, childName,
+                                context, childAttrs, hasThemeOverride);
+                        final ViewGroup group = (ViewGroup) parent;
+
+                        final TypedArray a = context.obtainStyledAttributes(
+                                attrs, com.android.internal.R.styleable.Include);
+                        final int id = a.getResourceId(
+                                com.android.internal.R.styleable.Include_id, View.NO_ID);
+                        final int visibility = a.getInt(
+                                com.android.internal.R.styleable.Include_visibility, -1);
+                        a.recycle();
+
+                        // We try to load the layout params set in the <include /> tag. If
+                        // they don't exist, we will rely on the layout params set in the
+                        // included XML file.
+                        // During a layoutparams generation, a runtime exception is thrown
+                        // if either layout_width or layout_height is missing. We catch
+                        // this exception and set localParams accordingly: true means we
+                        // successfully loaded layout params from the <include /> tag,
+                        // false means we need to rely on the included layout params.
+                        ViewGroup.LayoutParams params = null;
+                        try {
+                            // ---- START CHANGES
+                            sIsInInclude = true;
+                            // ---- END CHANGES
+
+                            params = group.generateLayoutParams(attrs);
+                        } catch (RuntimeException ignored) {
+                            // Ignore, just fail over to child attrs.
+                        } finally {
+                            // ---- START CHANGES
+                            sIsInInclude = false;
+                            // ---- END CHANGES
+                        }
+                        if (params == null) {
+                            params = group.generateLayoutParams(childAttrs);
+                        }
+                        view.setLayoutParams(params);
+
+                        // Inflate all children.
+                        thisInflater.rInflateChildren(childParser, view, childAttrs, true);
+
+                        if (id != View.NO_ID) {
+                            view.setId(id);
+                        }
+
+                        switch (visibility) {
+                            case 0:
+                                view.setVisibility(View.VISIBLE);
+                                break;
+                            case 1:
+                                view.setVisibility(View.INVISIBLE);
+                                break;
+                            case 2:
+                                view.setVisibility(View.GONE);
+                                break;
+                        }
+
+                        group.addView(view);
+                    }
+                } finally {
+                    childParser.close();
+                }
+            }
+        } else {
+            Bridge.getLog().error(ILayoutLog.TAG_BROKEN,
+                    "<include /> can only be used inside of a ViewGroup",
+                    null, null);
+        }
+
+        LayoutInflater.consumeChildElements(parser);
+    }
+
+    @LayoutlibDelegate
+    /* package */ static void initPrecompiledViews(LayoutInflater thisInflater) {
+        initPrecompiledViews(thisInflater, false);
+    }
+
+    @LayoutlibDelegate
+    /* package */ static void initPrecompiledViews(LayoutInflater thisInflater,
+            boolean enablePrecompiledViews) {
+        thisInflater.initPrecompiledViews_Original(enablePrecompiledViews);
+    }
+}
diff --git a/android/view/MagnificationSpec.java b/android/view/MagnificationSpec.java
new file mode 100644
index 0000000..50d3113
--- /dev/null
+++ b/android/view/MagnificationSpec.java
@@ -0,0 +1,136 @@
+/*
+ * 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.view;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class represents spec for performing screen magnification.
+ *
+ * @hide
+ */
+public class MagnificationSpec implements Parcelable {
+
+    /** The magnification scaling factor. */
+    public float scale = 1.0f;
+
+    /**
+     * The X coordinate, in unscaled screen-relative pixels, around which
+     * magnification is focused.
+     */
+    public float offsetX;
+
+    /**
+     * The Y coordinate, in unscaled screen-relative pixels, around which
+     * magnification is focused.
+     */
+    public float offsetY;
+
+    public void initialize(float scale, float offsetX, float offsetY) {
+        if (scale < 1) {
+            throw new IllegalArgumentException("Scale must be greater than or equal to one!");
+        }
+        this.scale = scale;
+        this.offsetX = offsetX;
+        this.offsetY = offsetY;
+    }
+
+    public boolean isNop() {
+        return scale == 1.0f && offsetX == 0 && offsetY == 0;
+    }
+
+    public void clear() {
+       scale = 1.0f;
+       offsetX = 0.0f;
+       offsetY = 0.0f;
+    }
+
+    public void setTo(MagnificationSpec other) {
+        scale = other.scale;
+        offsetX = other.offsetX;
+        offsetY = other.offsetY;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeFloat(scale);
+        parcel.writeFloat(offsetX);
+        parcel.writeFloat(offsetY);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object other) {
+        if (this == other) {
+            return true;
+        }
+
+        if (other == null || getClass() != other.getClass()) {
+            return false;
+        }
+
+        final MagnificationSpec s = (MagnificationSpec) other;
+        return scale == s.scale && offsetX == s.offsetX && offsetY == s.offsetY;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = (scale != +0.0f ? Float.floatToIntBits(scale) : 0);
+        result = 31 * result + (offsetX != +0.0f ? Float.floatToIntBits(offsetX) : 0);
+        result = 31 * result + (offsetY != +0.0f ? Float.floatToIntBits(offsetY) : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("<scale:");
+        builder.append(Float.toString(scale));
+        builder.append(",offsetX:");
+        builder.append(Float.toString(offsetX));
+        builder.append(",offsetY:");
+        builder.append(Float.toString(offsetY));
+        builder.append(">");
+        return builder.toString();
+    }
+
+    private void initFromParcel(Parcel parcel) {
+        scale = parcel.readFloat();
+        offsetX = parcel.readFloat();
+        offsetY = parcel.readFloat();
+    }
+
+    public static final @android.annotation.NonNull Creator<MagnificationSpec> CREATOR = new Creator<MagnificationSpec>() {
+        @Override
+        public MagnificationSpec[] newArray(int size) {
+            return new MagnificationSpec[size];
+        }
+
+        @Override
+        public MagnificationSpec createFromParcel(Parcel parcel) {
+            MagnificationSpec spec = new MagnificationSpec();
+            spec.initFromParcel(parcel);
+            return spec;
+        }
+    };
+}
diff --git a/android/view/Menu.java b/android/view/Menu.java
new file mode 100644
index 0000000..6d1f740
--- /dev/null
+++ b/android/view/Menu.java
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.StringRes;
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+
+/**
+ * Interface for managing the items in a menu.
+ * <p>
+ * By default, every Activity supports an options menu of actions or options.
+ * You can add items to this menu and handle clicks on your additions. The
+ * easiest way of adding menu items is inflating an XML file into the
+ * {@link Menu} via {@link MenuInflater}. The easiest way of attaching code to
+ * clicks is via {@link Activity#onOptionsItemSelected(MenuItem)} and
+ * {@link Activity#onContextItemSelected(MenuItem)}.
+ * <p>
+ * Different menu types support different features:
+ * <ol>
+ * <li><b>Context menus</b>: Do not support item shortcuts and item icons.
+ * <li><b>Options menus</b>: The <b>icon menus</b> do not support item check
+ * marks and only show the item's
+ * {@link MenuItem#setTitleCondensed(CharSequence) condensed title}. The
+ * <b>expanded menus</b> (only available if six or more menu items are visible,
+ * reached via the 'More' item in the icon menu) do not show item icons, and
+ * item check marks are discouraged.
+ * <li><b>Sub menus</b>: Do not support item icons, or nested sub menus.
+ * </ol>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about creating menus, read the
+ * <a href="{@docRoot}guide/topics/ui/menus.html">Menus</a> developer guide.</p>
+ * </div>
+ */
+public interface Menu {
+
+    /**
+     * This is the part of an order integer that the user can provide.
+     * @hide
+     */
+    static final int USER_MASK = 0x0000ffff;
+    /**
+     * Bit shift of the user portion of the order integer.
+     * @hide
+     */
+    static final int USER_SHIFT = 0;
+
+    /**
+     * This is the part of an order integer that supplies the category of the
+     * item.
+     * @hide
+     */
+    static final int CATEGORY_MASK = 0xffff0000;
+    /**
+     * Bit shift of the category portion of the order integer.
+     * @hide
+     */
+    static final int CATEGORY_SHIFT = 16;
+
+    /**
+     * A mask of all supported modifiers for MenuItem's keyboard shortcuts
+     */
+    static final int SUPPORTED_MODIFIERS_MASK = KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON
+            | KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON | KeyEvent.META_SYM_ON
+            | KeyEvent.META_FUNCTION_ON;
+
+    /**
+     * Value to use for group and item identifier integers when you don't care
+     * about them.
+     */
+    static final int NONE = 0;
+
+    /**
+     * First value for group and item identifier integers.
+     */
+    static final int FIRST = 1;
+
+    // Implementation note: Keep these CATEGORY_* in sync with the category enum
+    // in attrs.xml
+
+    /**
+     * Category code for the order integer for items/groups that are part of a
+     * container -- or/add this with your base value.
+     */
+    static final int CATEGORY_CONTAINER = 0x00010000;
+
+    /**
+     * Category code for the order integer for items/groups that are provided by
+     * the system -- or/add this with your base value.
+     */
+    static final int CATEGORY_SYSTEM = 0x00020000;
+
+    /**
+     * Category code for the order integer for items/groups that are
+     * user-supplied secondary (infrequently used) options -- or/add this with
+     * your base value.
+     */
+    static final int CATEGORY_SECONDARY = 0x00030000;
+
+    /**
+     * Category code for the order integer for items/groups that are 
+     * alternative actions on the data that is currently displayed -- or/add
+     * this with your base value.
+     */
+    static final int CATEGORY_ALTERNATIVE = 0x00040000;
+
+    /**
+     * Flag for {@link #addIntentOptions}: if set, do not automatically remove
+     * any existing menu items in the same group.
+     */
+    static final int FLAG_APPEND_TO_GROUP = 0x0001;
+
+    /**
+     * Flag for {@link #performShortcut}: if set, do not close the menu after
+     * executing the shortcut.
+     */
+    static final int FLAG_PERFORM_NO_CLOSE = 0x0001;
+
+    /**
+     * Flag for {@link #performShortcut(int, KeyEvent, int)}: if set, always
+     * close the menu after executing the shortcut. Closing the menu also resets
+     * the prepared state.
+     */
+    static final int FLAG_ALWAYS_PERFORM_CLOSE = 0x0002;
+    
+    /**
+     * Add a new item to the menu. This item displays the given title for its
+     * label.
+     * 
+     * @param title The text to display for the item.
+     * @return The newly added menu item.
+     */
+    public MenuItem add(CharSequence title);
+    
+    /**
+     * Add a new item to the menu. This item displays the given title for its
+     * label.
+     * 
+     * @param titleRes Resource identifier of title string.
+     * @return The newly added menu item.
+     */
+    public MenuItem add(@StringRes int titleRes);
+
+    /**
+     * Add a new item to the menu. This item displays the given title for its
+     * label.
+     * 
+     * @param groupId The group identifier that this item should be part of.
+     *        This can be used to define groups of items for batch state
+     *        changes. Normally use {@link #NONE} if an item should not be in a
+     *        group.
+     * @param itemId Unique item ID. Use {@link #NONE} if you do not need a
+     *        unique ID.
+     * @param order The order for the item. Use {@link #NONE} if you do not care
+     *        about the order. See {@link MenuItem#getOrder()}.
+     * @param title The text to display for the item.
+     * @return The newly added menu item.
+     */
+    public MenuItem add(int groupId, int itemId, int order, CharSequence title);
+
+    /**
+     * Variation on {@link #add(int, int, int, CharSequence)} that takes a
+     * string resource identifier instead of the string itself.
+     * 
+     * @param groupId The group identifier that this item should be part of.
+     *        This can also be used to define groups of items for batch state
+     *        changes. Normally use {@link #NONE} if an item should not be in a
+     *        group.
+     * @param itemId Unique item ID. Use {@link #NONE} if you do not need a
+     *        unique ID.
+     * @param order The order for the item. Use {@link #NONE} if you do not care
+     *        about the order. See {@link MenuItem#getOrder()}.
+     * @param titleRes Resource identifier of title string.
+     * @return The newly added menu item.
+     */
+    public MenuItem add(int groupId, int itemId, int order, @StringRes int titleRes);
+
+    /**
+     * Add a new sub-menu to the menu. This item displays the given title for
+     * its label. To modify other attributes on the submenu's menu item, use
+     * {@link SubMenu#getItem()}.
+     * 
+     * @param title The text to display for the item.
+     * @return The newly added sub-menu
+     */
+    SubMenu addSubMenu(final CharSequence title);
+
+    /**
+     * Add a new sub-menu to the menu. This item displays the given title for
+     * its label. To modify other attributes on the submenu's menu item, use
+     * {@link SubMenu#getItem()}.
+     * 
+     * @param titleRes Resource identifier of title string.
+     * @return The newly added sub-menu
+     */
+    SubMenu addSubMenu(@StringRes final int titleRes);
+
+    /**
+     * Add a new sub-menu to the menu. This item displays the given
+     * <var>title</var> for its label. To modify other attributes on the
+     * submenu's menu item, use {@link SubMenu#getItem()}.
+     *<p>
+     * Note that you can only have one level of sub-menus, i.e. you cannnot add
+     * a subMenu to a subMenu: An {@link UnsupportedOperationException} will be
+     * thrown if you try.
+     * 
+     * @param groupId The group identifier that this item should be part of.
+     *        This can also be used to define groups of items for batch state
+     *        changes. Normally use {@link #NONE} if an item should not be in a
+     *        group.
+     * @param itemId Unique item ID. Use {@link #NONE} if you do not need a
+     *        unique ID.
+     * @param order The order for the item. Use {@link #NONE} if you do not care
+     *        about the order. See {@link MenuItem#getOrder()}.
+     * @param title The text to display for the item.
+     * @return The newly added sub-menu
+     */
+    SubMenu addSubMenu(final int groupId, final int itemId, int order, final CharSequence title);
+
+    /**
+     * Variation on {@link #addSubMenu(int, int, int, CharSequence)} that takes
+     * a string resource identifier for the title instead of the string itself.
+     * 
+     * @param groupId The group identifier that this item should be part of.
+     *        This can also be used to define groups of items for batch state
+     *        changes. Normally use {@link #NONE} if an item should not be in a group.
+     * @param itemId Unique item ID. Use {@link #NONE} if you do not need a unique ID.
+     * @param order The order for the item. Use {@link #NONE} if you do not care about the
+     *        order. See {@link MenuItem#getOrder()}.
+     * @param titleRes Resource identifier of title string.
+     * @return The newly added sub-menu
+     */
+    SubMenu addSubMenu(int groupId, int itemId, int order, @StringRes int titleRes);
+
+    /**
+     * Add a group of menu items corresponding to actions that can be performed
+     * for a particular Intent. The Intent is most often configured with a null
+     * action, the data that the current activity is working with, and includes
+     * either the {@link Intent#CATEGORY_ALTERNATIVE} or
+     * {@link Intent#CATEGORY_SELECTED_ALTERNATIVE} to find activities that have
+     * said they would like to be included as optional action. You can, however,
+     * use any Intent you want.
+     * 
+     * <p>
+     * See {@link android.content.pm.PackageManager#queryIntentActivityOptions}
+     * for more * details on the <var>caller</var>, <var>specifics</var>, and
+     * <var>intent</var> arguments. The list returned by that function is used
+     * to populate the resulting menu items.
+     * 
+     * <p>
+     * All of the menu items of possible options for the intent will be added
+     * with the given group and id. You can use the group to control ordering of
+     * the items in relation to other items in the menu. Normally this function
+     * will automatically remove any existing items in the menu in the same
+     * group and place a divider above and below the added items; this behavior
+     * can be modified with the <var>flags</var> parameter. For each of the
+     * generated items {@link MenuItem#setIntent} is called to associate the
+     * appropriate Intent with the item; this means the activity will
+     * automatically be started for you without having to do anything else.
+     * 
+     * @param groupId The group identifier that the items should be part of.
+     *        This can also be used to define groups of items for batch state
+     *        changes. Normally use {@link #NONE} if the items should not be in
+     *        a group.
+     * @param itemId Unique item ID. Use {@link #NONE} if you do not need a
+     *        unique ID.
+     * @param order The order for the items. Use {@link #NONE} if you do not
+     *        care about the order. See {@link MenuItem#getOrder()}.
+     * @param caller The current activity component name as defined by
+     *        queryIntentActivityOptions().
+     * @param specifics Specific items to place first as defined by
+     *        queryIntentActivityOptions().
+     * @param intent Intent describing the kinds of items to populate in the
+     *        list as defined by queryIntentActivityOptions().
+     * @param flags Additional options controlling how the items are added.
+     * @param outSpecificItems Optional array in which to place the menu items
+     *        that were generated for each of the <var>specifics</var> that were
+     *        requested. Entries may be null if no activity was found for that
+     *        specific action.
+     * @return The number of menu items that were added.
+     * 
+     * @see #FLAG_APPEND_TO_GROUP
+     * @see MenuItem#setIntent
+     * @see android.content.pm.PackageManager#queryIntentActivityOptions
+     */
+    public int addIntentOptions(int groupId, int itemId, int order,
+                                ComponentName caller, Intent[] specifics,
+                                Intent intent, int flags, MenuItem[] outSpecificItems);
+
+    /**
+     * Remove the item with the given identifier.
+     *
+     * @param id The item to be removed.  If there is no item with this
+     *           identifier, nothing happens.
+     */
+    public void removeItem(int id);
+
+    /**
+     * Remove all items in the given group.
+     *
+     * @param groupId The group to be removed.  If there are no items in this
+     *           group, nothing happens.
+     */
+    public void removeGroup(int groupId);
+
+    /**
+     * Remove all existing items from the menu, leaving it empty as if it had
+     * just been created.
+     */
+    public void clear();
+
+    /**
+     * Control whether a particular group of items can show a check mark.  This
+     * is similar to calling {@link MenuItem#setCheckable} on all of the menu items
+     * with the given group identifier, but in addition you can control whether
+     * this group contains a mutually-exclusive set items.  This should be called
+     * after the items of the group have been added to the menu.
+     *
+     * @param group The group of items to operate on.
+     * @param checkable Set to true to allow a check mark, false to
+     *                  disallow.  The default is false.
+     * @param exclusive If set to true, only one item in this group can be
+     *                  checked at a time; checking an item will automatically
+     *                  uncheck all others in the group.  If set to false, each
+     *                  item can be checked independently of the others.
+     *
+     * @see MenuItem#setCheckable
+     * @see MenuItem#setChecked
+     */
+    public void setGroupCheckable(int group, boolean checkable, boolean exclusive);
+
+    /**
+     * Show or hide all menu items that are in the given group.
+     *
+     * @param group The group of items to operate on.
+     * @param visible If true the items are visible, else they are hidden.
+     *
+     * @see MenuItem#setVisible
+     */
+    public void setGroupVisible(int group, boolean visible);
+    
+    /**
+     * Enable or disable all menu items that are in the given group.
+     *
+     * @param group The group of items to operate on.
+     * @param enabled If true the items will be enabled, else they will be disabled.
+     *
+     * @see MenuItem#setEnabled
+     */
+    public void setGroupEnabled(int group, boolean enabled);
+    
+    /**
+     * Return whether the menu currently has item items that are visible.
+     *
+     * @return True if there is one or more item visible,
+     *         else false.
+     */
+    public boolean hasVisibleItems();
+
+    /**
+     * Return the menu item with a particular identifier.
+     *
+     * @param id The identifier to find.
+     *
+     * @return The menu item object, or null if there is no item with
+     *         this identifier.
+     */
+    public MenuItem findItem(int id);
+
+    /**
+     * Get the number of items in the menu.  Note that this will change any
+     * times items are added or removed from the menu.
+     *
+     * @return The item count.
+     */
+    public int size();
+
+    /**
+     * Gets the menu item at the given index.
+     * 
+     * @param index The index of the menu item to return.
+     * @return The menu item.
+     * @exception IndexOutOfBoundsException
+     *                when {@code index < 0 || >= size()}
+     */
+    public MenuItem getItem(int index);
+    
+    /**
+     * Closes the menu, if open.
+     */
+    public void close();
+    
+    /**
+     * Execute the menu item action associated with the given shortcut
+     * character.
+     *
+     * @param keyCode The keycode of the shortcut key.
+     * @param event Key event message.
+     * @param flags Additional option flags or 0.
+     *
+     * @return If the given shortcut exists and is shown, returns
+     *         true; else returns false.
+     *
+     * @see #FLAG_PERFORM_NO_CLOSE
+     */
+    public boolean performShortcut(int keyCode, KeyEvent event, int flags);
+
+    /**
+     * Is a keypress one of the defined shortcut keys for this window.
+     * @param keyCode the key code from {@link KeyEvent} to check.
+     * @param event the {@link KeyEvent} to use to help check.
+     */
+    boolean isShortcutKey(int keyCode, KeyEvent event);
+    
+    /**
+     * Execute the menu item action associated with the given menu identifier.
+     * 
+     * @param id Identifier associated with the menu item. 
+     * @param flags Additional option flags or 0.
+     * 
+     * @return If the given identifier exists and is shown, returns
+     *         true; else returns false.
+     * 
+     * @see #FLAG_PERFORM_NO_CLOSE
+     */
+    public boolean performIdentifierAction(int id, int flags);
+
+
+    /**
+     * Control whether the menu should be running in qwerty mode (alphabetic
+     * shortcuts) or 12-key mode (numeric shortcuts).
+     * 
+     * @param isQwerty If true the menu will use alphabetic shortcuts; else it
+     *                 will use numeric shortcuts.
+     */
+    public void setQwertyMode(boolean isQwerty);
+
+    /**
+     * Enable or disable the group dividers.
+     */
+    default void setGroupDividerEnabled(boolean groupDividerEnabled) {
+    }
+}
\ No newline at end of file
diff --git a/android/view/MenuInflater.java b/android/view/MenuInflater.java
new file mode 100644
index 0000000..b6b11ab
--- /dev/null
+++ b/android/view/MenuInflater.java
@@ -0,0 +1,570 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.MenuRes;
+import android.app.Activity;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.BlendMode;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.view.menu.MenuItemImpl;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+/**
+ * This class is used to instantiate menu XML files into Menu objects.
+ * <p>
+ * For performance reasons, menu inflation relies heavily on pre-processing of
+ * XML files that is done at build time. Therefore, it is not currently possible
+ * to use MenuInflater with an XmlPullParser over a plain XML file at runtime;
+ * it only works with an XmlPullParser returned from a compiled resource (R.
+ * <em>something</em> file.)
+ */
+public class MenuInflater {
+    private static final String LOG_TAG = "MenuInflater";
+
+    /** Menu tag name in XML. */
+    private static final String XML_MENU = "menu";
+
+    /** Group tag name in XML. */
+    private static final String XML_GROUP = "group";
+
+    /** Item tag name in XML. */
+    private static final String XML_ITEM = "item";
+
+    private static final int NO_ID = 0;
+
+    private static final Class<?>[] ACTION_VIEW_CONSTRUCTOR_SIGNATURE = new Class[] {Context.class};
+
+    private static final Class<?>[] ACTION_PROVIDER_CONSTRUCTOR_SIGNATURE = ACTION_VIEW_CONSTRUCTOR_SIGNATURE;
+
+    private final Object[] mActionViewConstructorArguments;
+
+    private final Object[] mActionProviderConstructorArguments;
+
+    private Context mContext;
+    private Object mRealOwner;
+
+    /**
+     * Constructs a menu inflater.
+     *
+     * @see Activity#getMenuInflater()
+     */
+    public MenuInflater(Context context) {
+        mContext = context;
+        mActionViewConstructorArguments = new Object[] {context};
+        mActionProviderConstructorArguments = mActionViewConstructorArguments;
+    }
+
+    /**
+     * Constructs a menu inflater.
+     *
+     * @see Activity#getMenuInflater()
+     * @hide
+     */
+    public MenuInflater(Context context, Object realOwner) {
+        mContext = context;
+        mRealOwner = realOwner;
+        mActionViewConstructorArguments = new Object[] {context};
+        mActionProviderConstructorArguments = mActionViewConstructorArguments;
+    }
+
+    /**
+     * Inflate a menu hierarchy from the specified XML resource. Throws
+     * {@link InflateException} if there is an error.
+     *
+     * @param menuRes Resource ID for an XML layout resource to load (e.g.,
+     *            <code>R.menu.main_activity</code>)
+     * @param menu The Menu to inflate into. The items and submenus will be
+     *            added to this Menu.
+     */
+    public void inflate(@MenuRes int menuRes, Menu menu) {
+        XmlResourceParser parser = null;
+        try {
+            parser = mContext.getResources().getLayout(menuRes);
+            AttributeSet attrs = Xml.asAttributeSet(parser);
+
+            parseMenu(parser, attrs, menu);
+        } catch (XmlPullParserException e) {
+            throw new InflateException("Error inflating menu XML", e);
+        } catch (IOException e) {
+            throw new InflateException("Error inflating menu XML", e);
+        } finally {
+            if (parser != null) parser.close();
+        }
+    }
+
+    /**
+     * Called internally to fill the given menu. If a sub menu is seen, it will
+     * call this recursively.
+     */
+    private void parseMenu(XmlPullParser parser, AttributeSet attrs, Menu menu)
+            throws XmlPullParserException, IOException {
+        MenuState menuState = new MenuState(menu);
+
+        int eventType = parser.getEventType();
+        String tagName;
+        boolean lookingForEndOfUnknownTag = false;
+        String unknownTagName = null;
+
+        // This loop will skip to the menu start tag
+        do {
+            if (eventType == XmlPullParser.START_TAG) {
+                tagName = parser.getName();
+                if (tagName.equals(XML_MENU)) {
+                    // Go to next tag
+                    eventType = parser.next();
+                    break;
+                }
+
+                throw new RuntimeException("Expecting menu, got " + tagName);
+            }
+            eventType = parser.next();
+        } while (eventType != XmlPullParser.END_DOCUMENT);
+
+        boolean reachedEndOfMenu = false;
+        while (!reachedEndOfMenu) {
+            switch (eventType) {
+                case XmlPullParser.START_TAG:
+                    if (lookingForEndOfUnknownTag) {
+                        break;
+                    }
+
+                    tagName = parser.getName();
+                    if (tagName.equals(XML_GROUP)) {
+                        menuState.readGroup(attrs);
+                    } else if (tagName.equals(XML_ITEM)) {
+                        menuState.readItem(attrs);
+                    } else if (tagName.equals(XML_MENU)) {
+                        // A menu start tag denotes a submenu for an item
+                        SubMenu subMenu = menuState.addSubMenuItem();
+                        registerMenu(subMenu, attrs);
+
+                        // Parse the submenu into returned SubMenu
+                        parseMenu(parser, attrs, subMenu);
+                    } else {
+                        lookingForEndOfUnknownTag = true;
+                        unknownTagName = tagName;
+                    }
+                    break;
+
+                case XmlPullParser.END_TAG:
+                    tagName = parser.getName();
+                    if (lookingForEndOfUnknownTag && tagName.equals(unknownTagName)) {
+                        lookingForEndOfUnknownTag = false;
+                        unknownTagName = null;
+                    } else if (tagName.equals(XML_GROUP)) {
+                        menuState.resetGroup();
+                    } else if (tagName.equals(XML_ITEM)) {
+                        // Add the item if it hasn't been added (if the item was
+                        // a submenu, it would have been added already)
+                        if (!menuState.hasAddedItem()) {
+                            if (menuState.itemActionProvider != null &&
+                                    menuState.itemActionProvider.hasSubMenu()) {
+                                registerMenu(menuState.addSubMenuItem(), attrs);
+                            } else {
+                                registerMenu(menuState.addItem(), attrs);
+                            }
+                        }
+                    } else if (tagName.equals(XML_MENU)) {
+                        reachedEndOfMenu = true;
+                    }
+                    break;
+
+                case XmlPullParser.END_DOCUMENT:
+                    throw new RuntimeException("Unexpected end of document");
+            }
+
+            eventType = parser.next();
+        }
+    }
+
+    /**
+     * The method is a hook for layoutlib to do its magic.
+     * Nothing is needed outside of LayoutLib. However, it should not be deleted because it
+     * appears to do nothing.
+     */
+    private void registerMenu(@SuppressWarnings("unused") MenuItem item,
+            @SuppressWarnings("unused") AttributeSet set) {
+    }
+
+    /**
+     * The method is a hook for layoutlib to do its magic.
+     * Nothing is needed outside of LayoutLib. However, it should not be deleted because it
+     * appears to do nothing.
+     */
+    private void registerMenu(@SuppressWarnings("unused") SubMenu subMenu,
+            @SuppressWarnings("unused") AttributeSet set) {
+    }
+
+    // Needed by layoutlib.
+    /*package*/ Context getContext() {
+        return mContext;
+    }
+
+    private static class InflatedOnMenuItemClickListener
+            implements MenuItem.OnMenuItemClickListener {
+        private static final Class<?>[] PARAM_TYPES = new Class[] { MenuItem.class };
+
+        private Object mRealOwner;
+        private Method mMethod;
+
+        public InflatedOnMenuItemClickListener(Object realOwner, String methodName) {
+            mRealOwner = realOwner;
+            Class<?> c = realOwner.getClass();
+            try {
+                mMethod = c.getMethod(methodName, PARAM_TYPES);
+            } catch (Exception e) {
+                InflateException ex = new InflateException(
+                        "Couldn't resolve menu item onClick handler " + methodName +
+                        " in class " + c.getName());
+                ex.initCause(e);
+                throw ex;
+            }
+        }
+
+        public boolean onMenuItemClick(MenuItem item) {
+            try {
+                if (mMethod.getReturnType() == Boolean.TYPE) {
+                    return (Boolean) mMethod.invoke(mRealOwner, item);
+                } else {
+                    mMethod.invoke(mRealOwner, item);
+                    return true;
+                }
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    private Object getRealOwner() {
+        if (mRealOwner == null) {
+            mRealOwner = findRealOwner(mContext);
+        }
+        return mRealOwner;
+    }
+
+    private Object findRealOwner(Object owner) {
+        if (owner instanceof Activity) {
+            return owner;
+        }
+        if (owner instanceof ContextWrapper) {
+            return findRealOwner(((ContextWrapper) owner).getBaseContext());
+        }
+        return owner;
+    }
+
+    /**
+     * State for the current menu.
+     * <p>
+     * Groups can not be nested unless there is another menu (which will have
+     * its state class).
+     */
+    private class MenuState {
+        private Menu menu;
+
+        /*
+         * Group state is set on items as they are added, allowing an item to
+         * override its group state. (As opposed to set on items at the group end tag.)
+         */
+        private int groupId;
+        private int groupCategory;
+        private int groupOrder;
+        private int groupCheckable;
+        private boolean groupVisible;
+        private boolean groupEnabled;
+
+        private boolean itemAdded;
+        private int itemId;
+        private int itemCategoryOrder;
+        private CharSequence itemTitle;
+        private CharSequence itemTitleCondensed;
+        private int itemIconResId;
+        private ColorStateList itemIconTintList = null;
+        private BlendMode mItemIconBlendMode = null;
+        private char itemAlphabeticShortcut;
+        private int itemAlphabeticModifiers;
+        private char itemNumericShortcut;
+        private int itemNumericModifiers;
+        /**
+         * Sync to attrs.xml enum:
+         * - 0: none
+         * - 1: all
+         * - 2: exclusive
+         */
+        private int itemCheckable;
+        private boolean itemChecked;
+        private boolean itemVisible;
+        private boolean itemEnabled;
+
+        /**
+         * Sync to attrs.xml enum, values in MenuItem:
+         * - 0: never
+         * - 1: ifRoom
+         * - 2: always
+         * - -1: Safe sentinel for "no value".
+         */
+        private int itemShowAsAction;
+
+        private int itemActionViewLayout;
+        private String itemActionViewClassName;
+        private String itemActionProviderClassName;
+
+        private String itemListenerMethodName;
+
+        private ActionProvider itemActionProvider;
+
+        private CharSequence itemContentDescription;
+        private CharSequence itemTooltipText;
+
+        private static final int defaultGroupId = NO_ID;
+        private static final int defaultItemId = NO_ID;
+        private static final int defaultItemCategory = 0;
+        private static final int defaultItemOrder = 0;
+        private static final int defaultItemCheckable = 0;
+        private static final boolean defaultItemChecked = false;
+        private static final boolean defaultItemVisible = true;
+        private static final boolean defaultItemEnabled = true;
+
+        public MenuState(final Menu menu) {
+            this.menu = menu;
+
+            resetGroup();
+        }
+
+        public void resetGroup() {
+            groupId = defaultGroupId;
+            groupCategory = defaultItemCategory;
+            groupOrder = defaultItemOrder;
+            groupCheckable = defaultItemCheckable;
+            groupVisible = defaultItemVisible;
+            groupEnabled = defaultItemEnabled;
+        }
+
+        /**
+         * Called when the parser is pointing to a group tag.
+         */
+        public void readGroup(AttributeSet attrs) {
+            TypedArray a = mContext.obtainStyledAttributes(attrs,
+                    com.android.internal.R.styleable.MenuGroup);
+
+            groupId = a.getResourceId(com.android.internal.R.styleable.MenuGroup_id, defaultGroupId);
+            groupCategory = a.getInt(com.android.internal.R.styleable.MenuGroup_menuCategory, defaultItemCategory);
+            groupOrder = a.getInt(com.android.internal.R.styleable.MenuGroup_orderInCategory, defaultItemOrder);
+            groupCheckable = a.getInt(com.android.internal.R.styleable.MenuGroup_checkableBehavior, defaultItemCheckable);
+            groupVisible = a.getBoolean(com.android.internal.R.styleable.MenuGroup_visible, defaultItemVisible);
+            groupEnabled = a.getBoolean(com.android.internal.R.styleable.MenuGroup_enabled, defaultItemEnabled);
+
+            a.recycle();
+        }
+
+        /**
+         * Called when the parser is pointing to an item tag.
+         */
+        public void readItem(AttributeSet attrs) {
+            TypedArray a = mContext.obtainStyledAttributes(attrs,
+                    com.android.internal.R.styleable.MenuItem);
+
+            // Inherit attributes from the group as default value
+            itemId = a.getResourceId(com.android.internal.R.styleable.MenuItem_id, defaultItemId);
+            final int category = a.getInt(com.android.internal.R.styleable.MenuItem_menuCategory, groupCategory);
+            final int order = a.getInt(com.android.internal.R.styleable.MenuItem_orderInCategory, groupOrder);
+            itemCategoryOrder = (category & Menu.CATEGORY_MASK) | (order & Menu.USER_MASK);
+            itemTitle = a.getText(com.android.internal.R.styleable.MenuItem_title);
+            itemTitleCondensed = a.getText(com.android.internal.R.styleable.MenuItem_titleCondensed);
+            itemIconResId = a.getResourceId(com.android.internal.R.styleable.MenuItem_icon, 0);
+            if (a.hasValue(com.android.internal.R.styleable.MenuItem_iconTintMode)) {
+                mItemIconBlendMode = Drawable.parseBlendMode(a.getInt(
+                        com.android.internal.R.styleable.MenuItem_iconTintMode, -1),
+                        mItemIconBlendMode);
+            } else {
+                // Reset to null so that it's not carried over to the next item
+                mItemIconBlendMode = null;
+            }
+            if (a.hasValue(com.android.internal.R.styleable.MenuItem_iconTint)) {
+                itemIconTintList = a.getColorStateList(
+                        com.android.internal.R.styleable.MenuItem_iconTint);
+            } else {
+                // Reset to null so that it's not carried over to the next item
+                itemIconTintList = null;
+            }
+
+            itemAlphabeticShortcut =
+                    getShortcut(a.getString(com.android.internal.R.styleable.MenuItem_alphabeticShortcut));
+            itemAlphabeticModifiers =
+                    a.getInt(com.android.internal.R.styleable.MenuItem_alphabeticModifiers,
+                            KeyEvent.META_CTRL_ON);
+            itemNumericShortcut =
+                    getShortcut(a.getString(com.android.internal.R.styleable.MenuItem_numericShortcut));
+            itemNumericModifiers =
+                    a.getInt(com.android.internal.R.styleable.MenuItem_numericModifiers,
+                            KeyEvent.META_CTRL_ON);
+            if (a.hasValue(com.android.internal.R.styleable.MenuItem_checkable)) {
+                // Item has attribute checkable, use it
+                itemCheckable = a.getBoolean(com.android.internal.R.styleable.MenuItem_checkable, false) ? 1 : 0;
+            } else {
+                // Item does not have attribute, use the group's (group can have one more state
+                // for checkable that represents the exclusive checkable)
+                itemCheckable = groupCheckable;
+            }
+            itemChecked = a.getBoolean(com.android.internal.R.styleable.MenuItem_checked, defaultItemChecked);
+            itemVisible = a.getBoolean(com.android.internal.R.styleable.MenuItem_visible, groupVisible);
+            itemEnabled = a.getBoolean(com.android.internal.R.styleable.MenuItem_enabled, groupEnabled);
+            itemShowAsAction = a.getInt(com.android.internal.R.styleable.MenuItem_showAsAction, -1);
+            itemListenerMethodName = a.getString(com.android.internal.R.styleable.MenuItem_onClick);
+            itemActionViewLayout = a.getResourceId(com.android.internal.R.styleable.MenuItem_actionLayout, 0);
+            itemActionViewClassName = a.getString(com.android.internal.R.styleable.MenuItem_actionViewClass);
+            itemActionProviderClassName = a.getString(com.android.internal.R.styleable.MenuItem_actionProviderClass);
+
+            final boolean hasActionProvider = itemActionProviderClassName != null;
+            if (hasActionProvider && itemActionViewLayout == 0 && itemActionViewClassName == null) {
+                itemActionProvider = newInstance(itemActionProviderClassName,
+                            ACTION_PROVIDER_CONSTRUCTOR_SIGNATURE,
+                            mActionProviderConstructorArguments);
+            } else {
+                if (hasActionProvider) {
+                    Log.w(LOG_TAG, "Ignoring attribute 'actionProviderClass'."
+                            + " Action view already specified.");
+                }
+                itemActionProvider = null;
+            }
+
+            itemContentDescription =
+                    a.getText(com.android.internal.R.styleable.MenuItem_contentDescription);
+            itemTooltipText = a.getText(com.android.internal.R.styleable.MenuItem_tooltipText);
+
+            a.recycle();
+
+            itemAdded = false;
+        }
+
+        private char getShortcut(String shortcutString) {
+            if (shortcutString == null) {
+                return 0;
+            } else {
+                return shortcutString.charAt(0);
+            }
+        }
+
+        private void setItem(MenuItem item) {
+            item.setChecked(itemChecked)
+                .setVisible(itemVisible)
+                .setEnabled(itemEnabled)
+                .setCheckable(itemCheckable >= 1)
+                .setTitleCondensed(itemTitleCondensed)
+                .setIcon(itemIconResId)
+                .setAlphabeticShortcut(itemAlphabeticShortcut, itemAlphabeticModifiers)
+                .setNumericShortcut(itemNumericShortcut, itemNumericModifiers);
+
+            if (itemShowAsAction >= 0) {
+                item.setShowAsAction(itemShowAsAction);
+            }
+
+            if (mItemIconBlendMode != null) {
+                item.setIconTintBlendMode(mItemIconBlendMode);
+            }
+
+            if (itemIconTintList != null) {
+                item.setIconTintList(itemIconTintList);
+            }
+
+            if (itemListenerMethodName != null) {
+                if (mContext.isRestricted()) {
+                    throw new IllegalStateException("The android:onClick attribute cannot "
+                            + "be used within a restricted context");
+                }
+                item.setOnMenuItemClickListener(
+                        new InflatedOnMenuItemClickListener(getRealOwner(), itemListenerMethodName));
+            }
+
+            if (item instanceof MenuItemImpl) {
+                MenuItemImpl impl = (MenuItemImpl) item;
+                if (itemCheckable >= 2) {
+                    impl.setExclusiveCheckable(true);
+                }
+            }
+
+            boolean actionViewSpecified = false;
+            if (itemActionViewClassName != null) {
+                View actionView = (View) newInstance(itemActionViewClassName,
+                        ACTION_VIEW_CONSTRUCTOR_SIGNATURE, mActionViewConstructorArguments);
+                item.setActionView(actionView);
+                actionViewSpecified = true;
+            }
+            if (itemActionViewLayout > 0) {
+                if (!actionViewSpecified) {
+                    item.setActionView(itemActionViewLayout);
+                    actionViewSpecified = true;
+                } else {
+                    Log.w(LOG_TAG, "Ignoring attribute 'itemActionViewLayout'."
+                            + " Action view already specified.");
+                }
+            }
+            if (itemActionProvider != null) {
+                item.setActionProvider(itemActionProvider);
+            }
+
+            item.setContentDescription(itemContentDescription);
+            item.setTooltipText(itemTooltipText);
+        }
+
+        public MenuItem addItem() {
+            itemAdded = true;
+            MenuItem item = menu.add(groupId, itemId, itemCategoryOrder, itemTitle);
+            setItem(item);
+            return item;
+        }
+
+        public SubMenu addSubMenuItem() {
+            itemAdded = true;
+            SubMenu subMenu = menu.addSubMenu(groupId, itemId, itemCategoryOrder, itemTitle);
+            setItem(subMenu.getItem());
+            return subMenu;
+        }
+
+        public boolean hasAddedItem() {
+            return itemAdded;
+        }
+
+        @SuppressWarnings("unchecked")
+        private <T> T newInstance(String className, Class<?>[] constructorSignature,
+                Object[] arguments) {
+            try {
+                Class<?> clazz = mContext.getClassLoader().loadClass(className);
+                Constructor<?> constructor = clazz.getConstructor(constructorSignature);
+                constructor.setAccessible(true);
+                return (T) constructor.newInstance(arguments);
+            } catch (Exception e) {
+                Log.w(LOG_TAG, "Cannot instantiate class: " + className, e);
+            }
+            return null;
+        }
+    }
+}
diff --git a/android/view/MenuInflater_Delegate.java b/android/view/MenuInflater_Delegate.java
new file mode 100644
index 0000000..818d741
--- /dev/null
+++ b/android/view/MenuInflater_Delegate.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import com.android.ide.common.rendering.api.ILayoutLog;
+import com.android.ide.common.rendering.api.ViewInfo;
+import com.android.internal.view.menu.BridgeMenuItemImpl;
+import com.android.internal.view.menu.MenuView;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link MenuInflater}
+ * <p/>
+ * Through the layoutlib_create tool, the original  methods of MenuInflater have been
+ * replaced by calls to methods of the same name in this delegate class.
+ * <p/>
+ * The main purpose of the class is to get the view key from the menu xml parser and add it to
+ * the menu item. The view key is used by the IDE to match the individual view elements to the
+ * corresponding xml tag in the menu/layout file.
+ * <p/>
+ * For Menus, the views may be reused and the {@link MenuItem} is a better object to hold the
+ * view key than the {@link MenuView.ItemView}. At the time of computation of the rest of {@link
+ * ViewInfo}, we check the corresponding view key in the menu item for the view and add it
+ */
+public class MenuInflater_Delegate {
+    @LayoutlibDelegate
+    /*package*/ static void registerMenu(MenuInflater thisInflater, MenuItem menuItem,
+            AttributeSet attrs) {
+        if (menuItem instanceof BridgeMenuItemImpl) {
+            Context context = thisInflater.getContext();
+            context = BridgeContext.getBaseContext(context);
+            if (context instanceof BridgeContext) {
+                Object viewKey = BridgeInflater.getViewKeyFromParser(
+                        attrs, ((BridgeContext) context), null, false);
+                ((BridgeMenuItemImpl) menuItem).setViewCookie(viewKey);
+                return;
+            }
+        }
+
+        String menuItemName = menuItem != null ? menuItem.getClass().getName() : null;
+        if (menuItemName == null ||
+                !menuItemName.startsWith("android.support.") ||
+                !menuItemName.startsWith("androidx.")) {
+            // This means that Bridge did not take over the instantiation of some object properly.
+            // This is most likely a bug in the LayoutLib code.
+            // We suppress this error for AppCompat menus since we do not support them in the menu
+            // editor yet.
+            Bridge.getLog().warning(ILayoutLog.TAG_BROKEN,
+                    "Action Bar Menu rendering may be incorrect.", null, null);
+        }
+
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void registerMenu(MenuInflater thisInflater, SubMenu subMenu,
+            AttributeSet parser) {
+        registerMenu(thisInflater, subMenu.getItem(), parser);
+    }
+
+}
diff --git a/android/view/MenuItem.java b/android/view/MenuItem.java
new file mode 100644
index 0000000..a2fb596
--- /dev/null
+++ b/android/view/MenuItem.java
@@ -0,0 +1,866 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.DrawableRes;
+import android.annotation.LayoutRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.app.Activity;
+import android.content.Intent;
+import android.content.res.ColorStateList;
+import android.graphics.BlendMode;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.View.OnCreateContextMenuListener;
+
+/**
+ * Interface for direct access to a previously created menu item.
+ * <p>
+ * An Item is returned by calling one of the {@link android.view.Menu#add}
+ * methods.
+ * <p>
+ * For a feature set of specific menu types, see {@link Menu}.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For information about creating menus, read the
+ * <a href="{@docRoot}guide/topics/ui/menus.html">Menus</a> developer guide.</p>
+ * </div>
+ */
+public interface MenuItem {
+    /*
+     * These should be kept in sync with attrs.xml enum constants for showAsAction
+     */
+    /** Never show this item as a button in an Action Bar. */
+    public static final int SHOW_AS_ACTION_NEVER = 0;
+    /** Show this item as a button in an Action Bar if the system decides there is room for it. */
+    public static final int SHOW_AS_ACTION_IF_ROOM = 1;
+    /**
+     * Always show this item as a button in an Action Bar.
+     * Use sparingly! If too many items are set to always show in the Action Bar it can
+     * crowd the Action Bar and degrade the user experience on devices with smaller screens.
+     * A good rule of thumb is to have no more than 2 items set to always show at a time.
+     */
+    public static final int SHOW_AS_ACTION_ALWAYS = 2;
+
+    /**
+     * When this item is in the action bar, always show it with a text label even if
+     * it also has an icon specified.
+     */
+    public static final int SHOW_AS_ACTION_WITH_TEXT = 4;
+
+    /**
+     * This item's action view collapses to a normal menu item.
+     * When expanded, the action view temporarily takes over
+     * a larger segment of its container.
+     */
+    public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8;
+
+    /**
+     * Interface definition for a callback to be invoked when a menu item is
+     * clicked.
+     *
+     * @see Activity#onContextItemSelected(MenuItem)
+     * @see Activity#onOptionsItemSelected(MenuItem)
+     */
+    public interface OnMenuItemClickListener {
+        /**
+         * Called when a menu item has been invoked.  This is the first code
+         * that is executed; if it returns true, no other callbacks will be
+         * executed.
+         *
+         * @param item The menu item that was invoked.
+         *
+         * @return Return true to consume this click and prevent others from
+         *         executing.
+         */
+        public boolean onMenuItemClick(MenuItem item);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when a menu item
+     * marked with {@link MenuItem#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW} is
+     * expanded or collapsed.
+     *
+     * @see MenuItem#expandActionView()
+     * @see MenuItem#collapseActionView()
+     * @see MenuItem#setShowAsActionFlags(int)
+     */
+    public interface OnActionExpandListener {
+        /**
+         * Called when a menu item with {@link MenuItem#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}
+         * is expanded.
+         * @param item Item that was expanded
+         * @return true if the item should expand, false if expansion should be suppressed.
+         */
+        public boolean onMenuItemActionExpand(MenuItem item);
+
+        /**
+         * Called when a menu item with {@link MenuItem#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}
+         * is collapsed.
+         * @param item Item that was collapsed
+         * @return true if the item should collapse, false if collapsing should be suppressed.
+         */
+        public boolean onMenuItemActionCollapse(MenuItem item);
+    }
+
+    /**
+     * Return the identifier for this menu item.  The identifier can not
+     * be changed after the menu is created.
+     *
+     * @return The menu item's identifier.
+     */
+    public int getItemId();
+
+    /**
+     * Return the group identifier that this menu item is part of. The group
+     * identifier can not be changed after the menu is created.
+     * 
+     * @return The menu item's group identifier.
+     */
+    public int getGroupId();
+
+    /**
+     * Return the category and order within the category of this item. This
+     * item will be shown before all items (within its category) that have
+     * order greater than this value.
+     * <p>
+     * An order integer contains the item's category (the upper bits of the
+     * integer; set by or/add the category with the order within the
+     * category) and the ordering of the item within that category (the
+     * lower bits). Example categories are {@link Menu#CATEGORY_SYSTEM},
+     * {@link Menu#CATEGORY_SECONDARY}, {@link Menu#CATEGORY_ALTERNATIVE},
+     * {@link Menu#CATEGORY_CONTAINER}. See {@link Menu} for a full list.
+     * 
+     * @return The order of this item.
+     */
+    public int getOrder();
+    
+    /**
+     * Change the title associated with this item.
+     *
+     * @param title The new text to be displayed.
+     * @return This Item so additional setters can be called.
+     */
+    public MenuItem setTitle(CharSequence title);
+
+    /**
+     * Change the title associated with this item.
+     * <p>
+     * Some menu types do not sufficient space to show the full title, and
+     * instead a condensed title is preferred. See {@link Menu} for more
+     * information.
+     * 
+     * @param title The resource id of the new text to be displayed.
+     * @return This Item so additional setters can be called.
+     * @see #setTitleCondensed(CharSequence)
+     */
+    
+    public MenuItem setTitle(@StringRes int title);
+
+    /**
+     * Retrieve the current title of the item.
+     *
+     * @return The title.
+     */
+    public CharSequence getTitle();
+
+    /**
+     * Change the condensed title associated with this item. The condensed
+     * title is used in situations where the normal title may be too long to
+     * be displayed.
+     * 
+     * @param title The new text to be displayed as the condensed title.
+     * @return This Item so additional setters can be called.
+     */
+    public MenuItem setTitleCondensed(CharSequence title);
+
+    /**
+     * Retrieve the current condensed title of the item. If a condensed
+     * title was never set, it will return the normal title.
+     * 
+     * @return The condensed title, if it exists.
+     *         Otherwise the normal title.
+     */
+    public CharSequence getTitleCondensed();
+
+    /**
+     * Change the icon associated with this item. This icon will not always be
+     * shown, so the title should be sufficient in describing this item. See
+     * {@link Menu} for the menu types that support icons.
+     * 
+     * @param icon The new icon (as a Drawable) to be displayed.
+     * @return This Item so additional setters can be called.
+     */
+    public MenuItem setIcon(Drawable icon);
+
+    /**
+     * Change the icon associated with this item. This icon will not always be
+     * shown, so the title should be sufficient in describing this item. See
+     * {@link Menu} for the menu types that support icons.
+     * <p>
+     * This method will set the resource ID of the icon which will be used to
+     * lazily get the Drawable when this item is being shown.
+     * 
+     * @param iconRes The new icon (as a resource ID) to be displayed.
+     * @return This Item so additional setters can be called.
+     */
+    public MenuItem setIcon(@DrawableRes int iconRes);
+    
+    /**
+     * Returns the icon for this item as a Drawable (getting it from resources if it hasn't been
+     * loaded before). Note that if you call {@link #setIconTintList(ColorStateList)} or
+     * {@link #setIconTintMode(PorterDuff.Mode)} on this item, and you use a custom menu presenter
+     * in your application, you have to apply the tinting explicitly on the {@link Drawable}
+     * returned by this method.
+     * 
+     * @return The icon as a Drawable.
+     */
+    public Drawable getIcon();
+
+    /**
+     * Applies a tint to this item's icon. Does not modify the
+     * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
+     * <p>
+     * Subsequent calls to {@link #setIcon(Drawable)} or {@link #setIcon(int)} will
+     * automatically mutate the icon and apply the specified tint and
+     * tint mode using
+     * {@link Drawable#setTintList(ColorStateList)}.
+     *
+     * @param tint the tint to apply, may be {@code null} to clear tint
+     *
+     * @attr ref android.R.styleable#MenuItem_iconTint
+     * @see #getIconTintList()
+     * @see Drawable#setTintList(ColorStateList)
+     */
+    public default MenuItem setIconTintList(@Nullable ColorStateList tint) { return this; }
+
+    /**
+     * @return the tint applied to this item's icon
+     * @attr ref android.R.styleable#MenuItem_iconTint
+     * @see #setIconTintList(ColorStateList)
+     */
+    @Nullable
+    public default ColorStateList getIconTintList() { return null; }
+
+    /**
+     * Specifies the blending mode used to apply the tint specified by
+     * {@link #setIconTintList(ColorStateList)} to this item's icon. The default mode is
+     * {@link PorterDuff.Mode#SRC_IN}.
+     *
+     * @param tintMode the blending mode used to apply the tint, may be
+     *                 {@code null} to clear tint
+     * @attr ref android.R.styleable#MenuItem_iconTintMode
+     * @see #setIconTintList(ColorStateList)
+     * @see Drawable#setTintMode(PorterDuff.Mode)
+     * @see Drawable#setTintBlendMode(BlendMode)
+     */
+    default @NonNull MenuItem setIconTintMode(@Nullable PorterDuff.Mode tintMode) {
+        return this;
+    }
+
+    /**
+     * Specifies the blending mode used to apply the tint specified by
+     * {@link #setIconTintList(ColorStateList)} to this item's icon. The default mode is
+     * {@link BlendMode#SRC_IN}.
+     *
+     * @param blendMode the blending mode used to apply the tint, may be
+     *                 {@code null} to clear tint
+     * @attr ref android.R.styleable#MenuItem_iconTintMode
+     * @see #setIconTintList(ColorStateList)
+     */
+    default @NonNull MenuItem setIconTintBlendMode(@Nullable BlendMode blendMode) {
+        PorterDuff.Mode mode = BlendMode.blendModeToPorterDuffMode(blendMode);
+        if (mode != null) {
+            return setIconTintMode(mode);
+        } else {
+            return this;
+        }
+    }
+
+    /**
+     * Returns the blending mode used to apply the tint to this item's icon, if specified.
+     *
+     * @return the blending mode used to apply the tint to this item's icon
+     * @attr ref android.R.styleable#MenuItem_iconTintMode
+     * @see #setIconTintMode(PorterDuff.Mode)
+     * @see #setIconTintBlendMode(BlendMode)
+     *
+     */
+    @Nullable
+    public default PorterDuff.Mode getIconTintMode() { return null; }
+
+    /**
+     * Returns the blending mode used to apply the tint to this item's icon, if specified.
+     *
+     * @return the blending mode used to apply the tint to this item's icon
+     * @attr ref android.R.styleable#MenuItem_iconTintMode
+     * @see #setIconTintBlendMode(BlendMode)
+     *
+     */
+    @Nullable
+    default BlendMode getIconTintBlendMode() {
+        PorterDuff.Mode mode = getIconTintMode();
+        if (mode != null) {
+            return BlendMode.fromValue(mode.nativeInt);
+        } else {
+            return null;
+        }
+    }
+    
+    /**
+     * Change the Intent associated with this item.  By default there is no
+     * Intent associated with a menu item.  If you set one, and nothing
+     * else handles the item, then the default behavior will be to call
+     * {@link android.content.Context#startActivity} with the given Intent.
+     *
+     * <p>Note that setIntent() can not be used with the versions of
+     * {@link Menu#add} that take a Runnable, because {@link Runnable#run}
+     * does not return a value so there is no way to tell if it handled the
+     * item.  In this case it is assumed that the Runnable always handles
+     * the item, and the intent will never be started.
+     *
+     * @see #getIntent
+     * @param intent The Intent to associated with the item.  This Intent
+     *               object is <em>not</em> copied, so be careful not to
+     *               modify it later.
+     * @return This Item so additional setters can be called.
+     */
+    public MenuItem setIntent(Intent intent);
+
+    /**
+     * Return the Intent associated with this item.  This returns a
+     * reference to the Intent which you can change as desired to modify
+     * what the Item is holding.
+     *
+     * @see #setIntent
+     * @return Returns the last value supplied to {@link #setIntent}, or
+     *         null.
+     */
+    public Intent getIntent();
+
+    /**
+     * Change both the numeric and alphabetic shortcut associated with this
+     * item. Note that the shortcut will be triggered when the key that
+     * generates the given character is pressed along with the corresponding
+     * modifier key. The default modifier is  {@link KeyEvent#META_CTRL_ON} in
+     * case nothing is specified. Also note that case is not significant and
+     * that alphabetic shortcut characters will be handled in lower case.
+     * <p>
+     * See {@link Menu} for the menu types that support shortcuts.
+     *
+     * @param numericChar The numeric shortcut key. This is the shortcut when
+     *        using a numeric (e.g., 12-key) keyboard.
+     * @param alphaChar The alphabetic shortcut key. This is the shortcut when
+     *        using a keyboard with alphabetic keys.
+     * @return This Item so additional setters can be called.
+     */
+    public MenuItem setShortcut(char numericChar, char alphaChar);
+
+    /**
+     * Change both the numeric and alphabetic shortcut associated with this
+     * item. Note that the shortcut will be triggered when the key that
+     * generates the given character is pressed along with the corresponding
+     * modifier key. Also note that case is not significant and that alphabetic
+     * shortcut characters will be handled in lower case.
+     * <p>
+     * See {@link Menu} for the menu types that support shortcuts.
+     *
+     * @param numericChar The numeric shortcut key. This is the shortcut when
+     *        using a numeric (e.g., 12-key) keyboard.
+     * @param numericModifiers The numeric modifier associated with the shortcut. It should
+     *        be a combination of {@link KeyEvent#META_META_ON}, {@link KeyEvent#META_CTRL_ON},
+     *        {@link KeyEvent#META_ALT_ON}, {@link KeyEvent#META_SHIFT_ON},
+     *        {@link KeyEvent#META_SYM_ON}, {@link KeyEvent#META_FUNCTION_ON}.
+     * @param alphaChar The alphabetic shortcut key. This is the shortcut when
+     *        using a keyboard with alphabetic keys.
+     * @param alphaModifiers The alphabetic modifier associated with the shortcut. It should
+     *        be a combination of {@link KeyEvent#META_META_ON}, {@link KeyEvent#META_CTRL_ON},
+     *        {@link KeyEvent#META_ALT_ON}, {@link KeyEvent#META_SHIFT_ON},
+     *        {@link KeyEvent#META_SYM_ON}, {@link KeyEvent#META_FUNCTION_ON}.
+     * @return This Item so additional setters can be called.
+     */
+    default public MenuItem setShortcut(char numericChar, char alphaChar, int numericModifiers,
+            int alphaModifiers) {
+        if ((alphaModifiers & Menu.SUPPORTED_MODIFIERS_MASK) == KeyEvent.META_CTRL_ON
+                && (numericModifiers & Menu.SUPPORTED_MODIFIERS_MASK) == KeyEvent.META_CTRL_ON) {
+            return setShortcut(numericChar, alphaChar);
+        } else {
+            return this;
+        }
+    }
+
+    /**
+     * Change the numeric shortcut associated with this item.
+     * <p>
+     * See {@link Menu} for the menu types that support shortcuts.
+     *
+     * @param numericChar The numeric shortcut key.  This is the shortcut when
+     *                 using a 12-key (numeric) keyboard.
+     * @return This Item so additional setters can be called.
+     */
+    public MenuItem setNumericShortcut(char numericChar);
+
+    /**
+     * Change the numeric shortcut and modifiers associated with this item.
+     * <p>
+     * See {@link Menu} for the menu types that support shortcuts.
+     *
+     * @param numericChar The numeric shortcut key.  This is the shortcut when
+     *                 using a 12-key (numeric) keyboard.
+     * @param numericModifiers The modifier associated with the shortcut. It should
+     *        be a combination of {@link KeyEvent#META_META_ON}, {@link KeyEvent#META_CTRL_ON},
+     *        {@link KeyEvent#META_ALT_ON}, {@link KeyEvent#META_SHIFT_ON},
+     *        {@link KeyEvent#META_SYM_ON}, {@link KeyEvent#META_FUNCTION_ON}.
+     * @return This Item so additional setters can be called.
+     */
+    default public MenuItem setNumericShortcut(char numericChar, int numericModifiers) {
+        if ((numericModifiers & Menu.SUPPORTED_MODIFIERS_MASK) == KeyEvent.META_CTRL_ON) {
+            return setNumericShortcut(numericChar);
+        } else {
+            return this;
+        }
+    }
+
+    /**
+     * Return the char for this menu item's numeric (12-key) shortcut.
+     *
+     * @return Numeric character to use as a shortcut.
+     */
+    public char getNumericShortcut();
+
+    /**
+     * Return the modifiers for this menu item's numeric (12-key) shortcut.
+     * The modifier is a combination of {@link KeyEvent#META_META_ON},
+     * {@link KeyEvent#META_CTRL_ON}, {@link KeyEvent#META_ALT_ON},
+     * {@link KeyEvent#META_SHIFT_ON}, {@link KeyEvent#META_SYM_ON},
+     * {@link KeyEvent#META_FUNCTION_ON}.
+     * For example, {@link KeyEvent#META_FUNCTION_ON}|{@link KeyEvent#META_CTRL_ON}
+     *
+     * @return Modifier associated with the numeric shortcut.
+     */
+    default public int getNumericModifiers() {
+        return KeyEvent.META_CTRL_ON;
+    }
+
+    /**
+     * Change the alphabetic shortcut associated with this item. The shortcut
+     * will be triggered when the key that generates the given character is
+     * pressed along with the corresponding modifier key. The default modifier
+     * is {@link KeyEvent#META_CTRL_ON} in case nothing is specified. Case is
+     * not significant and shortcut characters will be displayed in lower case.
+     * Note that menu items with the characters '\b' or '\n' as shortcuts will
+     * get triggered by the Delete key or Carriage Return key, respectively.
+     * <p>
+     * See {@link Menu} for the menu types that support shortcuts.
+     *
+     * @param alphaChar The alphabetic shortcut key. This is the shortcut when
+     *        using a keyboard with alphabetic keys.
+     * @return This Item so additional setters can be called.
+     */
+    public MenuItem setAlphabeticShortcut(char alphaChar);
+
+    /**
+     * Change the alphabetic shortcut associated with this item. The shortcut
+     * will be triggered when the key that generates the given character is
+     * pressed along with the modifier keys. Case is not significant and shortcut
+     * characters will be displayed in lower case. Note that menu items with
+     * the characters '\b' or '\n' as shortcuts will get triggered by the
+     * Delete key or Carriage Return key, respectively.
+     * <p>
+     * See {@link Menu} for the menu types that support shortcuts.
+     *
+     * @param alphaChar The alphabetic shortcut key. This is the shortcut when
+     *        using a keyboard with alphabetic keys.
+     * @param alphaModifiers The modifier associated with the shortcut. It should
+     *        be a combination of {@link KeyEvent#META_META_ON}, {@link KeyEvent#META_CTRL_ON},
+     *        {@link KeyEvent#META_ALT_ON}, {@link KeyEvent#META_SHIFT_ON},
+     *        {@link KeyEvent#META_SYM_ON}, {@link KeyEvent#META_FUNCTION_ON}.
+     * @return This Item so additional setters can be called.
+     */
+    default public MenuItem setAlphabeticShortcut(char alphaChar, int alphaModifiers) {
+        if ((alphaModifiers & Menu.SUPPORTED_MODIFIERS_MASK) == KeyEvent.META_CTRL_ON) {
+            return setAlphabeticShortcut(alphaChar);
+        } else {
+            return this;
+        }
+    }
+
+    /**
+     * Return the char for this menu item's alphabetic shortcut.
+     *
+     * @return Alphabetic character to use as a shortcut.
+     */
+    public char getAlphabeticShortcut();
+
+    /**
+     * Return the modifier for this menu item's alphabetic shortcut.
+     * The modifier is a combination of {@link KeyEvent#META_META_ON},
+     * {@link KeyEvent#META_CTRL_ON}, {@link KeyEvent#META_ALT_ON},
+     * {@link KeyEvent#META_SHIFT_ON}, {@link KeyEvent#META_SYM_ON},
+     * {@link KeyEvent#META_FUNCTION_ON}.
+     * For example, {@link KeyEvent#META_FUNCTION_ON}|{@link KeyEvent#META_CTRL_ON}
+     *
+     * @return Modifier associated with the keyboard shortcut.
+     */
+    default public int getAlphabeticModifiers() {
+        return KeyEvent.META_CTRL_ON;
+    }
+
+    /**
+     * Control whether this item can display a check mark. Setting this does
+     * not actually display a check mark (see {@link #setChecked} for that);
+     * rather, it ensures there is room in the item in which to display a
+     * check mark.
+     * <p>
+     * See {@link Menu} for the menu types that support check marks.
+     *
+     * @param checkable Set to true to allow a check mark, false to
+     *            disallow. The default is false.
+     * @see #setChecked
+     * @see #isCheckable
+     * @see Menu#setGroupCheckable
+     * @return This Item so additional setters can be called.
+     */
+    public MenuItem setCheckable(boolean checkable);
+
+    /**
+     * Return whether the item can currently display a check mark.
+     *
+     * @return If a check mark can be displayed, returns true.
+     *
+     * @see #setCheckable
+     */
+    public boolean isCheckable();
+
+    /**
+     * Control whether this item is shown with a check mark.  Note that you
+     * must first have enabled checking with {@link #setCheckable} or else
+     * the check mark will not appear.  If this item is a member of a group that contains
+     * mutually-exclusive items (set via {@link Menu#setGroupCheckable(int, boolean, boolean)},
+     * the other items in the group will be unchecked.
+     * <p>
+     * See {@link Menu} for the menu types that support check marks.
+     *
+     * @see #setCheckable
+     * @see #isChecked
+     * @see Menu#setGroupCheckable
+     * @param checked Set to true to display a check mark, false to hide
+     *                it.  The default value is false.
+     * @return This Item so additional setters can be called.
+     */
+    public MenuItem setChecked(boolean checked);
+
+    /**
+     * Return whether the item is currently displaying a check mark.
+     *
+     * @return If a check mark is displayed, returns true.
+     *
+     * @see #setChecked
+     */
+    public boolean isChecked();
+
+    /**
+     * Sets the visibility of the menu item. Even if a menu item is not visible,
+     * it may still be invoked via its shortcut (to completely disable an item,
+     * set it to invisible and {@link #setEnabled(boolean) disabled}).
+     * 
+     * @param visible If true then the item will be visible; if false it is
+     *        hidden.
+     * @return This Item so additional setters can be called.
+     */
+    public MenuItem setVisible(boolean visible);
+
+    /**
+     * Return the visibility of the menu item.
+     *
+     * @return If true the item is visible; else it is hidden.
+     */
+    public boolean isVisible();
+
+    /**
+     * Sets whether the menu item is enabled. Disabling a menu item will not
+     * allow it to be invoked via its shortcut. The menu item will still be
+     * visible.
+     * 
+     * @param enabled If true then the item will be invokable; if false it is
+     *        won't be invokable.
+     * @return This Item so additional setters can be called.
+     */
+    public MenuItem setEnabled(boolean enabled);
+
+    /**
+     * Return the enabled state of the menu item.
+     *
+     * @return If true the item is enabled and hence invokable; else it is not.
+     */
+    public boolean isEnabled();
+
+    /**
+     * Check whether this item has an associated sub-menu.  I.e. it is a
+     * sub-menu of another menu.
+     *
+     * @return If true this item has a menu; else it is a
+     *         normal item.
+     */
+    public boolean hasSubMenu();
+
+    /**
+     * Get the sub-menu to be invoked when this item is selected, if it has
+     * one. See {@link #hasSubMenu()}.
+     *
+     * @return The associated menu if there is one, else null
+     */
+    public SubMenu getSubMenu();
+
+    /**
+     * Set a custom listener for invocation of this menu item. In most
+     * situations, it is more efficient and easier to use
+     * {@link Activity#onOptionsItemSelected(MenuItem)} or
+     * {@link Activity#onContextItemSelected(MenuItem)}.
+     * 
+     * @param menuItemClickListener The object to receive invokations.
+     * @return This Item so additional setters can be called.
+     * @see Activity#onOptionsItemSelected(MenuItem)
+     * @see Activity#onContextItemSelected(MenuItem)
+     */
+    public MenuItem setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener menuItemClickListener);
+
+    /**
+     * Gets the extra information linked to this menu item.  This extra
+     * information is set by the View that added this menu item to the
+     * menu.
+     * 
+     * @see OnCreateContextMenuListener
+     * @return The extra information linked to the View that added this
+     *         menu item to the menu. This can be null.
+     */
+    public ContextMenuInfo getMenuInfo();
+    
+    /**
+     * Sets how this item should display in the presence of an Action Bar.
+     * The parameter actionEnum is a flag set. One of {@link #SHOW_AS_ACTION_ALWAYS},
+     * {@link #SHOW_AS_ACTION_IF_ROOM}, or {@link #SHOW_AS_ACTION_NEVER} should
+     * be used, and you may optionally OR the value with {@link #SHOW_AS_ACTION_WITH_TEXT}.
+     * SHOW_AS_ACTION_WITH_TEXT requests that when the item is shown as an action,
+     * it should be shown with a text label.
+     *
+     * @param actionEnum How the item should display. One of
+     * {@link #SHOW_AS_ACTION_ALWAYS}, {@link #SHOW_AS_ACTION_IF_ROOM}, or
+     * {@link #SHOW_AS_ACTION_NEVER}. SHOW_AS_ACTION_NEVER is the default.
+     * 
+     * @see android.app.ActionBar
+     * @see #setActionView(View)
+     */
+    public void setShowAsAction(int actionEnum);
+
+    /**
+     * Sets how this item should display in the presence of an Action Bar.
+     * The parameter actionEnum is a flag set. One of {@link #SHOW_AS_ACTION_ALWAYS},
+     * {@link #SHOW_AS_ACTION_IF_ROOM}, or {@link #SHOW_AS_ACTION_NEVER} should
+     * be used, and you may optionally OR the value with {@link #SHOW_AS_ACTION_WITH_TEXT}.
+     * SHOW_AS_ACTION_WITH_TEXT requests that when the item is shown as an action,
+     * it should be shown with a text label.
+     *
+     * <p>Note: This method differs from {@link #setShowAsAction(int)} only in that it
+     * returns the current MenuItem instance for call chaining.
+     *
+     * @param actionEnum How the item should display. One of
+     * {@link #SHOW_AS_ACTION_ALWAYS}, {@link #SHOW_AS_ACTION_IF_ROOM}, or
+     * {@link #SHOW_AS_ACTION_NEVER}. SHOW_AS_ACTION_NEVER is the default.
+     *
+     * @see android.app.ActionBar
+     * @see #setActionView(View)
+     * @return This MenuItem instance for call chaining.
+     */
+    public MenuItem setShowAsActionFlags(int actionEnum);
+
+    /**
+     * Set an action view for this menu item. An action view will be displayed in place
+     * of an automatically generated menu item element in the UI when this item is shown
+     * as an action within a parent.
+     * <p>
+     *   <strong>Note:</strong> Setting an action view overrides the action provider
+     *           set via {@link #setActionProvider(ActionProvider)}.
+     * </p>
+     *
+     * @param view View to use for presenting this item to the user.
+     * @return This Item so additional setters can be called.
+     *
+     * @see #setShowAsAction(int)
+     */
+    public MenuItem setActionView(View view);
+
+    /**
+     * Set an action view for this menu item. An action view will be displayed in place
+     * of an automatically generated menu item element in the UI when this item is shown
+     * as an action within a parent.
+     * <p>
+     *   <strong>Note:</strong> Setting an action view overrides the action provider
+     *           set via {@link #setActionProvider(ActionProvider)}.
+     * </p>
+     *
+     * @param resId Layout resource to use for presenting this item to the user.
+     * @return This Item so additional setters can be called.
+     *
+     * @see #setShowAsAction(int)
+     */
+    public MenuItem setActionView(@LayoutRes int resId);
+
+    /**
+     * Returns the currently set action view for this menu item.
+     *
+     * @return This item's action view
+     *
+     * @see #setActionView(View)
+     * @see #setShowAsAction(int)
+     */
+    public View getActionView();
+
+    /**
+     * Sets the {@link ActionProvider} responsible for creating an action view if
+     * the item is placed on the action bar. The provider also provides a default
+     * action invoked if the item is placed in the overflow menu.
+     * <p>
+     *   <strong>Note:</strong> Setting an action provider overrides the action view
+     *           set via {@link #setActionView(int)} or {@link #setActionView(View)}.
+     * </p>
+     *
+     * @param actionProvider The action provider.
+     * @return This Item so additional setters can be called.
+     *
+     * @see ActionProvider
+     */
+    public MenuItem setActionProvider(ActionProvider actionProvider);
+
+    /**
+     * Gets the {@link ActionProvider}.
+     *
+     * @return The action provider.
+     *
+     * @see ActionProvider
+     * @see #setActionProvider(ActionProvider)
+     */
+    public ActionProvider getActionProvider();
+
+    /**
+     * Expand the action view associated with this menu item.
+     * The menu item must have an action view set, as well as
+     * the showAsAction flag {@link #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}.
+     * If a listener has been set using {@link #setOnActionExpandListener(OnActionExpandListener)}
+     * it will have its {@link OnActionExpandListener#onMenuItemActionExpand(MenuItem)}
+     * method invoked. The listener may return false from this method to prevent expanding
+     * the action view.
+     *
+     * @return true if the action view was expanded, false otherwise.
+     */
+    public boolean expandActionView();
+
+    /**
+     * Collapse the action view associated with this menu item.
+     * The menu item must have an action view set, as well as the showAsAction flag
+     * {@link #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}. If a listener has been set using
+     * {@link #setOnActionExpandListener(OnActionExpandListener)} it will have its
+     * {@link OnActionExpandListener#onMenuItemActionCollapse(MenuItem)} method invoked.
+     * The listener may return false from this method to prevent collapsing the action view.
+     *
+     * @return true if the action view was collapsed, false otherwise.
+     */
+    public boolean collapseActionView();
+
+    /**
+     * Returns true if this menu item's action view has been expanded.
+     *
+     * @return true if the item's action view is expanded, false otherwise.
+     *
+     * @see #expandActionView()
+     * @see #collapseActionView()
+     * @see #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW
+     * @see OnActionExpandListener
+     */
+    public boolean isActionViewExpanded();
+
+    /**
+     * Set an {@link OnActionExpandListener} on this menu item to be notified when
+     * the associated action view is expanded or collapsed. The menu item must
+     * be configured to expand or collapse its action view using the flag
+     * {@link #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}.
+     *
+     * @param listener Listener that will respond to expand/collapse events
+     * @return This menu item instance for call chaining
+     */
+    public MenuItem setOnActionExpandListener(OnActionExpandListener listener);
+
+    /**
+     * Change the content description associated with this menu item.
+     *
+     * @param contentDescription The new content description.
+     */
+    default MenuItem setContentDescription(CharSequence contentDescription) {
+        return this;
+    }
+
+    /**
+     * Retrieve the content description associated with this menu item.
+     *
+     * @return The content description.
+     */
+    default CharSequence getContentDescription() {
+        return null;
+    }
+
+    /**
+     * Change the tooltip text associated with this menu item.
+     *
+     * @param tooltipText The new tooltip text.
+     */
+    default MenuItem setTooltipText(CharSequence tooltipText) {
+        return this;
+    }
+
+    /**
+     * Retrieve the tooltip text associated with this menu item.
+     *
+     * @return The tooltip text.
+     */
+    default CharSequence getTooltipText() {
+        return null;
+    }
+
+    /**
+     * Returns true if {@link #setShowAsAction(int)} was set to {@link #SHOW_AS_ACTION_ALWAYS}.
+     * Default value is {@code false}.
+     *
+     * @hide
+     */
+    default boolean requiresActionButton() {
+        return false;
+    }
+
+    /**
+     * Returns true if {@link #setShowAsAction(int)} was set to {@link #SHOW_AS_ACTION_NEVER}.
+     * Default value is {@code true}.
+     *
+     * @hide
+     */
+    default boolean requiresOverflow() {
+        return true;
+    }
+}
diff --git a/android/view/MotionEvent.java b/android/view/MotionEvent.java
new file mode 100644
index 0000000..69ff64f
--- /dev/null
+++ b/android/view/MotionEvent.java
@@ -0,0 +1,4260 @@
+/*
+ * 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.view;
+
+import static android.os.IInputConstants.INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.Matrix;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.SparseArray;
+
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
+import java.lang.annotation.Retention;
+import java.util.Objects;
+
+/**
+ * Object used to report movement (mouse, pen, finger, trackball) events.
+ * Motion events may hold either absolute or relative movements and other data,
+ * depending on the type of device.
+ *
+ * <h3>Overview</h3>
+ * <p>
+ * Motion events describe movements in terms of an action code and a set of axis values.
+ * The action code specifies the state change that occurred such as a pointer going
+ * down or up.  The axis values describe the position and other movement properties.
+ * </p><p>
+ * For example, when the user first touches the screen, the system delivers a touch
+ * event to the appropriate {@link View} with the action code {@link #ACTION_DOWN}
+ * and a set of axis values that include the X and Y coordinates of the touch and
+ * information about the pressure, size and orientation of the contact area.
+ * </p><p>
+ * Some devices can report multiple movement traces at the same time.  Multi-touch
+ * screens emit one movement trace for each finger.  The individual fingers or
+ * other objects that generate movement traces are referred to as <em>pointers</em>.
+ * Motion events contain information about all of the pointers that are currently active
+ * even if some of them have not moved since the last event was delivered.
+ * </p><p>
+ * The number of pointers only ever changes by one as individual pointers go up and down,
+ * except when the gesture is canceled.
+ * </p><p>
+ * Each pointer has a unique id that is assigned when it first goes down
+ * (indicated by {@link #ACTION_DOWN} or {@link #ACTION_POINTER_DOWN}).  A pointer id
+ * remains valid until the pointer eventually goes up (indicated by {@link #ACTION_UP}
+ * or {@link #ACTION_POINTER_UP}) or when the gesture is canceled (indicated by
+ * {@link #ACTION_CANCEL}).
+ * </p><p>
+ * The MotionEvent class provides many methods to query the position and other properties of
+ * pointers, such as {@link #getX(int)}, {@link #getY(int)}, {@link #getAxisValue},
+ * {@link #getPointerId(int)}, {@link #getToolType(int)}, and many others.  Most of these
+ * methods accept the pointer index as a parameter rather than the pointer id.
+ * The pointer index of each pointer in the event ranges from 0 to one less than the value
+ * returned by {@link #getPointerCount()}.
+ * </p><p>
+ * The order in which individual pointers appear within a motion event is undefined.
+ * Thus the pointer index of a pointer can change from one event to the next but
+ * the pointer id of a pointer is guaranteed to remain constant as long as the pointer
+ * remains active.  Use the {@link #getPointerId(int)} method to obtain the
+ * pointer id of a pointer to track it across all subsequent motion events in a gesture.
+ * Then for successive motion events, use the {@link #findPointerIndex(int)} method
+ * to obtain the pointer index for a given pointer id in that motion event.
+ * </p><p>
+ * Mouse and stylus buttons can be retrieved using {@link #getButtonState()}.  It is a
+ * good idea to check the button state while handling {@link #ACTION_DOWN} as part
+ * of a touch event.  The application may choose to perform some different action
+ * if the touch event starts due to a secondary button click, such as presenting a
+ * context menu.
+ * </p>
+ *
+ * <h3>Batching</h3>
+ * <p>
+ * For efficiency, motion events with {@link #ACTION_MOVE} may batch together
+ * multiple movement samples within a single object.  The most current
+ * pointer coordinates are available using {@link #getX(int)} and {@link #getY(int)}.
+ * Earlier coordinates within the batch are accessed using {@link #getHistoricalX(int, int)}
+ * and {@link #getHistoricalY(int, int)}.  The coordinates are "historical" only
+ * insofar as they are older than the current coordinates in the batch; however,
+ * they are still distinct from any other coordinates reported in prior motion events.
+ * To process all coordinates in the batch in time order, first consume the historical
+ * coordinates then consume the current coordinates.
+ * </p><p>
+ * Example: Consuming all samples for all pointers in a motion event in time order.
+ * </p><p><pre><code>
+ * void printSamples(MotionEvent ev) {
+ *     final int historySize = ev.getHistorySize();
+ *     final int pointerCount = ev.getPointerCount();
+ *     for (int h = 0; h &lt; historySize; h++) {
+ *         System.out.printf("At time %d:", ev.getHistoricalEventTime(h));
+ *         for (int p = 0; p &lt; pointerCount; p++) {
+ *             System.out.printf("  pointer %d: (%f,%f)",
+ *                 ev.getPointerId(p), ev.getHistoricalX(p, h), ev.getHistoricalY(p, h));
+ *         }
+ *     }
+ *     System.out.printf("At time %d:", ev.getEventTime());
+ *     for (int p = 0; p &lt; pointerCount; p++) {
+ *         System.out.printf("  pointer %d: (%f,%f)",
+ *             ev.getPointerId(p), ev.getX(p), ev.getY(p));
+ *     }
+ * }
+ * </code></pre></p>
+ *
+ * <h3>Device Types</h3>
+ * <p>
+ * The interpretation of the contents of a MotionEvent varies significantly depending
+ * on the source class of the device.
+ * </p><p>
+ * On pointing devices with source class {@link InputDevice#SOURCE_CLASS_POINTER}
+ * such as touch screens, the pointer coordinates specify absolute
+ * positions such as view X/Y coordinates.  Each complete gesture is represented
+ * by a sequence of motion events with actions that describe pointer state transitions
+ * and movements.  A gesture starts with a motion event with {@link #ACTION_DOWN}
+ * that provides the location of the first pointer down.  As each additional
+ * pointer that goes down or up, the framework will generate a motion event with
+ * {@link #ACTION_POINTER_DOWN} or {@link #ACTION_POINTER_UP} accordingly.
+ * Pointer movements are described by motion events with {@link #ACTION_MOVE}.
+ * Finally, a gesture end either when the final pointer goes up as represented
+ * by a motion event with {@link #ACTION_UP} or when gesture is canceled
+ * with {@link #ACTION_CANCEL}.
+ * </p><p>
+ * Some pointing devices such as mice may support vertical and/or horizontal scrolling.
+ * A scroll event is reported as a generic motion event with {@link #ACTION_SCROLL} that
+ * includes the relative scroll offset in the {@link #AXIS_VSCROLL} and
+ * {@link #AXIS_HSCROLL} axes.  See {@link #getAxisValue(int)} for information
+ * about retrieving these additional axes.
+ * </p><p>
+ * On trackball devices with source class {@link InputDevice#SOURCE_CLASS_TRACKBALL},
+ * the pointer coordinates specify relative movements as X/Y deltas.
+ * A trackball gesture consists of a sequence of movements described by motion
+ * events with {@link #ACTION_MOVE} interspersed with occasional {@link #ACTION_DOWN}
+ * or {@link #ACTION_UP} motion events when the trackball button is pressed or released.
+ * </p><p>
+ * On joystick devices with source class {@link InputDevice#SOURCE_CLASS_JOYSTICK},
+ * the pointer coordinates specify the absolute position of the joystick axes.
+ * The joystick axis values are normalized to a range of -1.0 to 1.0 where 0.0 corresponds
+ * to the center position.  More information about the set of available axes and the
+ * range of motion can be obtained using {@link InputDevice#getMotionRange}.
+ * Some common joystick axes are {@link #AXIS_X}, {@link #AXIS_Y},
+ * {@link #AXIS_HAT_X}, {@link #AXIS_HAT_Y}, {@link #AXIS_Z} and {@link #AXIS_RZ}.
+ * </p><p>
+ * Refer to {@link InputDevice} for more information about how different kinds of
+ * input devices and sources represent pointer coordinates.
+ * </p>
+ *
+ * <h3>Consistency Guarantees</h3>
+ * <p>
+ * Motion events are always delivered to views as a consistent stream of events.
+ * What constitutes a consistent stream varies depending on the type of device.
+ * For touch events, consistency implies that pointers go down one at a time,
+ * move around as a group and then go up one at a time or are canceled.
+ * </p><p>
+ * While the framework tries to deliver consistent streams of motion events to
+ * views, it cannot guarantee it.  Some events may be dropped or modified by
+ * containing views in the application before they are delivered thereby making
+ * the stream of events inconsistent.  Views should always be prepared to
+ * handle {@link #ACTION_CANCEL} and should tolerate anomalous
+ * situations such as receiving a new {@link #ACTION_DOWN} without first having
+ * received an {@link #ACTION_UP} for the prior gesture.
+ * </p>
+ */
+public final class MotionEvent extends InputEvent implements Parcelable {
+    private static final String TAG = "MotionEvent";
+    private static final long NS_PER_MS = 1000000;
+    private static final String LABEL_PREFIX = "AXIS_";
+
+    private static final boolean DEBUG_CONCISE_TOSTRING = false;
+
+    /**
+     * An invalid pointer id.
+     *
+     * This value (-1) can be used as a placeholder to indicate that a pointer id
+     * has not been assigned or is not available.  It cannot appear as
+     * a pointer id inside a {@link MotionEvent}.
+     */
+    public static final int INVALID_POINTER_ID = -1;
+
+    /**
+     * Bit mask of the parts of the action code that are the action itself.
+     */
+    public static final int ACTION_MASK             = 0xff;
+
+    /**
+     * Constant for {@link #getActionMasked}: A pressed gesture has started, the
+     * motion contains the initial starting location.
+     * <p>
+     * This is also a good time to check the button state to distinguish
+     * secondary and tertiary button clicks and handle them appropriately.
+     * Use {@link #getButtonState} to retrieve the button state.
+     * </p>
+     */
+    public static final int ACTION_DOWN             = 0;
+
+    /**
+     * Constant for {@link #getActionMasked}: A pressed gesture has finished, the
+     * motion contains the final release location as well as any intermediate
+     * points since the last down or move event.
+     */
+    public static final int ACTION_UP               = 1;
+
+    /**
+     * Constant for {@link #getActionMasked}: A change has happened during a
+     * press gesture (between {@link #ACTION_DOWN} and {@link #ACTION_UP}).
+     * The motion contains the most recent point, as well as any intermediate
+     * points since the last down or move event.
+     */
+    public static final int ACTION_MOVE             = 2;
+
+    /**
+     * Constant for {@link #getActionMasked}: The current gesture has been aborted.
+     * You will not receive any more points in it.  You should treat this as
+     * an up event, but not perform any action that you normally would.
+     */
+    public static final int ACTION_CANCEL           = 3;
+
+    /**
+     * Constant for {@link #getActionMasked}: A movement has happened outside of the
+     * normal bounds of the UI element.  This does not provide a full gesture,
+     * but only the initial location of the movement/touch.
+     * <p>
+     * Note: Because the location of any event will be outside the
+     * bounds of the view hierarchy, it will not get dispatched to
+     * any children of a ViewGroup by default. Therefore,
+     * movements with ACTION_OUTSIDE should be handled in either the
+     * root {@link View} or in the appropriate {@link Window.Callback}
+     * (e.g. {@link android.app.Activity} or {@link android.app.Dialog}).
+     * </p>
+     */
+    public static final int ACTION_OUTSIDE          = 4;
+
+    /**
+     * Constant for {@link #getActionMasked}: A non-primary pointer has gone down.
+     * <p>
+     * Use {@link #getActionIndex} to retrieve the index of the pointer that changed.
+     * </p><p>
+     * The index is encoded in the {@link #ACTION_POINTER_INDEX_MASK} bits of the
+     * unmasked action returned by {@link #getAction}.
+     * </p>
+     */
+    public static final int ACTION_POINTER_DOWN     = 5;
+
+    /**
+     * Constant for {@link #getActionMasked}: A non-primary pointer has gone up.
+     * <p>
+     * Use {@link #getActionIndex} to retrieve the index of the pointer that changed.
+     * </p><p>
+     * The index is encoded in the {@link #ACTION_POINTER_INDEX_MASK} bits of the
+     * unmasked action returned by {@link #getAction}.
+     * </p>
+     */
+    public static final int ACTION_POINTER_UP       = 6;
+
+    /**
+     * Constant for {@link #getActionMasked}: A change happened but the pointer
+     * is not down (unlike {@link #ACTION_MOVE}).  The motion contains the most
+     * recent point, as well as any intermediate points since the last
+     * hover move event.
+     * <p>
+     * This action is always delivered to the window or view under the pointer.
+     * </p><p>
+     * This action is not a touch event so it is delivered to
+     * {@link View#onGenericMotionEvent(MotionEvent)} rather than
+     * {@link View#onTouchEvent(MotionEvent)}.
+     * </p>
+     */
+    public static final int ACTION_HOVER_MOVE       = 7;
+
+    /**
+     * Constant for {@link #getActionMasked}: The motion event contains relative
+     * vertical and/or horizontal scroll offsets.  Use {@link #getAxisValue(int)}
+     * to retrieve the information from {@link #AXIS_VSCROLL} and {@link #AXIS_HSCROLL}.
+     * The pointer may or may not be down when this event is dispatched.
+     * <p>
+     * This action is always delivered to the window or view under the pointer, which
+     * may not be the window or view currently touched.
+     * </p><p>
+     * This action is not a touch event so it is delivered to
+     * {@link View#onGenericMotionEvent(MotionEvent)} rather than
+     * {@link View#onTouchEvent(MotionEvent)}.
+     * </p>
+     */
+    public static final int ACTION_SCROLL           = 8;
+
+    /**
+     * Constant for {@link #getActionMasked}: The pointer is not down but has entered the
+     * boundaries of a window or view.
+     * <p>
+     * This action is always delivered to the window or view under the pointer.
+     * </p><p>
+     * This action is not a touch event so it is delivered to
+     * {@link View#onGenericMotionEvent(MotionEvent)} rather than
+     * {@link View#onTouchEvent(MotionEvent)}.
+     * </p>
+     */
+    public static final int ACTION_HOVER_ENTER      = 9;
+
+    /**
+     * Constant for {@link #getActionMasked}: The pointer is not down but has exited the
+     * boundaries of a window or view.
+     * <p>
+     * This action is always delivered to the window or view that was previously under the pointer.
+     * </p><p>
+     * This action is not a touch event so it is delivered to
+     * {@link View#onGenericMotionEvent(MotionEvent)} rather than
+     * {@link View#onTouchEvent(MotionEvent)}.
+     * </p>
+     */
+    public static final int ACTION_HOVER_EXIT       = 10;
+
+    /**
+     * Constant for {@link #getActionMasked}: A button has been pressed.
+     *
+     * <p>
+     * Use {@link #getActionButton()} to get which button was pressed.
+     * </p><p>
+     * This action is not a touch event so it is delivered to
+     * {@link View#onGenericMotionEvent(MotionEvent)} rather than
+     * {@link View#onTouchEvent(MotionEvent)}.
+     * </p>
+     */
+    public static final int ACTION_BUTTON_PRESS   = 11;
+
+    /**
+     * Constant for {@link #getActionMasked}: A button has been released.
+     *
+     * <p>
+     * Use {@link #getActionButton()} to get which button was released.
+     * </p><p>
+     * This action is not a touch event so it is delivered to
+     * {@link View#onGenericMotionEvent(MotionEvent)} rather than
+     * {@link View#onTouchEvent(MotionEvent)}.
+     * </p>
+     */
+    public static final int ACTION_BUTTON_RELEASE  = 12;
+
+    /**
+     * Bits in the action code that represent a pointer index, used with
+     * {@link #ACTION_POINTER_DOWN} and {@link #ACTION_POINTER_UP}.  Shifting
+     * down by {@link #ACTION_POINTER_INDEX_SHIFT} provides the actual pointer
+     * index where the data for the pointer going up or down can be found; you can
+     * get its identifier with {@link #getPointerId(int)} and the actual
+     * data with {@link #getX(int)} etc.
+     *
+     * @see #getActionIndex
+     */
+    public static final int ACTION_POINTER_INDEX_MASK  = 0xff00;
+
+    /**
+     * Bit shift for the action bits holding the pointer index as
+     * defined by {@link #ACTION_POINTER_INDEX_MASK}.
+     *
+     * @see #getActionIndex
+     */
+    public static final int ACTION_POINTER_INDEX_SHIFT = 8;
+
+    /**
+     * @deprecated Use {@link #ACTION_POINTER_INDEX_MASK} to retrieve the
+     * data index associated with {@link #ACTION_POINTER_DOWN}.
+     */
+    @Deprecated
+    public static final int ACTION_POINTER_1_DOWN   = ACTION_POINTER_DOWN | 0x0000;
+
+    /**
+     * @deprecated Use {@link #ACTION_POINTER_INDEX_MASK} to retrieve the
+     * data index associated with {@link #ACTION_POINTER_DOWN}.
+     */
+    @Deprecated
+    public static final int ACTION_POINTER_2_DOWN   = ACTION_POINTER_DOWN | 0x0100;
+
+    /**
+     * @deprecated Use {@link #ACTION_POINTER_INDEX_MASK} to retrieve the
+     * data index associated with {@link #ACTION_POINTER_DOWN}.
+     */
+    @Deprecated
+    public static final int ACTION_POINTER_3_DOWN   = ACTION_POINTER_DOWN | 0x0200;
+
+    /**
+     * @deprecated Use {@link #ACTION_POINTER_INDEX_MASK} to retrieve the
+     * data index associated with {@link #ACTION_POINTER_UP}.
+     */
+    @Deprecated
+    public static final int ACTION_POINTER_1_UP     = ACTION_POINTER_UP | 0x0000;
+
+    /**
+     * @deprecated Use {@link #ACTION_POINTER_INDEX_MASK} to retrieve the
+     * data index associated with {@link #ACTION_POINTER_UP}.
+     */
+    @Deprecated
+    public static final int ACTION_POINTER_2_UP     = ACTION_POINTER_UP | 0x0100;
+
+    /**
+     * @deprecated Use {@link #ACTION_POINTER_INDEX_MASK} to retrieve the
+     * data index associated with {@link #ACTION_POINTER_UP}.
+     */
+    @Deprecated
+    public static final int ACTION_POINTER_3_UP     = ACTION_POINTER_UP | 0x0200;
+
+    /**
+     * @deprecated Renamed to {@link #ACTION_POINTER_INDEX_MASK} to match
+     * the actual data contained in these bits.
+     */
+    @Deprecated
+    public static final int ACTION_POINTER_ID_MASK  = 0xff00;
+
+    /**
+     * @deprecated Renamed to {@link #ACTION_POINTER_INDEX_SHIFT} to match
+     * the actual data contained in these bits.
+     */
+    @Deprecated
+    public static final int ACTION_POINTER_ID_SHIFT = 8;
+
+    /**
+     * This flag indicates that the window that received this motion event is partly
+     * or wholly obscured by another visible window above it. This flag is set to true
+     * if the event directly passed through the obscured area.
+     *
+     * A security sensitive application can check this flag to identify situations in which
+     * a malicious application may have covered up part of its content for the purpose
+     * of misleading the user or hijacking touches.  An appropriate response might be
+     * to drop the suspect touches or to take additional precautions to confirm the user's
+     * actual intent.
+     */
+    public static final int FLAG_WINDOW_IS_OBSCURED = 0x1;
+
+    /**
+     * This flag indicates that the window that received this motion event is partly
+     * or wholly obscured by another visible window above it. This flag is set to true
+     * even if the event did not directly pass through the obscured area.
+     *
+     * A security sensitive application can check this flag to identify situations in which
+     * a malicious application may have covered up part of its content for the purpose
+     * of misleading the user or hijacking touches.  An appropriate response might be
+     * to drop the suspect touches or to take additional precautions to confirm the user's
+     * actual intent.
+     *
+     * Unlike FLAG_WINDOW_IS_OBSCURED, this is true even if the window that received this event is
+     * obstructed in areas other than the touched location.
+     */
+    public static final int FLAG_WINDOW_IS_PARTIALLY_OBSCURED = 0x2;
+
+    /**
+     * This private flag is only set on {@link #ACTION_HOVER_MOVE} events and indicates that
+     * this event will be immediately followed by a {@link #ACTION_HOVER_EXIT}. It is used to
+     * prevent generating redundant {@link #ACTION_HOVER_ENTER} events.
+     * @hide
+     */
+    public static final int FLAG_HOVER_EXIT_PENDING = 0x4;
+
+    /**
+     * This flag indicates that the event has been generated by a gesture generator. It
+     * provides a hint to the GestureDetector to not apply any touch slop.
+     *
+     * @hide
+     */
+    public static final int FLAG_IS_GENERATED_GESTURE = 0x8;
+
+    /**
+     * This flag associated with {@link #ACTION_POINTER_UP}, this indicates that the pointer
+     * has been canceled. Typically this is used for palm event when the user has accidental
+     * touches.
+     * @hide
+     */
+    public static final int FLAG_CANCELED = 0x20;
+
+    /**
+     * This flag indicates that the event will not cause a focus change if it is directed to an
+     * unfocused window, even if it an {@link #ACTION_DOWN}. This is typically used with pointer
+     * gestures to allow the user to direct gestures to an unfocused window without bringing the
+     * window into focus.
+     * @hide
+     */
+    public static final int FLAG_NO_FOCUS_CHANGE = 0x40;
+
+    /**
+     * This flag indicates that this event was modified by or generated from an accessibility
+     * service. Value = 0x800
+     * @hide
+     */
+    @TestApi
+    public static final int FLAG_IS_ACCESSIBILITY_EVENT = INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT;
+
+    /**
+     * Private flag that indicates when the system has detected that this motion event
+     * may be inconsistent with respect to the sequence of previously delivered motion events,
+     * such as when a pointer move event is sent but the pointer is not down.
+     *
+     * @hide
+     * @see #isTainted
+     * @see #setTainted
+     */
+    public static final int FLAG_TAINTED = 0x80000000;
+
+    /**
+     * Private flag indicating that this event was synthesized by the system and should be delivered
+     * to the accessibility focused view first. When being dispatched such an event is not handled
+     * by predecessors of the accessibility focused view and after the event reaches that view the
+     * flag is cleared and normal event dispatch is performed. This ensures that the platform can
+     * click on any view that has accessibility focus which is semantically equivalent to asking the
+     * view to perform a click accessibility action but more generic as views not implementing click
+     * action correctly can still be activated.
+     *
+     * @hide
+     * @see #isTargetAccessibilityFocus()
+     * @see #setTargetAccessibilityFocus(boolean)
+     */
+    public static final int FLAG_TARGET_ACCESSIBILITY_FOCUS = 0x40000000;
+
+    /**
+     * Flag indicating the motion event intersected the top edge of the screen.
+     */
+    public static final int EDGE_TOP = 0x00000001;
+
+    /**
+     * Flag indicating the motion event intersected the bottom edge of the screen.
+     */
+    public static final int EDGE_BOTTOM = 0x00000002;
+
+    /**
+     * Flag indicating the motion event intersected the left edge of the screen.
+     */
+    public static final int EDGE_LEFT = 0x00000004;
+
+    /**
+     * Flag indicating the motion event intersected the right edge of the screen.
+     */
+    public static final int EDGE_RIGHT = 0x00000008;
+
+    /**
+     * Axis constant: X axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a touch screen, reports the absolute X screen position of the center of
+     * the touch contact area.  The units are display pixels.
+     * <li>For a touch pad, reports the absolute X surface position of the center of the touch
+     * contact area.  The units are device-dependent; use {@link InputDevice#getMotionRange(int)}
+     * to query the effective range of values.
+     * <li>For a mouse, reports the absolute X screen position of the mouse pointer.
+     * The units are display pixels.
+     * <li>For a trackball, reports the relative horizontal displacement of the trackball.
+     * The value is normalized to a range from -1.0 (left) to 1.0 (right).
+     * <li>For a joystick, reports the absolute X position of the joystick.
+     * The value is normalized to a range from -1.0 (left) to 1.0 (right).
+     * </ul>
+     * </p>
+     *
+     * @see #getX(int)
+     * @see #getHistoricalX(int, int)
+     * @see MotionEvent.PointerCoords#x
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_X = 0;
+
+    /**
+     * Axis constant: Y axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a touch screen, reports the absolute Y screen position of the center of
+     * the touch contact area.  The units are display pixels.
+     * <li>For a touch pad, reports the absolute Y surface position of the center of the touch
+     * contact area.  The units are device-dependent; use {@link InputDevice#getMotionRange(int)}
+     * to query the effective range of values.
+     * <li>For a mouse, reports the absolute Y screen position of the mouse pointer.
+     * The units are display pixels.
+     * <li>For a trackball, reports the relative vertical displacement of the trackball.
+     * The value is normalized to a range from -1.0 (up) to 1.0 (down).
+     * <li>For a joystick, reports the absolute Y position of the joystick.
+     * The value is normalized to a range from -1.0 (up or far) to 1.0 (down or near).
+     * </ul>
+     * </p>
+     *
+     * @see #getY(int)
+     * @see #getHistoricalY(int, int)
+     * @see MotionEvent.PointerCoords#y
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_Y = 1;
+
+    /**
+     * Axis constant: Pressure axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a touch screen or touch pad, reports the approximate pressure applied to the surface
+     * by a finger or other tool.  The value is normalized to a range from
+     * 0 (no pressure at all) to 1 (normal pressure), although values higher than 1
+     * may be generated depending on the calibration of the input device.
+     * <li>For a trackball, the value is set to 1 if the trackball button is pressed
+     * or 0 otherwise.
+     * <li>For a mouse, the value is set to 1 if the primary mouse button is pressed
+     * or 0 otherwise.
+     * </ul>
+     * </p>
+     *
+     * @see #getPressure(int)
+     * @see #getHistoricalPressure(int, int)
+     * @see MotionEvent.PointerCoords#pressure
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_PRESSURE = 2;
+
+    /**
+     * Axis constant: Size axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a touch screen or touch pad, reports the approximate size of the contact area in
+     * relation to the maximum detectable size for the device.  The value is normalized
+     * to a range from 0 (smallest detectable size) to 1 (largest detectable size),
+     * although it is not a linear scale.  This value is of limited use.
+     * To obtain calibrated size information, use
+     * {@link #AXIS_TOUCH_MAJOR} or {@link #AXIS_TOOL_MAJOR}.
+     * </ul>
+     * </p>
+     *
+     * @see #getSize(int)
+     * @see #getHistoricalSize(int, int)
+     * @see MotionEvent.PointerCoords#size
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_SIZE = 3;
+
+    /**
+     * Axis constant: TouchMajor axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a touch screen, reports the length of the major axis of an ellipse that
+     * represents the touch area at the point of contact.
+     * The units are display pixels.
+     * <li>For a touch pad, reports the length of the major axis of an ellipse that
+     * represents the touch area at the point of contact.
+     * The units are device-dependent; use {@link InputDevice#getMotionRange(int)}
+     * to query the effective range of values.
+     * </ul>
+     * </p>
+     *
+     * @see #getTouchMajor(int)
+     * @see #getHistoricalTouchMajor(int, int)
+     * @see MotionEvent.PointerCoords#touchMajor
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_TOUCH_MAJOR = 4;
+
+    /**
+     * Axis constant: TouchMinor axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a touch screen, reports the length of the minor axis of an ellipse that
+     * represents the touch area at the point of contact.
+     * The units are display pixels.
+     * <li>For a touch pad, reports the length of the minor axis of an ellipse that
+     * represents the touch area at the point of contact.
+     * The units are device-dependent; use {@link InputDevice#getMotionRange(int)}
+     * to query the effective range of values.
+     * </ul>
+     * </p><p>
+     * When the touch is circular, the major and minor axis lengths will be equal to one another.
+     * </p>
+     *
+     * @see #getTouchMinor(int)
+     * @see #getHistoricalTouchMinor(int, int)
+     * @see MotionEvent.PointerCoords#touchMinor
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_TOUCH_MINOR = 5;
+
+    /**
+     * Axis constant: ToolMajor axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a touch screen, reports the length of the major axis of an ellipse that
+     * represents the size of the approaching finger or tool used to make contact.
+     * <li>For a touch pad, reports the length of the major axis of an ellipse that
+     * represents the size of the approaching finger or tool used to make contact.
+     * The units are device-dependent; use {@link InputDevice#getMotionRange(int)}
+     * to query the effective range of values.
+     * </ul>
+     * </p><p>
+     * When the touch is circular, the major and minor axis lengths will be equal to one another.
+     * </p><p>
+     * The tool size may be larger than the touch size since the tool may not be fully
+     * in contact with the touch sensor.
+     * </p>
+     *
+     * @see #getToolMajor(int)
+     * @see #getHistoricalToolMajor(int, int)
+     * @see MotionEvent.PointerCoords#toolMajor
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_TOOL_MAJOR = 6;
+
+    /**
+     * Axis constant: ToolMinor axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a touch screen, reports the length of the minor axis of an ellipse that
+     * represents the size of the approaching finger or tool used to make contact.
+     * <li>For a touch pad, reports the length of the minor axis of an ellipse that
+     * represents the size of the approaching finger or tool used to make contact.
+     * The units are device-dependent; use {@link InputDevice#getMotionRange(int)}
+     * to query the effective range of values.
+     * </ul>
+     * </p><p>
+     * When the touch is circular, the major and minor axis lengths will be equal to one another.
+     * </p><p>
+     * The tool size may be larger than the touch size since the tool may not be fully
+     * in contact with the touch sensor.
+     * </p>
+     *
+     * @see #getToolMinor(int)
+     * @see #getHistoricalToolMinor(int, int)
+     * @see MotionEvent.PointerCoords#toolMinor
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_TOOL_MINOR = 7;
+
+    /**
+     * Axis constant: Orientation axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a touch screen or touch pad, reports the orientation of the finger
+     * or tool in radians relative to the vertical plane of the device.
+     * An angle of 0 radians indicates that the major axis of contact is oriented
+     * upwards, is perfectly circular or is of unknown orientation.  A positive angle
+     * indicates that the major axis of contact is oriented to the right.  A negative angle
+     * indicates that the major axis of contact is oriented to the left.
+     * The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians
+     * (finger pointing fully right).
+     * <li>For a stylus, the orientation indicates the direction in which the stylus
+     * is pointing in relation to the vertical axis of the current orientation of the screen.
+     * The range is from -PI radians to PI radians, where 0 is pointing up,
+     * -PI/2 radians is pointing left, -PI or PI radians is pointing down, and PI/2 radians
+     * is pointing right.  See also {@link #AXIS_TILT}.
+     * </ul>
+     * </p>
+     *
+     * @see #getOrientation(int)
+     * @see #getHistoricalOrientation(int, int)
+     * @see MotionEvent.PointerCoords#orientation
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_ORIENTATION = 8;
+
+    /**
+     * Axis constant: Vertical Scroll axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a mouse, reports the relative movement of the vertical scroll wheel.
+     * The value is normalized to a range from -1.0 (down) to 1.0 (up).
+     * </ul>
+     * </p><p>
+     * This axis should be used to scroll views vertically.
+     * </p>
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_VSCROLL = 9;
+
+    /**
+     * Axis constant: Horizontal Scroll axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a mouse, reports the relative movement of the horizontal scroll wheel.
+     * The value is normalized to a range from -1.0 (left) to 1.0 (right).
+     * </ul>
+     * </p><p>
+     * This axis should be used to scroll views horizontally.
+     * </p>
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_HSCROLL = 10;
+
+    /**
+     * Axis constant: Z axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a joystick, reports the absolute Z position of the joystick.
+     * The value is normalized to a range from -1.0 (high) to 1.0 (low).
+     * <em>On game pads with two analog joysticks, this axis is often reinterpreted
+     * to report the absolute X position of the second joystick instead.</em>
+     * </ul>
+     * </p>
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_Z = 11;
+
+    /**
+     * Axis constant: X Rotation axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a joystick, reports the absolute rotation angle about the X axis.
+     * The value is normalized to a range from -1.0 (counter-clockwise) to 1.0 (clockwise).
+     * </ul>
+     * </p>
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_RX = 12;
+
+    /**
+     * Axis constant: Y Rotation axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a joystick, reports the absolute rotation angle about the Y axis.
+     * The value is normalized to a range from -1.0 (counter-clockwise) to 1.0 (clockwise).
+     * </ul>
+     * </p>
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_RY = 13;
+
+    /**
+     * Axis constant: Z Rotation axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a joystick, reports the absolute rotation angle about the Z axis.
+     * The value is normalized to a range from -1.0 (counter-clockwise) to 1.0 (clockwise).
+     * <em>On game pads with two analog joysticks, this axis is often reinterpreted
+     * to report the absolute Y position of the second joystick instead.</em>
+     * </ul>
+     * </p>
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_RZ = 14;
+
+    /**
+     * Axis constant: Hat X axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a joystick, reports the absolute X position of the directional hat control.
+     * The value is normalized to a range from -1.0 (left) to 1.0 (right).
+     * </ul>
+     * </p>
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_HAT_X = 15;
+
+    /**
+     * Axis constant: Hat Y axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a joystick, reports the absolute Y position of the directional hat control.
+     * The value is normalized to a range from -1.0 (up) to 1.0 (down).
+     * </ul>
+     * </p>
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_HAT_Y = 16;
+
+    /**
+     * Axis constant: Left Trigger axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a joystick, reports the absolute position of the left trigger control.
+     * The value is normalized to a range from 0.0 (released) to 1.0 (fully pressed).
+     * </ul>
+     * </p>
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_LTRIGGER = 17;
+
+    /**
+     * Axis constant: Right Trigger axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a joystick, reports the absolute position of the right trigger control.
+     * The value is normalized to a range from 0.0 (released) to 1.0 (fully pressed).
+     * </ul>
+     * </p>
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_RTRIGGER = 18;
+
+    /**
+     * Axis constant: Throttle axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a joystick, reports the absolute position of the throttle control.
+     * The value is normalized to a range from 0.0 (fully open) to 1.0 (fully closed).
+     * </ul>
+     * </p>
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_THROTTLE = 19;
+
+    /**
+     * Axis constant: Rudder axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a joystick, reports the absolute position of the rudder control.
+     * The value is normalized to a range from -1.0 (turn left) to 1.0 (turn right).
+     * </ul>
+     * </p>
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_RUDDER = 20;
+
+    /**
+     * Axis constant: Wheel axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a joystick, reports the absolute position of the steering wheel control.
+     * The value is normalized to a range from -1.0 (turn left) to 1.0 (turn right).
+     * </ul>
+     * </p>
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_WHEEL = 21;
+
+    /**
+     * Axis constant: Gas axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a joystick, reports the absolute position of the gas (accelerator) control.
+     * The value is normalized to a range from 0.0 (no acceleration)
+     * to 1.0 (maximum acceleration).
+     * </ul>
+     * </p>
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_GAS = 22;
+
+    /**
+     * Axis constant: Brake axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a joystick, reports the absolute position of the brake control.
+     * The value is normalized to a range from 0.0 (no braking) to 1.0 (maximum braking).
+     * </ul>
+     * </p>
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_BRAKE = 23;
+
+    /**
+     * Axis constant: Distance axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a stylus, reports the distance of the stylus from the screen.
+     * A value of 0.0 indicates direct contact and larger values indicate increasing
+     * distance from the surface.
+     * </ul>
+     * </p>
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_DISTANCE = 24;
+
+    /**
+     * Axis constant: Tilt axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a stylus, reports the tilt angle of the stylus in radians where
+     * 0 radians indicates that the stylus is being held perpendicular to the
+     * surface, and PI/2 radians indicates that the stylus is being held flat
+     * against the surface.
+     * </ul>
+     * </p>
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int, int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_TILT = 25;
+
+    /**
+     * Axis constant: Generic scroll axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>Reports the relative movement of the generic scrolling device.
+     * </ul>
+     * </p><p>
+     * This axis should be used for scroll events that are neither strictly vertical nor horizontal.
+     * A good example would be the rotation of a rotary encoder input device.
+     * </p>
+     *
+     * @see #getAxisValue(int, int)
+     */
+    public static final int AXIS_SCROLL = 26;
+
+    /**
+     * Axis constant: The movement of x position of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a mouse, reports a difference of x position between the previous position.
+     * This is useful when pointer is captured, in that case the mouse pointer doesn't change
+     * the location but this axis reports the difference which allows the app to see
+     * how the mouse is moved.
+     * </ul>
+     * </p>
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int, int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_RELATIVE_X = 27;
+
+    /**
+     * Axis constant: The movement of y position of a motion event.
+     * <p>
+     * This is similar to {@link #AXIS_RELATIVE_X} but for y-axis.
+     * </p>
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int, int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_RELATIVE_Y = 28;
+
+    /**
+     * Axis constant: Generic 1 axis of a motion event.
+     * The interpretation of a generic axis is device-specific.
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_GENERIC_1 = 32;
+
+    /**
+     * Axis constant: Generic 2 axis of a motion event.
+     * The interpretation of a generic axis is device-specific.
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_GENERIC_2 = 33;
+
+    /**
+     * Axis constant: Generic 3 axis of a motion event.
+     * The interpretation of a generic axis is device-specific.
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_GENERIC_3 = 34;
+
+    /**
+     * Axis constant: Generic 4 axis of a motion event.
+     * The interpretation of a generic axis is device-specific.
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_GENERIC_4 = 35;
+
+    /**
+     * Axis constant: Generic 5 axis of a motion event.
+     * The interpretation of a generic axis is device-specific.
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_GENERIC_5 = 36;
+
+    /**
+     * Axis constant: Generic 6 axis of a motion event.
+     * The interpretation of a generic axis is device-specific.
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_GENERIC_6 = 37;
+
+    /**
+     * Axis constant: Generic 7 axis of a motion event.
+     * The interpretation of a generic axis is device-specific.
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_GENERIC_7 = 38;
+
+    /**
+     * Axis constant: Generic 8 axis of a motion event.
+     * The interpretation of a generic axis is device-specific.
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_GENERIC_8 = 39;
+
+    /**
+     * Axis constant: Generic 9 axis of a motion event.
+     * The interpretation of a generic axis is device-specific.
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_GENERIC_9 = 40;
+
+    /**
+     * Axis constant: Generic 10 axis of a motion event.
+     * The interpretation of a generic axis is device-specific.
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_GENERIC_10 = 41;
+
+    /**
+     * Axis constant: Generic 11 axis of a motion event.
+     * The interpretation of a generic axis is device-specific.
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_GENERIC_11 = 42;
+
+    /**
+     * Axis constant: Generic 12 axis of a motion event.
+     * The interpretation of a generic axis is device-specific.
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_GENERIC_12 = 43;
+
+    /**
+     * Axis constant: Generic 13 axis of a motion event.
+     * The interpretation of a generic axis is device-specific.
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_GENERIC_13 = 44;
+
+    /**
+     * Axis constant: Generic 14 axis of a motion event.
+     * The interpretation of a generic axis is device-specific.
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_GENERIC_14 = 45;
+
+    /**
+     * Axis constant: Generic 15 axis of a motion event.
+     * The interpretation of a generic axis is device-specific.
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_GENERIC_15 = 46;
+
+    /**
+     * Axis constant: Generic 16 axis of a motion event.
+     * The interpretation of a generic axis is device-specific.
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_GENERIC_16 = 47;
+
+    // NOTE: If you add a new axis here you must also add it to:
+    //  native/include/android/input.h
+    //  frameworks/base/include/ui/KeycodeLabels.h
+
+    // Symbolic names of all axes.
+    private static final SparseArray<String> AXIS_SYMBOLIC_NAMES = new SparseArray<String>();
+    static {
+        SparseArray<String> names = AXIS_SYMBOLIC_NAMES;
+        names.append(AXIS_X, "AXIS_X");
+        names.append(AXIS_Y, "AXIS_Y");
+        names.append(AXIS_PRESSURE, "AXIS_PRESSURE");
+        names.append(AXIS_SIZE, "AXIS_SIZE");
+        names.append(AXIS_TOUCH_MAJOR, "AXIS_TOUCH_MAJOR");
+        names.append(AXIS_TOUCH_MINOR, "AXIS_TOUCH_MINOR");
+        names.append(AXIS_TOOL_MAJOR, "AXIS_TOOL_MAJOR");
+        names.append(AXIS_TOOL_MINOR, "AXIS_TOOL_MINOR");
+        names.append(AXIS_ORIENTATION, "AXIS_ORIENTATION");
+        names.append(AXIS_VSCROLL, "AXIS_VSCROLL");
+        names.append(AXIS_HSCROLL, "AXIS_HSCROLL");
+        names.append(AXIS_Z, "AXIS_Z");
+        names.append(AXIS_RX, "AXIS_RX");
+        names.append(AXIS_RY, "AXIS_RY");
+        names.append(AXIS_RZ, "AXIS_RZ");
+        names.append(AXIS_HAT_X, "AXIS_HAT_X");
+        names.append(AXIS_HAT_Y, "AXIS_HAT_Y");
+        names.append(AXIS_LTRIGGER, "AXIS_LTRIGGER");
+        names.append(AXIS_RTRIGGER, "AXIS_RTRIGGER");
+        names.append(AXIS_THROTTLE, "AXIS_THROTTLE");
+        names.append(AXIS_RUDDER, "AXIS_RUDDER");
+        names.append(AXIS_WHEEL, "AXIS_WHEEL");
+        names.append(AXIS_GAS, "AXIS_GAS");
+        names.append(AXIS_BRAKE, "AXIS_BRAKE");
+        names.append(AXIS_DISTANCE, "AXIS_DISTANCE");
+        names.append(AXIS_TILT, "AXIS_TILT");
+        names.append(AXIS_SCROLL, "AXIS_SCROLL");
+        names.append(AXIS_RELATIVE_X, "AXIS_REALTIVE_X");
+        names.append(AXIS_RELATIVE_Y, "AXIS_REALTIVE_Y");
+        names.append(AXIS_GENERIC_1, "AXIS_GENERIC_1");
+        names.append(AXIS_GENERIC_2, "AXIS_GENERIC_2");
+        names.append(AXIS_GENERIC_3, "AXIS_GENERIC_3");
+        names.append(AXIS_GENERIC_4, "AXIS_GENERIC_4");
+        names.append(AXIS_GENERIC_5, "AXIS_GENERIC_5");
+        names.append(AXIS_GENERIC_6, "AXIS_GENERIC_6");
+        names.append(AXIS_GENERIC_7, "AXIS_GENERIC_7");
+        names.append(AXIS_GENERIC_8, "AXIS_GENERIC_8");
+        names.append(AXIS_GENERIC_9, "AXIS_GENERIC_9");
+        names.append(AXIS_GENERIC_10, "AXIS_GENERIC_10");
+        names.append(AXIS_GENERIC_11, "AXIS_GENERIC_11");
+        names.append(AXIS_GENERIC_12, "AXIS_GENERIC_12");
+        names.append(AXIS_GENERIC_13, "AXIS_GENERIC_13");
+        names.append(AXIS_GENERIC_14, "AXIS_GENERIC_14");
+        names.append(AXIS_GENERIC_15, "AXIS_GENERIC_15");
+        names.append(AXIS_GENERIC_16, "AXIS_GENERIC_16");
+    }
+
+    /**
+     * Button constant: Primary button (left mouse button).
+     *
+     * This button constant is not set in response to simple touches with a finger
+     * or stylus tip.  The user must actually push a button.
+     *
+     * @see #getButtonState
+     */
+    public static final int BUTTON_PRIMARY = 1 << 0;
+
+    /**
+     * Button constant: Secondary button (right mouse button).
+     *
+     * @see #getButtonState
+     */
+    public static final int BUTTON_SECONDARY = 1 << 1;
+
+    /**
+     * Button constant: Tertiary button (middle mouse button).
+     *
+     * @see #getButtonState
+     */
+    public static final int BUTTON_TERTIARY = 1 << 2;
+
+    /**
+     * Button constant: Back button pressed (mouse back button).
+     * <p>
+     * The system may send a {@link KeyEvent#KEYCODE_BACK} key press to the application
+     * when this button is pressed.
+     * </p>
+     *
+     * @see #getButtonState
+     */
+    public static final int BUTTON_BACK = 1 << 3;
+
+    /**
+     * Button constant: Forward button pressed (mouse forward button).
+     * <p>
+     * The system may send a {@link KeyEvent#KEYCODE_FORWARD} key press to the application
+     * when this button is pressed.
+     * </p>
+     *
+     * @see #getButtonState
+     */
+    public static final int BUTTON_FORWARD = 1 << 4;
+
+    /**
+     * Button constant: Primary stylus button pressed.
+     *
+     * @see #getButtonState
+     */
+    public static final int BUTTON_STYLUS_PRIMARY = 1 << 5;
+
+    /**
+     * Button constant: Secondary stylus button pressed.
+     *
+     * @see #getButtonState
+     */
+    public static final int BUTTON_STYLUS_SECONDARY = 1 << 6;
+
+    // NOTE: If you add a new axis here you must also add it to:
+    //  native/include/android/input.h
+
+    // Symbolic names of all button states in bit order from least significant
+    // to most significant.
+    private static final String[] BUTTON_SYMBOLIC_NAMES = new String[] {
+        "BUTTON_PRIMARY",
+        "BUTTON_SECONDARY",
+        "BUTTON_TERTIARY",
+        "BUTTON_BACK",
+        "BUTTON_FORWARD",
+        "BUTTON_STYLUS_PRIMARY",
+        "BUTTON_STYLUS_SECONDARY",
+        "0x00000080",
+        "0x00000100",
+        "0x00000200",
+        "0x00000400",
+        "0x00000800",
+        "0x00001000",
+        "0x00002000",
+        "0x00004000",
+        "0x00008000",
+        "0x00010000",
+        "0x00020000",
+        "0x00040000",
+        "0x00080000",
+        "0x00100000",
+        "0x00200000",
+        "0x00400000",
+        "0x00800000",
+        "0x01000000",
+        "0x02000000",
+        "0x04000000",
+        "0x08000000",
+        "0x10000000",
+        "0x20000000",
+        "0x40000000",
+        "0x80000000",
+    };
+
+    /**
+     * Classification constant: None.
+     *
+     * No additional information is available about the current motion event stream.
+     *
+     * @see #getClassification
+     */
+    public static final int CLASSIFICATION_NONE = 0;
+
+    /**
+     * Classification constant: Ambiguous gesture.
+     *
+     * The user's intent with respect to the current event stream is not yet determined.
+     * Gestural actions, such as scrolling, should be inhibited until the classification resolves
+     * to another value or the event stream ends.
+     *
+     * @see #getClassification
+     */
+    public static final int CLASSIFICATION_AMBIGUOUS_GESTURE = 1;
+
+    /**
+     * Classification constant: Deep press.
+     *
+     * The current event stream represents the user intentionally pressing harder on the screen.
+     * This classification type should be used to accelerate the long press behaviour.
+     *
+     * @see #getClassification
+     */
+    public static final int CLASSIFICATION_DEEP_PRESS = 2;
+
+    /** @hide */
+    @Retention(SOURCE)
+    @IntDef(prefix = { "CLASSIFICATION" }, value = {
+            CLASSIFICATION_NONE, CLASSIFICATION_AMBIGUOUS_GESTURE, CLASSIFICATION_DEEP_PRESS})
+    public @interface Classification {};
+
+    /**
+     * Tool type constant: Unknown tool type.
+     * This constant is used when the tool type is not known or is not relevant,
+     * such as for a trackball or other non-pointing device.
+     *
+     * @see #getToolType
+     */
+    public static final int TOOL_TYPE_UNKNOWN = 0;
+
+    /**
+     * Tool type constant: The tool is a finger.
+     *
+     * @see #getToolType
+     */
+    public static final int TOOL_TYPE_FINGER = 1;
+
+    /**
+     * Tool type constant: The tool is a stylus.
+     *
+     * @see #getToolType
+     */
+    public static final int TOOL_TYPE_STYLUS = 2;
+
+    /**
+     * Tool type constant: The tool is a mouse.
+     *
+     * @see #getToolType
+     */
+    public static final int TOOL_TYPE_MOUSE = 3;
+
+    /**
+     * Tool type constant: The tool is an eraser or a stylus being used in an inverted posture.
+     *
+     * @see #getToolType
+     */
+    public static final int TOOL_TYPE_ERASER = 4;
+
+    // NOTE: If you add a new tool type here you must also add it to:
+    //  native/include/android/input.h
+
+    // Symbolic names of all tool types.
+    private static final SparseArray<String> TOOL_TYPE_SYMBOLIC_NAMES = new SparseArray<String>();
+    static {
+        SparseArray<String> names = TOOL_TYPE_SYMBOLIC_NAMES;
+        names.append(TOOL_TYPE_UNKNOWN, "TOOL_TYPE_UNKNOWN");
+        names.append(TOOL_TYPE_FINGER, "TOOL_TYPE_FINGER");
+        names.append(TOOL_TYPE_STYLUS, "TOOL_TYPE_STYLUS");
+        names.append(TOOL_TYPE_MOUSE, "TOOL_TYPE_MOUSE");
+        names.append(TOOL_TYPE_ERASER, "TOOL_TYPE_ERASER");
+    }
+
+    // Private value for history pos that obtains the current sample.
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private static final int HISTORY_CURRENT = -0x80000000;
+
+    // This is essentially the same as native AMOTION_EVENT_INVALID_CURSOR_POSITION as they're all
+    // NaN and we use isnan() everywhere to check validity.
+    private static final float INVALID_CURSOR_POSITION = Float.NaN;
+
+    private static final int MAX_RECYCLED = 10;
+    private static final Object gRecyclerLock = new Object();
+    private static int gRecyclerUsed;
+    private static MotionEvent gRecyclerTop;
+
+    // Shared temporary objects used when translating coordinates supplied by
+    // the caller into single element PointerCoords and pointer id arrays.
+    private static final Object gSharedTempLock = new Object();
+    private static PointerCoords[] gSharedTempPointerCoords;
+    private static PointerProperties[] gSharedTempPointerProperties;
+    private static int[] gSharedTempPointerIndexMap;
+
+    private static final void ensureSharedTempPointerCapacity(int desiredCapacity) {
+        if (gSharedTempPointerCoords == null
+                || gSharedTempPointerCoords.length < desiredCapacity) {
+            int capacity = gSharedTempPointerCoords != null ? gSharedTempPointerCoords.length : 8;
+            while (capacity < desiredCapacity) {
+                capacity *= 2;
+            }
+            gSharedTempPointerCoords = PointerCoords.createArray(capacity);
+            gSharedTempPointerProperties = PointerProperties.createArray(capacity);
+            gSharedTempPointerIndexMap = new int[capacity];
+        }
+    }
+
+    // Pointer to the native MotionEvent object that contains the actual data.
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    private long mNativePtr;
+
+    private MotionEvent mNext;
+
+    private static native long nativeInitialize(long nativePtr,
+            int deviceId, int source, int displayId, int action, int flags, int edgeFlags,
+            int metaState, int buttonState, @Classification int classification,
+            float xOffset, float yOffset, float xPrecision, float yPrecision,
+            long downTimeNanos, long eventTimeNanos,
+            int pointerCount, PointerProperties[] pointerIds, PointerCoords[] pointerCoords);
+    private static native void nativeDispose(long nativePtr);
+    private static native void nativeAddBatch(long nativePtr, long eventTimeNanos,
+            PointerCoords[] pointerCoords, int metaState);
+    private static native void nativeGetPointerCoords(long nativePtr,
+            int pointerIndex, int historyPos, PointerCoords outPointerCoords);
+    private static native void nativeGetPointerProperties(long nativePtr,
+            int pointerIndex, PointerProperties outPointerProperties);
+
+    private static native long nativeReadFromParcel(long nativePtr, Parcel parcel);
+    private static native void nativeWriteToParcel(long nativePtr, Parcel parcel);
+
+    private static native String nativeAxisToString(int axis);
+    private static native int nativeAxisFromString(String label);
+
+    // -------------- @FastNative -------------------------
+
+    @FastNative
+    private static native int nativeGetPointerId(long nativePtr, int pointerIndex);
+    @FastNative
+    private static native int nativeGetToolType(long nativePtr, int pointerIndex);
+    @FastNative
+    private static native long nativeGetEventTimeNanos(long nativePtr, int historyPos);
+    @FastNative
+    @UnsupportedAppUsage
+    private static native float nativeGetRawAxisValue(long nativePtr,
+            int axis, int pointerIndex, int historyPos);
+    @FastNative
+    private static native float nativeGetAxisValue(long nativePtr,
+            int axis, int pointerIndex, int historyPos);
+    @FastNative
+    private static native void nativeTransform(long nativePtr, Matrix matrix);
+    @FastNative
+    private static native void nativeApplyTransform(long nativePtr, Matrix matrix);
+
+    // -------------- @CriticalNative ----------------------
+
+    @CriticalNative
+    private static native long nativeCopy(long destNativePtr, long sourceNativePtr,
+            boolean keepHistory);
+    @CriticalNative
+    private static native int nativeGetId(long nativePtr);
+    @CriticalNative
+    private static native int nativeGetDeviceId(long nativePtr);
+    @CriticalNative
+    private static native int nativeGetSource(long nativePtr);
+    @CriticalNative
+    private static native void nativeSetSource(long nativePtr, int source);
+    @CriticalNative
+    private static native int nativeGetDisplayId(long nativePtr);
+    @CriticalNative
+    private static native void nativeSetDisplayId(long nativePtr, int displayId);
+    @CriticalNative
+    private static native int nativeGetAction(long nativePtr);
+    @CriticalNative
+    private static native void nativeSetAction(long nativePtr, int action);
+    @CriticalNative
+    private static native boolean nativeIsTouchEvent(long nativePtr);
+    @CriticalNative
+    private static native int nativeGetFlags(long nativePtr);
+    @CriticalNative
+    private static native void nativeSetFlags(long nativePtr, int flags);
+    @CriticalNative
+    private static native int nativeGetEdgeFlags(long nativePtr);
+    @CriticalNative
+    private static native void nativeSetEdgeFlags(long nativePtr, int action);
+    @CriticalNative
+    private static native int nativeGetMetaState(long nativePtr);
+    @CriticalNative
+    private static native int nativeGetButtonState(long nativePtr);
+    @CriticalNative
+    private static native void nativeSetButtonState(long nativePtr, int buttonState);
+    @CriticalNative
+    private static native int nativeGetClassification(long nativePtr);
+    @CriticalNative
+    private static native int nativeGetActionButton(long nativePtr);
+    @CriticalNative
+    private static native void nativeSetActionButton(long nativePtr, int actionButton);
+    @CriticalNative
+    private static native void nativeOffsetLocation(long nativePtr, float deltaX, float deltaY);
+    @CriticalNative
+    private static native float nativeGetXOffset(long nativePtr);
+    @CriticalNative
+    private static native float nativeGetYOffset(long nativePtr);
+    @CriticalNative
+    private static native float nativeGetXPrecision(long nativePtr);
+    @CriticalNative
+    private static native float nativeGetYPrecision(long nativePtr);
+    @CriticalNative
+    private static native float nativeGetXCursorPosition(long nativePtr);
+    @CriticalNative
+    private static native float nativeGetYCursorPosition(long nativePtr);
+    @CriticalNative
+    private static native void nativeSetCursorPosition(long nativePtr, float x, float y);
+    @CriticalNative
+    private static native long nativeGetDownTimeNanos(long nativePtr);
+    @CriticalNative
+    private static native void nativeSetDownTimeNanos(long nativePtr, long downTime);
+
+    @CriticalNative
+    private static native int nativeGetPointerCount(long nativePtr);
+    @CriticalNative
+    private static native int nativeFindPointerIndex(long nativePtr, int pointerId);
+
+    @CriticalNative
+    private static native int nativeGetHistorySize(long nativePtr);
+
+    @CriticalNative
+    private static native void nativeScale(long nativePtr, float scale);
+
+    private MotionEvent() {
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mNativePtr != 0) {
+                nativeDispose(mNativePtr);
+                mNativePtr = 0;
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+
+    @UnsupportedAppUsage
+    static private MotionEvent obtain() {
+        final MotionEvent ev;
+        synchronized (gRecyclerLock) {
+            ev = gRecyclerTop;
+            if (ev == null) {
+                return new MotionEvent();
+            }
+            gRecyclerTop = ev.mNext;
+            gRecyclerUsed -= 1;
+        }
+        ev.mNext = null;
+        ev.prepareForReuse();
+        return ev;
+    }
+
+    /**
+     * Create a new MotionEvent, filling in all of the basic values that
+     * define the motion.
+     *
+     * @param downTime The time (in ms) when the user originally pressed down to start
+     * a stream of position events.  This must be obtained from {@link SystemClock#uptimeMillis()}.
+     * @param eventTime The the time (in ms) when this specific event was generated.  This
+     * must be obtained from {@link SystemClock#uptimeMillis()}.
+     * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
+     * @param pointerCount The number of pointers that will be in this event.
+     * @param pointerProperties An array of <em>pointerCount</em> values providing
+     * a {@link PointerProperties} property object for each pointer, which must
+     * include the pointer identifier.
+     * @param pointerCoords An array of <em>pointerCount</em> values providing
+     * a {@link PointerCoords} coordinate object for each pointer.
+     * @param metaState The state of any meta / modifier keys that were in effect when
+     * the event was generated.
+     * @param buttonState The state of buttons that are pressed.
+     * @param xPrecision The precision of the X coordinate being reported.
+     * @param yPrecision The precision of the Y coordinate being reported.
+     * @param deviceId The id for the device that this event came from.  An id of
+     * zero indicates that the event didn't come from a physical device; other
+     * numbers are arbitrary and you shouldn't depend on the values.
+     * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
+     * MotionEvent.
+     * @param source The source of this event.
+     * @param displayId The display ID associated with this event.
+     * @param flags The motion event flags.
+     * @hide
+     */
+    static public MotionEvent obtain(long downTime, long eventTime,
+            int action, int pointerCount, PointerProperties[] pointerProperties,
+            PointerCoords[] pointerCoords, int metaState, int buttonState,
+            float xPrecision, float yPrecision, int deviceId,
+            int edgeFlags, int source, int displayId, int flags) {
+        MotionEvent ev = obtain();
+        final boolean success = ev.initialize(deviceId, source, displayId, action, flags, edgeFlags,
+                metaState, buttonState, CLASSIFICATION_NONE, 0, 0, xPrecision, yPrecision,
+                downTime * NS_PER_MS, eventTime * NS_PER_MS,
+                pointerCount, pointerProperties, pointerCoords);
+        if (!success) {
+            Log.e(TAG, "Could not initialize MotionEvent");
+            ev.recycle();
+            return null;
+        }
+        return ev;
+    }
+
+    /**
+     * Create a new MotionEvent, filling in all of the basic values that
+     * define the motion.
+     *
+     * @param downTime The time (in ms) when the user originally pressed down to start
+     * a stream of position events.  This must be obtained from {@link SystemClock#uptimeMillis()}.
+     * @param eventTime The the time (in ms) when this specific event was generated.  This
+     * must be obtained from {@link SystemClock#uptimeMillis()}.
+     * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
+     * @param pointerCount The number of pointers that will be in this event.
+     * @param pointerProperties An array of <em>pointerCount</em> values providing
+     * a {@link PointerProperties} property object for each pointer, which must
+     * include the pointer identifier.
+     * @param pointerCoords An array of <em>pointerCount</em> values providing
+     * a {@link PointerCoords} coordinate object for each pointer.
+     * @param metaState The state of any meta / modifier keys that were in effect when
+     * the event was generated.
+     * @param buttonState The state of buttons that are pressed.
+     * @param xPrecision The precision of the X coordinate being reported.
+     * @param yPrecision The precision of the Y coordinate being reported.
+     * @param deviceId The id for the device that this event came from.  An id of
+     * zero indicates that the event didn't come from a physical device; other
+     * numbers are arbitrary and you shouldn't depend on the values.
+     * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
+     * MotionEvent.
+     * @param source The source of this event.
+     * @param flags The motion event flags.
+     */
+    public static MotionEvent obtain(long downTime, long eventTime,
+            int action, int pointerCount, PointerProperties[] pointerProperties,
+            PointerCoords[] pointerCoords, int metaState, int buttonState,
+            float xPrecision, float yPrecision, int deviceId,
+            int edgeFlags, int source, int flags) {
+        return obtain(downTime, eventTime, action, pointerCount, pointerProperties, pointerCoords,
+                metaState, buttonState, xPrecision, yPrecision, deviceId, edgeFlags, source,
+                DEFAULT_DISPLAY, flags);
+    }
+
+    /**
+     * Create a new MotionEvent, filling in all of the basic values that
+     * define the motion.
+     *
+     * @param downTime The time (in ms) when the user originally pressed down to start
+     * a stream of position events.  This must be obtained from {@link SystemClock#uptimeMillis()}.
+     * @param eventTime The the time (in ms) when this specific event was generated.  This
+     * must be obtained from {@link SystemClock#uptimeMillis()}.
+     * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
+     * @param pointerCount The number of pointers that will be in this event.
+     * @param pointerIds An array of <em>pointerCount</em> values providing
+     * an identifier for each pointer.
+     * @param pointerCoords An array of <em>pointerCount</em> values providing
+     * a {@link PointerCoords} coordinate object for each pointer.
+     * @param metaState The state of any meta / modifier keys that were in effect when
+     * the event was generated.
+     * @param xPrecision The precision of the X coordinate being reported.
+     * @param yPrecision The precision of the Y coordinate being reported.
+     * @param deviceId The id for the device that this event came from.  An id of
+     * zero indicates that the event didn't come from a physical device; other
+     * numbers are arbitrary and you shouldn't depend on the values.
+     * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
+     * MotionEvent.
+     * @param source The source of this event.
+     * @param flags The motion event flags.
+     *
+     * @deprecated Use {@link #obtain(long, long, int, int, PointerProperties[], PointerCoords[], int, int, float, float, int, int, int, int)}
+     * instead.
+     */
+    @Deprecated
+    static public MotionEvent obtain(long downTime, long eventTime,
+            int action, int pointerCount, int[] pointerIds, PointerCoords[] pointerCoords,
+            int metaState, float xPrecision, float yPrecision, int deviceId,
+            int edgeFlags, int source, int flags) {
+        synchronized (gSharedTempLock) {
+            ensureSharedTempPointerCapacity(pointerCount);
+            final PointerProperties[] pp = gSharedTempPointerProperties;
+            for (int i = 0; i < pointerCount; i++) {
+                pp[i].clear();
+                pp[i].id = pointerIds[i];
+            }
+            return obtain(downTime, eventTime, action, pointerCount, pp,
+                    pointerCoords, metaState, 0, xPrecision, yPrecision, deviceId,
+                    edgeFlags, source, flags);
+        }
+    }
+
+    /**
+     * Create a new MotionEvent, filling in all of the basic values that
+     * define the motion.
+     *
+     * @param downTime The time (in ms) when the user originally pressed down to start
+     * a stream of position events.  This must be obtained from {@link SystemClock#uptimeMillis()}.
+     * @param eventTime  The the time (in ms) when this specific event was generated.  This
+     * must be obtained from {@link SystemClock#uptimeMillis()}.
+     * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
+     * @param x The X coordinate of this event.
+     * @param y The Y coordinate of this event.
+     * @param pressure The current pressure of this event.  The pressure generally
+     * ranges from 0 (no pressure at all) to 1 (normal pressure), however
+     * values higher than 1 may be generated depending on the calibration of
+     * the input device.
+     * @param size A scaled value of the approximate size of the area being pressed when
+     * touched with the finger. The actual value in pixels corresponding to the finger
+     * touch is normalized with a device specific range of values
+     * and scaled to a value between 0 and 1.
+     * @param metaState The state of any meta / modifier keys that were in effect when
+     * the event was generated.
+     * @param xPrecision The precision of the X coordinate being reported.
+     * @param yPrecision The precision of the Y coordinate being reported.
+     * @param deviceId The id for the device that this event came from.  An id of
+     * zero indicates that the event didn't come from a physical device; other
+     * numbers are arbitrary and you shouldn't depend on the values.
+     * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
+     * MotionEvent.
+     */
+    static public MotionEvent obtain(long downTime, long eventTime, int action,
+            float x, float y, float pressure, float size, int metaState,
+            float xPrecision, float yPrecision, int deviceId, int edgeFlags) {
+        return obtain(downTime, eventTime, action, x, y, pressure, size, metaState,
+                xPrecision, yPrecision, deviceId, edgeFlags, InputDevice.SOURCE_UNKNOWN,
+                DEFAULT_DISPLAY);
+    }
+
+    /**
+     * Create a new MotionEvent, filling in all of the basic values that
+     * define the motion.
+     *
+     * @param downTime The time (in ms) when the user originally pressed down to start
+     * a stream of position events.  This must be obtained from {@link SystemClock#uptimeMillis()}.
+     * @param eventTime  The the time (in ms) when this specific event was generated.  This
+     * must be obtained from {@link SystemClock#uptimeMillis()}.
+     * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
+     * @param x The X coordinate of this event.
+     * @param y The Y coordinate of this event.
+     * @param pressure The current pressure of this event.  The pressure generally
+     * ranges from 0 (no pressure at all) to 1 (normal pressure), however
+     * values higher than 1 may be generated depending on the calibration of
+     * the input device.
+     * @param size A scaled value of the approximate size of the area being pressed when
+     * touched with the finger. The actual value in pixels corresponding to the finger
+     * touch is normalized with a device specific range of values
+     * and scaled to a value between 0 and 1.
+     * @param metaState The state of any meta / modifier keys that were in effect when
+     * the event was generated.
+     * @param xPrecision The precision of the X coordinate being reported.
+     * @param yPrecision The precision of the Y coordinate being reported.
+     * @param deviceId The id for the device that this event came from.  An id of
+     * zero indicates that the event didn't come from a physical device; other
+     * numbers are arbitrary and you shouldn't depend on the values.
+     * @param source The source of this event.
+     * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
+     * MotionEvent.
+     * @param displayId The display ID associated with this event.
+     * @hide
+     */
+    public static MotionEvent obtain(long downTime, long eventTime, int action,
+            float x, float y, float pressure, float size, int metaState,
+            float xPrecision, float yPrecision, int deviceId, int edgeFlags, int source,
+            int displayId) {
+        MotionEvent ev = obtain();
+        synchronized (gSharedTempLock) {
+            ensureSharedTempPointerCapacity(1);
+            final PointerProperties[] pp = gSharedTempPointerProperties;
+            pp[0].clear();
+            pp[0].id = 0;
+
+            final PointerCoords pc[] = gSharedTempPointerCoords;
+            pc[0].clear();
+            pc[0].x = x;
+            pc[0].y = y;
+            pc[0].pressure = pressure;
+            pc[0].size = size;
+
+            ev.initialize(deviceId, source, displayId,
+                    action, 0, edgeFlags, metaState, 0 /*buttonState*/, CLASSIFICATION_NONE,
+                    0, 0, xPrecision, yPrecision,
+                    downTime * NS_PER_MS, eventTime * NS_PER_MS,
+                    1, pp, pc);
+            return ev;
+        }
+    }
+
+    /**
+     * Create a new MotionEvent, filling in all of the basic values that
+     * define the motion.
+     *
+     * @param downTime The time (in ms) when the user originally pressed down to start
+     * a stream of position events.  This must be obtained from {@link SystemClock#uptimeMillis()}.
+     * @param eventTime  The the time (in ms) when this specific event was generated.  This
+     * must be obtained from {@link SystemClock#uptimeMillis()}.
+     * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
+     * @param pointerCount The number of pointers that are active in this event.
+     * @param x The X coordinate of this event.
+     * @param y The Y coordinate of this event.
+     * @param pressure The current pressure of this event.  The pressure generally
+     * ranges from 0 (no pressure at all) to 1 (normal pressure), however
+     * values higher than 1 may be generated depending on the calibration of
+     * the input device.
+     * @param size A scaled value of the approximate size of the area being pressed when
+     * touched with the finger. The actual value in pixels corresponding to the finger
+     * touch is normalized with a device specific range of values
+     * and scaled to a value between 0 and 1.
+     * @param metaState The state of any meta / modifier keys that were in effect when
+     * the event was generated.
+     * @param xPrecision The precision of the X coordinate being reported.
+     * @param yPrecision The precision of the Y coordinate being reported.
+     * @param deviceId The id for the device that this event came from.  An id of
+     * zero indicates that the event didn't come from a physical device; other
+     * numbers are arbitrary and you shouldn't depend on the values.
+     * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
+     * MotionEvent.
+     *
+     * @deprecated Use {@link #obtain(long, long, int, float, float, float, float, int, float, float, int, int)}
+     * instead.
+     */
+    @Deprecated
+    static public MotionEvent obtain(long downTime, long eventTime, int action,
+            int pointerCount, float x, float y, float pressure, float size, int metaState,
+            float xPrecision, float yPrecision, int deviceId, int edgeFlags) {
+        return obtain(downTime, eventTime, action, x, y, pressure, size,
+                metaState, xPrecision, yPrecision, deviceId, edgeFlags);
+    }
+
+    /**
+     * Create a new MotionEvent, filling in a subset of the basic motion
+     * values.  Those not specified here are: device id (always 0), pressure
+     * and size (always 1), x and y precision (always 1), and edgeFlags (always 0).
+     *
+     * @param downTime The time (in ms) when the user originally pressed down to start
+     * a stream of position events.  This must be obtained from {@link SystemClock#uptimeMillis()}.
+     * @param eventTime  The the time (in ms) when this specific event was generated.  This
+     * must be obtained from {@link SystemClock#uptimeMillis()}.
+     * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
+     * @param x The X coordinate of this event.
+     * @param y The Y coordinate of this event.
+     * @param metaState The state of any meta / modifier keys that were in effect when
+     * the event was generated.
+     */
+    static public MotionEvent obtain(long downTime, long eventTime, int action,
+            float x, float y, int metaState) {
+        return obtain(downTime, eventTime, action, x, y, 1.0f, 1.0f,
+                metaState, 1.0f, 1.0f, 0, 0);
+    }
+
+    /**
+     * Create a new MotionEvent, copying from an existing one.
+     */
+    static public MotionEvent obtain(MotionEvent other) {
+        if (other == null) {
+            throw new IllegalArgumentException("other motion event must not be null");
+        }
+
+        MotionEvent ev = obtain();
+        ev.mNativePtr = nativeCopy(ev.mNativePtr, other.mNativePtr, true /*keepHistory*/);
+        return ev;
+    }
+
+    /**
+     * Create a new MotionEvent, copying from an existing one, but not including
+     * any historical point information.
+     */
+    static public MotionEvent obtainNoHistory(MotionEvent other) {
+        if (other == null) {
+            throw new IllegalArgumentException("other motion event must not be null");
+        }
+
+        MotionEvent ev = obtain();
+        ev.mNativePtr = nativeCopy(ev.mNativePtr, other.mNativePtr, false /*keepHistory*/);
+        return ev;
+    }
+
+    private boolean initialize(int deviceId, int source, int displayId, int action, int flags,
+            int edgeFlags, int metaState, int buttonState, @Classification int classification,
+            float xOffset, float yOffset, float xPrecision, float yPrecision,
+            long downTimeNanos, long eventTimeNanos,
+            int pointerCount, PointerProperties[] pointerIds, PointerCoords[] pointerCoords) {
+        mNativePtr = nativeInitialize(mNativePtr, deviceId, source, displayId, action, flags,
+                edgeFlags, metaState, buttonState, classification, xOffset, yOffset,
+                xPrecision, yPrecision, downTimeNanos, eventTimeNanos, pointerCount, pointerIds,
+                pointerCoords);
+        if (mNativePtr == 0) {
+            return false;
+        }
+        updateCursorPosition();
+        return true;
+    }
+
+    /** @hide */
+    @Override
+    @UnsupportedAppUsage
+    public MotionEvent copy() {
+        return obtain(this);
+    }
+
+    /**
+     * Recycle the MotionEvent, to be re-used by a later caller.  After calling
+     * this function you must not ever touch the event again.
+     */
+    @Override
+    public final void recycle() {
+        super.recycle();
+
+        synchronized (gRecyclerLock) {
+            if (gRecyclerUsed < MAX_RECYCLED) {
+                gRecyclerUsed++;
+                mNext = gRecyclerTop;
+                gRecyclerTop = this;
+            }
+        }
+    }
+
+    /**
+     * Applies a scale factor to all points within this event.
+     *
+     * This method is used to adjust touch events to simulate different density
+     * displays for compatibility mode.  The values returned by {@link #getRawX()},
+     * {@link #getRawY()}, {@link #getXPrecision()} and {@link #getYPrecision()}
+     * are also affected by the scale factor.
+     *
+     * @param scale The scale factor to apply.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public final void scale(float scale) {
+        if (scale != 1.0f) {
+            nativeScale(mNativePtr, scale);
+        }
+    }
+
+    /** @hide */
+    @Override
+    public int getId() {
+        return nativeGetId(mNativePtr);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final int getDeviceId() {
+        return nativeGetDeviceId(mNativePtr);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final int getSource() {
+        return nativeGetSource(mNativePtr);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final void setSource(int source) {
+        if (source == getSource()) {
+            return;
+        }
+        nativeSetSource(mNativePtr, source);
+        updateCursorPosition();
+    }
+
+    /** @hide */
+    @Override
+    public int getDisplayId() {
+        return nativeGetDisplayId(mNativePtr);
+    }
+
+    /** @hide */
+    @TestApi
+    @Override
+    public void setDisplayId(int displayId) {
+        nativeSetDisplayId(mNativePtr, displayId);
+    }
+
+    /**
+     * Return the kind of action being performed.
+     * Consider using {@link #getActionMasked} and {@link #getActionIndex} to retrieve
+     * the separate masked action and pointer index.
+     * @return The action, such as {@link #ACTION_DOWN} or
+     * the combination of {@link #ACTION_POINTER_DOWN} with a shifted pointer index.
+     */
+    public final int getAction() {
+        return nativeGetAction(mNativePtr);
+    }
+
+    /**
+     * Return the masked action being performed, without pointer index information.
+     * Use {@link #getActionIndex} to return the index associated with pointer actions.
+     * @return The action, such as {@link #ACTION_DOWN} or {@link #ACTION_POINTER_DOWN}.
+     */
+    public final int getActionMasked() {
+        return nativeGetAction(mNativePtr) & ACTION_MASK;
+    }
+
+    /**
+     * For {@link #ACTION_POINTER_DOWN} or {@link #ACTION_POINTER_UP}
+     * as returned by {@link #getActionMasked}, this returns the associated
+     * pointer index.
+     * The index may be used with {@link #getPointerId(int)},
+     * {@link #getX(int)}, {@link #getY(int)}, {@link #getPressure(int)},
+     * and {@link #getSize(int)} to get information about the pointer that has
+     * gone down or up.
+     * @return The index associated with the action.
+     */
+    public final int getActionIndex() {
+        return (nativeGetAction(mNativePtr) & ACTION_POINTER_INDEX_MASK)
+                >> ACTION_POINTER_INDEX_SHIFT;
+    }
+
+    /**
+     * Returns true if this motion event is a touch event.
+     * <p>
+     * Specifically excludes pointer events with action {@link #ACTION_HOVER_MOVE},
+     * {@link #ACTION_HOVER_ENTER}, {@link #ACTION_HOVER_EXIT}, or {@link #ACTION_SCROLL}
+     * because they are not actually touch events (the pointer is not down).
+     * </p>
+     * @return True if this motion event is a touch event.
+     * @hide
+     */
+    public final boolean isTouchEvent() {
+        return nativeIsTouchEvent(mNativePtr);
+    }
+
+    /**
+     * Gets the motion event flags.
+     *
+     * @see #FLAG_WINDOW_IS_OBSCURED
+     */
+    public final int getFlags() {
+        return nativeGetFlags(mNativePtr);
+    }
+
+    /** @hide */
+    @Override
+    public final boolean isTainted() {
+        final int flags = getFlags();
+        return (flags & FLAG_TAINTED) != 0;
+    }
+
+    /** @hide */
+    @Override
+    public final void setTainted(boolean tainted) {
+        final int flags = getFlags();
+        nativeSetFlags(mNativePtr, tainted ? flags | FLAG_TAINTED : flags & ~FLAG_TAINTED);
+    }
+
+    /** @hide */
+    public  boolean isTargetAccessibilityFocus() {
+        final int flags = getFlags();
+        return (flags & FLAG_TARGET_ACCESSIBILITY_FOCUS) != 0;
+    }
+
+    /** @hide */
+    public void setTargetAccessibilityFocus(boolean targetsFocus) {
+        final int flags = getFlags();
+        nativeSetFlags(mNativePtr, targetsFocus
+                ? flags | FLAG_TARGET_ACCESSIBILITY_FOCUS
+                : flags & ~FLAG_TARGET_ACCESSIBILITY_FOCUS);
+    }
+
+    /** @hide */
+    public final boolean isHoverExitPending() {
+        final int flags = getFlags();
+        return (flags & FLAG_HOVER_EXIT_PENDING) != 0;
+    }
+
+    /** @hide */
+    public void setHoverExitPending(boolean hoverExitPending) {
+        final int flags = getFlags();
+        nativeSetFlags(mNativePtr, hoverExitPending
+                ? flags | FLAG_HOVER_EXIT_PENDING
+                : flags & ~FLAG_HOVER_EXIT_PENDING);
+    }
+
+    /**
+     * Returns the time (in ms) when the user originally pressed down to start
+     * a stream of position events.
+     */
+    public final long getDownTime() {
+        return nativeGetDownTimeNanos(mNativePtr) / NS_PER_MS;
+    }
+
+    /**
+     * Sets the time (in ms) when the user originally pressed down to start
+     * a stream of position events.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public final void setDownTime(long downTime) {
+        nativeSetDownTimeNanos(mNativePtr, downTime * NS_PER_MS);
+    }
+
+    /**
+     * Retrieve the time this event occurred,
+     * in the {@link android.os.SystemClock#uptimeMillis} time base.
+     *
+     * @return Returns the time this event occurred,
+     * in the {@link android.os.SystemClock#uptimeMillis} time base.
+     */
+    @Override
+    public final long getEventTime() {
+        return nativeGetEventTimeNanos(mNativePtr, HISTORY_CURRENT) / NS_PER_MS;
+    }
+
+    /**
+     * Retrieve the time this event occurred,
+     * in the {@link android.os.SystemClock#uptimeMillis} time base but with
+     * nanosecond precision.
+     * <p>
+     * The value is in nanosecond precision but it may not have nanosecond accuracy.
+     * </p>
+     *
+     * @return Returns the time this event occurred,
+     * in the {@link android.os.SystemClock#uptimeMillis} time base but with
+     * nanosecond precision.
+     *
+     * @hide
+     */
+    @Override
+    @UnsupportedAppUsage
+    public final long getEventTimeNano() {
+        return nativeGetEventTimeNanos(mNativePtr, HISTORY_CURRENT);
+    }
+
+    /**
+     * {@link #getX(int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
+     *
+     * @see #AXIS_X
+     */
+    public final float getX() {
+        return nativeGetAxisValue(mNativePtr, AXIS_X, 0, HISTORY_CURRENT);
+    }
+
+    /**
+     * {@link #getY(int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
+     *
+     * @see #AXIS_Y
+     */
+    public final float getY() {
+        return nativeGetAxisValue(mNativePtr, AXIS_Y, 0, HISTORY_CURRENT);
+    }
+
+    /**
+     * {@link #getPressure(int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
+     *
+     * @see #AXIS_PRESSURE
+     */
+    public final float getPressure() {
+        return nativeGetAxisValue(mNativePtr, AXIS_PRESSURE, 0, HISTORY_CURRENT);
+    }
+
+    /**
+     * {@link #getSize(int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
+     *
+     * @see #AXIS_SIZE
+     */
+    public final float getSize() {
+        return nativeGetAxisValue(mNativePtr, AXIS_SIZE, 0, HISTORY_CURRENT);
+    }
+
+    /**
+     * {@link #getTouchMajor(int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
+     *
+     * @see #AXIS_TOUCH_MAJOR
+     */
+    public final float getTouchMajor() {
+        return nativeGetAxisValue(mNativePtr, AXIS_TOUCH_MAJOR, 0, HISTORY_CURRENT);
+    }
+
+    /**
+     * {@link #getTouchMinor(int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
+     *
+     * @see #AXIS_TOUCH_MINOR
+     */
+    public final float getTouchMinor() {
+        return nativeGetAxisValue(mNativePtr, AXIS_TOUCH_MINOR, 0, HISTORY_CURRENT);
+    }
+
+    /**
+     * {@link #getToolMajor(int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
+     *
+     * @see #AXIS_TOOL_MAJOR
+     */
+    public final float getToolMajor() {
+        return nativeGetAxisValue(mNativePtr, AXIS_TOOL_MAJOR, 0, HISTORY_CURRENT);
+    }
+
+    /**
+     * {@link #getToolMinor(int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
+     *
+     * @see #AXIS_TOOL_MINOR
+     */
+    public final float getToolMinor() {
+        return nativeGetAxisValue(mNativePtr, AXIS_TOOL_MINOR, 0, HISTORY_CURRENT);
+    }
+
+    /**
+     * {@link #getOrientation(int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
+     *
+     * @see #AXIS_ORIENTATION
+     */
+    public final float getOrientation() {
+        return nativeGetAxisValue(mNativePtr, AXIS_ORIENTATION, 0, HISTORY_CURRENT);
+    }
+
+    /**
+     * {@link #getAxisValue(int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
+     *
+     * @param axis The axis identifier for the axis value to retrieve.
+     *
+     * @see #AXIS_X
+     * @see #AXIS_Y
+     */
+    public final float getAxisValue(int axis) {
+        return nativeGetAxisValue(mNativePtr, axis, 0, HISTORY_CURRENT);
+    }
+
+    /**
+     * The number of pointers of data contained in this event.  Always
+     * >= 1.
+     */
+    public final int getPointerCount() {
+        return nativeGetPointerCount(mNativePtr);
+    }
+
+    /**
+     * Return the pointer identifier associated with a particular pointer
+     * data index in this event.  The identifier tells you the actual pointer
+     * number associated with the data, accounting for individual pointers
+     * going up and down since the start of the current gesture.
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
+     */
+    public final int getPointerId(int pointerIndex) {
+        return nativeGetPointerId(mNativePtr, pointerIndex);
+    }
+
+    /**
+     * Gets the tool type of a pointer for the given pointer index.
+     * The tool type indicates the type of tool used to make contact such
+     * as a finger or stylus, if known.
+     *
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
+     * @return The tool type of the pointer.
+     *
+     * @see #TOOL_TYPE_UNKNOWN
+     * @see #TOOL_TYPE_FINGER
+     * @see #TOOL_TYPE_STYLUS
+     * @see #TOOL_TYPE_MOUSE
+     */
+    public final int getToolType(int pointerIndex) {
+        return nativeGetToolType(mNativePtr, pointerIndex);
+    }
+
+    /**
+     * Given a pointer identifier, find the index of its data in the event.
+     *
+     * @param pointerId The identifier of the pointer to be found.
+     * @return Returns either the index of the pointer (for use with
+     * {@link #getX(int)} et al.), or -1 if there is no data available for
+     * that pointer identifier.
+     */
+    public final int findPointerIndex(int pointerId) {
+        return nativeFindPointerIndex(mNativePtr, pointerId);
+    }
+
+    /**
+     * Returns the X coordinate of this event for the given pointer
+     * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+     * identifier for this index).
+     * Whole numbers are pixels; the
+     * value may have a fraction for input devices that are sub-pixel precise.
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
+     *
+     * @see #AXIS_X
+     */
+    public final float getX(int pointerIndex) {
+        return nativeGetAxisValue(mNativePtr, AXIS_X, pointerIndex, HISTORY_CURRENT);
+    }
+
+    /**
+     * Returns the Y coordinate of this event for the given pointer
+     * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+     * identifier for this index).
+     * Whole numbers are pixels; the
+     * value may have a fraction for input devices that are sub-pixel precise.
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
+     *
+     * @see #AXIS_Y
+     */
+    public final float getY(int pointerIndex) {
+        return nativeGetAxisValue(mNativePtr, AXIS_Y, pointerIndex, HISTORY_CURRENT);
+    }
+
+    /**
+     * Returns the current pressure of this event for the given pointer
+     * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+     * identifier for this index).
+     * The pressure generally
+     * ranges from 0 (no pressure at all) to 1 (normal pressure), however
+     * values higher than 1 may be generated depending on the calibration of
+     * the input device.
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
+     *
+     * @see #AXIS_PRESSURE
+     */
+    public final float getPressure(int pointerIndex) {
+        return nativeGetAxisValue(mNativePtr, AXIS_PRESSURE, pointerIndex, HISTORY_CURRENT);
+    }
+
+    /**
+     * Returns a scaled value of the approximate size for the given pointer
+     * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+     * identifier for this index).
+     * This represents some approximation of the area of the screen being
+     * pressed; the actual value in pixels corresponding to the
+     * touch is normalized with the device specific range of values
+     * and scaled to a value between 0 and 1. The value of size can be used to
+     * determine fat touch events.
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
+     *
+     * @see #AXIS_SIZE
+     */
+    public final float getSize(int pointerIndex) {
+        return nativeGetAxisValue(mNativePtr, AXIS_SIZE, pointerIndex, HISTORY_CURRENT);
+    }
+
+    /**
+     * Returns the length of the major axis of an ellipse that describes the touch
+     * area at the point of contact for the given pointer
+     * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+     * identifier for this index).
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
+     *
+     * @see #AXIS_TOUCH_MAJOR
+     */
+    public final float getTouchMajor(int pointerIndex) {
+        return nativeGetAxisValue(mNativePtr, AXIS_TOUCH_MAJOR, pointerIndex, HISTORY_CURRENT);
+    }
+
+    /**
+     * Returns the length of the minor axis of an ellipse that describes the touch
+     * area at the point of contact for the given pointer
+     * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+     * identifier for this index).
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
+     *
+     * @see #AXIS_TOUCH_MINOR
+     */
+    public final float getTouchMinor(int pointerIndex) {
+        return nativeGetAxisValue(mNativePtr, AXIS_TOUCH_MINOR, pointerIndex, HISTORY_CURRENT);
+    }
+
+    /**
+     * Returns the length of the major axis of an ellipse that describes the size of
+     * the approaching tool for the given pointer
+     * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+     * identifier for this index).
+     * The tool area represents the estimated size of the finger or pen that is
+     * touching the device independent of its actual touch area at the point of contact.
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
+     *
+     * @see #AXIS_TOOL_MAJOR
+     */
+    public final float getToolMajor(int pointerIndex) {
+        return nativeGetAxisValue(mNativePtr, AXIS_TOOL_MAJOR, pointerIndex, HISTORY_CURRENT);
+    }
+
+    /**
+     * Returns the length of the minor axis of an ellipse that describes the size of
+     * the approaching tool for the given pointer
+     * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+     * identifier for this index).
+     * The tool area represents the estimated size of the finger or pen that is
+     * touching the device independent of its actual touch area at the point of contact.
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
+     *
+     * @see #AXIS_TOOL_MINOR
+     */
+    public final float getToolMinor(int pointerIndex) {
+        return nativeGetAxisValue(mNativePtr, AXIS_TOOL_MINOR, pointerIndex, HISTORY_CURRENT);
+    }
+
+    /**
+     * Returns the orientation of the touch area and tool area in radians clockwise from vertical
+     * for the given pointer <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+     * identifier for this index).
+     * An angle of 0 radians indicates that the major axis of contact is oriented
+     * upwards, is perfectly circular or is of unknown orientation.  A positive angle
+     * indicates that the major axis of contact is oriented to the right.  A negative angle
+     * indicates that the major axis of contact is oriented to the left.
+     * The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians
+     * (finger pointing fully right).
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
+     *
+     * @see #AXIS_ORIENTATION
+     */
+    public final float getOrientation(int pointerIndex) {
+        return nativeGetAxisValue(mNativePtr, AXIS_ORIENTATION, pointerIndex, HISTORY_CURRENT);
+    }
+
+    /**
+     * Returns the value of the requested axis for the given pointer <em>index</em>
+     * (use {@link #getPointerId(int)} to find the pointer identifier for this index).
+     *
+     * @param axis The axis identifier for the axis value to retrieve.
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
+     * @return The value of the axis, or 0 if the axis is not available.
+     *
+     * @see #AXIS_X
+     * @see #AXIS_Y
+     */
+    public final float getAxisValue(int axis, int pointerIndex) {
+        return nativeGetAxisValue(mNativePtr, axis, pointerIndex, HISTORY_CURRENT);
+    }
+
+    /**
+     * Populates a {@link PointerCoords} object with pointer coordinate data for
+     * the specified pointer index.
+     *
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
+     * @param outPointerCoords The pointer coordinate object to populate.
+     *
+     * @see PointerCoords
+     */
+    public final void getPointerCoords(int pointerIndex, PointerCoords outPointerCoords) {
+        nativeGetPointerCoords(mNativePtr, pointerIndex, HISTORY_CURRENT, outPointerCoords);
+    }
+
+    /**
+     * Populates a {@link PointerProperties} object with pointer properties for
+     * the specified pointer index.
+     *
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
+     * @param outPointerProperties The pointer properties object to populate.
+     *
+     * @see PointerProperties
+     */
+    public final void getPointerProperties(int pointerIndex,
+            PointerProperties outPointerProperties) {
+        nativeGetPointerProperties(mNativePtr, pointerIndex, outPointerProperties);
+    }
+
+    /**
+     * Returns the state of any meta / modifier keys that were in effect when
+     * the event was generated.  This is the same values as those
+     * returned by {@link KeyEvent#getMetaState() KeyEvent.getMetaState}.
+     *
+     * @return an integer in which each bit set to 1 represents a pressed
+     *         meta key
+     *
+     * @see KeyEvent#getMetaState()
+     */
+    public final int getMetaState() {
+        return nativeGetMetaState(mNativePtr);
+    }
+
+    /**
+     * Gets the state of all buttons that are pressed such as a mouse or stylus button.
+     *
+     * @return The button state.
+     *
+     * @see #BUTTON_PRIMARY
+     * @see #BUTTON_SECONDARY
+     * @see #BUTTON_TERTIARY
+     * @see #BUTTON_FORWARD
+     * @see #BUTTON_BACK
+     * @see #BUTTON_STYLUS_PRIMARY
+     * @see #BUTTON_STYLUS_SECONDARY
+     */
+    public final int getButtonState() {
+        return nativeGetButtonState(mNativePtr);
+    }
+
+    /**
+     * Sets the bitfield indicating which buttons are pressed.
+     *
+     * @see #getButtonState()
+     * @hide
+     */
+    @TestApi
+    public final void setButtonState(int buttonState) {
+        nativeSetButtonState(mNativePtr, buttonState);
+    }
+
+    /**
+     * Returns the classification for the current gesture.
+     * The classification may change as more events become available for the same gesture.
+     *
+     * @see #CLASSIFICATION_NONE
+     * @see #CLASSIFICATION_AMBIGUOUS_GESTURE
+     * @see #CLASSIFICATION_DEEP_PRESS
+     */
+    public @Classification int getClassification() {
+        return nativeGetClassification(mNativePtr);
+    }
+
+    /**
+     * Gets which button has been modified during a press or release action.
+     *
+     * For actions other than {@link #ACTION_BUTTON_PRESS} and {@link #ACTION_BUTTON_RELEASE}
+     * the returned value is undefined.
+     *
+     * @see #getButtonState()
+     */
+    public final int getActionButton() {
+        return nativeGetActionButton(mNativePtr);
+    }
+
+    /**
+     * Sets the action button for the event.
+     *
+     * @see #getActionButton()
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @TestApi
+    public final void setActionButton(int button) {
+        nativeSetActionButton(mNativePtr, button);
+    }
+
+    /**
+     * Returns the original raw X coordinate of this event.  For touch
+     * events on the screen, this is the original location of the event
+     * on the screen, before it had been adjusted for the containing window
+     * and views.
+     *
+     * @see #getX(int)
+     * @see #AXIS_X
+     */
+    public final float getRawX() {
+        return nativeGetRawAxisValue(mNativePtr, AXIS_X, 0, HISTORY_CURRENT);
+    }
+
+    /**
+     * Returns the original raw Y coordinate of this event.  For touch
+     * events on the screen, this is the original location of the event
+     * on the screen, before it had been adjusted for the containing window
+     * and views.
+     *
+     * @see #getY(int)
+     * @see #AXIS_Y
+     */
+    public final float getRawY() {
+        return nativeGetRawAxisValue(mNativePtr, AXIS_Y, 0, HISTORY_CURRENT);
+    }
+
+    /**
+     * Returns the original raw X coordinate of this event.  For touch
+     * events on the screen, this is the original location of the event
+     * on the screen, before it had been adjusted for the containing window
+     * and views.
+     *
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
+     *
+     * @see #getX(int)
+     * @see #AXIS_X
+     */
+    public float getRawX(int pointerIndex) {
+        return nativeGetRawAxisValue(mNativePtr, AXIS_X, pointerIndex, HISTORY_CURRENT);
+    }
+
+    /**
+     * Returns the original raw Y coordinate of this event.  For touch
+     * events on the screen, this is the original location of the event
+     * on the screen, before it had been adjusted for the containing window
+     * and views.
+     *
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
+     *
+     * @see #getY(int)
+     * @see #AXIS_Y
+     */
+    public float getRawY(int pointerIndex) {
+        return nativeGetRawAxisValue(mNativePtr, AXIS_Y, pointerIndex, HISTORY_CURRENT);
+    }
+
+    /**
+     * Return the precision of the X coordinates being reported.  You can
+     * multiply this number with {@link #getX} to find the actual hardware
+     * value of the X coordinate.
+     * @return Returns the precision of X coordinates being reported.
+     *
+     * @see #AXIS_X
+     */
+    public final float getXPrecision() {
+        return nativeGetXPrecision(mNativePtr);
+    }
+
+    /**
+     * Return the precision of the Y coordinates being reported.  You can
+     * multiply this number with {@link #getY} to find the actual hardware
+     * value of the Y coordinate.
+     * @return Returns the precision of Y coordinates being reported.
+     *
+     * @see #AXIS_Y
+     */
+    public final float getYPrecision() {
+        return nativeGetYPrecision(mNativePtr);
+    }
+
+    /**
+     * Returns the x coordinate of mouse cursor position when this event is
+     * reported. This value is only valid if {@link #getSource()} returns
+     * {@link InputDevice#SOURCE_MOUSE}.
+     *
+     * @hide
+     */
+    public float getXCursorPosition() {
+        return nativeGetXCursorPosition(mNativePtr);
+    }
+
+    /**
+     * Returns the y coordinate of mouse cursor position when this event is
+     * reported. This value is only valid if {@link #getSource()} returns
+     * {@link InputDevice#SOURCE_MOUSE}.
+     *
+     * @hide
+     */
+    public float getYCursorPosition() {
+        return nativeGetYCursorPosition(mNativePtr);
+    }
+
+    /**
+     * Sets cursor position to given coordinates. The coordinate in parameters should be after
+     * offsetting. In other words, the effect of this function is {@link #getXCursorPosition()} and
+     * {@link #getYCursorPosition()} will return the same value passed in the parameters.
+     *
+     * @hide
+     */
+    private void setCursorPosition(float x, float y) {
+        nativeSetCursorPosition(mNativePtr, x, y);
+    }
+
+    /**
+     * Returns the number of historical points in this event.  These are
+     * movements that have occurred between this event and the previous event.
+     * This only applies to ACTION_MOVE events -- all other actions will have
+     * a size of 0.
+     *
+     * @return Returns the number of historical points in the event.
+     */
+    public final int getHistorySize() {
+        return nativeGetHistorySize(mNativePtr);
+    }
+
+    /**
+     * Returns the time that a historical movement occurred between this event
+     * and the previous event, in the {@link android.os.SystemClock#uptimeMillis} time base.
+     * <p>
+     * This only applies to ACTION_MOVE events.
+     * </p>
+     *
+     * @param pos Which historical value to return; must be less than
+     * {@link #getHistorySize}
+     * @return Returns the time that a historical movement occurred between this
+     * event and the previous event,
+     * in the {@link android.os.SystemClock#uptimeMillis} time base.
+     *
+     * @see #getHistorySize
+     * @see #getEventTime
+     */
+    public final long getHistoricalEventTime(int pos) {
+        return nativeGetEventTimeNanos(mNativePtr, pos) / NS_PER_MS;
+    }
+
+    /**
+     * Returns the time that a historical movement occurred between this event
+     * and the previous event, in the {@link android.os.SystemClock#uptimeMillis} time base
+     * but with nanosecond (instead of millisecond) precision.
+     * <p>
+     * This only applies to ACTION_MOVE events.
+     * </p><p>
+     * The value is in nanosecond precision but it may not have nanosecond accuracy.
+     * </p>
+     *
+     * @param pos Which historical value to return; must be less than
+     * {@link #getHistorySize}
+     * @return Returns the time that a historical movement occurred between this
+     * event and the previous event,
+     * in the {@link android.os.SystemClock#uptimeMillis} time base but with
+     * nanosecond (instead of millisecond) precision.
+     *
+     * @see #getHistorySize
+     * @see #getEventTime
+     *
+     * @hide
+     */
+    public final long getHistoricalEventTimeNano(int pos) {
+        return nativeGetEventTimeNanos(mNativePtr, pos);
+    }
+
+    /**
+     * {@link #getHistoricalX(int, int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
+     *
+     * @param pos Which historical value to return; must be less than
+     * {@link #getHistorySize}
+     *
+     * @see #getHistorySize
+     * @see #getX()
+     * @see #AXIS_X
+     */
+    public final float getHistoricalX(int pos) {
+        return nativeGetAxisValue(mNativePtr, AXIS_X, 0, pos);
+    }
+
+    /**
+     * {@link #getHistoricalY(int, int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
+     *
+     * @param pos Which historical value to return; must be less than
+     * {@link #getHistorySize}
+     *
+     * @see #getHistorySize
+     * @see #getY()
+     * @see #AXIS_Y
+     */
+    public final float getHistoricalY(int pos) {
+        return nativeGetAxisValue(mNativePtr, AXIS_Y, 0, pos);
+    }
+
+    /**
+     * {@link #getHistoricalPressure(int, int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
+     *
+     * @param pos Which historical value to return; must be less than
+     * {@link #getHistorySize}
+     *
+     * @see #getHistorySize
+     * @see #getPressure()
+     * @see #AXIS_PRESSURE
+     */
+    public final float getHistoricalPressure(int pos) {
+        return nativeGetAxisValue(mNativePtr, AXIS_PRESSURE, 0, pos);
+    }
+
+    /**
+     * {@link #getHistoricalSize(int, int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
+     *
+     * @param pos Which historical value to return; must be less than
+     * {@link #getHistorySize}
+     *
+     * @see #getHistorySize
+     * @see #getSize()
+     * @see #AXIS_SIZE
+     */
+    public final float getHistoricalSize(int pos) {
+        return nativeGetAxisValue(mNativePtr, AXIS_SIZE, 0, pos);
+    }
+
+    /**
+     * {@link #getHistoricalTouchMajor(int, int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
+     *
+     * @param pos Which historical value to return; must be less than
+     * {@link #getHistorySize}
+     *
+     * @see #getHistorySize
+     * @see #getTouchMajor()
+     * @see #AXIS_TOUCH_MAJOR
+     */
+    public final float getHistoricalTouchMajor(int pos) {
+        return nativeGetAxisValue(mNativePtr, AXIS_TOUCH_MAJOR, 0, pos);
+    }
+
+    /**
+     * {@link #getHistoricalTouchMinor(int, int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
+     *
+     * @param pos Which historical value to return; must be less than
+     * {@link #getHistorySize}
+     *
+     * @see #getHistorySize
+     * @see #getTouchMinor()
+     * @see #AXIS_TOUCH_MINOR
+     */
+    public final float getHistoricalTouchMinor(int pos) {
+        return nativeGetAxisValue(mNativePtr, AXIS_TOUCH_MINOR, 0, pos);
+    }
+
+    /**
+     * {@link #getHistoricalToolMajor(int, int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
+     *
+     * @param pos Which historical value to return; must be less than
+     * {@link #getHistorySize}
+     *
+     * @see #getHistorySize
+     * @see #getToolMajor()
+     * @see #AXIS_TOOL_MAJOR
+     */
+    public final float getHistoricalToolMajor(int pos) {
+        return nativeGetAxisValue(mNativePtr, AXIS_TOOL_MAJOR, 0, pos);
+    }
+
+    /**
+     * {@link #getHistoricalToolMinor(int, int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
+     *
+     * @param pos Which historical value to return; must be less than
+     * {@link #getHistorySize}
+     *
+     * @see #getHistorySize
+     * @see #getToolMinor()
+     * @see #AXIS_TOOL_MINOR
+     */
+    public final float getHistoricalToolMinor(int pos) {
+        return nativeGetAxisValue(mNativePtr, AXIS_TOOL_MINOR, 0, pos);
+    }
+
+    /**
+     * {@link #getHistoricalOrientation(int, int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
+     *
+     * @param pos Which historical value to return; must be less than
+     * {@link #getHistorySize}
+     *
+     * @see #getHistorySize
+     * @see #getOrientation()
+     * @see #AXIS_ORIENTATION
+     */
+    public final float getHistoricalOrientation(int pos) {
+        return nativeGetAxisValue(mNativePtr, AXIS_ORIENTATION, 0, pos);
+    }
+
+    /**
+     * {@link #getHistoricalAxisValue(int, int, int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
+     *
+     * @param axis The axis identifier for the axis value to retrieve.
+     * @param pos Which historical value to return; must be less than
+     * {@link #getHistorySize}
+     *
+     * @see #getHistorySize
+     * @see #getAxisValue(int)
+     * @see #AXIS_X
+     * @see #AXIS_Y
+     */
+    public final float getHistoricalAxisValue(int axis, int pos) {
+        return nativeGetAxisValue(mNativePtr, axis, 0, pos);
+    }
+
+    /**
+     * Returns a historical X coordinate, as per {@link #getX(int)}, that
+     * occurred between this event and the previous event for the given pointer.
+     * Only applies to ACTION_MOVE events.
+     *
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
+     * @param pos Which historical value to return; must be less than
+     * {@link #getHistorySize}
+     *
+     * @see #getHistorySize
+     * @see #getX(int)
+     * @see #AXIS_X
+     */
+    public final float getHistoricalX(int pointerIndex, int pos) {
+        return nativeGetAxisValue(mNativePtr, AXIS_X, pointerIndex, pos);
+    }
+
+    /**
+     * Returns a historical Y coordinate, as per {@link #getY(int)}, that
+     * occurred between this event and the previous event for the given pointer.
+     * Only applies to ACTION_MOVE events.
+     *
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
+     * @param pos Which historical value to return; must be less than
+     * {@link #getHistorySize}
+     *
+     * @see #getHistorySize
+     * @see #getY(int)
+     * @see #AXIS_Y
+     */
+    public final float getHistoricalY(int pointerIndex, int pos) {
+        return nativeGetAxisValue(mNativePtr, AXIS_Y, pointerIndex, pos);
+    }
+
+    /**
+     * Returns a historical pressure coordinate, as per {@link #getPressure(int)},
+     * that occurred between this event and the previous event for the given
+     * pointer.  Only applies to ACTION_MOVE events.
+     *
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
+     * @param pos Which historical value to return; must be less than
+     * {@link #getHistorySize}
+     *
+     * @see #getHistorySize
+     * @see #getPressure(int)
+     * @see #AXIS_PRESSURE
+     */
+    public final float getHistoricalPressure(int pointerIndex, int pos) {
+        return nativeGetAxisValue(mNativePtr, AXIS_PRESSURE, pointerIndex, pos);
+    }
+
+    /**
+     * Returns a historical size coordinate, as per {@link #getSize(int)}, that
+     * occurred between this event and the previous event for the given pointer.
+     * Only applies to ACTION_MOVE events.
+     *
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
+     * @param pos Which historical value to return; must be less than
+     * {@link #getHistorySize}
+     *
+     * @see #getHistorySize
+     * @see #getSize(int)
+     * @see #AXIS_SIZE
+     */
+    public final float getHistoricalSize(int pointerIndex, int pos) {
+        return nativeGetAxisValue(mNativePtr, AXIS_SIZE, pointerIndex, pos);
+    }
+
+    /**
+     * Returns a historical touch major axis coordinate, as per {@link #getTouchMajor(int)}, that
+     * occurred between this event and the previous event for the given pointer.
+     * Only applies to ACTION_MOVE events.
+     *
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
+     * @param pos Which historical value to return; must be less than
+     * {@link #getHistorySize}
+     *
+     * @see #getHistorySize
+     * @see #getTouchMajor(int)
+     * @see #AXIS_TOUCH_MAJOR
+     */
+    public final float getHistoricalTouchMajor(int pointerIndex, int pos) {
+        return nativeGetAxisValue(mNativePtr, AXIS_TOUCH_MAJOR, pointerIndex, pos);
+    }
+
+    /**
+     * Returns a historical touch minor axis coordinate, as per {@link #getTouchMinor(int)}, that
+     * occurred between this event and the previous event for the given pointer.
+     * Only applies to ACTION_MOVE events.
+     *
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
+     * @param pos Which historical value to return; must be less than
+     * {@link #getHistorySize}
+     *
+     * @see #getHistorySize
+     * @see #getTouchMinor(int)
+     * @see #AXIS_TOUCH_MINOR
+     */
+    public final float getHistoricalTouchMinor(int pointerIndex, int pos) {
+        return nativeGetAxisValue(mNativePtr, AXIS_TOUCH_MINOR, pointerIndex, pos);
+    }
+
+    /**
+     * Returns a historical tool major axis coordinate, as per {@link #getToolMajor(int)}, that
+     * occurred between this event and the previous event for the given pointer.
+     * Only applies to ACTION_MOVE events.
+     *
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
+     * @param pos Which historical value to return; must be less than
+     * {@link #getHistorySize}
+     *
+     * @see #getHistorySize
+     * @see #getToolMajor(int)
+     * @see #AXIS_TOOL_MAJOR
+     */
+    public final float getHistoricalToolMajor(int pointerIndex, int pos) {
+        return nativeGetAxisValue(mNativePtr, AXIS_TOOL_MAJOR, pointerIndex, pos);
+    }
+
+    /**
+     * Returns a historical tool minor axis coordinate, as per {@link #getToolMinor(int)}, that
+     * occurred between this event and the previous event for the given pointer.
+     * Only applies to ACTION_MOVE events.
+     *
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
+     * @param pos Which historical value to return; must be less than
+     * {@link #getHistorySize}
+     *
+     * @see #getHistorySize
+     * @see #getToolMinor(int)
+     * @see #AXIS_TOOL_MINOR
+     */
+    public final float getHistoricalToolMinor(int pointerIndex, int pos) {
+        return nativeGetAxisValue(mNativePtr, AXIS_TOOL_MINOR, pointerIndex, pos);
+    }
+
+    /**
+     * Returns a historical orientation coordinate, as per {@link #getOrientation(int)}, that
+     * occurred between this event and the previous event for the given pointer.
+     * Only applies to ACTION_MOVE events.
+     *
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
+     * @param pos Which historical value to return; must be less than
+     * {@link #getHistorySize}
+     *
+     * @see #getHistorySize
+     * @see #getOrientation(int)
+     * @see #AXIS_ORIENTATION
+     */
+    public final float getHistoricalOrientation(int pointerIndex, int pos) {
+        return nativeGetAxisValue(mNativePtr, AXIS_ORIENTATION, pointerIndex, pos);
+    }
+
+    /**
+     * Returns the historical value of the requested axis, as per {@link #getAxisValue(int, int)},
+     * occurred between this event and the previous event for the given pointer.
+     * Only applies to ACTION_MOVE events.
+     *
+     * @param axis The axis identifier for the axis value to retrieve.
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
+     * @param pos Which historical value to return; must be less than
+     * {@link #getHistorySize}
+     * @return The value of the axis, or 0 if the axis is not available.
+     *
+     * @see #AXIS_X
+     * @see #AXIS_Y
+     */
+    public final float getHistoricalAxisValue(int axis, int pointerIndex, int pos) {
+        return nativeGetAxisValue(mNativePtr, axis, pointerIndex, pos);
+    }
+
+    /**
+     * Populates a {@link PointerCoords} object with historical pointer coordinate data,
+     * as per {@link #getPointerCoords}, that occurred between this event and the previous
+     * event for the given pointer.
+     * Only applies to ACTION_MOVE events.
+     *
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
+     * @param pos Which historical value to return; must be less than
+     * {@link #getHistorySize}
+     * @param outPointerCoords The pointer coordinate object to populate.
+     *
+     * @see #getHistorySize
+     * @see #getPointerCoords
+     * @see PointerCoords
+     */
+    public final void getHistoricalPointerCoords(int pointerIndex, int pos,
+            PointerCoords outPointerCoords) {
+        nativeGetPointerCoords(mNativePtr, pointerIndex, pos, outPointerCoords);
+    }
+
+    /**
+     * Returns a bitfield indicating which edges, if any, were touched by this
+     * MotionEvent. For touch events, clients can use this to determine if the
+     * user's finger was touching the edge of the display.
+     *
+     * This property is only set for {@link #ACTION_DOWN} events.
+     *
+     * @see #EDGE_LEFT
+     * @see #EDGE_TOP
+     * @see #EDGE_RIGHT
+     * @see #EDGE_BOTTOM
+     */
+    public final int getEdgeFlags() {
+        return nativeGetEdgeFlags(mNativePtr);
+    }
+
+    /**
+     * Sets the bitfield indicating which edges, if any, were touched by this
+     * MotionEvent.
+     *
+     * @see #getEdgeFlags()
+     */
+    public final void setEdgeFlags(int flags) {
+        nativeSetEdgeFlags(mNativePtr, flags);
+    }
+
+    /**
+     * Sets this event's action.
+     */
+    public final void setAction(int action) {
+        nativeSetAction(mNativePtr, action);
+    }
+
+    /**
+     * Adjust this event's location.
+     * @param deltaX Amount to add to the current X coordinate of the event.
+     * @param deltaY Amount to add to the current Y coordinate of the event.
+     */
+    public final void offsetLocation(float deltaX, float deltaY) {
+        if (deltaX != 0.0f || deltaY != 0.0f) {
+            nativeOffsetLocation(mNativePtr, deltaX, deltaY);
+        }
+    }
+
+    /**
+     * Set this event's location.  Applies {@link #offsetLocation} with a
+     * delta from the current location to the given new location.
+     *
+     * @param x New absolute X location.
+     * @param y New absolute Y location.
+     */
+    public final void setLocation(float x, float y) {
+        float oldX = getX();
+        float oldY = getY();
+        offsetLocation(x - oldX, y - oldY);
+    }
+
+    /**
+     * Applies a transformation matrix to all of the points in the event.
+     *
+     * @param matrix The transformation matrix to apply.
+     */
+    public final void transform(Matrix matrix) {
+        if (matrix == null) {
+            throw new IllegalArgumentException("matrix must not be null");
+        }
+
+        nativeTransform(mNativePtr, matrix);
+    }
+
+    /**
+     * Transforms all of the points in the event directly instead of modifying the event's
+     * internal transform.
+     *
+     * @param matrix The transformation matrix to apply.
+     * @hide
+     */
+    public void applyTransform(Matrix matrix) {
+        if (matrix == null) {
+            throw new IllegalArgumentException("matrix must not be null");
+        }
+
+        nativeApplyTransform(mNativePtr, matrix);
+    }
+
+    /**
+     * Add a new movement to the batch of movements in this event.  The event's
+     * current location, position and size is updated to the new values.
+     * The current values in the event are added to a list of historical values.
+     *
+     * Only applies to {@link #ACTION_MOVE} or {@link #ACTION_HOVER_MOVE} events.
+     *
+     * @param eventTime The time stamp (in ms) for this data.
+     * @param x The new X position.
+     * @param y The new Y position.
+     * @param pressure The new pressure.
+     * @param size The new size.
+     * @param metaState Meta key state.
+     */
+    public final void addBatch(long eventTime, float x, float y,
+            float pressure, float size, int metaState) {
+        synchronized (gSharedTempLock) {
+            ensureSharedTempPointerCapacity(1);
+            final PointerCoords[] pc = gSharedTempPointerCoords;
+            pc[0].clear();
+            pc[0].x = x;
+            pc[0].y = y;
+            pc[0].pressure = pressure;
+            pc[0].size = size;
+
+            nativeAddBatch(mNativePtr, eventTime * NS_PER_MS, pc, metaState);
+        }
+    }
+
+    /**
+     * Add a new movement to the batch of movements in this event.  The event's
+     * current location, position and size is updated to the new values.
+     * The current values in the event are added to a list of historical values.
+     *
+     * Only applies to {@link #ACTION_MOVE} or {@link #ACTION_HOVER_MOVE} events.
+     *
+     * @param eventTime The time stamp (in ms) for this data.
+     * @param pointerCoords The new pointer coordinates.
+     * @param metaState Meta key state.
+     */
+    public final void addBatch(long eventTime, PointerCoords[] pointerCoords, int metaState) {
+        nativeAddBatch(mNativePtr, eventTime * NS_PER_MS, pointerCoords, metaState);
+    }
+
+    /**
+     * Adds all of the movement samples of the specified event to this one if
+     * it is compatible.  To be compatible, the event must have the same device id,
+     * source, display id, action, flags, classification, pointer count, pointer properties.
+     *
+     * Only applies to {@link #ACTION_MOVE} or {@link #ACTION_HOVER_MOVE} events.
+     *
+     * @param event The event whose movements samples should be added to this one
+     * if possible.
+     * @return True if batching was performed or false if batching was not possible.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public final boolean addBatch(MotionEvent event) {
+        final int action = nativeGetAction(mNativePtr);
+        if (action != ACTION_MOVE && action != ACTION_HOVER_MOVE) {
+            return false;
+        }
+        if (action != nativeGetAction(event.mNativePtr)) {
+            return false;
+        }
+
+        if (nativeGetDeviceId(mNativePtr) != nativeGetDeviceId(event.mNativePtr)
+                || nativeGetSource(mNativePtr) != nativeGetSource(event.mNativePtr)
+                || nativeGetDisplayId(mNativePtr) != nativeGetDisplayId(event.mNativePtr)
+                || nativeGetFlags(mNativePtr) != nativeGetFlags(event.mNativePtr)
+                || nativeGetClassification(mNativePtr)
+                        != nativeGetClassification(event.mNativePtr)) {
+            return false;
+        }
+
+        final int pointerCount = nativeGetPointerCount(mNativePtr);
+        if (pointerCount != nativeGetPointerCount(event.mNativePtr)) {
+            return false;
+        }
+
+        synchronized (gSharedTempLock) {
+            ensureSharedTempPointerCapacity(Math.max(pointerCount, 2));
+            final PointerProperties[] pp = gSharedTempPointerProperties;
+            final PointerCoords[] pc = gSharedTempPointerCoords;
+
+            for (int i = 0; i < pointerCount; i++) {
+                nativeGetPointerProperties(mNativePtr, i, pp[0]);
+                nativeGetPointerProperties(event.mNativePtr, i, pp[1]);
+                if (!pp[0].equals(pp[1])) {
+                    return false;
+                }
+            }
+
+            final int metaState = nativeGetMetaState(event.mNativePtr);
+            final int historySize = nativeGetHistorySize(event.mNativePtr);
+            for (int h = 0; h <= historySize; h++) {
+                final int historyPos = (h == historySize ? HISTORY_CURRENT : h);
+
+                for (int i = 0; i < pointerCount; i++) {
+                    nativeGetPointerCoords(event.mNativePtr, i, historyPos, pc[i]);
+                }
+
+                final long eventTimeNanos = nativeGetEventTimeNanos(event.mNativePtr, historyPos);
+                nativeAddBatch(mNativePtr, eventTimeNanos, pc, metaState);
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns true if all points in the motion event are completely within the specified bounds.
+     * @hide
+     */
+    public final boolean isWithinBoundsNoHistory(float left, float top,
+            float right, float bottom) {
+        final int pointerCount = nativeGetPointerCount(mNativePtr);
+        for (int i = 0; i < pointerCount; i++) {
+            final float x = nativeGetAxisValue(mNativePtr, AXIS_X, i, HISTORY_CURRENT);
+            final float y = nativeGetAxisValue(mNativePtr, AXIS_Y, i, HISTORY_CURRENT);
+            if (x < left || x > right || y < top || y > bottom) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static final float clamp(float value, float low, float high) {
+        if (value < low) {
+            return low;
+        } else if (value > high) {
+            return high;
+        }
+        return value;
+    }
+
+    /**
+     * Returns a new motion events whose points have been clamped to the specified bounds.
+     * @hide
+     */
+    public final MotionEvent clampNoHistory(float left, float top, float right, float bottom) {
+        MotionEvent ev = obtain();
+        synchronized (gSharedTempLock) {
+            final int pointerCount = nativeGetPointerCount(mNativePtr);
+
+            ensureSharedTempPointerCapacity(pointerCount);
+            final PointerProperties[] pp = gSharedTempPointerProperties;
+            final PointerCoords[] pc = gSharedTempPointerCoords;
+
+            for (int i = 0; i < pointerCount; i++) {
+                nativeGetPointerProperties(mNativePtr, i, pp[i]);
+                nativeGetPointerCoords(mNativePtr, i, HISTORY_CURRENT, pc[i]);
+                pc[i].x = clamp(pc[i].x, left, right);
+                pc[i].y = clamp(pc[i].y, top, bottom);
+            }
+            ev.initialize(nativeGetDeviceId(mNativePtr), nativeGetSource(mNativePtr),
+                    nativeGetDisplayId(mNativePtr),
+                    nativeGetAction(mNativePtr), nativeGetFlags(mNativePtr),
+                    nativeGetEdgeFlags(mNativePtr), nativeGetMetaState(mNativePtr),
+                    nativeGetButtonState(mNativePtr), nativeGetClassification(mNativePtr),
+                    nativeGetXOffset(mNativePtr), nativeGetYOffset(mNativePtr),
+                    nativeGetXPrecision(mNativePtr), nativeGetYPrecision(mNativePtr),
+                    nativeGetDownTimeNanos(mNativePtr),
+                    nativeGetEventTimeNanos(mNativePtr, HISTORY_CURRENT),
+                    pointerCount, pp, pc);
+            return ev;
+        }
+    }
+
+    /**
+     * Gets an integer where each pointer id present in the event is marked as a bit.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public final int getPointerIdBits() {
+        int idBits = 0;
+        final int pointerCount = nativeGetPointerCount(mNativePtr);
+        for (int i = 0; i < pointerCount; i++) {
+            idBits |= 1 << nativeGetPointerId(mNativePtr, i);
+        }
+        return idBits;
+    }
+
+    /**
+     * Splits a motion event such that it includes only a subset of pointer ids.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public final MotionEvent split(int idBits) {
+        MotionEvent ev = obtain();
+        synchronized (gSharedTempLock) {
+            final int oldPointerCount = nativeGetPointerCount(mNativePtr);
+            ensureSharedTempPointerCapacity(oldPointerCount);
+            final PointerProperties[] pp = gSharedTempPointerProperties;
+            final PointerCoords[] pc = gSharedTempPointerCoords;
+            final int[] map = gSharedTempPointerIndexMap;
+
+            final int oldAction = nativeGetAction(mNativePtr);
+            final int oldActionMasked = oldAction & ACTION_MASK;
+            final int oldActionPointerIndex = (oldAction & ACTION_POINTER_INDEX_MASK)
+                    >> ACTION_POINTER_INDEX_SHIFT;
+            int newActionPointerIndex = -1;
+            int newPointerCount = 0;
+            for (int i = 0; i < oldPointerCount; i++) {
+                nativeGetPointerProperties(mNativePtr, i, pp[newPointerCount]);
+                final int idBit = 1 << pp[newPointerCount].id;
+                if ((idBit & idBits) != 0) {
+                    if (i == oldActionPointerIndex) {
+                        newActionPointerIndex = newPointerCount;
+                    }
+                    map[newPointerCount] = i;
+                    newPointerCount += 1;
+                }
+            }
+
+            if (newPointerCount == 0) {
+                throw new IllegalArgumentException("idBits did not match any ids in the event");
+            }
+
+            final int newAction;
+            if (oldActionMasked == ACTION_POINTER_DOWN || oldActionMasked == ACTION_POINTER_UP) {
+                if (newActionPointerIndex < 0) {
+                    // An unrelated pointer changed.
+                    newAction = ACTION_MOVE;
+                } else if (newPointerCount == 1) {
+                    // The first/last pointer went down/up.
+                    newAction = oldActionMasked == ACTION_POINTER_DOWN
+                            ? ACTION_DOWN
+                            : (getFlags() & FLAG_CANCELED) == 0 ? ACTION_UP : ACTION_CANCEL;
+                } else {
+                    // A secondary pointer went down/up.
+                    newAction = oldActionMasked
+                            | (newActionPointerIndex << ACTION_POINTER_INDEX_SHIFT);
+                }
+            } else {
+                // Simple up/down/cancel/move or other motion action.
+                newAction = oldAction;
+            }
+
+            final int historySize = nativeGetHistorySize(mNativePtr);
+            for (int h = 0; h <= historySize; h++) {
+                final int historyPos = h == historySize ? HISTORY_CURRENT : h;
+
+                for (int i = 0; i < newPointerCount; i++) {
+                    nativeGetPointerCoords(mNativePtr, map[i], historyPos, pc[i]);
+                }
+
+                final long eventTimeNanos = nativeGetEventTimeNanos(mNativePtr, historyPos);
+                if (h == 0) {
+                    ev.initialize(nativeGetDeviceId(mNativePtr), nativeGetSource(mNativePtr),
+                            nativeGetDisplayId(mNativePtr),
+                            newAction, nativeGetFlags(mNativePtr),
+                            nativeGetEdgeFlags(mNativePtr), nativeGetMetaState(mNativePtr),
+                            nativeGetButtonState(mNativePtr), nativeGetClassification(mNativePtr),
+                            nativeGetXOffset(mNativePtr), nativeGetYOffset(mNativePtr),
+                            nativeGetXPrecision(mNativePtr), nativeGetYPrecision(mNativePtr),
+                            nativeGetDownTimeNanos(mNativePtr), eventTimeNanos,
+                            newPointerCount, pp, pc);
+                } else {
+                    nativeAddBatch(ev.mNativePtr, eventTimeNanos, pc, 0);
+                }
+            }
+            return ev;
+        }
+    }
+
+    /**
+     * Calculate new cursor position for events from mouse. This is used to split, clamp and inject
+     * events.
+     *
+     * <p>If the source is mouse, it sets cursor position to the centroid of all pointers because
+     * InputReader maps multiple fingers on a touchpad to locations around cursor position in screen
+     * coordinates so that the mouse cursor is at the centroid of all pointers.
+     *
+     * <p>If the source is not mouse it sets cursor position to NaN.
+     */
+    private void updateCursorPosition() {
+        if (getSource() != InputDevice.SOURCE_MOUSE) {
+            setCursorPosition(INVALID_CURSOR_POSITION, INVALID_CURSOR_POSITION);
+            return;
+        }
+
+        float x = 0;
+        float y = 0;
+
+        final int pointerCount = getPointerCount();
+        for (int i = 0; i < pointerCount; ++i) {
+            x += getX(i);
+            y += getY(i);
+        }
+
+        // If pointer count is 0, divisions below yield NaN, which is an acceptable result for this
+        // corner case.
+        x /= pointerCount;
+        y /= pointerCount;
+        setCursorPosition(x, y);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder msg = new StringBuilder();
+        msg.append("MotionEvent { action=").append(actionToString(getAction()));
+        appendUnless("0", msg, ", actionButton=", buttonStateToString(getActionButton()));
+
+        final int pointerCount = getPointerCount();
+        for (int i = 0; i < pointerCount; i++) {
+            appendUnless(i, msg, ", id[" + i + "]=", getPointerId(i));
+            float x = getX(i);
+            float y = getY(i);
+            if (!DEBUG_CONCISE_TOSTRING || x != 0f || y != 0f) {
+                msg.append(", x[").append(i).append("]=").append(x);
+                msg.append(", y[").append(i).append("]=").append(y);
+            }
+            appendUnless(TOOL_TYPE_SYMBOLIC_NAMES.get(TOOL_TYPE_FINGER),
+                    msg, ", toolType[" + i + "]=", toolTypeToString(getToolType(i)));
+        }
+
+        appendUnless("0", msg, ", buttonState=", MotionEvent.buttonStateToString(getButtonState()));
+        appendUnless(classificationToString(CLASSIFICATION_NONE), msg, ", classification=",
+                classificationToString(getClassification()));
+        appendUnless("0", msg, ", metaState=", KeyEvent.metaStateToString(getMetaState()));
+        appendUnless("0", msg, ", flags=0x", Integer.toHexString(getFlags()));
+        appendUnless("0", msg, ", edgeFlags=0x", Integer.toHexString(getEdgeFlags()));
+        appendUnless(1, msg, ", pointerCount=", pointerCount);
+        appendUnless(0, msg, ", historySize=", getHistorySize());
+        msg.append(", eventTime=").append(getEventTime());
+        if (!DEBUG_CONCISE_TOSTRING) {
+            msg.append(", downTime=").append(getDownTime());
+            msg.append(", deviceId=").append(getDeviceId());
+            msg.append(", source=0x").append(Integer.toHexString(getSource()));
+            msg.append(", displayId=").append(getDisplayId());
+            msg.append(", eventId=").append(getId());
+        }
+        msg.append(" }");
+        return msg.toString();
+    }
+
+    private static <T> void appendUnless(T defValue, StringBuilder sb, String key, T value) {
+        if (DEBUG_CONCISE_TOSTRING && Objects.equals(defValue, value)) return;
+        sb.append(key).append(value);
+    }
+
+    /**
+     * Returns a string that represents the symbolic name of the specified unmasked action
+     * such as "ACTION_DOWN", "ACTION_POINTER_DOWN(3)" or an equivalent numeric constant
+     * such as "35" if unknown.
+     *
+     * @param action The unmasked action.
+     * @return The symbolic name of the specified action.
+     * @see #getAction()
+     */
+    public static String actionToString(int action) {
+        switch (action) {
+            case ACTION_DOWN:
+                return "ACTION_DOWN";
+            case ACTION_UP:
+                return "ACTION_UP";
+            case ACTION_CANCEL:
+                return "ACTION_CANCEL";
+            case ACTION_OUTSIDE:
+                return "ACTION_OUTSIDE";
+            case ACTION_MOVE:
+                return "ACTION_MOVE";
+            case ACTION_HOVER_MOVE:
+                return "ACTION_HOVER_MOVE";
+            case ACTION_SCROLL:
+                return "ACTION_SCROLL";
+            case ACTION_HOVER_ENTER:
+                return "ACTION_HOVER_ENTER";
+            case ACTION_HOVER_EXIT:
+                return "ACTION_HOVER_EXIT";
+            case ACTION_BUTTON_PRESS:
+                return "ACTION_BUTTON_PRESS";
+            case ACTION_BUTTON_RELEASE:
+                return "ACTION_BUTTON_RELEASE";
+        }
+        int index = (action & ACTION_POINTER_INDEX_MASK) >> ACTION_POINTER_INDEX_SHIFT;
+        switch (action & ACTION_MASK) {
+            case ACTION_POINTER_DOWN:
+                return "ACTION_POINTER_DOWN(" + index + ")";
+            case ACTION_POINTER_UP:
+                return "ACTION_POINTER_UP(" + index + ")";
+            default:
+                return Integer.toString(action);
+        }
+    }
+
+    /**
+     * Returns a string that represents the symbolic name of the specified axis
+     * such as "AXIS_X" or an equivalent numeric constant such as "42" if unknown.
+     *
+     * @param axis The axis.
+     * @return The symbolic name of the specified axis.
+     */
+    public static String axisToString(int axis) {
+        String symbolicName = nativeAxisToString(axis);
+        return symbolicName != null ? LABEL_PREFIX + symbolicName : Integer.toString(axis);
+    }
+
+    /**
+     * Gets an axis by its symbolic name such as "AXIS_X" or an
+     * equivalent numeric constant such as "42".
+     *
+     * @param symbolicName The symbolic name of the axis.
+     * @return The axis or -1 if not found.
+     * @see KeyEvent#keyCodeToString(int)
+     */
+    public static int axisFromString(String symbolicName) {
+        if (symbolicName.startsWith(LABEL_PREFIX)) {
+            symbolicName = symbolicName.substring(LABEL_PREFIX.length());
+            int axis = nativeAxisFromString(symbolicName);
+            if (axis >= 0) {
+                return axis;
+            }
+        }
+        try {
+            return Integer.parseInt(symbolicName, 10);
+        } catch (NumberFormatException ex) {
+            return -1;
+        }
+    }
+
+    /**
+     * Returns a string that represents the symbolic name of the specified combined
+     * button state flags such as "0", "BUTTON_PRIMARY",
+     * "BUTTON_PRIMARY|BUTTON_SECONDARY" or an equivalent numeric constant such as "0x10000000"
+     * if unknown.
+     *
+     * @param buttonState The button state.
+     * @return The symbolic name of the specified combined button state flags.
+     * @hide
+     */
+    public static String buttonStateToString(int buttonState) {
+        if (buttonState == 0) {
+            return "0";
+        }
+        StringBuilder result = null;
+        int i = 0;
+        while (buttonState != 0) {
+            final boolean isSet = (buttonState & 1) != 0;
+            buttonState >>>= 1; // unsigned shift!
+            if (isSet) {
+                final String name = BUTTON_SYMBOLIC_NAMES[i];
+                if (result == null) {
+                    if (buttonState == 0) {
+                        return name;
+                    }
+                    result = new StringBuilder(name);
+                } else {
+                    result.append('|');
+                    result.append(name);
+                }
+            }
+            i += 1;
+        }
+        return result.toString();
+    }
+
+    /**
+     * Returns a string that represents the symbolic name of the specified classification.
+     *
+     * @param classification The classification type.
+     * @return The symbolic name of this classification.
+     * @hide
+     */
+    public static String classificationToString(@Classification int classification) {
+        switch (classification) {
+            case CLASSIFICATION_NONE:
+                return "NONE";
+            case CLASSIFICATION_AMBIGUOUS_GESTURE:
+                return "AMBIGUOUS_GESTURE";
+            case CLASSIFICATION_DEEP_PRESS:
+                return "DEEP_PRESS";
+
+        }
+        return "NONE";
+    }
+
+    /**
+     * Returns a string that represents the symbolic name of the specified tool type
+     * such as "TOOL_TYPE_FINGER" or an equivalent numeric constant such as "42" if unknown.
+     *
+     * @param toolType The tool type.
+     * @return The symbolic name of the specified tool type.
+     * @hide
+     */
+    public static String toolTypeToString(int toolType) {
+        String symbolicName = TOOL_TYPE_SYMBOLIC_NAMES.get(toolType);
+        return symbolicName != null ? symbolicName : Integer.toString(toolType);
+    }
+
+    /**
+     * Checks if a mouse or stylus button (or combination of buttons) is pressed.
+     * @param button Button (or combination of buttons).
+     * @return True if specified buttons are pressed.
+     *
+     * @see #BUTTON_PRIMARY
+     * @see #BUTTON_SECONDARY
+     * @see #BUTTON_TERTIARY
+     * @see #BUTTON_FORWARD
+     * @see #BUTTON_BACK
+     * @see #BUTTON_STYLUS_PRIMARY
+     * @see #BUTTON_STYLUS_SECONDARY
+     */
+    public final boolean isButtonPressed(int button) {
+        if (button == 0) {
+            return false;
+        }
+        return (getButtonState() & button) == button;
+    }
+
+    /**
+     * Gets a rotation matrix that (when applied to a motionevent) will rotate that motion event
+     * such that the result coordinates end up in the same physical location on a display whose
+     * coordinates are rotated by `rotation`.
+     *
+     * For example, rotating 0,0 by 90 degrees will move a point from the physical top-left to
+     * the bottom-left of the 90-degree-rotated display.
+     *
+     * @hide
+     */
+    public static Matrix createRotateMatrix(
+            @Surface.Rotation int rotation, int displayW, int displayH) {
+        if (rotation == Surface.ROTATION_0) {
+            return new Matrix(Matrix.IDENTITY_MATRIX);
+        }
+        // values is row-major
+        float[] values = null;
+        if (rotation == Surface.ROTATION_90) {
+            values = new float[]{0, 1, 0,
+                    -1, 0, displayH,
+                    0, 0, 1};
+        } else if (rotation == Surface.ROTATION_180) {
+            values = new float[]{-1, 0, displayW,
+                    0, -1, displayH,
+                    0, 0, 1};
+        } else if (rotation == Surface.ROTATION_270) {
+            values = new float[]{0, -1, displayW,
+                    1, 0, 0,
+                    0, 0, 1};
+        }
+        Matrix toOrient = new Matrix();
+        toOrient.setValues(values);
+        return toOrient;
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<MotionEvent> CREATOR
+            = new Parcelable.Creator<MotionEvent>() {
+        public MotionEvent createFromParcel(Parcel in) {
+            in.readInt(); // skip token, we already know this is a MotionEvent
+            return MotionEvent.createFromParcelBody(in);
+        }
+
+        public MotionEvent[] newArray(int size) {
+            return new MotionEvent[size];
+        }
+    };
+
+    /** @hide */
+    public static MotionEvent createFromParcelBody(Parcel in) {
+        MotionEvent ev = obtain();
+        ev.mNativePtr = nativeReadFromParcel(ev.mNativePtr, in);
+        return ev;
+    }
+
+    /** @hide */
+    @Override
+    public final void cancel() {
+        setAction(ACTION_CANCEL);
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(PARCEL_TOKEN_MOTION_EVENT);
+        nativeWriteToParcel(mNativePtr, out);
+    }
+
+    /**
+     * Transfer object for pointer coordinates.
+     *
+     * Objects of this type can be used to specify the pointer coordinates when
+     * creating new {@link MotionEvent} objects and to query pointer coordinates
+     * in bulk.
+     *
+     * Refer to {@link InputDevice} for information about how different kinds of
+     * input devices and sources represent pointer coordinates.
+     */
+    public static final class PointerCoords {
+        private static final int INITIAL_PACKED_AXIS_VALUES = 8;
+        @UnsupportedAppUsage
+        private long mPackedAxisBits;
+        @UnsupportedAppUsage
+        private float[] mPackedAxisValues;
+
+        /**
+         * Creates a pointer coords object with all axes initialized to zero.
+         */
+        public PointerCoords() {
+        }
+
+        /**
+         * Creates a pointer coords object as a copy of the
+         * contents of another pointer coords object.
+         *
+         * @param other The pointer coords object to copy.
+         */
+        public PointerCoords(PointerCoords other) {
+            copyFrom(other);
+        }
+
+        /** @hide */
+        @UnsupportedAppUsage
+        public static PointerCoords[] createArray(int size) {
+            PointerCoords[] array = new PointerCoords[size];
+            for (int i = 0; i < size; i++) {
+                array[i] = new PointerCoords();
+            }
+            return array;
+        }
+
+        /**
+         * The X component of the pointer movement.
+         *
+         * @see MotionEvent#AXIS_X
+         */
+        public float x;
+
+        /**
+         * The Y component of the pointer movement.
+         *
+         * @see MotionEvent#AXIS_Y
+         */
+        public float y;
+
+        /**
+         * A normalized value that describes the pressure applied to the device
+         * by a finger or other tool.
+         * The pressure generally ranges from 0 (no pressure at all) to 1 (normal pressure),
+         * although values higher than 1 may be generated depending on the calibration of
+         * the input device.
+         *
+         * @see MotionEvent#AXIS_PRESSURE
+         */
+        public float pressure;
+
+        /**
+         * A normalized value that describes the approximate size of the pointer touch area
+         * in relation to the maximum detectable size of the device.
+         * It represents some approximation of the area of the screen being
+         * pressed; the actual value in pixels corresponding to the
+         * touch is normalized with the device specific range of values
+         * and scaled to a value between 0 and 1. The value of size can be used to
+         * determine fat touch events.
+         *
+         * @see MotionEvent#AXIS_SIZE
+         */
+        public float size;
+
+        /**
+         * The length of the major axis of an ellipse that describes the touch area at
+         * the point of contact.
+         * If the device is a touch screen, the length is reported in pixels, otherwise it is
+         * reported in device-specific units.
+         *
+         * @see MotionEvent#AXIS_TOUCH_MAJOR
+         */
+        public float touchMajor;
+
+        /**
+         * The length of the minor axis of an ellipse that describes the touch area at
+         * the point of contact.
+         * If the device is a touch screen, the length is reported in pixels, otherwise it is
+         * reported in device-specific units.
+         *
+         * @see MotionEvent#AXIS_TOUCH_MINOR
+         */
+        public float touchMinor;
+
+        /**
+         * The length of the major axis of an ellipse that describes the size of
+         * the approaching tool.
+         * The tool area represents the estimated size of the finger or pen that is
+         * touching the device independent of its actual touch area at the point of contact.
+         * If the device is a touch screen, the length is reported in pixels, otherwise it is
+         * reported in device-specific units.
+         *
+         * @see MotionEvent#AXIS_TOOL_MAJOR
+         */
+        public float toolMajor;
+
+        /**
+         * The length of the minor axis of an ellipse that describes the size of
+         * the approaching tool.
+         * The tool area represents the estimated size of the finger or pen that is
+         * touching the device independent of its actual touch area at the point of contact.
+         * If the device is a touch screen, the length is reported in pixels, otherwise it is
+         * reported in device-specific units.
+         *
+         * @see MotionEvent#AXIS_TOOL_MINOR
+         */
+        public float toolMinor;
+
+        /**
+         * The orientation of the touch area and tool area in radians clockwise from vertical.
+         * An angle of 0 radians indicates that the major axis of contact is oriented
+         * upwards, is perfectly circular or is of unknown orientation.  A positive angle
+         * indicates that the major axis of contact is oriented to the right.  A negative angle
+         * indicates that the major axis of contact is oriented to the left.
+         * The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians
+         * (finger pointing fully right).
+         *
+         * @see MotionEvent#AXIS_ORIENTATION
+         */
+        public float orientation;
+
+        /**
+         * Clears the contents of this object.
+         * Resets all axes to zero.
+         */
+        public void clear() {
+            mPackedAxisBits = 0;
+
+            x = 0;
+            y = 0;
+            pressure = 0;
+            size = 0;
+            touchMajor = 0;
+            touchMinor = 0;
+            toolMajor = 0;
+            toolMinor = 0;
+            orientation = 0;
+        }
+
+        /**
+         * Copies the contents of another pointer coords object.
+         *
+         * @param other The pointer coords object to copy.
+         */
+        public void copyFrom(PointerCoords other) {
+            final long bits = other.mPackedAxisBits;
+            mPackedAxisBits = bits;
+            if (bits != 0) {
+                final float[] otherValues = other.mPackedAxisValues;
+                final int count = Long.bitCount(bits);
+                float[] values = mPackedAxisValues;
+                if (values == null || count > values.length) {
+                    values = new float[otherValues.length];
+                    mPackedAxisValues = values;
+                }
+                System.arraycopy(otherValues, 0, values, 0, count);
+            }
+
+            x = other.x;
+            y = other.y;
+            pressure = other.pressure;
+            size = other.size;
+            touchMajor = other.touchMajor;
+            touchMinor = other.touchMinor;
+            toolMajor = other.toolMajor;
+            toolMinor = other.toolMinor;
+            orientation = other.orientation;
+        }
+
+        /**
+         * Gets the value associated with the specified axis.
+         *
+         * @param axis The axis identifier for the axis value to retrieve.
+         * @return The value associated with the axis, or 0 if none.
+         *
+         * @see MotionEvent#AXIS_X
+         * @see MotionEvent#AXIS_Y
+         */
+        public float getAxisValue(int axis) {
+            switch (axis) {
+                case AXIS_X:
+                    return x;
+                case AXIS_Y:
+                    return y;
+                case AXIS_PRESSURE:
+                    return pressure;
+                case AXIS_SIZE:
+                    return size;
+                case AXIS_TOUCH_MAJOR:
+                    return touchMajor;
+                case AXIS_TOUCH_MINOR:
+                    return touchMinor;
+                case AXIS_TOOL_MAJOR:
+                    return toolMajor;
+                case AXIS_TOOL_MINOR:
+                    return toolMinor;
+                case AXIS_ORIENTATION:
+                    return orientation;
+                default: {
+                    if (axis < 0 || axis > 63) {
+                        throw new IllegalArgumentException("Axis out of range.");
+                    }
+                    final long bits = mPackedAxisBits;
+                    final long axisBit = 0x8000000000000000L >>> axis;
+                    if ((bits & axisBit) == 0) {
+                        return 0;
+                    }
+                    final int index = Long.bitCount(bits & ~(0xFFFFFFFFFFFFFFFFL >>> axis));
+                    return mPackedAxisValues[index];
+                }
+            }
+        }
+
+        /**
+         * Sets the value associated with the specified axis.
+         *
+         * @param axis The axis identifier for the axis value to assign.
+         * @param value The value to set.
+         *
+         * @see MotionEvent#AXIS_X
+         * @see MotionEvent#AXIS_Y
+         */
+        public void setAxisValue(int axis, float value) {
+            switch (axis) {
+                case AXIS_X:
+                    x = value;
+                    break;
+                case AXIS_Y:
+                    y = value;
+                    break;
+                case AXIS_PRESSURE:
+                    pressure = value;
+                    break;
+                case AXIS_SIZE:
+                    size = value;
+                    break;
+                case AXIS_TOUCH_MAJOR:
+                    touchMajor = value;
+                    break;
+                case AXIS_TOUCH_MINOR:
+                    touchMinor = value;
+                    break;
+                case AXIS_TOOL_MAJOR:
+                    toolMajor = value;
+                    break;
+                case AXIS_TOOL_MINOR:
+                    toolMinor = value;
+                    break;
+                case AXIS_ORIENTATION:
+                    orientation = value;
+                    break;
+                default: {
+                    if (axis < 0 || axis > 63) {
+                        throw new IllegalArgumentException("Axis out of range.");
+                    }
+                    final long bits = mPackedAxisBits;
+                    final long axisBit = 0x8000000000000000L >>> axis;
+                    final int index = Long.bitCount(bits & ~(0xFFFFFFFFFFFFFFFFL >>> axis));
+                    float[] values = mPackedAxisValues;
+                    if ((bits & axisBit) == 0) {
+                        if (values == null) {
+                            values = new float[INITIAL_PACKED_AXIS_VALUES];
+                            mPackedAxisValues = values;
+                        } else {
+                            final int count = Long.bitCount(bits);
+                            if (count < values.length) {
+                                if (index != count) {
+                                    System.arraycopy(values, index, values, index + 1,
+                                            count - index);
+                                }
+                            } else {
+                                float[] newValues = new float[count * 2];
+                                System.arraycopy(values, 0, newValues, 0, index);
+                                System.arraycopy(values, index, newValues, index + 1,
+                                        count - index);
+                                values = newValues;
+                                mPackedAxisValues = values;
+                            }
+                        }
+                        mPackedAxisBits = bits | axisBit;
+                    }
+                    values[index] = value;
+                }
+            }
+        }
+    }
+
+    /**
+     * Transfer object for pointer properties.
+     *
+     * Objects of this type can be used to specify the pointer id and tool type
+     * when creating new {@link MotionEvent} objects and to query pointer properties in bulk.
+     */
+    public static final class PointerProperties {
+        /**
+         * Creates a pointer properties object with an invalid pointer id.
+         */
+        public PointerProperties() {
+            clear();
+        }
+
+        /**
+         * Creates a pointer properties object as a copy of the contents of
+         * another pointer properties object.
+         * @param other
+         */
+        public PointerProperties(PointerProperties other) {
+            copyFrom(other);
+        }
+
+        /** @hide */
+        @UnsupportedAppUsage
+        public static PointerProperties[] createArray(int size) {
+            PointerProperties[] array = new PointerProperties[size];
+            for (int i = 0; i < size; i++) {
+                array[i] = new PointerProperties();
+            }
+            return array;
+        }
+
+        /**
+         * The pointer id.
+         * Initially set to {@link #INVALID_POINTER_ID} (-1).
+         *
+         * @see MotionEvent#getPointerId(int)
+         */
+        public int id;
+
+        /**
+         * The pointer tool type.
+         * Initially set to 0.
+         *
+         * @see MotionEvent#getToolType(int)
+         */
+        public int toolType;
+
+        /**
+         * Resets the pointer properties to their initial values.
+         */
+        public void clear() {
+            id = INVALID_POINTER_ID;
+            toolType = TOOL_TYPE_UNKNOWN;
+        }
+
+        /**
+         * Copies the contents of another pointer properties object.
+         *
+         * @param other The pointer properties object to copy.
+         */
+        public void copyFrom(PointerProperties other) {
+            id = other.id;
+            toolType = other.toolType;
+        }
+
+        @Override
+        public boolean equals(@Nullable Object other) {
+            if (other instanceof PointerProperties) {
+                return equals((PointerProperties)other);
+            }
+            return false;
+        }
+
+        private boolean equals(PointerProperties other) {
+            return other != null && id == other.id && toolType == other.toolType;
+        }
+
+        @Override
+        public int hashCode() {
+            return id | (toolType << 8);
+        }
+    }
+}
diff --git a/android/view/NativeVectorDrawableAnimator.java b/android/view/NativeVectorDrawableAnimator.java
new file mode 100644
index 0000000..b0556a3
--- /dev/null
+++ b/android/view/NativeVectorDrawableAnimator.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+/**
+ * Exists just to allow for android.graphics & android.view package separation
+ *
+ * TODO: Get off of this coupling more cleanly somehow
+ *
+ * @hide
+ */
+public interface NativeVectorDrawableAnimator {
+    /** @hide */
+    long getAnimatorNativePtr();
+}
diff --git a/android/view/NotificationHeaderView.java b/android/view/NotificationHeaderView.java
new file mode 100644
index 0000000..42b798c
--- /dev/null
+++ b/android/view/NotificationHeaderView.java
@@ -0,0 +1,351 @@
+/*
+ * 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.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Outline;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.widget.RelativeLayout;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import com.android.internal.R;
+import com.android.internal.widget.CachingIconView;
+import com.android.internal.widget.NotificationExpandButton;
+
+import java.util.ArrayList;
+
+/**
+ * A header of a notification view
+ *
+ * @hide
+ */
[email protected]
+public class NotificationHeaderView extends RelativeLayout {
+    private final int mTouchableHeight;
+    private OnClickListener mExpandClickListener;
+    private HeaderTouchListener mTouchListener = new HeaderTouchListener();
+    private NotificationTopLineView mTopLineView;
+    private NotificationExpandButton mExpandButton;
+    private View mAltExpandTarget;
+    private CachingIconView mIcon;
+    private Drawable mBackground;
+    private boolean mEntireHeaderClickable;
+    private boolean mExpandOnlyOnButton;
+    private boolean mAcceptAllTouches;
+
+    ViewOutlineProvider mProvider = new ViewOutlineProvider() {
+        @Override
+        public void getOutline(View view, Outline outline) {
+            if (mBackground != null) {
+                outline.setRect(0, 0, getWidth(), getHeight());
+                outline.setAlpha(1f);
+            }
+        }
+    };
+
+    public NotificationHeaderView(Context context) {
+        this(context, null);
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public NotificationHeaderView(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public NotificationHeaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public NotificationHeaderView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        Resources res = getResources();
+        mTouchableHeight = res.getDimensionPixelSize(R.dimen.notification_header_touchable_height);
+        mEntireHeaderClickable = res.getBoolean(R.bool.config_notificationHeaderClickableForExpand);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mIcon = findViewById(R.id.icon);
+        mTopLineView = findViewById(R.id.notification_top_line);
+        mExpandButton = findViewById(R.id.expand_button);
+        mAltExpandTarget = findViewById(R.id.alternate_expand_target);
+        setClipToPadding(false);
+    }
+
+    /**
+     * Set a {@link Drawable} to be displayed as a background on the header.
+     */
+    public void setHeaderBackgroundDrawable(Drawable drawable) {
+        if (drawable != null) {
+            setWillNotDraw(false);
+            mBackground = drawable;
+            mBackground.setCallback(this);
+            setOutlineProvider(mProvider);
+        } else {
+            setWillNotDraw(true);
+            mBackground = null;
+            setOutlineProvider(null);
+        }
+        invalidate();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        if (mBackground != null) {
+            mBackground.setBounds(0, 0, getWidth(), getHeight());
+            mBackground.draw(canvas);
+        }
+    }
+
+    @Override
+    protected boolean verifyDrawable(@NonNull Drawable who) {
+        return super.verifyDrawable(who) || who == mBackground;
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        if (mBackground != null && mBackground.isStateful()) {
+            mBackground.setState(getDrawableState());
+        }
+    }
+
+    private void updateTouchListener() {
+        if (mExpandClickListener == null) {
+            setOnTouchListener(null);
+            return;
+        }
+        setOnTouchListener(mTouchListener);
+        mTouchListener.bindTouchRects();
+    }
+
+    @Override
+    public void setOnClickListener(@Nullable OnClickListener l) {
+        mExpandClickListener = l;
+        mExpandButton.setOnClickListener(mExpandClickListener);
+        mAltExpandTarget.setOnClickListener(mExpandClickListener);
+        updateTouchListener();
+    }
+
+    /**
+     * Sets the extra margin at the end of the top line of left-aligned text + icons.
+     * This value will have the margin required to accommodate the expand button added to it.
+     *
+     * @param extraMarginEnd extra margin in px
+     */
+    public void setTopLineExtraMarginEnd(int extraMarginEnd) {
+        mTopLineView.setHeaderTextMarginEnd(extraMarginEnd);
+    }
+
+    /**
+     * Sets the extra margin at the end of the top line of left-aligned text + icons.
+     * This value will have the margin required to accommodate the expand button added to it.
+     *
+     * @param extraMarginEndDp extra margin in dp
+     */
+    @RemotableViewMethod
+    public void setTopLineExtraMarginEndDp(float extraMarginEndDp) {
+        setTopLineExtraMarginEnd(
+                (int) (extraMarginEndDp * getResources().getDisplayMetrics().density));
+    }
+
+    /**
+     * This is used to make the low-priority header show the bolded text of a title.
+     *
+     * @param styleTextAsTitle true if this header's text is to have the style of a title
+     */
+    @RemotableViewMethod
+    public void styleTextAsTitle(boolean styleTextAsTitle) {
+        int styleResId = styleTextAsTitle
+                ? R.style.TextAppearance_DeviceDefault_Notification_Title
+                : R.style.TextAppearance_DeviceDefault_Notification_Info;
+        // Most of the time, we're showing text in the minimized state
+        View headerText = findViewById(R.id.header_text);
+        if (headerText instanceof TextView) {
+            ((TextView) headerText).setTextAppearance(styleResId);
+        }
+        // If there's no summary or text, we show the app name instead of nothing
+        View appNameText = findViewById(R.id.app_name_text);
+        if (appNameText instanceof TextView) {
+            ((TextView) appNameText).setTextAppearance(styleResId);
+        }
+    }
+
+    /**
+     * Handles clicks on the header based on the region tapped.
+     */
+    public class HeaderTouchListener implements OnTouchListener {
+
+        private final ArrayList<Rect> mTouchRects = new ArrayList<>();
+        private Rect mExpandButtonRect;
+        private Rect mAltExpandTargetRect;
+        private int mTouchSlop;
+        private boolean mTrackGesture;
+        private float mDownX;
+        private float mDownY;
+
+        public HeaderTouchListener() {
+        }
+
+        public void bindTouchRects() {
+            mTouchRects.clear();
+            addRectAroundView(mIcon);
+            mExpandButtonRect = addRectAroundView(mExpandButton);
+            mAltExpandTargetRect = addRectAroundView(mAltExpandTarget);
+            addWidthRect();
+            mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+        }
+
+        private void addWidthRect() {
+            Rect r = new Rect();
+            r.top = 0;
+            r.bottom = mTouchableHeight;
+            r.left = 0;
+            r.right = getWidth();
+            mTouchRects.add(r);
+        }
+
+        private Rect addRectAroundView(View view) {
+            final Rect r = getRectAroundView(view);
+            mTouchRects.add(r);
+            return r;
+        }
+
+        private Rect getRectAroundView(View view) {
+            float size = 48 * getResources().getDisplayMetrics().density;
+            float width = Math.max(size, view.getWidth());
+            float height = Math.max(size, view.getHeight());
+            final Rect r = new Rect();
+            if (view.getVisibility() == GONE) {
+                view = getFirstChildNotGone();
+                r.left = (int) (view.getLeft() - width / 2.0f);
+            } else {
+                r.left = (int) ((view.getLeft() + view.getRight()) / 2.0f - width / 2.0f);
+            }
+            r.top = (int) ((view.getTop() + view.getBottom()) / 2.0f - height / 2.0f);
+            r.bottom = (int) (r.top + height);
+            r.right = (int) (r.left + width);
+            return r;
+        }
+
+        @Override
+        public boolean onTouch(View v, MotionEvent event) {
+            float x = event.getX();
+            float y = event.getY();
+            switch (event.getActionMasked() & MotionEvent.ACTION_MASK) {
+                case MotionEvent.ACTION_DOWN:
+                    mTrackGesture = false;
+                    if (isInside(x, y)) {
+                        mDownX = x;
+                        mDownY = y;
+                        mTrackGesture = true;
+                        return true;
+                    }
+                    break;
+                case MotionEvent.ACTION_MOVE:
+                    if (mTrackGesture) {
+                        if (Math.abs(mDownX - x) > mTouchSlop
+                                || Math.abs(mDownY - y) > mTouchSlop) {
+                            mTrackGesture = false;
+                        }
+                    }
+                    break;
+                case MotionEvent.ACTION_UP:
+                    if (mTrackGesture) {
+                        float topLineX = mTopLineView.getX();
+                        float topLineY = mTopLineView.getY();
+                        if (mTopLineView.onTouchUp(x - topLineX, y - topLineY,
+                                mDownX - topLineX, mDownY - topLineY)) {
+                            break;
+                        }
+                        mExpandButton.performClick();
+                    }
+                    break;
+            }
+            return mTrackGesture;
+        }
+
+        private boolean isInside(float x, float y) {
+            if (mAcceptAllTouches) {
+                return true;
+            }
+            if (mExpandOnlyOnButton) {
+                return mExpandButtonRect.contains((int) x, (int) y)
+                        || mAltExpandTargetRect.contains((int) x, (int) y);
+            }
+            for (int i = 0; i < mTouchRects.size(); i++) {
+                Rect r = mTouchRects.get(i);
+                if (r.contains((int) x, (int) y)) {
+                    return true;
+                }
+            }
+            float topLineX = x - mTopLineView.getX();
+            float topLineY = y - mTopLineView.getY();
+            return mTopLineView.isInTouchRect(topLineX, topLineY);
+        }
+    }
+
+    private View getFirstChildNotGone() {
+        for (int i = 0; i < getChildCount(); i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                return child;
+            }
+        }
+        return this;
+    }
+
+    @Override
+    public boolean hasOverlappingRendering() {
+        return false;
+    }
+
+    public boolean isInTouchRect(float x, float y) {
+        if (mExpandClickListener == null) {
+            return false;
+        }
+        return mTouchListener.isInside(x, y);
+    }
+
+    /**
+     * Sets whether or not all touches to this header view will register as a click. Note that
+     * if the config value for {@code config_notificationHeaderClickableForExpand} is {@code true},
+     * then calling this method with {@code false} will not override that configuration.
+     */
+    @RemotableViewMethod
+    public void setAcceptAllTouches(boolean acceptAllTouches) {
+        mAcceptAllTouches = mEntireHeaderClickable || acceptAllTouches;
+    }
+
+    /**
+     * Sets whether only the expand icon itself should serve as the expand target.
+     */
+    @RemotableViewMethod
+    public void setExpandOnlyOnButton(boolean expandOnlyOnButton) {
+        mExpandOnlyOnButton = expandOnlyOnButton;
+    }
+}
diff --git a/android/view/NotificationTopLineView.java b/android/view/NotificationTopLineView.java
new file mode 100644
index 0000000..bd20f5b
--- /dev/null
+++ b/android/view/NotificationTopLineView.java
@@ -0,0 +1,478 @@
+/*
+ * 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.view;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.widget.RemoteViews;
+
+import com.android.internal.R;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * The top line of content in a notification view.
+ * This includes the text views and badges but excludes the icon and the expander.
+ *
+ * @hide
+ */
[email protected]
+public class NotificationTopLineView extends ViewGroup {
+    private final OverflowAdjuster mOverflowAdjuster = new OverflowAdjuster();
+    private final int mGravityY;
+    private final int mChildMinWidth;
+    private final int mChildHideWidth;
+    @Nullable private View mAppName;
+    @Nullable private View mTitle;
+    private View mHeaderText;
+    private View mHeaderTextDivider;
+    private View mSecondaryHeaderText;
+    private View mSecondaryHeaderTextDivider;
+    private OnClickListener mFeedbackListener;
+    private HeaderTouchListener mTouchListener = new HeaderTouchListener();
+    private View mFeedbackIcon;
+    private int mHeaderTextMarginEnd;
+
+    private Set<View> mViewsToDisappear = new HashSet<>();
+
+    private int mMaxAscent;
+    private int mMaxDescent;
+
+    public NotificationTopLineView(Context context) {
+        this(context, null);
+    }
+
+    public NotificationTopLineView(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public NotificationTopLineView(Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public NotificationTopLineView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        Resources res = getResources();
+        mChildMinWidth = res.getDimensionPixelSize(R.dimen.notification_header_shrink_min_width);
+        mChildHideWidth = res.getDimensionPixelSize(R.dimen.notification_header_shrink_hide_width);
+
+        // NOTE: Implementation only supports TOP, BOTTOM, and CENTER_VERTICAL gravities,
+        // with CENTER_VERTICAL being the default.
+        int[] attrIds = {android.R.attr.gravity};
+        TypedArray ta = context.obtainStyledAttributes(attrs, attrIds, defStyleAttr, defStyleRes);
+        int gravity = ta.getInt(0, 0);
+        ta.recycle();
+        if ((gravity & Gravity.BOTTOM) == Gravity.BOTTOM) {
+            mGravityY = Gravity.BOTTOM;
+        } else if ((gravity & Gravity.TOP) == Gravity.TOP) {
+            mGravityY = Gravity.TOP;
+        } else {
+            mGravityY = Gravity.CENTER_VERTICAL;
+        }
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mAppName = findViewById(R.id.app_name_text);
+        mTitle = findViewById(R.id.title);
+        mHeaderText = findViewById(R.id.header_text);
+        mHeaderTextDivider = findViewById(R.id.header_text_divider);
+        mSecondaryHeaderText = findViewById(R.id.header_text_secondary);
+        mSecondaryHeaderTextDivider = findViewById(R.id.header_text_secondary_divider);
+        mFeedbackIcon = findViewById(R.id.feedback);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final int givenWidth = MeasureSpec.getSize(widthMeasureSpec);
+        final int givenHeight = MeasureSpec.getSize(heightMeasureSpec);
+        final boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST;
+        int wrapContentWidthSpec = MeasureSpec.makeMeasureSpec(givenWidth, MeasureSpec.AT_MOST);
+        int heightSpec = MeasureSpec.makeMeasureSpec(givenHeight, MeasureSpec.AT_MOST);
+        int totalWidth = getPaddingStart();
+        int maxChildHeight = -1;
+        mMaxAscent = -1;
+        mMaxDescent = -1;
+        for (int i = 0; i < getChildCount(); i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() == GONE) {
+                // We'll give it the rest of the space in the end
+                continue;
+            }
+            final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+            int childWidthSpec = getChildMeasureSpec(wrapContentWidthSpec,
+                    lp.leftMargin + lp.rightMargin, lp.width);
+            int childHeightSpec = getChildMeasureSpec(heightSpec,
+                    lp.topMargin + lp.bottomMargin, lp.height);
+            child.measure(childWidthSpec, childHeightSpec);
+            totalWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth();
+            int childBaseline = child.getBaseline();
+            int childHeight = child.getMeasuredHeight();
+            if (childBaseline != -1) {
+                mMaxAscent = Math.max(mMaxAscent, childBaseline);
+                mMaxDescent = Math.max(mMaxDescent, childHeight - childBaseline);
+            }
+            maxChildHeight = Math.max(maxChildHeight, childHeight);
+        }
+
+        mViewsToDisappear.clear();
+        // Ensure that there is at least enough space for the icons
+        int endMargin = Math.max(mHeaderTextMarginEnd, getPaddingEnd());
+        if (totalWidth > givenWidth - endMargin) {
+            int overFlow = totalWidth - givenWidth + endMargin;
+
+            mOverflowAdjuster.resetForOverflow(overFlow, heightSpec)
+                    // First shrink the app name, down to a minimum size
+                    .adjust(mAppName, null, mChildMinWidth)
+                    // Next, shrink the header text (this usually has subText)
+                    //   This shrinks the subtext first, but not all the way (yet!)
+                    .adjust(mHeaderText, mHeaderTextDivider, mChildMinWidth)
+                    // Next, shrink the secondary header text  (this rarely has conversationTitle)
+                    .adjust(mSecondaryHeaderText, mSecondaryHeaderTextDivider, 0)
+                    // Next, shrink the title text (this has contentTitle; only in headerless views)
+                    .adjust(mTitle, null, mChildMinWidth)
+                    // Next, shrink the header down to 0 if still necessary.
+                    .adjust(mHeaderText, mHeaderTextDivider, 0)
+                    // Finally, shrink the title to 0 if necessary (media is super cramped)
+                    .adjust(mTitle, null, 0)
+                    // Clean up
+                    .finish();
+        }
+        setMeasuredDimension(givenWidth, wrapHeight ? maxChildHeight : givenHeight);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        final boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+        final int width = getWidth();
+        int start = getPaddingStart();
+        int childCount = getChildCount();
+        int ownHeight = b - t;
+        int childSpace = ownHeight - mPaddingTop - mPaddingBottom;
+
+        // Instead of centering the baseline, pick a baseline that centers views which align to it.
+        // Only used when mGravityY is CENTER_VERTICAL
+        int baselineY = mPaddingTop + ((childSpace - (mMaxAscent + mMaxDescent)) / 2) + mMaxAscent;
+
+        for (int i = 0; i < childCount; i++) {
+            View child = getChildAt(i);
+            if (child.getVisibility() == GONE) {
+                continue;
+            }
+            int childHeight = child.getMeasuredHeight();
+            MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
+
+            // Calculate vertical alignment of the views, accounting for the view baselines
+            int childTop;
+            int childBaseline = child.getBaseline();
+            switch (mGravityY) {
+                case Gravity.TOP:
+                    childTop = mPaddingTop + params.topMargin;
+                    if (childBaseline != -1) {
+                        childTop += mMaxAscent - childBaseline;
+                    }
+                    break;
+                case Gravity.CENTER_VERTICAL:
+                    if (childBaseline != -1) {
+                        // Align baselines vertically only if the child is smaller than us
+                        if (childSpace - childHeight > 0) {
+                            childTop = baselineY - childBaseline;
+                        } else {
+                            childTop = mPaddingTop + (childSpace - childHeight) / 2;
+                        }
+                    } else {
+                        childTop = mPaddingTop + ((childSpace - childHeight) / 2)
+                                + params.topMargin - params.bottomMargin;
+                    }
+                    break;
+                case Gravity.BOTTOM:
+                    int childBottom = ownHeight - mPaddingBottom;
+                    childTop = childBottom - childHeight - params.bottomMargin;
+                    if (childBaseline != -1) {
+                        int descent = childHeight - childBaseline;
+                        childTop -= (mMaxDescent - descent);
+                    }
+                    break;
+                default:
+                    childTop = mPaddingTop;
+            }
+            if (mViewsToDisappear.contains(child)) {
+                child.layout(start, childTop, start, childTop + childHeight);
+            } else {
+                start += params.getMarginStart();
+                int end = start + child.getMeasuredWidth();
+                int layoutLeft = isRtl ? width - end : start;
+                int layoutRight = isRtl ? width - start : end;
+                start = end + params.getMarginEnd();
+                child.layout(layoutLeft, childTop, layoutRight, childTop + childHeight);
+            }
+        }
+        updateTouchListener();
+    }
+
+    @Override
+    public LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new MarginLayoutParams(getContext(), attrs);
+    }
+
+    private void updateTouchListener() {
+        if (mFeedbackListener == null) {
+            setOnTouchListener(null);
+            return;
+        }
+        setOnTouchListener(mTouchListener);
+        mTouchListener.bindTouchRects();
+    }
+
+    /**
+     * Sets onclick listener for feedback icon.
+     */
+    public void setFeedbackOnClickListener(OnClickListener l) {
+        mFeedbackListener = l;
+        mFeedbackIcon.setOnClickListener(mFeedbackListener);
+        updateTouchListener();
+    }
+
+    /**
+     * Sets the margin end for the text portion of the header, excluding right-aligned elements
+     *
+     * @param headerTextMarginEnd margin size
+     */
+    public void setHeaderTextMarginEnd(int headerTextMarginEnd) {
+        if (mHeaderTextMarginEnd != headerTextMarginEnd) {
+            mHeaderTextMarginEnd = headerTextMarginEnd;
+            requestLayout();
+        }
+    }
+
+    /**
+     * Get the current margin end value for the header text
+     *
+     * @return margin size
+     */
+    public int getHeaderTextMarginEnd() {
+        return mHeaderTextMarginEnd;
+    }
+
+    /**
+     * Set padding at the start of the view.
+     */
+    public void setPaddingStart(int paddingStart) {
+        setPaddingRelative(paddingStart, getPaddingTop(), getPaddingEnd(), getPaddingBottom());
+    }
+
+    private class HeaderTouchListener implements OnTouchListener {
+
+        private Rect mFeedbackRect;
+        private int mTouchSlop;
+        private boolean mTrackGesture;
+        private float mDownX;
+        private float mDownY;
+
+        HeaderTouchListener() {
+        }
+
+        public void bindTouchRects() {
+            mFeedbackRect = getRectAroundView(mFeedbackIcon);
+            mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+        }
+
+        private Rect getRectAroundView(View view) {
+            float size = 48 * getResources().getDisplayMetrics().density;
+            float width = Math.max(size, view.getWidth());
+            float height = Math.max(size, view.getHeight());
+            final Rect r = new Rect();
+            if (view.getVisibility() == GONE) {
+                view = getFirstChildNotGone();
+                r.left = (int) (view.getLeft() - width / 2.0f);
+            } else {
+                r.left = (int) ((view.getLeft() + view.getRight()) / 2.0f - width / 2.0f);
+            }
+            r.top = (int) ((view.getTop() + view.getBottom()) / 2.0f - height / 2.0f);
+            r.bottom = (int) (r.top + height);
+            r.right = (int) (r.left + width);
+            return r;
+        }
+
+        @Override
+        public boolean onTouch(View v, MotionEvent event) {
+            float x = event.getX();
+            float y = event.getY();
+            switch (event.getActionMasked() & MotionEvent.ACTION_MASK) {
+                case MotionEvent.ACTION_DOWN:
+                    mTrackGesture = false;
+                    if (isInside(x, y)) {
+                        mDownX = x;
+                        mDownY = y;
+                        mTrackGesture = true;
+                        return true;
+                    }
+                    break;
+                case MotionEvent.ACTION_MOVE:
+                    if (mTrackGesture) {
+                        if (Math.abs(mDownX - x) > mTouchSlop
+                                || Math.abs(mDownY - y) > mTouchSlop) {
+                            mTrackGesture = false;
+                        }
+                    }
+                    break;
+                case MotionEvent.ACTION_UP:
+                    if (mTrackGesture && onTouchUp(x, y, mDownX, mDownY)) {
+                        return true;
+                    }
+                    break;
+            }
+            return mTrackGesture;
+        }
+
+        private boolean onTouchUp(float upX, float upY, float downX, float downY) {
+            if (mFeedbackIcon.isVisibleToUser()
+                    && (mFeedbackRect.contains((int) upX, (int) upY)
+                    || mFeedbackRect.contains((int) downX, (int) downY))) {
+                mFeedbackIcon.performClick();
+                return true;
+            }
+            return false;
+        }
+
+        private boolean isInside(float x, float y) {
+            return mFeedbackRect.contains((int) x, (int) y);
+        }
+    }
+
+    private View getFirstChildNotGone() {
+        for (int i = 0; i < getChildCount(); i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                return child;
+            }
+        }
+        return this;
+    }
+
+    @Override
+    public boolean hasOverlappingRendering() {
+        return false;
+    }
+
+    /**
+     * Determine if the given point is touching an active part of the top line.
+     */
+    public boolean isInTouchRect(float x, float y) {
+        if (mFeedbackListener == null) {
+            return false;
+        }
+        return mTouchListener.isInside(x, y);
+    }
+
+    /**
+     * Perform a click on an active part of the top line, if touching.
+     */
+    public boolean onTouchUp(float upX, float upY, float downX, float downY) {
+        if (mFeedbackListener == null) {
+            return false;
+        }
+        return mTouchListener.onTouchUp(upX, upY, downX, downY);
+    }
+
+    private final class OverflowAdjuster {
+        private int mOverflow;
+        private int mHeightSpec;
+        private View mRegrowView;
+
+        OverflowAdjuster resetForOverflow(int overflow, int heightSpec) {
+            mOverflow = overflow;
+            mHeightSpec = heightSpec;
+            mRegrowView = null;
+            return this;
+        }
+
+        /**
+         * Shrink the targetView's width by up to overFlow, down to minimumWidth.
+         * @param targetView the view to shrink the width of
+         * @param targetDivider a divider view which should be set to 0 width if the targetView is
+         * @param minimumWidth the minimum width allowed for the targetView
+         * @return this object
+         */
+        OverflowAdjuster adjust(View targetView, View targetDivider, int minimumWidth) {
+            if (mOverflow <= 0 || targetView == null || targetView.getVisibility() == View.GONE) {
+                return this;
+            }
+            final int oldWidth = targetView.getMeasuredWidth();
+            if (oldWidth <= minimumWidth) {
+                return this;
+            }
+            // we're too big
+            int newSize = Math.max(minimumWidth, oldWidth - mOverflow);
+            if (minimumWidth == 0 && newSize < mChildHideWidth
+                    && mRegrowView != null && mRegrowView != targetView) {
+                // View is so small it's better to hide it entirely (and its divider and margins)
+                // so we can give that space back to another previously shrunken view.
+                newSize = 0;
+            }
+
+            int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
+            targetView.measure(childWidthSpec, mHeightSpec);
+            mOverflow -= oldWidth - newSize;
+
+            if (newSize == 0) {
+                mViewsToDisappear.add(targetView);
+                mOverflow -= getHorizontalMargins(targetView);
+                if (targetDivider != null && targetDivider.getVisibility() != GONE) {
+                    mViewsToDisappear.add(targetDivider);
+                    int oldDividerWidth = targetDivider.getMeasuredWidth();
+                    int dividerWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.AT_MOST);
+                    targetDivider.measure(dividerWidthSpec, mHeightSpec);
+                    mOverflow -= (oldDividerWidth + getHorizontalMargins(targetDivider));
+                }
+            }
+            if (mOverflow < 0 && mRegrowView != null) {
+                // We're now under-flowing, so regrow the last view.
+                final int regrowCurrentSize = mRegrowView.getMeasuredWidth();
+                final int maxSize = regrowCurrentSize - mOverflow;
+                int regrowWidthSpec = MeasureSpec.makeMeasureSpec(maxSize, MeasureSpec.AT_MOST);
+                mRegrowView.measure(regrowWidthSpec, mHeightSpec);
+                finish();
+                return this;
+            }
+
+            if (newSize != 0) {
+                // if we shrunk this view (but did not completely hide it) store it for potential
+                // re-growth if we proactively shorten a future view.
+                mRegrowView = targetView;
+            }
+            return this;
+        }
+
+        void finish() {
+            resetForOverflow(0, 0);
+        }
+
+        private int getHorizontalMargins(View view) {
+            MarginLayoutParams params = (MarginLayoutParams) view.getLayoutParams();
+            return params.getMarginStart() + params.getMarginEnd();
+        }
+    }
+}
diff --git a/android/view/OnReceiveContentListener.java b/android/view/OnReceiveContentListener.java
new file mode 100644
index 0000000..f2a763a
--- /dev/null
+++ b/android/view/OnReceiveContentListener.java
@@ -0,0 +1,123 @@
+/*
+ * 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.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * Listener for apps to implement handling for insertion of content. Content may be both text and
+ * non-text (plain/styled text, HTML, images, videos, audio files, etc).
+ *
+ * <p>This listener can be attached to different types of UI components using
+ * {@link View#setOnReceiveContentListener}.
+ *
+ * <p>Here is a sample implementation that handles content URIs and delegates the processing for
+ * text and everything else to the platform:<br>
+ * <pre class="prettyprint">
+ * // (1) Define the listener
+ * public class MyReceiver implements OnReceiveContentListener {
+ *     public static final String[] MIME_TYPES = new String[] {"image/*", "video/*"};
+ *
+ *     &#64;Override
+ *     public ContentInfo onReceiveContent(View view, ContentInfo payload) {
+ *         Pair&lt;ContentInfo, ContentInfo&gt; split =
+ *                 ContentInfoCompat.partition(payload, item -&gt; item.getUri() != null);
+ *         ContentInfo uriContent = split.first;
+ *         ContentInfo remaining = split.second;
+ *         if (uriContent != null) {
+ *             ClipData clip = uriContent.getClip();
+ *             for (int i = 0; i < clip.getItemCount(); i++) {
+ *                 Uri uri = clip.getItemAt(i).getUri();
+ *                 // ... app-specific logic to handle the URI ...
+ *             }
+ *         }
+ *         // Return anything that we didn't handle ourselves. This preserves the default platform
+ *         // behavior for text and anything else for which we are not implementing custom handling.
+ *         return remaining;
+ *     }
+ * }
+ *
+ * // (2) Register the listener
+ * public class MyActivity extends Activity {
+ *     &#64;Override
+ *     public void onCreate(Bundle savedInstanceState) {
+ *         // ...
+ *
+ *         EditText myInput = findViewById(R.id.my_input);
+ *         myInput.setOnReceiveContentListener(MyReceiver.MIME_TYPES, new MyReceiver());
+ *     }
+ * </pre>
+ */
+public interface OnReceiveContentListener {
+    /**
+     * Receive the given content.
+     *
+     * <p>Implementations should handle any content items of interest and return all unhandled
+     * items to preserve the default platform behavior for content that does not have app-specific
+     * handling. For example, an implementation may provide handling for content URIs (to provide
+     * support for inserting images, etc) and delegate the processing of text to the platform to
+     * preserve the common behavior for inserting text. See the class javadoc for a sample
+     * implementation.
+     *
+     * <h3>Handling different content</h3>
+     * <ul>
+     *     <li>Text. If the {@link ContentInfo#getSource() source} is
+     *     {@link ContentInfo#SOURCE_AUTOFILL autofill}, the view's content should be fully
+     *     replaced by the passed-in text. For sources other than autofill, the passed-in text
+     *     should overwrite the current selection or be inserted at the current cursor position
+     *     if there is no selection.
+     *     <li>Non-text content (e.g. images). The content may be inserted inline if the widget
+     *     supports this, or it may be added as an attachment (could potentially be shown in a
+     *     completely separate view).
+     * </ul>
+     *
+     * <h3>URI permissions</h3>
+     * <p>{@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION Read permissions} are
+     * granted automatically by the platform for any
+     * {@link android.content.ContentResolver#SCHEME_CONTENT content URIs} in the payload passed
+     * to this listener. Permissions are transient and will be released automatically by the
+     * platform.
+     * <p>Processing of content should normally be done in a service or activity.
+     * For long-running processing, using {@code androidx.work.WorkManager} is recommended.
+     * When implementing this, permissions should be extended to the target service or activity
+     * by passing the content using {@link android.content.Intent#setClipData Intent.setClipData}
+     * and {@link android.content.Intent#addFlags(int) setting} the flag
+     * {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION FLAG_GRANT_READ_URI_PERMISSION}.
+     * <p>Alternatively, if using a background thread within the current context to process the
+     * content, a reference to the {@code payload} object should be maintained to ensure that
+     * permissions are not revoked prematurely.
+     *
+     * @param view The view where the content insertion was requested.
+     * @param payload The content to insert and related metadata. The payload may contain multiple
+     *                items and their MIME types may be different (e.g. an image item and a text
+     *                item). The payload may also contain items whose MIME type is not in the list
+     *                of MIME types specified when
+     *                {@link View#setOnReceiveContentListener setting} the listener. For
+     *                those items, the listener may reject the content (defer to the default
+     *                platform behavior) or execute some other fallback logic (e.g. show an
+     *                appropriate message to the user).
+     *
+     * @return The portion of the passed-in content whose processing should be delegated to
+     * the platform. Return null if all content was handled in some way. Actual insertion of
+     * the content may be processed asynchronously in the background and may or may not
+     * succeed even if this method returns null. For example, an app may end up not inserting
+     * an item if it exceeds the app's size limit for that type of content.
+     */
+    @Nullable
+    ContentInfo onReceiveContent(@NonNull View view, @NonNull ContentInfo payload);
+}
diff --git a/android/view/OrientationEventListener.java b/android/view/OrientationEventListener.java
new file mode 100644
index 0000000..cd48a4f
--- /dev/null
+++ b/android/view/OrientationEventListener.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.util.Log;
+
+/**
+ * Helper class for receiving notifications from the SensorManager when
+ * the orientation of the device has changed.
+ */
+public abstract class OrientationEventListener {
+    private static final String TAG = "OrientationEventListener";
+    private static final boolean DEBUG = false;
+    private static final boolean localLOGV = false;
+    private int mOrientation = ORIENTATION_UNKNOWN;
+    private SensorManager mSensorManager;
+    private boolean mEnabled = false;
+    private int mRate;
+    private Sensor mSensor;
+    private SensorEventListener mSensorEventListener;
+    private OrientationListener mOldListener;
+    
+    /**
+     * Returned from onOrientationChanged when the device orientation cannot be determined
+     * (typically when the device is in a close to flat position).
+     *
+     *  @see #onOrientationChanged
+     */
+    public static final int ORIENTATION_UNKNOWN = -1;
+
+    /**
+     * Creates a new OrientationEventListener.
+     * 
+     * @param context for the OrientationEventListener.
+     */
+    public OrientationEventListener(Context context) {
+        this(context, SensorManager.SENSOR_DELAY_NORMAL);
+    }
+    
+    /**
+     * Creates a new OrientationEventListener.
+     * 
+     * @param context for the OrientationEventListener.
+     * @param rate at which sensor events are processed (see also
+     * {@link android.hardware.SensorManager SensorManager}). Use the default
+     * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL 
+     * SENSOR_DELAY_NORMAL} for simple screen orientation change detection.
+     */
+    public OrientationEventListener(Context context, int rate) {
+        mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
+        mRate = rate;
+        mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+        if (mSensor != null) {
+            // Create listener only if sensors do exist
+            mSensorEventListener = new SensorEventListenerImpl();
+        }
+    }
+    
+    void registerListener(OrientationListener lis) {
+        mOldListener = lis;
+    }
+
+    /**
+     * Enables the OrientationEventListener so it will monitor the sensor and call
+     * {@link #onOrientationChanged} when the device orientation changes.
+     */
+    public void enable() {
+        if (mSensor == null) {
+            Log.w(TAG, "Cannot detect sensors. Not enabled");
+            return;
+        }
+        if (mEnabled == false) {
+            if (localLOGV) Log.d(TAG, "OrientationEventListener enabled");
+            mSensorManager.registerListener(mSensorEventListener, mSensor, mRate);
+            mEnabled = true;
+        }
+    }
+
+    /**
+     * Disables the OrientationEventListener.
+     */
+    public void disable() {
+        if (mSensor == null) {
+            Log.w(TAG, "Cannot detect sensors. Invalid disable");
+            return;
+        }
+        if (mEnabled == true) {
+            if (localLOGV) Log.d(TAG, "OrientationEventListener disabled");
+            mSensorManager.unregisterListener(mSensorEventListener);
+            mEnabled = false;
+        }
+    }
+
+    class SensorEventListenerImpl implements SensorEventListener {
+        private static final int _DATA_X = 0;
+        private static final int _DATA_Y = 1;
+        private static final int _DATA_Z = 2;
+        
+        public void onSensorChanged(SensorEvent event) {
+            float[] values = event.values;
+            int orientation = ORIENTATION_UNKNOWN;
+            float X = -values[_DATA_X];
+            float Y = -values[_DATA_Y];
+            float Z = -values[_DATA_Z];        
+            float magnitude = X*X + Y*Y;
+            // Don't trust the angle if the magnitude is small compared to the y value
+            if (magnitude * 4 >= Z*Z) {
+                float OneEightyOverPi = 57.29577957855f;
+                float angle = (float)Math.atan2(-Y, X) * OneEightyOverPi;
+                orientation = 90 - (int)Math.round(angle);
+                // normalize to 0 - 359 range
+                while (orientation >= 360) {
+                    orientation -= 360;
+                } 
+                while (orientation < 0) {
+                    orientation += 360;
+                }
+            }
+            if (mOldListener != null) {
+                mOldListener.onSensorChanged(Sensor.TYPE_ACCELEROMETER, event.values);
+            }
+            if (orientation != mOrientation) {
+                mOrientation = orientation;
+                onOrientationChanged(orientation);
+            }
+        }
+
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+
+        }
+    }
+    
+    /*
+     * Returns true if sensor is enabled and false otherwise
+     */
+    public boolean canDetectOrientation() {
+        return mSensor != null;
+    }
+
+    /**
+     * Called when the orientation of the device has changed.
+     * orientation parameter is in degrees, ranging from 0 to 359.
+     * orientation is 0 degrees when the device is oriented in its natural position,
+     * 90 degrees when its left side is at the top, 180 degrees when it is upside down, 
+     * and 270 degrees when its right side is to the top.
+     * {@link #ORIENTATION_UNKNOWN} is returned when the device is close to flat
+     * and the orientation cannot be determined.
+     *
+     * @param orientation The new orientation of the device.
+     *
+     *  @see #ORIENTATION_UNKNOWN
+     */
+    abstract public void onOrientationChanged(int orientation);
+}
diff --git a/android/view/OrientationListener.java b/android/view/OrientationListener.java
new file mode 100644
index 0000000..ce8074e
--- /dev/null
+++ b/android/view/OrientationListener.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.content.Context;
+import android.hardware.SensorListener;
+
+/**
+ * Helper class for receiving notifications from the SensorManager when
+ * the orientation of the device has changed.
+ *  @deprecated use {@link android.view.OrientationEventListener} instead.
+ *  This class internally uses the OrientationEventListener.
+ */
+@Deprecated
+public abstract class OrientationListener implements SensorListener {
+    private OrientationEventListener mOrientationEventLis;
+    
+    /**
+     * Returned from onOrientationChanged when the device orientation cannot be determined
+     * (typically when the device is in a close to flat position).
+     *
+     *  @see #onOrientationChanged
+     */
+    public static final int ORIENTATION_UNKNOWN = OrientationEventListener.ORIENTATION_UNKNOWN;
+
+    /**
+     * Creates a new OrientationListener.
+     * 
+     * @param context for the OrientationListener.
+     */
+    public OrientationListener(Context context) {
+        mOrientationEventLis = new OrientationEventListenerInternal(context);
+    }
+
+    /**
+     * Creates a new OrientationListener.
+     * 
+     * @param context for the OrientationListener.
+     * @param rate at which sensor events are processed (see also
+     * {@link android.hardware.SensorManager SensorManager}). Use the default
+     * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL 
+     * SENSOR_DELAY_NORMAL} for simple screen orientation change detection.
+     */
+    public OrientationListener(Context context, int rate) {
+        mOrientationEventLis = new OrientationEventListenerInternal(context, rate);
+    }
+    
+    class OrientationEventListenerInternal extends OrientationEventListener {
+        OrientationEventListenerInternal(Context context) {
+            super(context);
+        }
+        
+        OrientationEventListenerInternal(Context context, int rate) {
+            super(context, rate);
+            // register so that onSensorChanged gets invoked
+            registerListener(OrientationListener.this);
+        }
+                
+        public void onOrientationChanged(int orientation) {
+            OrientationListener.this.onOrientationChanged(orientation);
+        }
+    }
+    
+    /**
+     * Enables the OrientationListener so it will monitor the sensor and call
+     * {@link #onOrientationChanged} when the device orientation changes.
+     */
+    public void enable() {
+        mOrientationEventLis.enable();
+    }
+
+    /**
+     * Disables the OrientationListener.
+     */
+    public void disable() {
+        mOrientationEventLis.disable();
+    }
+    
+    public void onAccuracyChanged(int sensor, int accuracy) {
+    }
+    
+    public void onSensorChanged(int sensor, float[] values) {
+        // just ignore the call here onOrientationChanged is invoked anyway
+    }
+
+
+    /**
+     * Look at {@link android.view.OrientationEventListener#onOrientationChanged}
+     * for method description and usage
+     * @param orientation The new orientation of the device.
+     *
+     *  @see #ORIENTATION_UNKNOWN
+     */
+    abstract public void onOrientationChanged(int orientation);
+    
+}
diff --git a/android/view/PendingInsetsController.java b/android/view/PendingInsetsController.java
new file mode 100644
index 0000000..c61baf6
--- /dev/null
+++ b/android/view/PendingInsetsController.java
@@ -0,0 +1,244 @@
+/*
+ * 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.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.CancellationSignal;
+import android.view.WindowInsets.Type.InsetsType;
+import android.view.animation.Interpolator;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+
+/**
+ * An insets controller that keeps track of pending requests. This is such that an app can freely
+ * use {@link WindowInsetsController} before the view root is attached during activity startup.
+ * @hide
+ */
+public class PendingInsetsController implements WindowInsetsController {
+
+    private static final int KEEP_BEHAVIOR = -1;
+    private final ArrayList<PendingRequest> mRequests = new ArrayList<>();
+    private @Appearance int mAppearance;
+    private @Appearance int mAppearanceMask;
+    private @Behavior int mBehavior = KEEP_BEHAVIOR;
+    private boolean mAnimationsDisabled;
+    private final InsetsState mDummyState = new InsetsState();
+    private InsetsController mReplayedInsetsController;
+    private ArrayList<OnControllableInsetsChangedListener> mControllableInsetsChangedListeners
+            = new ArrayList<>();
+    private int mCaptionInsetsHeight = 0;
+
+    @Override
+    public void show(int types) {
+        if (mReplayedInsetsController != null) {
+            mReplayedInsetsController.show(types);
+        } else {
+            mRequests.add(new ShowRequest(types));
+        }
+    }
+
+    @Override
+    public void hide(int types) {
+        if (mReplayedInsetsController != null) {
+            mReplayedInsetsController.hide(types);
+        } else {
+            mRequests.add(new HideRequest(types));
+        }
+    }
+
+    @Override
+    public void setSystemBarsAppearance(int appearance, int mask) {
+        if (mReplayedInsetsController != null) {
+            mReplayedInsetsController.setSystemBarsAppearance(appearance, mask);
+        } else {
+            mAppearance = (mAppearance & ~mask) | (appearance & mask);
+            mAppearanceMask |= mask;
+        }
+    }
+
+    @Override
+    public int getSystemBarsAppearance() {
+        if (mReplayedInsetsController != null) {
+            return mReplayedInsetsController.getSystemBarsAppearance();
+        }
+        return mAppearance;
+    }
+
+    @Override
+    public void setCaptionInsetsHeight(int height) {
+        mCaptionInsetsHeight = height;
+    }
+
+    @Override
+    public void setSystemBarsBehavior(int behavior) {
+        if (mReplayedInsetsController != null) {
+            mReplayedInsetsController.setSystemBarsBehavior(behavior);
+        } else {
+            mBehavior = behavior;
+        }
+    }
+
+    @Override
+    public int getSystemBarsBehavior() {
+        if (mReplayedInsetsController != null) {
+            return mReplayedInsetsController.getSystemBarsBehavior();
+        }
+        if (mBehavior == KEEP_BEHAVIOR) {
+            return BEHAVIOR_DEFAULT;
+        }
+        return mBehavior;
+    }
+
+    @Override
+    public void setAnimationsDisabled(boolean disable) {
+        if (mReplayedInsetsController != null) {
+            mReplayedInsetsController.setAnimationsDisabled(disable);
+        } else {
+            mAnimationsDisabled = disable;
+        }
+    }
+
+    @Override
+    public InsetsState getState() {
+        return mDummyState;
+    }
+
+    @Override
+    public boolean isRequestedVisible(int type) {
+
+        // Method is only used once real insets controller is attached, so no need to traverse
+        // requests here.
+        return InsetsState.getDefaultVisibility(type);
+    }
+
+    @Override
+    public void addOnControllableInsetsChangedListener(
+            OnControllableInsetsChangedListener listener) {
+        if (mReplayedInsetsController != null) {
+            mReplayedInsetsController.addOnControllableInsetsChangedListener(listener);
+        } else {
+            mControllableInsetsChangedListeners.add(listener);
+            listener.onControllableInsetsChanged(this, 0);
+        }
+    }
+
+    @Override
+    public void removeOnControllableInsetsChangedListener(
+            OnControllableInsetsChangedListener listener) {
+        if (mReplayedInsetsController != null) {
+            mReplayedInsetsController.removeOnControllableInsetsChangedListener(listener);
+        } else {
+            mControllableInsetsChangedListeners.remove(listener);
+        }
+    }
+
+    /**
+     * Replays the commands on {@code controller} and attaches it to this instance such that any
+     * calls will be forwarded to the real instance in the future.
+     */
+    @VisibleForTesting
+    public void replayAndAttach(InsetsController controller) {
+        if (mBehavior != KEEP_BEHAVIOR) {
+            controller.setSystemBarsBehavior(mBehavior);
+        }
+        if (mAppearanceMask != 0) {
+            controller.setSystemBarsAppearance(mAppearance, mAppearanceMask);
+        }
+        if (mCaptionInsetsHeight != 0) {
+            controller.setCaptionInsetsHeight(mCaptionInsetsHeight);
+        }
+        if (mAnimationsDisabled) {
+            controller.setAnimationsDisabled(true);
+        }
+        int size = mRequests.size();
+        for (int i = 0; i < size; i++) {
+            mRequests.get(i).replay(controller);
+        }
+        size = mControllableInsetsChangedListeners.size();
+        for (int i = 0; i < size; i++) {
+            controller.addOnControllableInsetsChangedListener(
+                    mControllableInsetsChangedListeners.get(i));
+        }
+
+        // Reset all state so it doesn't get applied twice just in case
+        mRequests.clear();
+        mControllableInsetsChangedListeners.clear();
+        mBehavior = KEEP_BEHAVIOR;
+        mAppearance = 0;
+        mAppearanceMask = 0;
+        mAnimationsDisabled = false;
+
+        // After replaying, we forward everything directly to the replayed instance.
+        mReplayedInsetsController = controller;
+    }
+
+    /**
+     * Detaches the controller to no longer forward calls to the real instance.
+     */
+    @VisibleForTesting
+    public void detach() {
+        mReplayedInsetsController = null;
+    }
+
+    @Override
+    public void controlWindowInsetsAnimation(@InsetsType int types, long durationMillis,
+            @Nullable Interpolator interpolator,
+            CancellationSignal cancellationSignal,
+            @NonNull WindowInsetsAnimationControlListener listener) {
+        if (mReplayedInsetsController != null) {
+            mReplayedInsetsController.controlWindowInsetsAnimation(types, durationMillis,
+                    interpolator, cancellationSignal, listener);
+        } else {
+            listener.onCancelled(null);
+        }
+    }
+
+    private interface PendingRequest {
+        void replay(InsetsController controller);
+    }
+
+    private static class ShowRequest implements PendingRequest {
+
+        private final @InsetsType int mTypes;
+
+        public ShowRequest(int types) {
+            mTypes = types;
+        }
+
+        @Override
+        public void replay(InsetsController controller) {
+            controller.show(mTypes);
+        }
+    }
+
+    private static class HideRequest implements PendingRequest {
+
+        private final @InsetsType int mTypes;
+
+        public HideRequest(int types) {
+            mTypes = types;
+        }
+
+        @Override
+        public void replay(InsetsController controller) {
+            controller.hide(mTypes);
+        }
+    }
+}
diff --git a/android/view/PixelCopy.java b/android/view/PixelCopy.java
new file mode 100644
index 0000000..2797a4d
--- /dev/null
+++ b/android/view/PixelCopy.java
@@ -0,0 +1,298 @@
+/*
+ * 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.view;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.view.ViewTreeObserver.OnDrawListener;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Provides a mechanisms to issue pixel copy requests to allow for copy
+ * operations from {@link Surface} to {@link Bitmap}
+ */
+public final class PixelCopy {
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({SUCCESS, ERROR_UNKNOWN, ERROR_TIMEOUT, ERROR_SOURCE_NO_DATA,
+        ERROR_SOURCE_INVALID, ERROR_DESTINATION_INVALID})
+    public @interface CopyResultStatus {}
+
+    /** The pixel copy request succeeded */
+    public static final int SUCCESS = 0;
+
+    /** The pixel copy request failed with an unknown error. */
+    public static final int ERROR_UNKNOWN = 1;
+
+    /**
+     * A timeout occurred while trying to acquire a buffer from the source to
+     * copy from.
+     */
+    public static final int ERROR_TIMEOUT = 2;
+
+    /**
+     * The source has nothing to copy from. When the source is a {@link Surface}
+     * this means that no buffers have been queued yet. Wait for the source
+     * to produce a frame and try again.
+     */
+    public static final int ERROR_SOURCE_NO_DATA = 3;
+
+    /**
+     * It is not possible to copy from the source. This can happen if the source
+     * is hardware-protected or destroyed.
+     */
+    public static final int ERROR_SOURCE_INVALID = 4;
+
+    /**
+     * The destination isn't a valid copy target. If the destination is a bitmap
+     * this can occur if the bitmap is too large for the hardware to copy to.
+     * It can also occur if the destination has been destroyed.
+     */
+    public static final int ERROR_DESTINATION_INVALID = 5;
+
+    /**
+     * Listener for observing the completion of a PixelCopy request.
+     */
+    public interface OnPixelCopyFinishedListener {
+        /**
+         * Callback for when a pixel copy request has completed. This will be called
+         * regardless of whether the copy succeeded or failed.
+         *
+         * @param copyResult Contains the resulting status of the copy request.
+         * This will either be {@link PixelCopy#SUCCESS} or one of the
+         * <code>PixelCopy.ERROR_*</code> values.
+         */
+        void onPixelCopyFinished(@CopyResultStatus int copyResult);
+    }
+
+    /**
+     * Requests for the display content of a {@link SurfaceView} to be copied
+     * into a provided {@link Bitmap}.
+     *
+     * The contents of the source will be scaled to fit exactly inside the bitmap.
+     * The pixel format of the source buffer will be converted, as part of the copy,
+     * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
+     * in the SurfaceView's Surface will be used as the source of the copy.
+     *
+     * @param source The source from which to copy
+     * @param dest The destination of the copy. The source will be scaled to
+     * match the width, height, and format of this bitmap.
+     * @param listener Callback for when the pixel copy request completes
+     * @param listenerThread The callback will be invoked on this Handler when
+     * the copy is finished.
+     */
+    public static void request(@NonNull SurfaceView source, @NonNull Bitmap dest,
+            @NonNull OnPixelCopyFinishedListener listener, @NonNull Handler listenerThread) {
+        request(source.getHolder().getSurface(), dest, listener, listenerThread);
+    }
+
+    /**
+     * Requests for the display content of a {@link SurfaceView} to be copied
+     * into a provided {@link Bitmap}.
+     *
+     * The contents of the source will be scaled to fit exactly inside the bitmap.
+     * The pixel format of the source buffer will be converted, as part of the copy,
+     * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
+     * in the SurfaceView's Surface will be used as the source of the copy.
+     *
+     * @param source The source from which to copy
+     * @param srcRect The area of the source to copy from. If this is null
+     * the copy area will be the entire surface. The rect will be clamped to
+     * the bounds of the Surface.
+     * @param dest The destination of the copy. The source will be scaled to
+     * match the width, height, and format of this bitmap.
+     * @param listener Callback for when the pixel copy request completes
+     * @param listenerThread The callback will be invoked on this Handler when
+     * the copy is finished.
+     */
+    public static void request(@NonNull SurfaceView source, @Nullable Rect srcRect,
+            @NonNull Bitmap dest, @NonNull OnPixelCopyFinishedListener listener,
+            @NonNull Handler listenerThread) {
+        request(source.getHolder().getSurface(), srcRect,
+                dest, listener, listenerThread);
+    }
+
+    /**
+     * Requests a copy of the pixels from a {@link Surface} to be copied into
+     * a provided {@link Bitmap}.
+     *
+     * The contents of the source will be scaled to fit exactly inside the bitmap.
+     * The pixel format of the source buffer will be converted, as part of the copy,
+     * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
+     * in the Surface will be used as the source of the copy.
+     *
+     * @param source The source from which to copy
+     * @param dest The destination of the copy. The source will be scaled to
+     * match the width, height, and format of this bitmap.
+     * @param listener Callback for when the pixel copy request completes
+     * @param listenerThread The callback will be invoked on this Handler when
+     * the copy is finished.
+     */
+    public static void request(@NonNull Surface source, @NonNull Bitmap dest,
+            @NonNull OnPixelCopyFinishedListener listener, @NonNull Handler listenerThread) {
+        request(source, null, dest, listener, listenerThread);
+    }
+
+    /**
+     * Requests a copy of the pixels at the provided {@link Rect} from
+     * a {@link Surface} to be copied into a provided {@link Bitmap}.
+     *
+     * The contents of the source rect will be scaled to fit exactly inside the bitmap.
+     * The pixel format of the source buffer will be converted, as part of the copy,
+     * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
+     * in the Surface will be used as the source of the copy.
+     *
+     * @param source The source from which to copy
+     * @param srcRect The area of the source to copy from. If this is null
+     * the copy area will be the entire surface. The rect will be clamped to
+     * the bounds of the Surface.
+     * @param dest The destination of the copy. The source will be scaled to
+     * match the width, height, and format of this bitmap.
+     * @param listener Callback for when the pixel copy request completes
+     * @param listenerThread The callback will be invoked on this Handler when
+     * the copy is finished.
+     */
+    public static void request(@NonNull Surface source, @Nullable Rect srcRect,
+            @NonNull Bitmap dest, @NonNull OnPixelCopyFinishedListener listener,
+            @NonNull Handler listenerThread) {
+        validateBitmapDest(dest);
+        if (!source.isValid()) {
+            throw new IllegalArgumentException("Surface isn't valid, source.isValid() == false");
+        }
+        if (srcRect != null && srcRect.isEmpty()) {
+            throw new IllegalArgumentException("sourceRect is empty");
+        }
+        // TODO: Make this actually async and fast and cool and stuff
+        int result = ThreadedRenderer.copySurfaceInto(source, srcRect, dest);
+        listenerThread.post(new Runnable() {
+            @Override
+            public void run() {
+                listener.onPixelCopyFinished(result);
+            }
+        });
+    }
+
+    /**
+     * Requests a copy of the pixels from a {@link Window} to be copied into
+     * a provided {@link Bitmap}.
+     *
+     * The contents of the source will be scaled to fit exactly inside the bitmap.
+     * The pixel format of the source buffer will be converted, as part of the copy,
+     * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
+     * in the Window's Surface will be used as the source of the copy.
+     *
+     * Note: This is limited to being able to copy from Window's with a non-null
+     * DecorView. If {@link Window#peekDecorView()} is null this throws an
+     * {@link IllegalArgumentException}. It will similarly throw an exception
+     * if the DecorView has not yet acquired a backing surface. It is recommended
+     * that {@link OnDrawListener} is used to ensure that at least one draw
+     * has happened before trying to copy from the window, otherwise either
+     * an {@link IllegalArgumentException} will be thrown or an error will
+     * be returned to the {@link OnPixelCopyFinishedListener}.
+     *
+     * @param source The source from which to copy
+     * @param dest The destination of the copy. The source will be scaled to
+     * match the width, height, and format of this bitmap.
+     * @param listener Callback for when the pixel copy request completes
+     * @param listenerThread The callback will be invoked on this Handler when
+     * the copy is finished.
+     */
+    public static void request(@NonNull Window source, @NonNull Bitmap dest,
+            @NonNull OnPixelCopyFinishedListener listener, @NonNull Handler listenerThread) {
+        request(source, null, dest, listener, listenerThread);
+    }
+
+    /**
+     * Requests a copy of the pixels at the provided {@link Rect} from
+     * a {@link Window} to be copied into a provided {@link Bitmap}.
+     *
+     * The contents of the source rect will be scaled to fit exactly inside the bitmap.
+     * The pixel format of the source buffer will be converted, as part of the copy,
+     * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
+     * in the Window's Surface will be used as the source of the copy.
+     *
+     * Note: This is limited to being able to copy from Window's with a non-null
+     * DecorView. If {@link Window#peekDecorView()} is null this throws an
+     * {@link IllegalArgumentException}. It will similarly throw an exception
+     * if the DecorView has not yet acquired a backing surface. It is recommended
+     * that {@link OnDrawListener} is used to ensure that at least one draw
+     * has happened before trying to copy from the window, otherwise either
+     * an {@link IllegalArgumentException} will be thrown or an error will
+     * be returned to the {@link OnPixelCopyFinishedListener}.
+     *
+     * @param source The source from which to copy
+     * @param srcRect The area of the source to copy from. If this is null
+     * the copy area will be the entire surface. The rect will be clamped to
+     * the bounds of the Surface.
+     * @param dest The destination of the copy. The source will be scaled to
+     * match the width, height, and format of this bitmap.
+     * @param listener Callback for when the pixel copy request completes
+     * @param listenerThread The callback will be invoked on this Handler when
+     * the copy is finished.
+     */
+    public static void request(@NonNull Window source, @Nullable Rect srcRect,
+            @NonNull Bitmap dest, @NonNull OnPixelCopyFinishedListener listener,
+            @NonNull Handler listenerThread) {
+        validateBitmapDest(dest);
+        if (source == null) {
+            throw new IllegalArgumentException("source is null");
+        }
+        if (source.peekDecorView() == null) {
+            throw new IllegalArgumentException(
+                    "Only able to copy windows with decor views");
+        }
+        Surface surface = null;
+        final ViewRootImpl root = source.peekDecorView().getViewRootImpl();
+        if (root != null) {
+            surface = root.mSurface;
+            final Rect surfaceInsets = root.mWindowAttributes.surfaceInsets;
+            if (srcRect == null) {
+                srcRect = new Rect(surfaceInsets.left, surfaceInsets.top,
+                        root.mWidth + surfaceInsets.left, root.mHeight + surfaceInsets.top);
+            } else {
+                srcRect.offset(surfaceInsets.left, surfaceInsets.top);
+            }
+        }
+        if (surface == null || !surface.isValid()) {
+            throw new IllegalArgumentException(
+                    "Window doesn't have a backing surface!");
+        }
+        request(surface, srcRect, dest, listener, listenerThread);
+    }
+
+    private static void validateBitmapDest(Bitmap bitmap) {
+        // TODO: Pre-check max texture dimens if we can
+        if (bitmap == null) {
+            throw new IllegalArgumentException("Bitmap cannot be null");
+        }
+        if (bitmap.isRecycled()) {
+            throw new IllegalArgumentException("Bitmap is recycled");
+        }
+        if (!bitmap.isMutable()) {
+            throw new IllegalArgumentException("Bitmap is immutable");
+        }
+    }
+
+    private PixelCopy() {}
+}
diff --git a/android/view/PointerIcon.java b/android/view/PointerIcon.java
new file mode 100644
index 0000000..38ae03d
--- /dev/null
+++ b/android/view/PointerIcon.java
@@ -0,0 +1,626 @@
+/*
+ * 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.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.XmlRes;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.hardware.display.DisplayManager;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.util.XmlUtils;
+
+/**
+ * Represents an icon that can be used as a mouse pointer.
+ * <p>
+ * Pointer icons can be provided either by the system using system types,
+ * or by applications using bitmaps or application resources.
+ * </p>
+ */
+public final class PointerIcon implements Parcelable {
+    private static final String TAG = "PointerIcon";
+
+    /** {@hide} Type constant: Custom icon with a user-supplied bitmap. */
+    public static final int TYPE_CUSTOM = -1;
+
+    /** Type constant: Null icon.  It has no bitmap. */
+    public static final int TYPE_NULL = 0;
+
+    /** Type constant: no icons are specified. If all views uses this, then falls back
+     * to the default type, but this is helpful to distinguish a view explicitly want
+     * to have the default icon.
+     * @hide
+     */
+    public static final int TYPE_NOT_SPECIFIED = 1;
+
+    /** Type constant: Arrow icon.  (Default mouse pointer) */
+    public static final int TYPE_ARROW = 1000;
+
+    /** {@hide} Type constant: Spot hover icon for touchpads. */
+    public static final int TYPE_SPOT_HOVER = 2000;
+
+    /** {@hide} Type constant: Spot touch icon for touchpads. */
+    public static final int TYPE_SPOT_TOUCH = 2001;
+
+    /** {@hide} Type constant: Spot anchor icon for touchpads. */
+    public static final int TYPE_SPOT_ANCHOR = 2002;
+
+    // Type constants for additional predefined icons for mice.
+    /** Type constant: context-menu. */
+    public static final int TYPE_CONTEXT_MENU = 1001;
+
+    /** Type constant: hand. */
+    public static final int TYPE_HAND = 1002;
+
+    /** Type constant: help. */
+    public static final int TYPE_HELP = 1003;
+
+    /** Type constant: wait. */
+    public static final int TYPE_WAIT = 1004;
+
+    /** Type constant: cell. */
+    public static final int TYPE_CELL = 1006;
+
+    /** Type constant: crosshair. */
+    public static final int TYPE_CROSSHAIR = 1007;
+
+    /** Type constant: text. */
+    public static final int TYPE_TEXT = 1008;
+
+    /** Type constant: vertical-text. */
+    public static final int TYPE_VERTICAL_TEXT = 1009;
+
+    /** Type constant: alias (indicating an alias of/shortcut to something is
+      * to be created. */
+    public static final int TYPE_ALIAS = 1010;
+
+    /** Type constant: copy. */
+    public static final int TYPE_COPY = 1011;
+
+    /** Type constant: no-drop. */
+    public static final int TYPE_NO_DROP = 1012;
+
+    /** Type constant: all-scroll. */
+    public static final int TYPE_ALL_SCROLL = 1013;
+
+    /** Type constant: horizontal double arrow mainly for resizing. */
+    public static final int TYPE_HORIZONTAL_DOUBLE_ARROW = 1014;
+
+    /** Type constant: vertical double arrow mainly for resizing. */
+    public static final int TYPE_VERTICAL_DOUBLE_ARROW = 1015;
+
+    /** Type constant: diagonal double arrow -- top-right to bottom-left. */
+    public static final int TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW = 1016;
+
+    /** Type constant: diagonal double arrow -- top-left to bottom-right. */
+    public static final int TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW = 1017;
+
+    /** Type constant: zoom-in. */
+    public static final int TYPE_ZOOM_IN = 1018;
+
+    /** Type constant: zoom-out. */
+    public static final int TYPE_ZOOM_OUT = 1019;
+
+    /** Type constant: grab. */
+    public static final int TYPE_GRAB = 1020;
+
+    /** Type constant: grabbing. */
+    public static final int TYPE_GRABBING = 1021;
+
+    // OEM private types should be defined starting at this range to avoid
+    // conflicts with any system types that may be defined in the future.
+    private static final int TYPE_OEM_FIRST = 10000;
+
+    /** The default pointer icon. */
+    public static final int TYPE_DEFAULT = TYPE_ARROW;
+
+    private static final PointerIcon gNullIcon = new PointerIcon(TYPE_NULL);
+    private static final SparseArray<SparseArray<PointerIcon>> gSystemIconsByDisplay =
+            new SparseArray<SparseArray<PointerIcon>>();
+    private static boolean sUseLargeIcons = false;
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    private final int mType;
+    private int mSystemIconResourceId;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private Bitmap mBitmap;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private float mHotSpotX;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private float mHotSpotY;
+    // The bitmaps for the additional frame of animated pointer icon. Note that the first frame
+    // will be stored in mBitmap.
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private Bitmap mBitmapFrames[];
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private int mDurationPerFrame;
+
+    /**
+     * Listener for displays lifecycle.
+     * @hide
+     */
+    private static DisplayManager.DisplayListener sDisplayListener;
+
+    private PointerIcon(int type) {
+        mType = type;
+    }
+
+    /**
+     * Gets a special pointer icon that has no bitmap.
+     *
+     * @return The null pointer icon.
+     *
+     * @see #TYPE_NULL
+     * @hide
+     */
+    public static PointerIcon getNullIcon() {
+        return gNullIcon;
+    }
+
+    /**
+     * Gets the default pointer icon.
+     *
+     * @param context The context.
+     * @return The default pointer icon.
+     *
+     * @throws IllegalArgumentException if context is null.
+     * @hide
+     */
+    public static PointerIcon getDefaultIcon(@NonNull Context context) {
+        return getSystemIcon(context, TYPE_DEFAULT);
+    }
+
+    /**
+     * Gets a system pointer icon for the given type.
+     * If typeis not recognized, returns the default pointer icon.
+     *
+     * @param context The context.
+     * @param type The pointer icon type.
+     * @return The pointer icon.
+     *
+     * @throws IllegalArgumentException if context is null.
+     */
+    public static PointerIcon getSystemIcon(@NonNull Context context, int type) {
+        if (context == null) {
+            throw new IllegalArgumentException("context must not be null");
+        }
+
+        if (type == TYPE_NULL) {
+            return gNullIcon;
+        }
+
+        if (sDisplayListener == null) {
+            registerDisplayListener(context);
+        }
+
+        final int displayId = context.getDisplayId();
+        SparseArray<PointerIcon> systemIcons = gSystemIconsByDisplay.get(displayId);
+        if (systemIcons == null) {
+            systemIcons = new SparseArray<>();
+            gSystemIconsByDisplay.put(displayId, systemIcons);
+        }
+
+        PointerIcon icon = systemIcons.get(type);
+        // Reload if not in the same display.
+        if (icon != null) {
+            return icon;
+        }
+
+        int typeIndex = getSystemIconTypeIndex(type);
+        if (typeIndex == 0) {
+            typeIndex = getSystemIconTypeIndex(TYPE_DEFAULT);
+        }
+
+        int defStyle = sUseLargeIcons ?
+                com.android.internal.R.style.LargePointer : com.android.internal.R.style.Pointer;
+        TypedArray a = context.obtainStyledAttributes(null,
+                com.android.internal.R.styleable.Pointer,
+                0, defStyle);
+        int resourceId = a.getResourceId(typeIndex, -1);
+        a.recycle();
+
+        if (resourceId == -1) {
+            Log.w(TAG, "Missing theme resources for pointer icon type " + type);
+            return type == TYPE_DEFAULT ? gNullIcon : getSystemIcon(context, TYPE_DEFAULT);
+        }
+
+        icon = new PointerIcon(type);
+        if ((resourceId & 0xff000000) == 0x01000000) {
+            icon.mSystemIconResourceId = resourceId;
+        } else {
+            icon.loadResource(context, context.getResources(), resourceId);
+        }
+        systemIcons.append(type, icon);
+        return icon;
+    }
+
+    /**
+     * Updates wheter accessibility large icons are used or not.
+     * @hide
+     */
+    public static void setUseLargeIcons(boolean use) {
+        sUseLargeIcons = use;
+        gSystemIconsByDisplay.clear();
+    }
+
+    /**
+     * Creates a custom pointer icon from the given bitmap and hotspot information.
+     *
+     * @param bitmap The bitmap for the icon.
+     * @param hotSpotX The X offset of the pointer icon hotspot in the bitmap.
+     *        Must be within the [0, bitmap.getWidth()) range.
+     * @param hotSpotY The Y offset of the pointer icon hotspot in the bitmap.
+     *        Must be within the [0, bitmap.getHeight()) range.
+     * @return A pointer icon for this bitmap.
+     *
+     * @throws IllegalArgumentException if bitmap is null, or if the x/y hotspot
+     *         parameters are invalid.
+     */
+    public static PointerIcon create(@NonNull Bitmap bitmap, float hotSpotX, float hotSpotY) {
+        if (bitmap == null) {
+            throw new IllegalArgumentException("bitmap must not be null");
+        }
+        validateHotSpot(bitmap, hotSpotX, hotSpotY);
+
+        PointerIcon icon = new PointerIcon(TYPE_CUSTOM);
+        icon.mBitmap = bitmap;
+        icon.mHotSpotX = hotSpotX;
+        icon.mHotSpotY = hotSpotY;
+        return icon;
+    }
+
+    /**
+     * Loads a custom pointer icon from an XML resource.
+     * <p>
+     * The XML resource should have the following form:
+     * <code>
+     * &lt;?xml version="1.0" encoding="utf-8"?&gt;
+     * &lt;pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+     *   android:bitmap="@drawable/my_pointer_bitmap"
+     *   android:hotSpotX="24"
+     *   android:hotSpotY="24" /&gt;
+     * </code>
+     * </p>
+     *
+     * @param resources The resources object.
+     * @param resourceId The resource id.
+     * @return The pointer icon.
+     *
+     * @throws IllegalArgumentException if resources is null.
+     * @throws Resources.NotFoundException if the resource was not found or the drawable
+     * linked in the resource was not found.
+     */
+    public static PointerIcon load(@NonNull Resources resources, @XmlRes int resourceId) {
+        if (resources == null) {
+            throw new IllegalArgumentException("resources must not be null");
+        }
+
+        PointerIcon icon = new PointerIcon(TYPE_CUSTOM);
+        icon.loadResource(null, resources, resourceId);
+        return icon;
+    }
+
+    /**
+     * Loads the bitmap and hotspot information for a pointer icon, if it is not already loaded.
+     * Returns a pointer icon (not necessarily the same instance) with the information filled in.
+     *
+     * @param context The context.
+     * @return The loaded pointer icon.
+     *
+     * @throws IllegalArgumentException if context is null.
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    public PointerIcon load(@NonNull Context context) {
+        if (context == null) {
+            throw new IllegalArgumentException("context must not be null");
+        }
+
+        if (mSystemIconResourceId == 0 || mBitmap != null) {
+            return this;
+        }
+
+        PointerIcon result = new PointerIcon(mType);
+        result.mSystemIconResourceId = mSystemIconResourceId;
+        result.loadResource(context, context.getResources(), mSystemIconResourceId);
+        return result;
+    }
+
+    /** @hide */
+    public int getType() {
+        return mType;
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<PointerIcon> CREATOR
+            = new Parcelable.Creator<PointerIcon>() {
+        public PointerIcon createFromParcel(Parcel in) {
+            int type = in.readInt();
+            if (type == TYPE_NULL) {
+                return getNullIcon();
+            }
+
+            int systemIconResourceId = in.readInt();
+            if (systemIconResourceId != 0) {
+                PointerIcon icon = new PointerIcon(type);
+                icon.mSystemIconResourceId = systemIconResourceId;
+                return icon;
+            }
+
+            Bitmap bitmap = Bitmap.CREATOR.createFromParcel(in);
+            float hotSpotX = in.readFloat();
+            float hotSpotY = in.readFloat();
+            return PointerIcon.create(bitmap, hotSpotX, hotSpotY);
+        }
+
+        public PointerIcon[] newArray(int size) {
+            return new PointerIcon[size];
+        }
+    };
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mType);
+
+        if (mType != TYPE_NULL) {
+            out.writeInt(mSystemIconResourceId);
+            if (mSystemIconResourceId == 0) {
+                mBitmap.writeToParcel(out, flags);
+                out.writeFloat(mHotSpotX);
+                out.writeFloat(mHotSpotY);
+            }
+        }
+    }
+
+    @Override
+    public boolean equals(@Nullable Object other) {
+        if (this == other) {
+            return true;
+        }
+
+        if (other == null || !(other instanceof PointerIcon)) {
+            return false;
+        }
+
+        PointerIcon otherIcon = (PointerIcon) other;
+        if (mType != otherIcon.mType
+                || mSystemIconResourceId != otherIcon.mSystemIconResourceId) {
+            return false;
+        }
+
+        if (mSystemIconResourceId == 0 && (mBitmap != otherIcon.mBitmap
+                || mHotSpotX != otherIcon.mHotSpotX
+                || mHotSpotY != otherIcon.mHotSpotY)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     *  Get the Bitmap from the Drawable.
+     *
+     *  If the Bitmap needed to be scaled up to account for density, BitmapDrawable
+     *  handles this at draw time. But this class doesn't actually draw the Bitmap;
+     *  it is just a holder for native code to access its SkBitmap. So this needs to
+     *  get a version that is scaled to account for density.
+     */
+    private Bitmap getBitmapFromDrawable(BitmapDrawable bitmapDrawable) {
+        Bitmap bitmap = bitmapDrawable.getBitmap();
+        final int scaledWidth  = bitmapDrawable.getIntrinsicWidth();
+        final int scaledHeight = bitmapDrawable.getIntrinsicHeight();
+        if (scaledWidth == bitmap.getWidth() && scaledHeight == bitmap.getHeight()) {
+            return bitmap;
+        }
+
+        Rect src = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
+        RectF dst = new RectF(0, 0, scaledWidth, scaledHeight);
+
+        Bitmap scaled = Bitmap.createBitmap(scaledWidth, scaledHeight, bitmap.getConfig());
+        Canvas canvas = new Canvas(scaled);
+        Paint paint = new Paint();
+        paint.setFilterBitmap(true);
+        canvas.drawBitmap(bitmap, src, dst, paint);
+        return scaled;
+    }
+
+    private void loadResource(Context context, Resources resources, @XmlRes int resourceId) {
+        final XmlResourceParser parser = resources.getXml(resourceId);
+        final int bitmapRes;
+        final float hotSpotX;
+        final float hotSpotY;
+        try {
+            XmlUtils.beginDocument(parser, "pointer-icon");
+
+            final TypedArray a = resources.obtainAttributes(
+                    parser, com.android.internal.R.styleable.PointerIcon);
+            bitmapRes = a.getResourceId(com.android.internal.R.styleable.PointerIcon_bitmap, 0);
+            hotSpotX = a.getDimension(com.android.internal.R.styleable.PointerIcon_hotSpotX, 0);
+            hotSpotY = a.getDimension(com.android.internal.R.styleable.PointerIcon_hotSpotY, 0);
+            a.recycle();
+        } catch (Exception ex) {
+            throw new IllegalArgumentException("Exception parsing pointer icon resource.", ex);
+        } finally {
+            parser.close();
+        }
+
+        if (bitmapRes == 0) {
+            throw new IllegalArgumentException("<pointer-icon> is missing bitmap attribute.");
+        }
+
+        Drawable drawable;
+        if (context == null) {
+            drawable = resources.getDrawable(bitmapRes);
+        } else {
+            drawable = context.getDrawable(bitmapRes);
+        }
+        if (drawable instanceof AnimationDrawable) {
+            // Extract animation frame bitmaps.
+            final AnimationDrawable animationDrawable = (AnimationDrawable) drawable;
+            final int frames = animationDrawable.getNumberOfFrames();
+            drawable = animationDrawable.getFrame(0);
+            if (frames == 1) {
+                Log.w(TAG, "Animation icon with single frame -- simply treating the first "
+                        + "frame as a normal bitmap icon.");
+            } else {
+                // Assumes they have the exact duration.
+                mDurationPerFrame = animationDrawable.getDuration(0);
+                mBitmapFrames = new Bitmap[frames - 1];
+                final int width = drawable.getIntrinsicWidth();
+                final int height = drawable.getIntrinsicHeight();
+                for (int i = 1; i < frames; ++i) {
+                    Drawable drawableFrame = animationDrawable.getFrame(i);
+                    if (!(drawableFrame instanceof BitmapDrawable)) {
+                        throw new IllegalArgumentException("Frame of an animated pointer icon "
+                                + "must refer to a bitmap drawable.");
+                    }
+                    if (drawableFrame.getIntrinsicWidth() != width ||
+                        drawableFrame.getIntrinsicHeight() != height) {
+                        throw new IllegalArgumentException("The bitmap size of " + i + "-th frame "
+                                + "is different. All frames should have the exact same size and "
+                                + "share the same hotspot.");
+                    }
+                    BitmapDrawable bitmapDrawableFrame = (BitmapDrawable) drawableFrame;
+                    mBitmapFrames[i - 1] = getBitmapFromDrawable(bitmapDrawableFrame);
+                }
+            }
+        }
+        if (!(drawable instanceof BitmapDrawable)) {
+            throw new IllegalArgumentException("<pointer-icon> bitmap attribute must "
+                    + "refer to a bitmap drawable.");
+        }
+
+        BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
+        final Bitmap bitmap = getBitmapFromDrawable(bitmapDrawable);
+        validateHotSpot(bitmap, hotSpotX, hotSpotY);
+        // Set the properties now that we have successfully loaded the icon.
+        mBitmap = bitmap;
+        mHotSpotX = hotSpotX;
+        mHotSpotY = hotSpotY;
+    }
+
+    private static void validateHotSpot(Bitmap bitmap, float hotSpotX, float hotSpotY) {
+        if (hotSpotX < 0 || hotSpotX >= bitmap.getWidth()) {
+            throw new IllegalArgumentException("x hotspot lies outside of the bitmap area");
+        }
+        if (hotSpotY < 0 || hotSpotY >= bitmap.getHeight()) {
+            throw new IllegalArgumentException("y hotspot lies outside of the bitmap area");
+        }
+    }
+
+    private static int getSystemIconTypeIndex(int type) {
+        switch (type) {
+            case TYPE_ARROW:
+                return com.android.internal.R.styleable.Pointer_pointerIconArrow;
+            case TYPE_SPOT_HOVER:
+                return com.android.internal.R.styleable.Pointer_pointerIconSpotHover;
+            case TYPE_SPOT_TOUCH:
+                return com.android.internal.R.styleable.Pointer_pointerIconSpotTouch;
+            case TYPE_SPOT_ANCHOR:
+                return com.android.internal.R.styleable.Pointer_pointerIconSpotAnchor;
+            case TYPE_HAND:
+                return com.android.internal.R.styleable.Pointer_pointerIconHand;
+            case TYPE_CONTEXT_MENU:
+                return com.android.internal.R.styleable.Pointer_pointerIconContextMenu;
+            case TYPE_HELP:
+                return com.android.internal.R.styleable.Pointer_pointerIconHelp;
+            case TYPE_WAIT:
+                return com.android.internal.R.styleable.Pointer_pointerIconWait;
+            case TYPE_CELL:
+                return com.android.internal.R.styleable.Pointer_pointerIconCell;
+            case TYPE_CROSSHAIR:
+                return com.android.internal.R.styleable.Pointer_pointerIconCrosshair;
+            case TYPE_TEXT:
+                return com.android.internal.R.styleable.Pointer_pointerIconText;
+            case TYPE_VERTICAL_TEXT:
+                return com.android.internal.R.styleable.Pointer_pointerIconVerticalText;
+            case TYPE_ALIAS:
+                return com.android.internal.R.styleable.Pointer_pointerIconAlias;
+            case TYPE_COPY:
+                return com.android.internal.R.styleable.Pointer_pointerIconCopy;
+            case TYPE_ALL_SCROLL:
+                return com.android.internal.R.styleable.Pointer_pointerIconAllScroll;
+            case TYPE_NO_DROP:
+                return com.android.internal.R.styleable.Pointer_pointerIconNodrop;
+            case TYPE_HORIZONTAL_DOUBLE_ARROW:
+                return com.android.internal.R.styleable.Pointer_pointerIconHorizontalDoubleArrow;
+            case TYPE_VERTICAL_DOUBLE_ARROW:
+                return com.android.internal.R.styleable.Pointer_pointerIconVerticalDoubleArrow;
+            case TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW:
+                return com.android.internal.R.styleable.
+                        Pointer_pointerIconTopRightDiagonalDoubleArrow;
+            case TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW:
+                return com.android.internal.R.styleable.
+                        Pointer_pointerIconTopLeftDiagonalDoubleArrow;
+            case TYPE_ZOOM_IN:
+                return com.android.internal.R.styleable.Pointer_pointerIconZoomIn;
+            case TYPE_ZOOM_OUT:
+                return com.android.internal.R.styleable.Pointer_pointerIconZoomOut;
+            case TYPE_GRAB:
+                return com.android.internal.R.styleable.Pointer_pointerIconGrab;
+            case TYPE_GRABBING:
+                return com.android.internal.R.styleable.Pointer_pointerIconGrabbing;
+            default:
+                return 0;
+        }
+    }
+
+    /**
+     * Manage system icon cache handled by display lifecycle.
+     * @param context The context.
+     */
+    private static void registerDisplayListener(@NonNull Context context) {
+        sDisplayListener = new DisplayManager.DisplayListener() {
+            @Override
+            public void onDisplayAdded(int displayId) {
+            }
+
+            @Override
+            public void onDisplayRemoved(int displayId) {
+                gSystemIconsByDisplay.remove(displayId);
+            }
+
+            @Override
+            public void onDisplayChanged(int displayId) {
+                gSystemIconsByDisplay.remove(displayId);
+            }
+        };
+
+        DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+        displayManager.registerDisplayListener(sDisplayListener, null /* handler */);
+    }
+
+}
diff --git a/android/view/PointerIcon_Delegate.java b/android/view/PointerIcon_Delegate.java
new file mode 100644
index 0000000..84c75f7
--- /dev/null
+++ b/android/view/PointerIcon_Delegate.java
@@ -0,0 +1,38 @@
+/*
+ * 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.view;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.content.Context;
+import android.content.res.Resources;
+
+public class PointerIcon_Delegate {
+
+    @LayoutlibDelegate
+    /*package*/ static void loadResource(PointerIcon icon, Context context, Resources resources,
+            int resourceId) {
+        // HACK: This bypasses the problem of having an enum resolved as a resourceId.
+        // PointerIcon would not be displayed by layoutlib anyway, so we always return the null
+        // icon.
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void registerDisplayListener(Context context) {
+        // Ignore this as we do not have a DisplayManager
+    }
+}
diff --git a/android/view/PrivacyIndicatorBounds.java b/android/view/PrivacyIndicatorBounds.java
new file mode 100644
index 0000000..4cf5eda
--- /dev/null
+++ b/android/view/PrivacyIndicatorBounds.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.view.Surface.ROTATION_0;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.os.Parcelable;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.DataClass;
+
+/**
+ * A class which manages the bounds of the privacy indicator
+ *
+ * @hide
+ */
+@DataClass(
+        genEqualsHashCode = true,
+        genConstructor = false,
+        genAidl = false,
+        genGetters = false
+)
+public class PrivacyIndicatorBounds implements Parcelable {
+
+    private final @NonNull Rect[] mStaticBounds;
+    private final int mRotation;
+
+    public PrivacyIndicatorBounds() {
+        mStaticBounds = new Rect[4];
+        mRotation = ROTATION_0;
+    }
+
+    public PrivacyIndicatorBounds(@NonNull Rect[] staticBounds, @Surface.Rotation int rotation) {
+        mStaticBounds = staticBounds;
+        mRotation = rotation;
+    }
+
+    /**
+     * Return a PrivacyIndicatorBounds with updated static bounds
+     */
+    public PrivacyIndicatorBounds updateStaticBounds(@NonNull Rect[] staticPositions) {
+        return new PrivacyIndicatorBounds(staticPositions, mRotation);
+    }
+
+    /**
+     * Update the bounds of the indicator for a specific rotation, or ROTATION_0, if the provided
+     * rotation integer isn't found
+     */
+    public PrivacyIndicatorBounds updateBoundsForRotation(@Nullable Rect bounds,
+            @Surface.Rotation int rotation) {
+        if (rotation >= mStaticBounds.length || rotation < 0) {
+            return this;
+        }
+        Rect[] newBounds = ArrayUtils.cloneOrNull(mStaticBounds);
+        newBounds[rotation] = bounds;
+        return updateStaticBounds(newBounds);
+    }
+
+    /**
+     * Return an inset PrivacyIndicatorBounds
+     */
+    public PrivacyIndicatorBounds inset(int insetLeft, int insetTop, int insetRight,
+            int insetBottom) {
+        if (insetLeft == 0 && insetTop == 0 && insetRight == 0 && insetBottom == 0) {
+            return this;
+        }
+        Rect[] insetStaticBounds = new Rect[mStaticBounds.length];
+        for (int i = 0; i < mStaticBounds.length; i++) {
+            insetStaticBounds[i] =
+                    insetRect(mStaticBounds[i], insetLeft, insetTop, insetRight, insetBottom);
+        }
+        return updateStaticBounds(insetStaticBounds);
+    }
+
+    private static Rect insetRect(Rect orig, int insetLeft, int insetTop, int insetRight,
+            int insetBottom) {
+        if (orig == null) {
+            return null;
+        }
+        int left = Math.max(0, orig.left - insetLeft);
+        int top = Math.max(0, orig.top - insetTop);
+        int right = Math.max(left, orig.right - insetRight);
+        int bottom = Math.max(top, orig.bottom - insetBottom);
+        return new Rect(left, top, right, bottom);
+    }
+
+    /**
+     * Return a PrivacyIndicatorBounds with the static position rotated.
+     */
+    public PrivacyIndicatorBounds rotate(@Surface.Rotation int rotation) {
+        if (rotation == ROTATION_0) {
+            return this;
+        }
+        return new PrivacyIndicatorBounds(mStaticBounds, rotation);
+    }
+
+    /**
+     * Returns a scaled PrivacyIndicatorBounds
+     */
+    public PrivacyIndicatorBounds scale(float scale) {
+        if (scale == 1f) {
+            return this;
+        }
+
+        Rect[] scaledStaticPos = new Rect[mStaticBounds.length];
+        for (int i = 0; i < mStaticBounds.length; i++) {
+            scaledStaticPos[i] = scaleRect(mStaticBounds[i], scale);
+        }
+        return new PrivacyIndicatorBounds(scaledStaticPos, mRotation);
+    }
+
+    private static Rect scaleRect(Rect orig, float scale) {
+        if (orig == null) {
+            return null;
+        }
+
+        Rect newRect = new Rect(orig);
+        newRect.scale(scale);
+        return newRect;
+    }
+
+    public Rect getStaticPrivacyIndicatorBounds() {
+        return mStaticBounds[mRotation];
+    }
+
+    @Override
+    public String toString() {
+        return "PrivacyIndicatorBounds {static bounds=" + getStaticPrivacyIndicatorBounds()
+                + " rotation=" + mRotation + "}";
+    }
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/PrivacyIndicatorBounds.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(PrivacyIndicatorBounds other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        PrivacyIndicatorBounds that = (PrivacyIndicatorBounds) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && java.util.Arrays.equals(mStaticBounds, that.mStaticBounds)
+                && mRotation == that.mRotation;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + java.util.Arrays.hashCode(mStaticBounds);
+        _hash = 31 * _hash + mRotation;
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeTypedArray(mStaticBounds, flags);
+        dest.writeInt(mRotation);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    protected PrivacyIndicatorBounds(@NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        Rect[] staticBounds = (Rect[]) in.createTypedArray(Rect.CREATOR);
+        int rotation = in.readInt();
+
+        this.mStaticBounds = staticBounds;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mStaticBounds);
+        this.mRotation = rotation;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<PrivacyIndicatorBounds> CREATOR
+            = new Parcelable.Creator<PrivacyIndicatorBounds>() {
+        @Override
+        public PrivacyIndicatorBounds[] newArray(int size) {
+            return new PrivacyIndicatorBounds[size];
+        }
+
+        @Override
+        public PrivacyIndicatorBounds createFromParcel(@NonNull android.os.Parcel in) {
+            return new PrivacyIndicatorBounds(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1621526273838L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/view/PrivacyIndicatorBounds.java",
+            inputSignatures = "private final @android.annotation.NonNull android.graphics.Rect[] mStaticBounds\nprivate final  int mRotation\npublic  android.view.PrivacyIndicatorBounds updateStaticBounds(android.graphics.Rect[])\npublic  android.view.PrivacyIndicatorBounds updateBoundsForRotation(android.graphics.Rect,int)\npublic  android.view.PrivacyIndicatorBounds inset(int,int,int,int)\nprivate static  android.graphics.Rect insetRect(android.graphics.Rect,int,int,int,int)\npublic  android.view.PrivacyIndicatorBounds rotate(int)\npublic  android.view.PrivacyIndicatorBounds scale(float)\nprivate static  android.graphics.Rect scaleRect(android.graphics.Rect,float)\npublic  android.graphics.Rect getStaticPrivacyIndicatorBounds()\npublic @java.lang.Override java.lang.String toString()\nclass PrivacyIndicatorBounds extends java.lang.Object implements [android.os.Parcelable]\[email protected](genEqualsHashCode=true, genConstructor=false, genAidl=false, genGetters=false)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/android/view/RectShadowPainter.java b/android/view/RectShadowPainter.java
new file mode 100644
index 0000000..88771a7
--- /dev/null
+++ b/android/view/RectShadowPainter.java
@@ -0,0 +1,205 @@
+/*
+ * 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.view;
+
+import com.android.layoutlib.bridge.impl.GcSnapshot;
+import com.android.layoutlib.bridge.impl.ResourceHelper;
+
+import android.graphics.BaseCanvas_Delegate;
+import android.graphics.Canvas;
+import android.graphics.Canvas_Delegate;
+import android.graphics.Color;
+import android.graphics.LinearGradient;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Path;
+import android.graphics.Path.FillType;
+import android.graphics.RadialGradient;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region.Op;
+import android.graphics.Shader.TileMode;
+
+import java.awt.Rectangle;
+
+/**
+ * Paints shadow for rounded rectangles. Inspiration from CardView. Couldn't use that directly,
+ * since it modifies the size of the content, that we can't do.
+ */
+public class RectShadowPainter {
+
+
+    private static final int START_COLOR = ResourceHelper.getColor("#37000000");
+    private static final int END_COLOR = ResourceHelper.getColor("#03000000");
+    private static final float PERPENDICULAR_ANGLE = 90f;
+
+    public static void paintShadow(Outline viewOutline, float elevation, Canvas canvas,
+            float alpha) {
+        Rect outline = new Rect();
+        if (!viewOutline.getRect(outline)) {
+            assert false : "Outline is not a rect shadow";
+            return;
+        }
+
+        // TODO replacing the algorithm here to create better shadow
+
+        float shadowSize = elevationToShadow(elevation);
+        int saved = modifyCanvas(canvas, shadowSize);
+        if (saved == -1) {
+            return;
+        }
+
+        float radius = viewOutline.getRadius();
+        if (radius <= 0) {
+            // We can not paint a shadow with radius 0
+            return;
+        }
+
+        try {
+            Paint cornerPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
+            cornerPaint.setStyle(Style.FILL);
+            Paint edgePaint = new Paint(cornerPaint);
+            edgePaint.setAntiAlias(false);
+            float outerArcRadius = radius + shadowSize;
+            int[] colors = {START_COLOR, START_COLOR, END_COLOR};
+            if (alpha != 1f) {
+                // Correct colors using the given component alpha
+                for (int i = 0; i < colors.length; i++) {
+                    colors[i] = Color.argb((int) (Color.alpha(colors[i]) * alpha), Color.red(colors[i]),
+                            Color.green(colors[i]), Color.blue(colors[i]));
+                }
+            }
+            cornerPaint.setShader(new RadialGradient(0, 0, outerArcRadius, colors,
+                    new float[]{0f, radius / outerArcRadius, 1f}, TileMode.CLAMP));
+            edgePaint.setShader(new LinearGradient(0, 0, -shadowSize, 0, colors[0], colors[2],
+                    TileMode.CLAMP));
+            Path path = new Path();
+            path.setFillType(FillType.EVEN_ODD);
+            // A rectangle bounding the complete shadow.
+            RectF shadowRect = new RectF(outline);
+            shadowRect.inset(-shadowSize, -shadowSize);
+            // A rectangle with edges corresponding to the straight edges of the outline.
+            RectF inset = new RectF(outline);
+            inset.inset(radius, radius);
+            // A rectangle used to represent the edge shadow.
+            RectF edgeShadowRect = new RectF();
+
+
+            // left and right sides.
+            edgeShadowRect.set(-shadowSize, 0f, 0f, inset.height());
+            // Left shadow
+            sideShadow(canvas, edgePaint, edgeShadowRect, outline.left, inset.top, 0);
+            // Right shadow
+            sideShadow(canvas, edgePaint, edgeShadowRect, outline.right, inset.bottom, 2);
+            // Top shadow
+            edgeShadowRect.set(-shadowSize, 0, 0, inset.width());
+            sideShadow(canvas, edgePaint, edgeShadowRect, inset.right, outline.top, 1);
+            // bottom shadow. This needs an inset so that blank doesn't appear when the content is
+            // moved up.
+            edgeShadowRect.set(-shadowSize, 0, shadowSize / 2f, inset.width());
+            edgePaint.setShader(new LinearGradient(edgeShadowRect.right, 0, edgeShadowRect.left, 0,
+                    colors, new float[]{0f, 1 / 3f, 1f}, TileMode.CLAMP));
+            sideShadow(canvas, edgePaint, edgeShadowRect, inset.left, outline.bottom, 3);
+
+            // Draw corners.
+            drawCorner(canvas, cornerPaint, path, inset.right, inset.bottom, outerArcRadius, 0);
+            drawCorner(canvas, cornerPaint, path, inset.left, inset.bottom, outerArcRadius, 1);
+            drawCorner(canvas, cornerPaint, path, inset.left, inset.top, outerArcRadius, 2);
+            drawCorner(canvas, cornerPaint, path, inset.right, inset.top, outerArcRadius, 3);
+        } finally {
+            canvas.restoreToCount(saved);
+        }
+    }
+
+    private static float elevationToShadow(float elevation) {
+        // The factor is chosen by eyeballing the shadow size on device and preview.
+        return elevation * 0.5f;
+    }
+
+    /**
+     * Translate canvas by half of shadow size up, so that it appears that light is coming
+     * slightly from above. Also, remove clipping, so that shadow is not clipped.
+     */
+    private static int modifyCanvas(Canvas canvas, float shadowSize) {
+        Rect clipBounds = canvas.getClipBounds();
+        if (clipBounds.isEmpty()) {
+            return -1;
+        }
+        int saved = canvas.save();
+        // Usually canvas has been translated to the top left corner of the view when this is
+        // called. So, setting a clip rect at 0,0 will clip the top left part of the shadow.
+        // Thus, we just expand in each direction by width and height of the canvas, while staying
+        // inside the original drawing region.
+        GcSnapshot snapshot = Canvas_Delegate.getDelegate(canvas).getSnapshot();
+        Rectangle originalClip = snapshot.getOriginalClip();
+        if (originalClip != null) {
+            canvas.clipRect(originalClip.x, originalClip.y, originalClip.x + originalClip.width,
+              originalClip.y + originalClip.height, Op.REPLACE);
+            canvas.clipRect(-canvas.getWidth(), -canvas.getHeight(), canvas.getWidth(),
+              canvas.getHeight(), Op.INTERSECT);
+        }
+        canvas.translate(0, shadowSize / 2f);
+        return saved;
+    }
+
+    private static void sideShadow(Canvas canvas, Paint edgePaint,
+            RectF edgeShadowRect, float dx, float dy, int rotations) {
+        if (isRectEmpty(edgeShadowRect)) {
+            return;
+        }
+        int saved = canvas.save();
+        canvas.translate(dx, dy);
+        canvas.rotate(rotations * PERPENDICULAR_ANGLE);
+        canvas.drawRect(edgeShadowRect, edgePaint);
+        canvas.restoreToCount(saved);
+    }
+
+    /**
+     * @param canvas Canvas to draw the rectangle on.
+     * @param paint Paint to use when drawing the corner.
+     * @param path A path to reuse. Prevents allocating memory for each path.
+     * @param x Center of circle, which this corner is a part of.
+     * @param y Center of circle, which this corner is a part of.
+     * @param radius radius of the arc
+     * @param rotations number of quarter rotations before starting to paint the arc.
+     */
+    private static void drawCorner(Canvas canvas, Paint paint, Path path, float x, float y,
+            float radius, int rotations) {
+        int saved = canvas.save();
+        canvas.translate(x, y);
+        path.reset();
+        path.arcTo(-radius, -radius, radius, radius, rotations * PERPENDICULAR_ANGLE,
+                PERPENDICULAR_ANGLE, false);
+        path.lineTo(0, 0);
+        path.close();
+        canvas.drawPath(path, paint);
+        canvas.restoreToCount(saved);
+    }
+
+    /**
+     * Differs from {@link RectF#isEmpty()} as this first converts the rect to int and then checks.
+     * <p/>
+     * This is required because {@link BaseCanvas_Delegate#native_drawRect(long, float, float,
+     * float,
+     * float, long)} casts the co-ordinates to int and we want to ensure that it doesn't end up
+     * drawing empty rectangles, which results in IllegalArgumentException.
+     */
+    private static boolean isRectEmpty(RectF rect) {
+        return (int) rect.left >= (int) rect.right || (int) rect.top >= (int) rect.bottom;
+    }
+}
diff --git a/android/view/RemotableViewMethod.java b/android/view/RemotableViewMethod.java
new file mode 100644
index 0000000..5eff848
--- /dev/null
+++ b/android/view/RemotableViewMethod.java
@@ -0,0 +1,41 @@
+/*
+ * 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.view;
+
+import android.annotation.TestApi;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @hide
+ * This annotation indicates that a method on a subclass of View
+ * is alllowed to be used with the {@link android.widget.RemoteViews} mechanism.
+ */
+@TestApi
+@Target({ ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RemotableViewMethod {
+    /**
+     * @return Method name which can be called on a background thread. It should have the
+     * same arguments as the original method and should return a {@link Runnable} (or null)
+     * which will be called on the UI thread.
+     */
+    String asyncImpl() default "";
+}
diff --git a/android/view/RemoteAccessibilityController.java b/android/view/RemoteAccessibilityController.java
new file mode 100644
index 0000000..bc0fab1
--- /dev/null
+++ b/android/view/RemoteAccessibilityController.java
@@ -0,0 +1,168 @@
+/*
+ * 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.view;
+
+import android.graphics.Matrix;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.accessibility.IAccessibilityEmbeddedConnection;
+
+class RemoteAccessibilityController {
+    private static final String TAG = "RemoteAccessibilityController";
+    private int mHostId;
+    private RemoteAccessibilityEmbeddedConnection mConnectionWrapper;
+    private Matrix mScreenMatrixForEmbeddedHierarchy = new Matrix();
+    private final float[] mMatrixValues = new float[9];
+    private View mHostView;
+
+    RemoteAccessibilityController(View v) {
+        mHostView = v;
+    }
+
+    private void runOnUiThread(Runnable runnable) {
+        final Handler h = mHostView.getHandler();
+        if (h != null && h.getLooper() != Looper.myLooper()) {
+            h.post(runnable);
+        } else {
+            runnable.run();
+        }
+    }
+
+    void assosciateHierarchy(IAccessibilityEmbeddedConnection connection,
+        IBinder leashToken, int hostId) {
+        mHostId = hostId;
+
+        try {
+            leashToken = connection.associateEmbeddedHierarchy(
+                leashToken, mHostId);
+            setRemoteAccessibilityEmbeddedConnection(connection, leashToken);
+        } catch (RemoteException e) {
+            Log.d(TAG, "Error in associateEmbeddedHierarchy " + e);
+        }
+    }
+
+    void disassosciateHierarchy() {
+        setRemoteAccessibilityEmbeddedConnection(null, null);
+    }
+
+    boolean alreadyAssociated(IAccessibilityEmbeddedConnection connection) {
+        if (mConnectionWrapper == null) {
+            return false;
+        }
+        return mConnectionWrapper.mConnection.equals(connection);
+    }
+
+    boolean connected() {
+      return mConnectionWrapper != null;
+    }
+
+    IBinder getLeashToken() {
+        return mConnectionWrapper.getLeashToken();
+    }
+
+    /**
+     * Wrapper of accessibility embedded connection for embedded view hierarchy.
+     */
+    private final class RemoteAccessibilityEmbeddedConnection implements IBinder.DeathRecipient {
+        private final IAccessibilityEmbeddedConnection mConnection;
+        private final IBinder mLeashToken;
+
+        RemoteAccessibilityEmbeddedConnection(IAccessibilityEmbeddedConnection connection,
+                IBinder leashToken) {
+            mConnection = connection;
+            mLeashToken = leashToken;
+        }
+
+        IAccessibilityEmbeddedConnection getConnection() {
+            return mConnection;
+        }
+
+        IBinder getLeashToken() {
+            return mLeashToken;
+        }
+
+        void linkToDeath() throws RemoteException {
+            mConnection.asBinder().linkToDeath(this, 0);
+        }
+
+        void unlinkToDeath() {
+            mConnection.asBinder().unlinkToDeath(this, 0);
+        }
+
+        @Override
+        public void binderDied() {
+            unlinkToDeath();
+            runOnUiThread(() -> {
+                if (mConnectionWrapper == this) {
+                    mConnectionWrapper = null;
+                }
+            });
+        }
+    }
+
+    private void setRemoteAccessibilityEmbeddedConnection(
+          IAccessibilityEmbeddedConnection connection, IBinder leashToken) {
+        try {
+            if (mConnectionWrapper != null) {
+                mConnectionWrapper.getConnection()
+                    .disassociateEmbeddedHierarchy();
+                mConnectionWrapper.unlinkToDeath();
+                mConnectionWrapper = null;
+            }
+            if (connection != null && leashToken != null) {
+                mConnectionWrapper =
+                    new RemoteAccessibilityEmbeddedConnection(connection, leashToken);
+                mConnectionWrapper.linkToDeath();
+            }
+        } catch (RemoteException e) {
+            Log.d(TAG, "Error while setRemoteEmbeddedConnection " + e);
+        }
+    }
+
+    private RemoteAccessibilityEmbeddedConnection getRemoteAccessibilityEmbeddedConnection() {
+        return mConnectionWrapper;
+    }
+
+    void setScreenMatrix(Matrix m) {
+        // If the screen matrix is identity or doesn't change, do nothing.
+        if (m.isIdentity() || m.equals(mScreenMatrixForEmbeddedHierarchy)) {
+            return;
+        }
+
+        try {
+            final RemoteAccessibilityEmbeddedConnection wrapper =
+                    getRemoteAccessibilityEmbeddedConnection();
+            if (wrapper == null) {
+                return;
+            }
+            m.getValues(mMatrixValues);
+            wrapper.getConnection().setScreenMatrix(mMatrixValues);
+            mScreenMatrixForEmbeddedHierarchy.set(m);
+        } catch (RemoteException e) {
+            Log.d(TAG, "Error while setScreenMatrix " + e);
+        }
+    }
+
+
+
+
+
+
+}
diff --git a/android/view/RemoteAnimationAdapter.java b/android/view/RemoteAnimationAdapter.java
new file mode 100644
index 0000000..a78036f
--- /dev/null
+++ b/android/view/RemoteAnimationAdapter.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.view;
+
+import android.app.ActivityOptions;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Object that describes how to run a remote animation.
+ * <p>
+ * A remote animation lets another app control the entire app transition. It does so by
+ * <ul>
+ *     <li>using {@link ActivityOptions#makeRemoteAnimation}</li>
+ *     <li>using {@link IWindowManager#overridePendingAppTransitionRemote}</li>
+ * </ul>
+ * to register a {@link RemoteAnimationAdapter} that describes how the animation should be run:
+ * Along some meta-data, this object contains a callback that gets invoked from window manager when
+ * the transition is ready to be started.
+ * <p>
+ * Window manager supplies a list of {@link RemoteAnimationTarget}s into the callback. Each target
+ * contains information about the activity that is animating as well as
+ * {@link RemoteAnimationTarget#leash}. The controlling app can modify the leash like any other
+ * {@link SurfaceControl}, including the possibility to synchronize updating the leash's surface
+ * properties with a frame to be drawn using
+ * {@link SurfaceControl.Transaction#deferTransactionUntil}.
+ * <p>
+ * When the animation is done, the controlling app can invoke
+ * {@link IRemoteAnimationFinishedCallback} that gets supplied into
+ * {@link IRemoteAnimationRunner#onStartAnimation}
+ *
+ * @hide
+ */
+public class RemoteAnimationAdapter implements Parcelable {
+
+    private final IRemoteAnimationRunner mRunner;
+    private final long mDuration;
+    private final long mStatusBarTransitionDelay;
+    private final boolean mChangeNeedsSnapshot;
+
+    /** @see #getCallingPid */
+    private int mCallingPid;
+    private int mCallingUid;
+
+    /**
+     * @param runner The interface that gets notified when we actually need to start the animation.
+     * @param duration The duration of the animation.
+     * @param changeNeedsSnapshot For change transitions, whether this should create a snapshot by
+     *                            screenshotting the task.
+     * @param statusBarTransitionDelay The desired delay for all visual animations in the
+     *        status bar caused by this app animation in millis.
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public RemoteAnimationAdapter(IRemoteAnimationRunner runner, long duration,
+            long statusBarTransitionDelay, boolean changeNeedsSnapshot) {
+        mRunner = runner;
+        mDuration = duration;
+        mChangeNeedsSnapshot = changeNeedsSnapshot;
+        mStatusBarTransitionDelay = statusBarTransitionDelay;
+    }
+
+    @UnsupportedAppUsage
+    public RemoteAnimationAdapter(IRemoteAnimationRunner runner, long duration,
+            long statusBarTransitionDelay) {
+        this(runner, duration, statusBarTransitionDelay, false /* changeNeedsSnapshot */);
+    }
+
+    public RemoteAnimationAdapter(Parcel in) {
+        mRunner = IRemoteAnimationRunner.Stub.asInterface(in.readStrongBinder());
+        mDuration = in.readLong();
+        mStatusBarTransitionDelay = in.readLong();
+        mChangeNeedsSnapshot = in.readBoolean();
+    }
+
+    public IRemoteAnimationRunner getRunner() {
+        return mRunner;
+    }
+
+    public long getDuration() {
+        return mDuration;
+    }
+
+    public long getStatusBarTransitionDelay() {
+        return mStatusBarTransitionDelay;
+    }
+
+    public boolean getChangeNeedsSnapshot() {
+        return mChangeNeedsSnapshot;
+    }
+
+    /**
+     * To be called by system_server to keep track which pid and uid is running this animation.
+     */
+    public void setCallingPidUid(int pid, int uid) {
+        mCallingPid = pid;
+        mCallingUid = uid;
+    }
+
+    /**
+     * @return The pid of the process running the animation.
+     */
+    public int getCallingPid() {
+        return mCallingPid;
+    }
+
+    /**
+     * @return The uid of the process running the animation.
+     */
+    public int getCallingUid() {
+        return mCallingUid;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStrongInterface(mRunner);
+        dest.writeLong(mDuration);
+        dest.writeLong(mStatusBarTransitionDelay);
+        dest.writeBoolean(mChangeNeedsSnapshot);
+    }
+
+    public static final @android.annotation.NonNull Creator<RemoteAnimationAdapter> CREATOR
+            = new Creator<RemoteAnimationAdapter>() {
+        public RemoteAnimationAdapter createFromParcel(Parcel in) {
+            return new RemoteAnimationAdapter(in);
+        }
+
+        public RemoteAnimationAdapter[] newArray(int size) {
+            return new RemoteAnimationAdapter[size];
+        }
+    };
+}
diff --git a/android/view/RemoteAnimationDefinition.java b/android/view/RemoteAnimationDefinition.java
new file mode 100644
index 0000000..ea97995
--- /dev/null
+++ b/android/view/RemoteAnimationDefinition.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.view;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+
+import android.annotation.Nullable;
+import android.app.WindowConfiguration.ActivityType;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.WindowManager.TransitionOldType;
+
+/**
+ * Defines which animation types should be overridden by which remote animation.
+ *
+ * @hide
+ */
+public class RemoteAnimationDefinition implements Parcelable {
+
+    private final SparseArray<RemoteAnimationAdapterEntry> mTransitionAnimationMap;
+
+    @UnsupportedAppUsage
+    public RemoteAnimationDefinition() {
+        mTransitionAnimationMap = new SparseArray<>();
+    }
+
+    /**
+     * Registers a remote animation for a specific transition.
+     *
+     * @param transition The old transition type. Must be one of WindowManager.TRANSIT_OLD_* values.
+     * @param activityTypeFilter The remote animation only runs if an activity with type of this
+     *                           parameter is involved in the transition.
+     * @param adapter The adapter that described how to run the remote animation.
+     */
+    @UnsupportedAppUsage
+    public void addRemoteAnimation(@TransitionOldType int transition,
+            @ActivityType int activityTypeFilter, RemoteAnimationAdapter adapter) {
+        mTransitionAnimationMap.put(transition,
+                new RemoteAnimationAdapterEntry(adapter, activityTypeFilter));
+    }
+
+    /**
+     * Registers a remote animation for a specific transition without defining an activity type
+     * filter.
+     *
+     * @param transition The old transition type. Must be one of WindowManager.TRANSIT_OLD_* values.
+     * @param adapter The adapter that described how to run the remote animation.
+     */
+    @UnsupportedAppUsage
+    public void addRemoteAnimation(@TransitionOldType int transition,
+            RemoteAnimationAdapter adapter) {
+        addRemoteAnimation(transition, ACTIVITY_TYPE_UNDEFINED, adapter);
+    }
+
+    /**
+     * Checks whether a remote animation for specific transition is defined.
+     *
+     * @param transition The old transition type. Must be one of WindowManager.TRANSIT_OLD_* values.
+     * @param activityTypes The set of activity types of activities that are involved in the
+     *                      transition. Will be used for filtering.
+     * @return Whether this definition has defined a remote animation for the specified transition.
+     */
+    public boolean hasTransition(@TransitionOldType int transition,
+            ArraySet<Integer> activityTypes) {
+        return getAdapter(transition, activityTypes) != null;
+    }
+
+    /**
+     * Retrieves the remote animation for a specific transition.
+     *
+     * @param transition The old transition type. Must be one of WindowManager.TRANSIT_OLD_* values.
+     * @param activityTypes The set of activity types of activities that are involved in the
+     *                      transition. Will be used for filtering.
+     * @return The remote animation adapter for the specified transition.
+     */
+    public @Nullable RemoteAnimationAdapter getAdapter(@TransitionOldType int transition,
+            ArraySet<Integer> activityTypes) {
+        final RemoteAnimationAdapterEntry entry = mTransitionAnimationMap.get(transition);
+        if (entry == null) {
+            return null;
+        }
+        if (entry.activityTypeFilter == ACTIVITY_TYPE_UNDEFINED
+                || activityTypes.contains(entry.activityTypeFilter)) {
+            return entry.adapter;
+        } else {
+            return null;
+        }
+    }
+
+    public RemoteAnimationDefinition(Parcel in) {
+        final int size = in.readInt();
+        mTransitionAnimationMap = new SparseArray<>(size);
+        for (int i = 0; i < size; i++) {
+            final int transition = in.readInt();
+            final RemoteAnimationAdapterEntry entry = in.readTypedObject(
+                    RemoteAnimationAdapterEntry.CREATOR);
+            mTransitionAnimationMap.put(transition, entry);
+        }
+    }
+
+    /**
+     * To be called by system_server to keep track which pid is running the remote animations inside
+     * this definition.
+     */
+    public void setCallingPidUid(int pid, int uid) {
+        for (int i = mTransitionAnimationMap.size() - 1; i >= 0; i--) {
+            mTransitionAnimationMap.valueAt(i).adapter.setCallingPidUid(pid, uid);
+        }
+    }
+
+    /**
+     * Links the death of the runner to the provided death recipient.
+     */
+    public void linkToDeath(IBinder.DeathRecipient deathRecipient) {
+        try {
+            for (int i = 0; i < mTransitionAnimationMap.size(); i++) {
+                mTransitionAnimationMap.valueAt(i).adapter.getRunner().asBinder()
+                        .linkToDeath(deathRecipient, 0 /* flags */);
+            }
+        } catch (RemoteException e) {
+            Slog.e("RemoteAnimationDefinition", "Failed to link to death recipient");
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        final int size = mTransitionAnimationMap.size();
+        dest.writeInt(size);
+        for (int i = 0; i < size; i++) {
+            dest.writeInt(mTransitionAnimationMap.keyAt(i));
+            dest.writeTypedObject(mTransitionAnimationMap.valueAt(i), flags);
+        }
+    }
+
+    public static final @android.annotation.NonNull Creator<RemoteAnimationDefinition> CREATOR =
+            new Creator<RemoteAnimationDefinition>() {
+        public RemoteAnimationDefinition createFromParcel(Parcel in) {
+            return new RemoteAnimationDefinition(in);
+        }
+
+        public RemoteAnimationDefinition[] newArray(int size) {
+            return new RemoteAnimationDefinition[size];
+        }
+    };
+
+    private static class RemoteAnimationAdapterEntry implements Parcelable {
+
+        final RemoteAnimationAdapter adapter;
+
+        /**
+         * Only run the transition if one of the activities matches the filter.
+         * {@link WindowConfiguration.ACTIVITY_TYPE_UNDEFINED} means no filter
+         */
+        @ActivityType final int activityTypeFilter;
+
+        RemoteAnimationAdapterEntry(RemoteAnimationAdapter adapter, int activityTypeFilter) {
+            this.adapter = adapter;
+            this.activityTypeFilter = activityTypeFilter;
+        }
+
+        private RemoteAnimationAdapterEntry(Parcel in) {
+            adapter = in.readTypedObject(RemoteAnimationAdapter.CREATOR);
+            activityTypeFilter = in.readInt();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeTypedObject(adapter, flags);
+            dest.writeInt(activityTypeFilter);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        private static final @android.annotation.NonNull Creator<RemoteAnimationAdapterEntry> CREATOR
+                = new Creator<RemoteAnimationAdapterEntry>() {
+
+            @Override
+            public RemoteAnimationAdapterEntry createFromParcel(Parcel in) {
+                return new RemoteAnimationAdapterEntry(in);
+            }
+
+            @Override
+            public RemoteAnimationAdapterEntry[] newArray(int size) {
+                return new RemoteAnimationAdapterEntry[size];
+            }
+        };
+    }
+}
diff --git a/android/view/RemoteAnimationTarget.java b/android/view/RemoteAnimationTarget.java
new file mode 100644
index 0000000..cdc099b
--- /dev/null
+++ b/android/view/RemoteAnimationTarget.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.view;
+
+import static android.graphics.GraphicsProtos.dumpPointProto;
+import static android.view.RemoteAnimationTargetProto.CLIP_RECT;
+import static android.view.RemoteAnimationTargetProto.CONTENT_INSETS;
+import static android.view.RemoteAnimationTargetProto.IS_TRANSLUCENT;
+import static android.view.RemoteAnimationTargetProto.LEASH;
+import static android.view.RemoteAnimationTargetProto.LOCAL_BOUNDS;
+import static android.view.RemoteAnimationTargetProto.MODE;
+import static android.view.RemoteAnimationTargetProto.POSITION;
+import static android.view.RemoteAnimationTargetProto.PREFIX_ORDER_INDEX;
+import static android.view.RemoteAnimationTargetProto.SCREEN_SPACE_BOUNDS;
+import static android.view.RemoteAnimationTargetProto.SOURCE_CONTAINER_BOUNDS;
+import static android.view.RemoteAnimationTargetProto.START_BOUNDS;
+import static android.view.RemoteAnimationTargetProto.START_LEASH;
+import static android.view.RemoteAnimationTargetProto.TASK_ID;
+import static android.view.RemoteAnimationTargetProto.WINDOW_CONFIGURATION;
+import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
+
+import android.annotation.IntDef;
+import android.app.ActivityManager;
+import android.app.TaskInfo;
+import android.app.WindowConfiguration;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.proto.ProtoOutputStream;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Describes an activity to be animated as part of a remote animation.
+ *
+ * @hide
+ */
+public class RemoteAnimationTarget implements Parcelable {
+
+    /**
+     * The app is in the set of opening apps of this transition.
+     */
+    public static final int MODE_OPENING = 0;
+
+    /**
+     * The app is in the set of closing apps of this transition.
+     */
+    public static final int MODE_CLOSING = 1;
+
+    /**
+     * The app is in the set of resizing apps (eg. mode change) of this transition.
+     */
+    public static final int MODE_CHANGING = 2;
+
+    @IntDef(prefix = { "MODE_" }, value = {
+            MODE_OPENING,
+            MODE_CLOSING,
+            MODE_CHANGING
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Mode {}
+
+    /**
+     * The {@link Mode} to describe whether this app is opening or closing.
+     */
+    @UnsupportedAppUsage
+    public final @Mode int mode;
+
+    /**
+     * The id of the task this app belongs to.
+     */
+    @UnsupportedAppUsage
+    public final int taskId;
+
+    /**
+     * The {@link SurfaceControl} object to actually control the transform of the app.
+     */
+    @UnsupportedAppUsage
+    public final SurfaceControl leash;
+
+    /**
+     * The {@link SurfaceControl} for the starting state of a target if this transition is
+     * MODE_CHANGING, {@code null)} otherwise. This is relative to the app window.
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public final SurfaceControl startLeash;
+
+    /**
+     * Whether the app is translucent and may reveal apps behind.
+     */
+    @UnsupportedAppUsage
+    public final boolean isTranslucent;
+
+    /**
+     * The clip rect window manager applies when clipping the app's main surface in screen space
+     * coordinates. This is just a hint to the animation runner: If running a clip-rect animation,
+     * anything that extends beyond these bounds will not have any effect. This implies that any
+     * clip-rect animation should likely stop at these bounds.
+     */
+    @UnsupportedAppUsage
+    public final Rect clipRect;
+
+    /**
+     * The insets of the main app window.
+     */
+    @UnsupportedAppUsage
+    public final Rect contentInsets;
+
+    /**
+     * The index of the element in the tree in prefix order. This should be used for z-layering
+     * to preserve original z-layer order in the hierarchy tree assuming no "boosting" needs to
+     * happen.
+     */
+    @UnsupportedAppUsage
+    public final int prefixOrderIndex;
+
+    /**
+     * The source position of the app, in screen spaces coordinates. If the position of the leash
+     * is modified from the controlling app, any animation transform needs to be offset by this
+     * amount.
+     * @deprecated Use {@link #localBounds} instead.
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public final Point position;
+
+    /**
+     * Bounds of the target relative to its parent.
+     * When the app target animating on its parent, we need to use the local coordinates relative to
+     * its parent with {@code localBounds.left} & {@code localBounds.top} rather than using
+     * {@code position} in screen coordinates.
+     */
+    public final Rect localBounds;
+
+    /**
+     * The bounds of the source container the app lives in, in screen space coordinates. If the crop
+     * of the leash is modified from the controlling app, it needs to take the source container
+     * bounds into account when calculating the crop.
+     * @deprecated Renamed to {@link #screenSpaceBounds}
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public final Rect sourceContainerBounds;
+
+    /**
+     * Bounds of the target relative to the screen. If the crop of the leash is modified from the
+     * controlling app, it needs to take the screen space bounds into account when calculating the
+     * crop.
+     */
+    public final Rect screenSpaceBounds;
+
+    /**
+     * The starting bounds of the source container in screen space coordinates. This is {@code null}
+     * if the animation target isn't MODE_CHANGING. Since this is the starting bounds, it's size
+     * should be equivalent to the size of the starting thumbnail. Note that sourceContainerBounds
+     * is the end bounds of a change transition.
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public final Rect startBounds;
+
+    /**
+     * The window configuration for the target.
+     */
+    @UnsupportedAppUsage
+    public final WindowConfiguration windowConfiguration;
+
+    /**
+     * Whether the task is not presented in Recents UI.
+     */
+    @UnsupportedAppUsage
+    public boolean isNotInRecents;
+
+    /**
+     * {@link TaskInfo} to allow the controller to identify information about the task.
+     *
+     * TODO: add this to proto dump
+     */
+    public ActivityManager.RunningTaskInfo taskInfo;
+
+    /**
+     * The {@link android.view.WindowManager.LayoutParams.WindowType} of this window. It's only used
+     * for non-app window.
+     */
+    public final @WindowManager.LayoutParams.WindowType int windowType;
+
+    public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent,
+            Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position,
+            Rect localBounds, Rect screenSpaceBounds,
+            WindowConfiguration windowConfig, boolean isNotInRecents,
+            SurfaceControl startLeash, Rect startBounds, ActivityManager.RunningTaskInfo taskInfo) {
+        this(taskId, mode, leash, isTranslucent, clipRect, contentInsets, prefixOrderIndex,
+                position, localBounds, screenSpaceBounds, windowConfig, isNotInRecents, startLeash,
+                startBounds, taskInfo, INVALID_WINDOW_TYPE);
+    }
+
+    public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent,
+            Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position,
+            Rect localBounds, Rect screenSpaceBounds,
+            WindowConfiguration windowConfig, boolean isNotInRecents,
+            SurfaceControl startLeash, Rect startBounds,
+            ActivityManager.RunningTaskInfo taskInfo,
+            @WindowManager.LayoutParams.WindowType int windowType) {
+        this.mode = mode;
+        this.taskId = taskId;
+        this.leash = leash;
+        this.isTranslucent = isTranslucent;
+        this.clipRect = new Rect(clipRect);
+        this.contentInsets = new Rect(contentInsets);
+        this.prefixOrderIndex = prefixOrderIndex;
+        this.position = new Point(position);
+        this.localBounds = new Rect(localBounds);
+        this.sourceContainerBounds = new Rect(screenSpaceBounds);
+        this.screenSpaceBounds = new Rect(screenSpaceBounds);
+        this.windowConfiguration = windowConfig;
+        this.isNotInRecents = isNotInRecents;
+        this.startLeash = startLeash;
+        this.startBounds = startBounds == null ? null : new Rect(startBounds);
+        this.taskInfo = taskInfo;
+        this.windowType = windowType;
+    }
+
+    public RemoteAnimationTarget(Parcel in) {
+        taskId = in.readInt();
+        mode = in.readInt();
+        leash = in.readTypedObject(SurfaceControl.CREATOR);
+        isTranslucent = in.readBoolean();
+        clipRect = in.readTypedObject(Rect.CREATOR);
+        contentInsets = in.readTypedObject(Rect.CREATOR);
+        prefixOrderIndex = in.readInt();
+        position = in.readTypedObject(Point.CREATOR);
+        localBounds = in.readTypedObject(Rect.CREATOR);
+        sourceContainerBounds = in.readTypedObject(Rect.CREATOR);
+        screenSpaceBounds = in.readTypedObject(Rect.CREATOR);
+        windowConfiguration = in.readTypedObject(WindowConfiguration.CREATOR);
+        isNotInRecents = in.readBoolean();
+        startLeash = in.readTypedObject(SurfaceControl.CREATOR);
+        startBounds = in.readTypedObject(Rect.CREATOR);
+        taskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
+        windowType = in.readInt();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(taskId);
+        dest.writeInt(mode);
+        dest.writeTypedObject(leash, 0 /* flags */);
+        dest.writeBoolean(isTranslucent);
+        dest.writeTypedObject(clipRect, 0 /* flags */);
+        dest.writeTypedObject(contentInsets, 0 /* flags */);
+        dest.writeInt(prefixOrderIndex);
+        dest.writeTypedObject(position, 0 /* flags */);
+        dest.writeTypedObject(localBounds, 0 /* flags */);
+        dest.writeTypedObject(sourceContainerBounds, 0 /* flags */);
+        dest.writeTypedObject(screenSpaceBounds, 0 /* flags */);
+        dest.writeTypedObject(windowConfiguration, 0 /* flags */);
+        dest.writeBoolean(isNotInRecents);
+        dest.writeTypedObject(startLeash, 0 /* flags */);
+        dest.writeTypedObject(startBounds, 0 /* flags */);
+        dest.writeTypedObject(taskInfo, 0 /* flags */);
+        dest.writeInt(windowType);
+    }
+
+    public void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix); pw.print("mode="); pw.print(mode);
+        pw.print(" taskId="); pw.print(taskId);
+        pw.print(" isTranslucent="); pw.print(isTranslucent);
+        pw.print(" clipRect="); clipRect.printShortString(pw);
+        pw.print(" contentInsets="); contentInsets.printShortString(pw);
+        pw.print(" prefixOrderIndex="); pw.print(prefixOrderIndex);
+        pw.print(" position="); printPoint(position, pw);
+        pw.print(" sourceContainerBounds="); sourceContainerBounds.printShortString(pw);
+        pw.print(" screenSpaceBounds="); screenSpaceBounds.printShortString(pw);
+        pw.print(" localBounds="); localBounds.printShortString(pw);
+        pw.println();
+        pw.print(prefix); pw.print("windowConfiguration="); pw.println(windowConfiguration);
+        pw.print(prefix); pw.print("leash="); pw.println(leash);
+        pw.print(prefix); pw.print("taskInfo="); pw.println(taskInfo);
+        pw.print(prefix); pw.print("windowType="); pw.print(windowType);
+    }
+
+    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(TASK_ID, taskId);
+        proto.write(MODE, mode);
+        leash.dumpDebug(proto, LEASH);
+        proto.write(IS_TRANSLUCENT, isTranslucent);
+        clipRect.dumpDebug(proto, CLIP_RECT);
+        contentInsets.dumpDebug(proto, CONTENT_INSETS);
+        proto.write(PREFIX_ORDER_INDEX, prefixOrderIndex);
+        dumpPointProto(position, proto, POSITION);
+        sourceContainerBounds.dumpDebug(proto, SOURCE_CONTAINER_BOUNDS);
+        screenSpaceBounds.dumpDebug(proto, SCREEN_SPACE_BOUNDS);
+        localBounds.dumpDebug(proto, LOCAL_BOUNDS);
+        windowConfiguration.dumpDebug(proto, WINDOW_CONFIGURATION);
+        if (startLeash != null) {
+            startLeash.dumpDebug(proto, START_LEASH);
+        }
+        if (startBounds != null) {
+            startBounds.dumpDebug(proto, START_BOUNDS);
+        }
+        proto.end(token);
+    }
+
+    private static void printPoint(Point p, PrintWriter pw) {
+        pw.print("["); pw.print(p.x); pw.print(","); pw.print(p.y); pw.print("]");
+    }
+
+    public static final @android.annotation.NonNull Creator<RemoteAnimationTarget> CREATOR
+            = new Creator<RemoteAnimationTarget>() {
+        public RemoteAnimationTarget createFromParcel(Parcel in) {
+            return new RemoteAnimationTarget(in);
+        }
+
+        public RemoteAnimationTarget[] newArray(int size) {
+            return new RemoteAnimationTarget[size];
+        }
+    };
+}
diff --git a/android/view/RenderNodeAnimator.java b/android/view/RenderNodeAnimator.java
new file mode 100644
index 0000000..31fdfb7
--- /dev/null
+++ b/android/view/RenderNodeAnimator.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.NonNull;
+import android.graphics.CanvasProperty;
+import android.graphics.Paint;
+
+/**
+ * @hide
+ */
+public class RenderNodeAnimator extends android.graphics.animation.RenderNodeAnimator
+        implements android.graphics.animation.RenderNodeAnimator.ViewListener {
+
+    private View mViewTarget;
+
+    public RenderNodeAnimator(int property, float finalValue) {
+        super(property, finalValue);
+    }
+
+    public RenderNodeAnimator(CanvasProperty<Float> property, float finalValue) {
+        super(property, finalValue);
+    }
+
+    public RenderNodeAnimator(CanvasProperty<Paint> property, int paintField, float finalValue) {
+        super(property, paintField, finalValue);
+    }
+
+    public RenderNodeAnimator(int x, int y, float startRadius, float endRadius) {
+        super(x, y, startRadius, endRadius);
+    }
+
+    @Override
+    public void onAlphaAnimationStart(float finalAlpha) {
+        // Alpha is a special snowflake that has the canonical value stored
+        // in mTransformationInfo instead of in RenderNode, so we need to update
+        // it with the final value here.
+        mViewTarget.ensureTransformationInfo();
+        mViewTarget.setAlphaInternal(finalAlpha);
+    }
+
+    @Override
+    public void invalidateParent(boolean forceRedraw) {
+        mViewTarget.invalidateViewProperty(true, false);
+    }
+
+    /** @hide */
+    public void setTarget(@NonNull View view) {
+        mViewTarget = view;
+        setViewListener(this);
+        setTarget(mViewTarget.mRenderNode);
+    }
+}
diff --git a/android/view/RoundScrollbarRenderer.java b/android/view/RoundScrollbarRenderer.java
new file mode 100644
index 0000000..df9e23e
--- /dev/null
+++ b/android/view/RoundScrollbarRenderer.java
@@ -0,0 +1,130 @@
+/*
+ * 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.view;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+/**
+ * Helper class for drawing round scroll bars on round Wear devices.
+ */
+class RoundScrollbarRenderer {
+    // The range of the scrollbar position represented as an angle in degrees.
+    private static final int SCROLLBAR_ANGLE_RANGE = 90;
+    private static final int MAX_SCROLLBAR_ANGLE_SWIPE = 16;
+    private static final int MIN_SCROLLBAR_ANGLE_SWIPE = 6;
+    private static final float WIDTH_PERCENTAGE = 0.02f;
+    private static final int DEFAULT_THUMB_COLOR = 0xFFE8EAED;
+    private static final int DEFAULT_TRACK_COLOR = 0x4CFFFFFF;
+
+    private final Paint mThumbPaint = new Paint();
+    private final Paint mTrackPaint = new Paint();
+    private final RectF mRect = new RectF();
+    private final View mParent;
+    private final int mMaskThickness;
+
+    public RoundScrollbarRenderer(View parent) {
+        // Paints for the round scrollbar.
+        // Set up the thumb paint
+        mThumbPaint.setAntiAlias(true);
+        mThumbPaint.setStrokeCap(Paint.Cap.ROUND);
+        mThumbPaint.setStyle(Paint.Style.STROKE);
+
+        // Set up the track paint
+        mTrackPaint.setAntiAlias(true);
+        mTrackPaint.setStrokeCap(Paint.Cap.ROUND);
+        mTrackPaint.setStyle(Paint.Style.STROKE);
+
+        mParent = parent;
+
+        // Fetch the resource indicating the thickness of CircularDisplayMask, rounding in the same
+        // way WindowManagerService.showCircularMask does. The scroll bar is inset by this amount so
+        // that it doesn't get clipped.
+        mMaskThickness = parent.getContext().getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.circular_display_mask_thickness);
+    }
+
+    public void drawRoundScrollbars(Canvas canvas, float alpha, Rect bounds) {
+        if (alpha == 0) {
+            return;
+        }
+        // Get information about the current scroll state of the parent view.
+        float maxScroll = mParent.computeVerticalScrollRange();
+        float scrollExtent = mParent.computeVerticalScrollExtent();
+        if (scrollExtent <= 0 || maxScroll <= scrollExtent) {
+            return;
+        }
+        float currentScroll = Math.max(0, mParent.computeVerticalScrollOffset());
+        float linearThumbLength = mParent.computeVerticalScrollExtent();
+        float thumbWidth = mParent.getWidth() * WIDTH_PERCENTAGE;
+        mThumbPaint.setStrokeWidth(thumbWidth);
+        mTrackPaint.setStrokeWidth(thumbWidth);
+
+        setThumbColor(applyAlpha(DEFAULT_THUMB_COLOR, alpha));
+        setTrackColor(applyAlpha(DEFAULT_TRACK_COLOR, alpha));
+
+        // Normalize the sweep angle for the scroll bar.
+        float sweepAngle = (linearThumbLength / maxScroll) * SCROLLBAR_ANGLE_RANGE;
+        sweepAngle = clamp(sweepAngle, MIN_SCROLLBAR_ANGLE_SWIPE, MAX_SCROLLBAR_ANGLE_SWIPE);
+        // Normalize the start angle so that it falls on the track.
+        float startAngle = (currentScroll * (SCROLLBAR_ANGLE_RANGE - sweepAngle))
+                / (maxScroll - linearThumbLength) - SCROLLBAR_ANGLE_RANGE / 2;
+        startAngle = clamp(startAngle, -SCROLLBAR_ANGLE_RANGE / 2,
+                SCROLLBAR_ANGLE_RANGE / 2 - sweepAngle);
+
+        // Draw the track and the thumb.
+        float inset = thumbWidth / 2 + mMaskThickness;
+        mRect.set(
+                bounds.left + inset,
+                bounds.top + inset,
+                bounds.right - inset,
+                bounds.bottom - inset);
+        canvas.drawArc(mRect, -SCROLLBAR_ANGLE_RANGE / 2, SCROLLBAR_ANGLE_RANGE, false,
+                mTrackPaint);
+        canvas.drawArc(mRect, startAngle, sweepAngle, false, mThumbPaint);
+    }
+
+    private static float clamp(float val, float min, float max) {
+        if (val < min) {
+            return min;
+        } else if (val > max) {
+            return max;
+        } else {
+            return val;
+        }
+    }
+
+    private static int applyAlpha(int color, float alpha) {
+        int alphaByte = (int) (Color.alpha(color) * alpha);
+        return Color.argb(alphaByte, Color.red(color), Color.green(color), Color.blue(color));
+    }
+
+    private void setThumbColor(int thumbColor) {
+        if (mThumbPaint.getColor() != thumbColor) {
+            mThumbPaint.setColor(thumbColor);
+        }
+    }
+
+    private void setTrackColor(int trackColor) {
+        if (mTrackPaint.getColor() != trackColor) {
+            mTrackPaint.setColor(trackColor);
+        }
+    }
+}
diff --git a/android/view/RoundedCorner.java b/android/view/RoundedCorner.java
new file mode 100644
index 0000000..d10fc44
--- /dev/null
+++ b/android/view/RoundedCorner.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.graphics.Point;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Represents a rounded corner of the display.
+ * <p>
+ * <img src="{@docRoot}reference/android/images/rounded_corner/rounded-corner-info.png" height="120"
+ * alt="A figure to describe what the rounded corner radius and the center point are. "/>
+ * </p>
+ *
+ * <p>Note: The rounded corner formed by the radius and the center is an approximation.</p>
+ *
+ * <p>{@link RoundedCorner} is immutable.</p>
+ */
+public final class RoundedCorner implements Parcelable {
+
+    /**
+     * The rounded corner is at the top-left of the screen.
+     */
+    public static final int POSITION_TOP_LEFT = 0;
+    /**
+     * The rounded corner is at the top-right of the screen.
+     */
+    public static final int POSITION_TOP_RIGHT = 1;
+    /**
+     * The rounded corner is at the bottom-right of the screen.
+     */
+    public static final int POSITION_BOTTOM_RIGHT = 2;
+    /**
+     * The rounded corner is at the bottom-left of the screen.
+     */
+    public static final int POSITION_BOTTOM_LEFT = 3;
+
+    /**
+     * @hide
+     */
+    @IntDef(prefix = { "POSITION_" }, value = {
+            POSITION_TOP_LEFT,
+            POSITION_TOP_RIGHT,
+            POSITION_BOTTOM_RIGHT,
+            POSITION_BOTTOM_LEFT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Position {}
+
+    private final @Position int mPosition;
+    private final int mRadius;
+    @NonNull
+    private final Point mCenter;
+
+    /**
+     * Creates an empty {@link RoundedCorner} on the given position.
+     * @hide
+     */
+    @VisibleForTesting
+    public RoundedCorner(@Position int position) {
+        mPosition = position;
+        mRadius = 0;
+        mCenter = new Point(0, 0);
+    }
+
+    /**
+     * Creates a {@link RoundedCorner}.
+     *
+     * <p>Note that this is only useful for tests. For production code, developers should always
+     * use a {@link RoundedCorner} obtained from the system via
+     * {@link WindowInsets#getRoundedCorner} or {@link Display#getRoundedCorner}.</p>
+     *
+     * @param position the position of the rounded corner.
+     * @param radius the radius of the rounded corner.
+     * @param centerX the x of center point of the rounded corner.
+     * @param centerY the y of center point of the rounded corner.
+     *
+     */
+    public RoundedCorner(@Position int position, int radius, int centerX,
+            int centerY) {
+        mPosition = position;
+        mRadius = radius;
+        mCenter = new Point(centerX, centerY);
+    }
+
+    /**
+     * Creates a {@link RoundedCorner} from a passed in {@link RoundedCorner}.
+     *
+     * @hide
+     */
+    RoundedCorner(RoundedCorner rc) {
+        mPosition = rc.getPosition();
+        mRadius = rc.getRadius();
+        mCenter = new Point(rc.getCenter());
+    }
+
+    /**
+     * Get the position of this {@link RoundedCorner}.
+     *
+     * @see #POSITION_TOP_LEFT
+     * @see #POSITION_TOP_RIGHT
+     * @see #POSITION_BOTTOM_RIGHT
+     * @see #POSITION_BOTTOM_LEFT
+     */
+    public @Position int getPosition() {
+        return mPosition;
+    }
+
+    /**
+     * Returns the radius of a quarter circle approximation of this {@link RoundedCorner}.
+     *
+     * @return the rounded corner radius of this {@link RoundedCorner}. Returns 0 if there is no
+     * rounded corner.
+     */
+    public int getRadius() {
+        return mRadius;
+    }
+
+    /**
+     * Returns the circle center of a quarter circle approximation of this {@link RoundedCorner}.
+     *
+     * @return the center point of this {@link RoundedCorner} in the application's coordinate.
+     */
+    @NonNull
+    public Point getCenter() {
+        return new Point(mCenter);
+    }
+
+    /**
+     * Checks whether this {@link RoundedCorner} exists and is inside the application's bounds.
+     *
+     * @return {@code false} if there is a rounded corner and is contained in the application's
+     *                       bounds. Otherwise return {@code true}.
+     *
+     * @hide
+     */
+    public boolean isEmpty() {
+        return mRadius == 0 || mCenter.x <= 0 || mCenter.y <= 0;
+    }
+
+    private String getPositionString(@Position int position) {
+        switch (position) {
+            case POSITION_TOP_LEFT:
+                return "TopLeft";
+            case POSITION_TOP_RIGHT:
+                return "TopRight";
+            case POSITION_BOTTOM_RIGHT:
+                return "BottomRight";
+            case POSITION_BOTTOM_LEFT:
+                return "BottomLeft";
+            default:
+                return "Invalid";
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o instanceof RoundedCorner) {
+            RoundedCorner r = (RoundedCorner) o;
+            return mPosition == r.mPosition && mRadius == r.mRadius
+                    && mCenter.equals(r.mCenter);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 0;
+        result = 31 * result + mPosition;
+        result = 31 * result + mRadius;
+        result = 31 * result + mCenter.hashCode();
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "RoundedCorner{"
+                + "position=" + getPositionString(mPosition)
+                + ", radius=" + mRadius
+                + ", center=" + mCenter
+                + '}';
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeInt(mPosition);
+        out.writeInt(mRadius);
+        out.writeInt(mCenter.x);
+        out.writeInt(mCenter.y);
+    }
+
+    public static final @NonNull Creator<RoundedCorner> CREATOR = new Creator<RoundedCorner>() {
+        @Override
+        public RoundedCorner createFromParcel(Parcel in) {
+            return new RoundedCorner(in.readInt(), in.readInt(), in.readInt(), in.readInt());
+        }
+
+        @Override
+        public RoundedCorner[] newArray(int size) {
+            return new RoundedCorner[size];
+        }
+    };
+}
diff --git a/android/view/RoundedCorners.java b/android/view/RoundedCorners.java
new file mode 100644
index 0000000..623d969
--- /dev/null
+++ b/android/view/RoundedCorners.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
+import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
+import static android.view.RoundedCorner.POSITION_TOP_LEFT;
+import static android.view.RoundedCorner.POSITION_TOP_RIGHT;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Pair;
+import android.view.RoundedCorner.Position;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Arrays;
+
+/**
+ * A class to create & manage all the {@link RoundedCorner} on the display.
+ *
+ * @hide
+ */
+public class RoundedCorners implements Parcelable {
+
+    public static final RoundedCorners NO_ROUNDED_CORNERS = new RoundedCorners(
+            new RoundedCorner(POSITION_TOP_LEFT), new RoundedCorner(POSITION_TOP_RIGHT),
+            new RoundedCorner(POSITION_BOTTOM_RIGHT), new RoundedCorner(POSITION_BOTTOM_LEFT));
+
+    /**
+     * The number of possible positions at which rounded corners can be located.
+     */
+    public static final int ROUNDED_CORNER_POSITION_LENGTH = 4;
+
+    private static final Object CACHE_LOCK = new Object();
+
+    @GuardedBy("CACHE_LOCK")
+    private static int sCachedDisplayWidth;
+    @GuardedBy("CACHE_LOCK")
+    private static int sCachedDisplayHeight;
+    @GuardedBy("CACHE_LOCK")
+    private static Pair<Integer, Integer> sCachedRadii;
+    @GuardedBy("CACHE_LOCK")
+    private static RoundedCorners sCachedRoundedCorners;
+
+    @VisibleForTesting
+    public final RoundedCorner[] mRoundedCorners;
+
+    public RoundedCorners(RoundedCorner[] roundedCorners) {
+        mRoundedCorners = roundedCorners;
+    }
+
+    public RoundedCorners(RoundedCorner topLeft, RoundedCorner topRight, RoundedCorner bottomRight,
+            RoundedCorner bottomLeft) {
+        mRoundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
+        mRoundedCorners[POSITION_TOP_LEFT] = topLeft;
+        mRoundedCorners[POSITION_TOP_RIGHT] = topRight;
+        mRoundedCorners[POSITION_BOTTOM_RIGHT] = bottomRight;
+        mRoundedCorners[POSITION_BOTTOM_LEFT] = bottomLeft;
+    }
+
+    public RoundedCorners(RoundedCorners roundedCorners) {
+        mRoundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
+        for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; ++i) {
+            mRoundedCorners[i] = new RoundedCorner(roundedCorners.mRoundedCorners[i]);
+        }
+    }
+
+    /**
+     * Creates the rounded corners according to @android:dimen/rounded_corner_radius,
+     * @android:dimen/rounded_corner_radius_top and @android:dimen/rounded_corner_radius_bottom
+     */
+    public static RoundedCorners fromResources(
+            Resources res, int displayWidth, int displayHeight) {
+        return fromRadii(loadRoundedCornerRadii(res), displayWidth, displayHeight);
+    }
+
+    /**
+     * Creates the rounded corners from radius
+     */
+    @VisibleForTesting
+    public static RoundedCorners fromRadii(Pair<Integer, Integer> radii, int displayWidth,
+            int displayHeight) {
+        if (radii == null) {
+            return null;
+        }
+
+        synchronized (CACHE_LOCK) {
+            if (radii.equals(sCachedRadii) && sCachedDisplayWidth == displayWidth
+                    && sCachedDisplayHeight == displayHeight) {
+                return sCachedRoundedCorners;
+            }
+        }
+
+        final RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
+        final int topRadius = radii.first > 0 ? radii.first : 0;
+        final int bottomRadius = radii.second > 0 ? radii.second : 0;
+        for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; i++) {
+            roundedCorners[i] = createRoundedCorner(
+                    i,
+                    i <= POSITION_TOP_RIGHT ? topRadius : bottomRadius,
+                    displayWidth,
+                    displayHeight);
+        }
+
+        final RoundedCorners result = new RoundedCorners(roundedCorners);
+        synchronized (CACHE_LOCK) {
+            sCachedDisplayWidth = displayWidth;
+            sCachedDisplayHeight = displayHeight;
+            sCachedRadii = radii;
+            sCachedRoundedCorners = result;
+        }
+        return result;
+    }
+
+    /**
+     * Loads the rounded corner radii from resources.
+     *
+     * @param res
+     * @return a Pair of radius. The first is the top rounded corner radius and second is the
+     * bottom corner radius.
+     */
+    @Nullable
+    private static Pair<Integer, Integer> loadRoundedCornerRadii(Resources res) {
+        final int radiusDefault = res.getDimensionPixelSize(R.dimen.rounded_corner_radius);
+        final int radiusTop = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_top);
+        final int radiusBottom = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_bottom);
+        if (radiusDefault == 0 && radiusTop == 0 && radiusBottom == 0) {
+            return null;
+        }
+        final Pair<Integer, Integer> radii = new Pair<>(
+                        radiusTop > 0 ? radiusTop : radiusDefault,
+                        radiusBottom > 0 ? radiusBottom : radiusDefault);
+        return radii;
+    }
+
+    /**
+     * Insets the reference frame of the rounded corners.
+     *
+     * @return a copy of this instance which has been inset
+     */
+    public RoundedCorners inset(int insetLeft, int insetTop, int insetRight, int insetBottom) {
+        final RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
+        for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; i++) {
+            roundedCorners[i] = insetRoundedCorner(i, insetLeft, insetTop, insetRight, insetBottom);
+        }
+        return new RoundedCorners(roundedCorners);
+    }
+
+    private RoundedCorner insetRoundedCorner(@Position int position, int insetLeft,
+            int insetTop, int insetRight, int insetBottom) {
+        if (mRoundedCorners[position].isEmpty()) {
+            return new RoundedCorner(position);
+        }
+
+        final int radius = mRoundedCorners[position].getRadius();
+        final Point center = mRoundedCorners[position].getCenter();
+        boolean hasRoundedCorner;
+        switch (position) {
+            case POSITION_TOP_LEFT:
+                hasRoundedCorner = radius > insetTop && radius > insetLeft;
+                break;
+            case POSITION_TOP_RIGHT:
+                hasRoundedCorner = radius > insetTop && radius > insetRight;
+                break;
+            case POSITION_BOTTOM_RIGHT:
+                hasRoundedCorner = radius > insetBottom && radius > insetRight;
+                break;
+            case POSITION_BOTTOM_LEFT:
+                hasRoundedCorner = radius > insetBottom && radius > insetLeft;
+                break;
+            default:
+                throw new IllegalArgumentException(
+                        "The position is not one of the RoundedCornerPosition =" + position);
+        }
+        return new RoundedCorner(
+                position, radius,
+                hasRoundedCorner ? center.x - insetLeft : 0,
+                hasRoundedCorner ? center.y - insetTop : 0);
+    }
+
+    /**
+     * Returns the {@link RoundedCorner} of the given position if there is one.
+     *
+     * @param position the position of the rounded corner on the display.
+     * @return the rounded corner of the given position. Returns {@code null} if
+     * {@link RoundedCorner#isEmpty()} is {@code true}.
+     */
+    @Nullable
+    public RoundedCorner getRoundedCorner(@Position int position) {
+        return mRoundedCorners[position].isEmpty()
+                ? null : new RoundedCorner(mRoundedCorners[position]);
+    }
+
+    /**
+     * Sets the rounded corner of given position.
+     *
+     * @param position the position of this rounded corner
+     * @param roundedCorner the rounded corner or null if there is none
+     */
+    public void setRoundedCorner(@Position int position, @Nullable RoundedCorner roundedCorner) {
+        mRoundedCorners[position] = roundedCorner == null
+                ? new RoundedCorner(position) : roundedCorner;
+    }
+
+    /**
+     * Returns an array of {@link RoundedCorner}s. Ordinal value of RoundedCornerPosition is used
+     * as an index of the array.
+     *
+     * @return an array of {@link RoundedCorner}s, one for each rounded corner area.
+     */
+    public RoundedCorner[] getAllRoundedCorners() {
+        RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
+        for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; ++i) {
+            roundedCorners[i] = new RoundedCorner(roundedCorners[i]);
+        }
+        return roundedCorners;
+    }
+
+    /**
+     * Returns a scaled RoundedCorners.
+     */
+    public RoundedCorners scale(float scale) {
+        if (scale == 1f) {
+            return this;
+        }
+
+        RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
+        for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; ++i) {
+            final RoundedCorner roundedCorner = mRoundedCorners[i];
+            roundedCorners[i] = new RoundedCorner(
+                    i,
+                    (int) (roundedCorner.getRadius() * scale),
+                    (int) (roundedCorner.getCenter().x * scale),
+                    (int) (roundedCorner.getCenter().y * scale));
+        }
+        return new RoundedCorners(roundedCorners);
+    }
+
+    /**
+     * Returns a rotated RoundedCorners.
+     */
+    public RoundedCorners rotate(@Surface.Rotation int rotation, int initialDisplayWidth,
+            int initialDisplayHeight) {
+        if (rotation == ROTATION_0) {
+            return this;
+        }
+        final boolean isSizeFlipped = rotation == ROTATION_90 || rotation == ROTATION_270;
+        RoundedCorner[] newCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
+        int newPosistion;
+        for (int i = 0; i < mRoundedCorners.length; i++) {
+            newPosistion = getRotatedIndex(i, rotation);
+            newCorners[newPosistion] = createRoundedCorner(
+                    newPosistion,
+                    mRoundedCorners[i].getRadius(),
+                    isSizeFlipped ? initialDisplayHeight : initialDisplayWidth,
+                    isSizeFlipped ? initialDisplayWidth : initialDisplayHeight);
+        }
+        return new RoundedCorners(newCorners);
+    }
+
+    private static RoundedCorner createRoundedCorner(@Position int position,
+            int radius, int displayWidth, int displayHeight) {
+        switch (position) {
+            case POSITION_TOP_LEFT:
+                return new RoundedCorner(
+                        POSITION_TOP_LEFT,
+                        radius,
+                        radius > 0 ? radius : 0,
+                        radius > 0 ? radius : 0);
+            case POSITION_TOP_RIGHT:
+                return new RoundedCorner(
+                        POSITION_TOP_RIGHT,
+                        radius,
+                        radius > 0 ? displayWidth - radius : 0,
+                        radius > 0 ? radius : 0);
+            case POSITION_BOTTOM_RIGHT:
+                return new RoundedCorner(
+                        POSITION_BOTTOM_RIGHT,
+                        radius,
+                        radius > 0 ? displayWidth - radius : 0,
+                        radius > 0 ? displayHeight - radius : 0);
+            case POSITION_BOTTOM_LEFT:
+                return new RoundedCorner(
+                        POSITION_BOTTOM_LEFT,
+                        radius,
+                        radius > 0 ? radius : 0,
+                        radius > 0 ? displayHeight - radius  : 0);
+            default:
+                throw new IllegalArgumentException(
+                        "The position is not one of the RoundedCornerPosition =" + position);
+        }
+    }
+
+    private static int getRotatedIndex(int position, int rotation) {
+        return (position - rotation + ROUNDED_CORNER_POSITION_LENGTH) % 4;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 0;
+        for (RoundedCorner roundedCorner : mRoundedCorners) {
+            result = result * 31 + roundedCorner.hashCode();
+        }
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o instanceof RoundedCorners) {
+            RoundedCorners r = (RoundedCorners) o;
+            return Arrays.deepEquals(mRoundedCorners, r.mRoundedCorners);
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return "RoundedCorners{" + Arrays.toString(mRoundedCorners) + "}";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        if (equals(NO_ROUNDED_CORNERS)) {
+            dest.writeInt(0);
+        } else {
+            dest.writeInt(1);
+            dest.writeTypedArray(mRoundedCorners, flags);
+        }
+    }
+
+    public static final @NonNull Creator<RoundedCorners> CREATOR = new Creator<RoundedCorners>() {
+        @Override
+        public RoundedCorners createFromParcel(Parcel in) {
+            int variant = in.readInt();
+            if (variant == 0) {
+                return NO_ROUNDED_CORNERS;
+            }
+            RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
+            in.readTypedArray(roundedCorners, RoundedCorner.CREATOR);
+            return new RoundedCorners(roundedCorners);
+        }
+
+        @Override
+        public RoundedCorners[] newArray(int size) {
+            return new RoundedCorners[size];
+        }
+    };
+}
diff --git a/android/view/ScaleGestureDetector.java b/android/view/ScaleGestureDetector.java
new file mode 100644
index 0000000..346f76c
--- /dev/null
+++ b/android/view/ScaleGestureDetector.java
@@ -0,0 +1,577 @@
+/*
+ * 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.view;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.Build;
+import android.os.Handler;
+
+/**
+ * Detects scaling transformation gestures using the supplied {@link MotionEvent}s.
+ * The {@link OnScaleGestureListener} callback will notify users when a particular
+ * gesture event has occurred.
+ *
+ * This class should only be used with {@link MotionEvent}s reported via touch.
+ *
+ * To use this class:
+ * <ul>
+ *  <li>Create an instance of the {@code ScaleGestureDetector} for your
+ *      {@link View}
+ *  <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call
+ *          {@link #onTouchEvent(MotionEvent)}. The methods defined in your
+ *          callback will be executed when the events occur.
+ * </ul>
+ */
+public class ScaleGestureDetector {
+    private static final String TAG = "ScaleGestureDetector";
+
+    /**
+     * The listener for receiving notifications when gestures occur.
+     * If you want to listen for all the different gestures then implement
+     * this interface. If you only want to listen for a subset it might
+     * be easier to extend {@link SimpleOnScaleGestureListener}.
+     *
+     * An application will receive events in the following order:
+     * <ul>
+     *  <li>One {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)}
+     *  <li>Zero or more {@link OnScaleGestureListener#onScale(ScaleGestureDetector)}
+     *  <li>One {@link OnScaleGestureListener#onScaleEnd(ScaleGestureDetector)}
+     * </ul>
+     */
+    public interface OnScaleGestureListener {
+        /**
+         * Responds to scaling events for a gesture in progress.
+         * Reported by pointer motion.
+         *
+         * @param detector The detector reporting the event - use this to
+         *          retrieve extended info about event state.
+         * @return Whether or not the detector should consider this event
+         *          as handled. If an event was not handled, the detector
+         *          will continue to accumulate movement until an event is
+         *          handled. This can be useful if an application, for example,
+         *          only wants to update scaling factors if the change is
+         *          greater than 0.01.
+         */
+        public boolean onScale(ScaleGestureDetector detector);
+
+        /**
+         * Responds to the beginning of a scaling gesture. Reported by
+         * new pointers going down.
+         *
+         * @param detector The detector reporting the event - use this to
+         *          retrieve extended info about event state.
+         * @return Whether or not the detector should continue recognizing
+         *          this gesture. For example, if a gesture is beginning
+         *          with a focal point outside of a region where it makes
+         *          sense, onScaleBegin() may return false to ignore the
+         *          rest of the gesture.
+         */
+        public boolean onScaleBegin(ScaleGestureDetector detector);
+
+        /**
+         * Responds to the end of a scale gesture. Reported by existing
+         * pointers going up.
+         *
+         * Once a scale has ended, {@link ScaleGestureDetector#getFocusX()}
+         * and {@link ScaleGestureDetector#getFocusY()} will return focal point
+         * of the pointers remaining on the screen.
+         *
+         * @param detector The detector reporting the event - use this to
+         *          retrieve extended info about event state.
+         */
+        public void onScaleEnd(ScaleGestureDetector detector);
+    }
+
+    /**
+     * A convenience class to extend when you only want to listen for a subset
+     * of scaling-related events. This implements all methods in
+     * {@link OnScaleGestureListener} but does nothing.
+     * {@link OnScaleGestureListener#onScale(ScaleGestureDetector)} returns
+     * {@code false} so that a subclass can retrieve the accumulated scale
+     * factor in an overridden onScaleEnd.
+     * {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)} returns
+     * {@code true}.
+     */
+    public static class SimpleOnScaleGestureListener implements OnScaleGestureListener {
+
+        public boolean onScale(ScaleGestureDetector detector) {
+            return false;
+        }
+
+        public boolean onScaleBegin(ScaleGestureDetector detector) {
+            return true;
+        }
+
+        public void onScaleEnd(ScaleGestureDetector detector) {
+            // Intentionally empty
+        }
+    }
+
+    private final Context mContext;
+    @UnsupportedAppUsage
+    private final OnScaleGestureListener mListener;
+
+    private float mFocusX;
+    private float mFocusY;
+
+    private boolean mQuickScaleEnabled;
+    private boolean mStylusScaleEnabled;
+
+    private float mCurrSpan;
+    private float mPrevSpan;
+    private float mInitialSpan;
+    private float mCurrSpanX;
+    private float mCurrSpanY;
+    private float mPrevSpanX;
+    private float mPrevSpanY;
+    private long mCurrTime;
+    private long mPrevTime;
+    private boolean mInProgress;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768938)
+    private int mSpanSlop;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768938)
+    private int mMinSpan;
+
+    private final Handler mHandler;
+
+    private float mAnchoredScaleStartX;
+    private float mAnchoredScaleStartY;
+    private int mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
+
+    private static final long TOUCH_STABILIZE_TIME = 128; // ms
+    private static final float SCALE_FACTOR = .5f;
+    private static final int ANCHORED_SCALE_MODE_NONE = 0;
+    private static final int ANCHORED_SCALE_MODE_DOUBLE_TAP = 1;
+    private static final int ANCHORED_SCALE_MODE_STYLUS = 2;
+
+
+    /**
+     * Consistency verifier for debugging purposes.
+     */
+    private final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
+            InputEventConsistencyVerifier.isInstrumentationEnabled() ?
+                    new InputEventConsistencyVerifier(this, 0) : null;
+    private GestureDetector mGestureDetector;
+
+    private boolean mEventBeforeOrAboveStartingGestureEvent;
+
+    /**
+     * Creates a ScaleGestureDetector with the supplied listener.
+     * You may only use this constructor from a {@link android.os.Looper Looper} thread.
+     *
+     * @param context the application's context
+     * @param listener the listener invoked for all the callbacks, this must
+     * not be null.
+     *
+     * @throws NullPointerException if {@code listener} is null.
+     */
+    public ScaleGestureDetector(Context context, OnScaleGestureListener listener) {
+        this(context, listener, null);
+    }
+
+    /**
+     * Creates a ScaleGestureDetector with the supplied listener.
+     * @see android.os.Handler#Handler()
+     *
+     * @param context the application's context
+     * @param listener the listener invoked for all the callbacks, this must
+     * not be null.
+     * @param handler the handler to use for running deferred listener events.
+     *
+     * @throws NullPointerException if {@code listener} is null.
+     */
+    public ScaleGestureDetector(Context context, OnScaleGestureListener listener,
+                                Handler handler) {
+        mContext = context;
+        mListener = listener;
+        final ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
+        mSpanSlop = viewConfiguration.getScaledTouchSlop() * 2;
+        mMinSpan = viewConfiguration.getScaledMinimumScalingSpan();
+        mHandler = handler;
+        // Quick scale is enabled by default after JB_MR2
+        final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
+        if (targetSdkVersion > Build.VERSION_CODES.JELLY_BEAN_MR2) {
+            setQuickScaleEnabled(true);
+        }
+        // Stylus scale is enabled by default after LOLLIPOP_MR1
+        if (targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
+            setStylusScaleEnabled(true);
+        }
+    }
+
+    /**
+     * Accepts MotionEvents and dispatches events to a {@link OnScaleGestureListener}
+     * when appropriate.
+     *
+     * <p>Applications should pass a complete and consistent event stream to this method.
+     * A complete and consistent event stream involves all MotionEvents from the initial
+     * ACTION_DOWN to the final ACTION_UP or ACTION_CANCEL.</p>
+     *
+     * @param event The event to process
+     * @return true if the event was processed and the detector wants to receive the
+     *         rest of the MotionEvents in this event stream.
+     */
+    public boolean onTouchEvent(MotionEvent event) {
+        if (mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
+        }
+
+        mCurrTime = event.getEventTime();
+
+        final int action = event.getActionMasked();
+
+        // Forward the event to check for double tap gesture
+        if (mQuickScaleEnabled) {
+            mGestureDetector.onTouchEvent(event);
+        }
+
+        final int count = event.getPointerCount();
+        final boolean isStylusButtonDown =
+                (event.getButtonState() & MotionEvent.BUTTON_STYLUS_PRIMARY) != 0;
+
+        final boolean anchoredScaleCancelled =
+                mAnchoredScaleMode == ANCHORED_SCALE_MODE_STYLUS && !isStylusButtonDown;
+        final boolean streamComplete = action == MotionEvent.ACTION_UP ||
+                action == MotionEvent.ACTION_CANCEL || anchoredScaleCancelled;
+
+        if (action == MotionEvent.ACTION_DOWN || streamComplete) {
+            // Reset any scale in progress with the listener.
+            // If it's an ACTION_DOWN we're beginning a new event stream.
+            // This means the app probably didn't give us all the events. Shame on it.
+            if (mInProgress) {
+                mListener.onScaleEnd(this);
+                mInProgress = false;
+                mInitialSpan = 0;
+                mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
+            } else if (inAnchoredScaleMode() && streamComplete) {
+                mInProgress = false;
+                mInitialSpan = 0;
+                mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
+            }
+
+            if (streamComplete) {
+                return true;
+            }
+        }
+
+        if (!mInProgress && mStylusScaleEnabled && !inAnchoredScaleMode()
+                && !streamComplete && isStylusButtonDown) {
+            // Start of a button scale gesture
+            mAnchoredScaleStartX = event.getX();
+            mAnchoredScaleStartY = event.getY();
+            mAnchoredScaleMode = ANCHORED_SCALE_MODE_STYLUS;
+            mInitialSpan = 0;
+        }
+
+        final boolean configChanged = action == MotionEvent.ACTION_DOWN ||
+                action == MotionEvent.ACTION_POINTER_UP ||
+                action == MotionEvent.ACTION_POINTER_DOWN || anchoredScaleCancelled;
+
+        final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
+        final int skipIndex = pointerUp ? event.getActionIndex() : -1;
+
+        // Determine focal point
+        float sumX = 0, sumY = 0;
+        final int div = pointerUp ? count - 1 : count;
+        final float focusX;
+        final float focusY;
+        if (inAnchoredScaleMode()) {
+            // In anchored scale mode, the focal pt is always where the double tap
+            // or button down gesture started
+            focusX = mAnchoredScaleStartX;
+            focusY = mAnchoredScaleStartY;
+            if (event.getY() < focusY) {
+                mEventBeforeOrAboveStartingGestureEvent = true;
+            } else {
+                mEventBeforeOrAboveStartingGestureEvent = false;
+            }
+        } else {
+            for (int i = 0; i < count; i++) {
+                if (skipIndex == i) continue;
+                sumX += event.getX(i);
+                sumY += event.getY(i);
+            }
+
+            focusX = sumX / div;
+            focusY = sumY / div;
+        }
+
+        // Determine average deviation from focal point
+        float devSumX = 0, devSumY = 0;
+        for (int i = 0; i < count; i++) {
+            if (skipIndex == i) continue;
+
+            // Convert the resulting diameter into a radius.
+            devSumX += Math.abs(event.getX(i) - focusX);
+            devSumY += Math.abs(event.getY(i) - focusY);
+        }
+        final float devX = devSumX / div;
+        final float devY = devSumY / div;
+
+        // Span is the average distance between touch points through the focal point;
+        // i.e. the diameter of the circle with a radius of the average deviation from
+        // the focal point.
+        final float spanX = devX * 2;
+        final float spanY = devY * 2;
+        final float span;
+        if (inAnchoredScaleMode()) {
+            span = spanY;
+        } else {
+            span = (float) Math.hypot(spanX, spanY);
+        }
+
+        // Dispatch begin/end events as needed.
+        // If the configuration changes, notify the app to reset its current state by beginning
+        // a fresh scale event stream.
+        final boolean wasInProgress = mInProgress;
+        mFocusX = focusX;
+        mFocusY = focusY;
+        if (!inAnchoredScaleMode() && mInProgress && (span < mMinSpan || configChanged)) {
+            mListener.onScaleEnd(this);
+            mInProgress = false;
+            mInitialSpan = span;
+        }
+        if (configChanged) {
+            mPrevSpanX = mCurrSpanX = spanX;
+            mPrevSpanY = mCurrSpanY = spanY;
+            mInitialSpan = mPrevSpan = mCurrSpan = span;
+        }
+
+        final int minSpan = inAnchoredScaleMode() ? mSpanSlop : mMinSpan;
+        if (!mInProgress && span >=  minSpan &&
+                (wasInProgress || Math.abs(span - mInitialSpan) > mSpanSlop)) {
+            mPrevSpanX = mCurrSpanX = spanX;
+            mPrevSpanY = mCurrSpanY = spanY;
+            mPrevSpan = mCurrSpan = span;
+            mPrevTime = mCurrTime;
+            mInProgress = mListener.onScaleBegin(this);
+        }
+
+        // Handle motion; focal point and span/scale factor are changing.
+        if (action == MotionEvent.ACTION_MOVE) {
+            mCurrSpanX = spanX;
+            mCurrSpanY = spanY;
+            mCurrSpan = span;
+
+            boolean updatePrev = true;
+
+            if (mInProgress) {
+                updatePrev = mListener.onScale(this);
+            }
+
+            if (updatePrev) {
+                mPrevSpanX = mCurrSpanX;
+                mPrevSpanY = mCurrSpanY;
+                mPrevSpan = mCurrSpan;
+                mPrevTime = mCurrTime;
+            }
+        }
+
+        return true;
+    }
+
+    private boolean inAnchoredScaleMode() {
+        return mAnchoredScaleMode != ANCHORED_SCALE_MODE_NONE;
+    }
+
+    /**
+     * Set whether the associated {@link OnScaleGestureListener} should receive onScale callbacks
+     * when the user performs a doubleTap followed by a swipe. Note that this is enabled by default
+     * if the app targets API 19 and newer.
+     * @param scales true to enable quick scaling, false to disable
+     */
+    public void setQuickScaleEnabled(boolean scales) {
+        mQuickScaleEnabled = scales;
+        if (mQuickScaleEnabled && mGestureDetector == null) {
+            GestureDetector.SimpleOnGestureListener gestureListener =
+                    new GestureDetector.SimpleOnGestureListener() {
+                        @Override
+                        public boolean onDoubleTap(MotionEvent e) {
+                            // Double tap: start watching for a swipe
+                            mAnchoredScaleStartX = e.getX();
+                            mAnchoredScaleStartY = e.getY();
+                            mAnchoredScaleMode = ANCHORED_SCALE_MODE_DOUBLE_TAP;
+                            return true;
+                        }
+                    };
+            mGestureDetector = new GestureDetector(mContext, gestureListener, mHandler);
+        }
+    }
+
+  /**
+   * Return whether the quick scale gesture, in which the user performs a double tap followed by a
+   * swipe, should perform scaling. {@see #setQuickScaleEnabled(boolean)}.
+   */
+    public boolean isQuickScaleEnabled() {
+        return mQuickScaleEnabled;
+    }
+
+    /**
+     * Sets whether the associates {@link OnScaleGestureListener} should receive
+     * onScale callbacks when the user uses a stylus and presses the button.
+     * Note that this is enabled by default if the app targets API 23 and newer.
+     *
+     * @param scales true to enable stylus scaling, false to disable.
+     */
+    public void setStylusScaleEnabled(boolean scales) {
+        mStylusScaleEnabled = scales;
+    }
+
+    /**
+     * Return whether the stylus scale gesture, in which the user uses a stylus and presses the
+     * button, should perform scaling. {@see #setStylusScaleEnabled(boolean)}
+     */
+    public boolean isStylusScaleEnabled() {
+        return mStylusScaleEnabled;
+    }
+
+    /**
+     * Returns {@code true} if a scale gesture is in progress.
+     */
+    public boolean isInProgress() {
+        return mInProgress;
+    }
+
+    /**
+     * Get the X coordinate of the current gesture's focal point.
+     * If a gesture is in progress, the focal point is between
+     * each of the pointers forming the gesture.
+     *
+     * If {@link #isInProgress()} would return false, the result of this
+     * function is undefined.
+     *
+     * @return X coordinate of the focal point in pixels.
+     */
+    public float getFocusX() {
+        return mFocusX;
+    }
+
+    /**
+     * Get the Y coordinate of the current gesture's focal point.
+     * If a gesture is in progress, the focal point is between
+     * each of the pointers forming the gesture.
+     *
+     * If {@link #isInProgress()} would return false, the result of this
+     * function is undefined.
+     *
+     * @return Y coordinate of the focal point in pixels.
+     */
+    public float getFocusY() {
+        return mFocusY;
+    }
+
+    /**
+     * Return the average distance between each of the pointers forming the
+     * gesture in progress through the focal point.
+     *
+     * @return Distance between pointers in pixels.
+     */
+    public float getCurrentSpan() {
+        return mCurrSpan;
+    }
+
+    /**
+     * Return the average X distance between each of the pointers forming the
+     * gesture in progress through the focal point.
+     *
+     * @return Distance between pointers in pixels.
+     */
+    public float getCurrentSpanX() {
+        return mCurrSpanX;
+    }
+
+    /**
+     * Return the average Y distance between each of the pointers forming the
+     * gesture in progress through the focal point.
+     *
+     * @return Distance between pointers in pixels.
+     */
+    public float getCurrentSpanY() {
+        return mCurrSpanY;
+    }
+
+    /**
+     * Return the previous average distance between each of the pointers forming the
+     * gesture in progress through the focal point.
+     *
+     * @return Previous distance between pointers in pixels.
+     */
+    public float getPreviousSpan() {
+        return mPrevSpan;
+    }
+
+    /**
+     * Return the previous average X distance between each of the pointers forming the
+     * gesture in progress through the focal point.
+     *
+     * @return Previous distance between pointers in pixels.
+     */
+    public float getPreviousSpanX() {
+        return mPrevSpanX;
+    }
+
+    /**
+     * Return the previous average Y distance between each of the pointers forming the
+     * gesture in progress through the focal point.
+     *
+     * @return Previous distance between pointers in pixels.
+     */
+    public float getPreviousSpanY() {
+        return mPrevSpanY;
+    }
+
+    /**
+     * Return the scaling factor from the previous scale event to the current
+     * event. This value is defined as
+     * ({@link #getCurrentSpan()} / {@link #getPreviousSpan()}).
+     *
+     * @return The current scaling factor.
+     */
+    public float getScaleFactor() {
+        if (inAnchoredScaleMode()) {
+            // Drag is moving up; the further away from the gesture
+            // start, the smaller the span should be, the closer,
+            // the larger the span, and therefore the larger the scale
+            final boolean scaleUp =
+                    (mEventBeforeOrAboveStartingGestureEvent && (mCurrSpan < mPrevSpan)) ||
+                    (!mEventBeforeOrAboveStartingGestureEvent && (mCurrSpan > mPrevSpan));
+            final float spanDiff = (Math.abs(1 - (mCurrSpan / mPrevSpan)) * SCALE_FACTOR);
+            return mPrevSpan <= mSpanSlop ? 1 : scaleUp ? (1 + spanDiff) : (1 - spanDiff);
+        }
+        return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1;
+    }
+
+    /**
+     * Return the time difference in milliseconds between the previous
+     * accepted scaling event and the current scaling event.
+     *
+     * @return Time difference since the last scaling event in milliseconds.
+     */
+    public long getTimeDelta() {
+        return mCurrTime - mPrevTime;
+    }
+
+    /**
+     * Return the event time of the current event being processed.
+     *
+     * @return Current event time in milliseconds.
+     */
+    public long getEventTime() {
+        return mCurrTime;
+    }
+}
\ No newline at end of file
diff --git a/android/view/ScrollCaptureCallback.java b/android/view/ScrollCaptureCallback.java
new file mode 100644
index 0000000..1688616
--- /dev/null
+++ b/android/view/ScrollCaptureCallback.java
@@ -0,0 +1,154 @@
+/*
+ * 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.view;
+
+import android.annotation.NonNull;
+import android.annotation.UiThread;
+import android.graphics.Rect;
+import android.os.CancellationSignal;
+
+import java.util.function.Consumer;
+
+/**
+ * A ScrollCaptureCallback is responsible for providing rendered snapshots of scrolling content for
+ * the scroll capture system. A single callback is responsible for providing support to a single
+ * scrolling UI element. At request time, the system will select the best candidate from among all
+ * callbacks registered within the window.
+ * <p>
+ * A callback is assigned to a View using {@link View#setScrollCaptureCallback}, or to the window as
+ * {@link Window#registerScrollCaptureCallback}. The point where the callback is registered defines
+ * the frame of reference for the bounds measurements used.
+ * <p>
+ * <b>Terminology</b>
+ * <dl>
+ * <dt>Containing View</dt>
+ * <dd>The view on which this callback is attached, or the root view of the window if the callback
+ * is assigned  directly to a window.</dd>
+ *
+ * <dt>Scroll Bounds</dt>
+ * <dd>A rectangle which describes an area within the containing view where scrolling content
+ * appears. This may be the entire view or any rectangle within. This defines a frame of reference
+ * for requests as well as the width and maximum height of a single request.</dd>
+ *
+ * <dt>Scroll Delta</dt>
+ * <dd>The distance the scroll position has moved since capture started. Implementations are
+ * responsible for tracking changes in vertical scroll position during capture. This is required to
+ * map the capture area to the correct location, given the current scroll position.
+ *
+ * <dt>Capture Area</dt>
+ * <dd>A rectangle which describes the area to capture, relative to scroll bounds. The vertical
+ * position remains relative to the starting scroll position and any movement since ("Scroll Delta")
+ * should be subtracted to locate the correct local position, and scrolled into view as necessary.
+ * </dd>
+ * </dl>
+ *
+ * @see View#setScrollCaptureHint(int)
+ * @see View#setScrollCaptureCallback(ScrollCaptureCallback)
+ * @see Window#registerScrollCaptureCallback(ScrollCaptureCallback)
+ */
+@UiThread
+public interface ScrollCaptureCallback {
+
+    /**
+     * The system is searching for the appropriate scrolling container to capture and would like to
+     * know the size and position of scrolling content handled by this callback.
+     * <p>
+     * To determine scroll bounds, an implementation should inset the visible bounds of the
+     * containing view to cover only the area where scrolling content may be positioned. This
+     * should cover only the content which tracks with scrolling movement.
+     * <p>
+     * Return the updated rectangle to {@link Consumer#accept onReady.accept}. If for any reason the
+     * scrolling content is not available to capture, a empty rectangle may be returned which will
+     * exclude this view from consideration.
+     * <p>
+     * This request may be cancelled via the provided {@link CancellationSignal}. When this happens,
+     * any future call to {@link Consumer#accept onReady.accept} will have no effect and this
+     * content will be omitted from the search results.
+     *
+     * @param signal  signal to cancel the operation in progress
+     * @param onReady consumer for the updated rectangle
+     */
+    void onScrollCaptureSearch(@NonNull CancellationSignal signal, @NonNull Consumer<Rect> onReady);
+
+    /**
+     * Scroll Capture has selected this callback to provide the scrolling image content.
+     * <p>
+     * {@link Runnable#run onReady.run} should be called when ready to begin handling image
+     * requests.
+     * <p>
+     * This request may be cancelled via the provided {@link CancellationSignal}. When this happens,
+     * any future call to {@link Runnable#run onReady.run} will have no effect and provided session
+     * will not be activated.
+     *
+     * @param session the current session, resources provided by it are valid for use until the
+     *                {@link #onScrollCaptureEnd(Runnable) session ends}
+     * @param signal  signal to cancel the operation in progress
+     * @param onReady signal used to report completion of the request
+     */
+    void onScrollCaptureStart(@NonNull ScrollCaptureSession session,
+            @NonNull CancellationSignal signal, @NonNull Runnable onReady);
+
+    /**
+     * An image capture has been requested from the scrolling content.
+     * <p>
+     * The requested rectangle describes an area inside the target view, relative to
+     * <code>scrollBounds</code>. The content may be offscreen, above or below the current visible
+     * portion of the target view. To handle the request, render the available portion of this
+     * rectangle to a buffer and return it via the Surface available from {@link
+     * ScrollCaptureSession#getSurface()}.
+     * <p>
+     * Note: Implementations are only required to render the requested content, and may do so into
+     * off-screen buffers without scrolling if they are able.
+     * <p>
+     * The resulting available portion of the request must be computed as a portion of {@code
+     * captureArea}, and sent to signal the operation is complete, using  {@link Consumer#accept
+     * onComplete.accept}. If the requested rectangle is  partially or fully out of bounds the
+     * resulting portion should be returned. If no portion is available (outside of available
+     * content), then skip sending any buffer and report an empty Rect as result.
+     * <p>
+     * This request may be cancelled via the provided {@link CancellationSignal}. When this happens,
+     * any future call to {@link Consumer#accept onComplete.accept} will be ignored until the next
+     * request.
+     *
+     * @param session the current session, resources provided by it are valid for use until the
+     *                {@link #onScrollCaptureEnd(Runnable) session ends}
+     * @param signal      signal to cancel the operation in progress
+     * @param captureArea the area to capture, a rectangle within {@code scrollBounds}
+     * @param onComplete  a consumer for the captured area
+     */
+    void onScrollCaptureImageRequest(@NonNull ScrollCaptureSession session,
+            @NonNull CancellationSignal signal, @NonNull Rect captureArea,
+            @NonNull Consumer<Rect> onComplete);
+
+    /**
+     * Signals that capture has ended. Implementations should release any temporary resources or
+     * references to objects in use during the capture. Any resources obtained from the session are
+     * now invalid and attempts to use them after this point may throw an exception.
+     * <p>
+     * The window should be returned to its original state when capture started. At a minimum, the
+     * content should be scrolled to its original position.
+     * <p>
+     * {@link Runnable#run onReady.run} should be called as soon as possible after the window is
+     * ready for normal interactive use. After the callback (or after a timeout, if not called) the
+     * screenshot tool will be dismissed and the window may become visible to the user at any time.
+     *
+     * @param onReady a callback to inform the system that the application has completed any
+     *                cleanup and is ready to become visible
+     */
+    void onScrollCaptureEnd(@NonNull Runnable onReady);
+}
+
diff --git a/android/view/ScrollCaptureConnection.java b/android/view/ScrollCaptureConnection.java
new file mode 100644
index 0000000..5fcb011
--- /dev/null
+++ b/android/view/ScrollCaptureConnection.java
@@ -0,0 +1,293 @@
+/*
+ * 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.view;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.BinderThread;
+import android.annotation.NonNull;
+import android.annotation.UiThread;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.CancellationSignal;
+import android.os.ICancellationSignal;
+import android.os.RemoteException;
+import android.os.Trace;
+import android.util.CloseGuard;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Mediator between a selected scroll capture target view and a remote process.
+ * <p>
+ * An instance is created to wrap the selected {@link ScrollCaptureCallback}.
+ *
+ * @hide
+ */
+public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub {
+
+    private static final String TAG = "ScrollCaptureConnection";
+
+    private final Object mLock = new Object();
+    private final Rect mScrollBounds;
+    private final Point mPositionInWindow;
+    private final Executor mUiThread;
+    private final CloseGuard mCloseGuard = new CloseGuard();
+
+
+    private ScrollCaptureCallback mLocal;
+    private IScrollCaptureCallbacks mRemote;
+
+    private ScrollCaptureSession mSession;
+
+    private CancellationSignal mCancellation;
+
+    private volatile boolean mActive;
+
+    /**
+     * Constructs a ScrollCaptureConnection.
+     *
+     * @param uiThread an executor for the UI thread of the containing View
+     * @param selectedTarget  the target the client is controlling
+     *
+     * @hide
+     */
+    public ScrollCaptureConnection(
+            @NonNull Executor uiThread,
+            @NonNull ScrollCaptureTarget selectedTarget) {
+        mUiThread = requireNonNull(uiThread, "<uiThread> must non-null");
+        requireNonNull(selectedTarget, "<selectedTarget> must non-null");
+        mScrollBounds = requireNonNull(Rect.copyOrNull(selectedTarget.getScrollBounds()),
+                "target.getScrollBounds() must be non-null to construct a client");
+        mLocal = selectedTarget.getCallback();
+        mPositionInWindow = new Point(selectedTarget.getPositionInWindow());
+    }
+
+    @BinderThread
+    @Override
+    public ICancellationSignal startCapture(@NonNull Surface surface,
+            @NonNull IScrollCaptureCallbacks remote) throws RemoteException {
+
+        mCloseGuard.open("close");
+
+        if (!surface.isValid()) {
+            throw new RemoteException(new IllegalArgumentException("surface must be valid"));
+        }
+        mRemote = requireNonNull(remote, "<callbacks> must non-null");
+
+        ICancellationSignal cancellation = CancellationSignal.createTransport();
+        mCancellation = CancellationSignal.fromTransport(cancellation);
+        mSession = new ScrollCaptureSession(surface, mScrollBounds, mPositionInWindow);
+
+        Runnable listener =
+                SafeCallback.create(mCancellation, mUiThread, this::onStartCaptureCompleted);
+        // -> UiThread
+        mUiThread.execute(() -> mLocal.onScrollCaptureStart(mSession, mCancellation, listener));
+        return cancellation;
+    }
+
+    @UiThread
+    private void onStartCaptureCompleted() {
+        mActive = true;
+        try {
+            mRemote.onCaptureStarted();
+        } catch (RemoteException e) {
+            Log.w(TAG, "Shutting down due to error: ", e);
+            close();
+        }
+    }
+
+    @BinderThread
+    @Override
+    public ICancellationSignal requestImage(Rect requestRect) throws RemoteException {
+        Trace.beginSection("requestImage");
+        checkActive();
+
+        ICancellationSignal cancellation = CancellationSignal.createTransport();
+        mCancellation = CancellationSignal.fromTransport(cancellation);
+
+        Consumer<Rect> listener =
+                SafeCallback.create(mCancellation, mUiThread, this::onImageRequestCompleted);
+        // -> UiThread
+        mUiThread.execute(() -> mLocal.onScrollCaptureImageRequest(
+                mSession, mCancellation, new Rect(requestRect), listener));
+        Trace.endSection();
+        return cancellation;
+    }
+
+    @UiThread
+    void onImageRequestCompleted(Rect capturedArea) {
+        try {
+            mRemote.onImageRequestCompleted(0, capturedArea);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Shutting down due to error: ", e);
+            close();
+        }
+    }
+
+    @BinderThread
+    @Override
+    public ICancellationSignal endCapture() throws RemoteException {
+        checkActive();
+
+        ICancellationSignal cancellation = CancellationSignal.createTransport();
+        mCancellation = CancellationSignal.fromTransport(cancellation);
+
+        Runnable listener =
+                SafeCallback.create(mCancellation, mUiThread, this::onEndCaptureCompleted);
+        // -> UiThread
+        mUiThread.execute(() -> mLocal.onScrollCaptureEnd(listener));
+        return cancellation;
+    }
+
+    @UiThread
+    private void onEndCaptureCompleted() {
+        mActive = false;
+        try {
+            if (mRemote != null) {
+                mRemote.onCaptureEnded();
+            }
+        } catch (RemoteException e) {
+            Log.w(TAG, "Caught exception confirming capture end!", e);
+        } finally {
+            close();
+        }
+    }
+
+    @BinderThread
+    @Override
+    public void close() {
+        if (mActive) {
+            if (mCancellation != null) {
+                Log.w(TAG, "close(): cancelling pending operation.");
+                mCancellation.cancel();
+                mCancellation = null;
+            }
+            Log.w(TAG, "close(): capture session still active! Ending now.");
+            // -> UiThread
+            final ScrollCaptureCallback callback = mLocal;
+            mUiThread.execute(() -> callback.onScrollCaptureEnd(() -> { /* ignore */ }));
+            mActive = false;
+        }
+        mActive = false;
+        mSession = null;
+        mRemote = null;
+        mLocal = null;
+        mCloseGuard.close();
+        Reference.reachabilityFence(this);
+    }
+
+    @VisibleForTesting
+    public boolean isActive() {
+        return mActive;
+    }
+
+    private void checkActive() throws RemoteException {
+        synchronized (mLock) {
+            if (!mActive) {
+                throw new RemoteException(new IllegalStateException("Not started!"));
+            }
+        }
+    }
+
+    /** @return a string representation of the state of this client */
+    public String toString() {
+        return "ScrollCaptureConnection{"
+                + "active=" + mActive
+                + ", session=" + mSession
+                + ", remote=" + mRemote
+                + ", local=" + mLocal
+                + "}";
+    }
+
+    protected void finalize() throws Throwable {
+        try {
+            mCloseGuard.warnIfOpen();
+            close();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private static class SafeCallback<T> {
+        private final CancellationSignal mSignal;
+        private final WeakReference<T> mTargetRef;
+        private final Executor mExecutor;
+        private boolean mExecuted;
+
+        protected SafeCallback(CancellationSignal signal, Executor executor, T target) {
+            mSignal = signal;
+            mTargetRef = new WeakReference<>(target);
+            mExecutor = executor;
+        }
+
+        // Provide the target to the consumer to invoke, forward on handler thread ONCE,
+        // and only if noy cancelled, and the target is still available (not collected)
+        protected final void maybeAccept(Consumer<T> targetConsumer) {
+            if (mExecuted) {
+                return;
+            }
+            mExecuted = true;
+            if (mSignal.isCanceled()) {
+                return;
+            }
+            T target = mTargetRef.get();
+            if (target == null) {
+                return;
+            }
+            mExecutor.execute(() -> targetConsumer.accept(target));
+        }
+
+        static Runnable create(CancellationSignal signal, Executor executor, Runnable target) {
+            return new RunnableCallback(signal, executor, target);
+        }
+
+        static <T> Consumer<T> create(CancellationSignal signal, Executor executor,
+                Consumer<T> target) {
+            return new ConsumerCallback<T>(signal, executor, target);
+        }
+    }
+
+    private static final class RunnableCallback extends SafeCallback<Runnable> implements Runnable {
+        RunnableCallback(CancellationSignal signal, Executor executor, Runnable target) {
+            super(signal, executor, target);
+        }
+
+        @Override
+        public void run() {
+            maybeAccept(Runnable::run);
+        }
+    }
+
+    private static final class ConsumerCallback<T> extends SafeCallback<Consumer<T>>
+            implements Consumer<T> {
+        ConsumerCallback(CancellationSignal signal, Executor executor, Consumer<T> target) {
+            super(signal, executor, target);
+        }
+
+        @Override
+        public void accept(T value) {
+            maybeAccept((target) -> target.accept(value));
+        }
+    }
+}
diff --git a/android/view/ScrollCaptureResponse.java b/android/view/ScrollCaptureResponse.java
new file mode 100644
index 0000000..8808827
--- /dev/null
+++ b/android/view/ScrollCaptureResponse.java
@@ -0,0 +1,393 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+import com.android.internal.util.DataClass;
+
+import java.util.ArrayList;
+
+/** @hide */
+@DataClass(genToString = true, genGetters = true)
+public class ScrollCaptureResponse implements Parcelable {
+
+    /** Developer-facing human readable description of the result. */
+    @NonNull
+    private String mDescription = "";
+
+    // Remaining fields are non-null when isConnected() == true
+
+    /** The active connection for a successful result. */
+    @Nullable
+    @DataClass.MaySetToNull
+    private IScrollCaptureConnection mConnection = null;
+
+    /** The bounds of the window within the display */
+    @Nullable
+    private Rect mWindowBounds = null;
+
+    /** The bounds of the scrolling content, in window space. */
+    @Nullable
+    private Rect mBoundsInWindow = null;
+
+    /** The current window title. */
+    @Nullable
+    private String mWindowTitle = null;
+
+    /** Carries additional logging and debugging information when enabled. */
+    @NonNull
+    @DataClass.PluralOf("message")
+    private ArrayList<String> mMessages = new ArrayList<>();
+
+    /** Whether an active connection is present. */
+    public boolean isConnected() {
+        return mConnection != null && mConnection.asBinder().isBinderAlive();
+    }
+
+    /** Closes a connection returned with this response. */
+    public void close() {
+        if (mConnection != null) {
+            try {
+                mConnection.close();
+            } catch (RemoteException e) {
+                // Ignore
+            }
+            mConnection = null;
+        }
+    }
+
+
+
+    // Code below generated by codegen v1.0.22.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/ScrollCaptureResponse.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    @DataClass.Generated.Member
+    /* package-private */ ScrollCaptureResponse(
+            @NonNull String description,
+            @Nullable IScrollCaptureConnection connection,
+            @Nullable Rect windowBounds,
+            @Nullable Rect boundsInWindow,
+            @Nullable String windowTitle,
+            @NonNull ArrayList<String> messages) {
+        this.mDescription = description;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mDescription);
+        this.mConnection = connection;
+        this.mWindowBounds = windowBounds;
+        this.mBoundsInWindow = boundsInWindow;
+        this.mWindowTitle = windowTitle;
+        this.mMessages = messages;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mMessages);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * Developer-facing human readable description of the result.
+     */
+    @DataClass.Generated.Member
+    public @NonNull String getDescription() {
+        return mDescription;
+    }
+
+    /**
+     * The active connection for a successful result.
+     */
+    @DataClass.Generated.Member
+    public @Nullable IScrollCaptureConnection getConnection() {
+        return mConnection;
+    }
+
+    /**
+     * The bounds of the window within the display
+     */
+    @DataClass.Generated.Member
+    public @Nullable Rect getWindowBounds() {
+        return mWindowBounds;
+    }
+
+    /**
+     * The bounds of the scrolling content, in window space.
+     */
+    @DataClass.Generated.Member
+    public @Nullable Rect getBoundsInWindow() {
+        return mBoundsInWindow;
+    }
+
+    /**
+     * The current window title.
+     */
+    @DataClass.Generated.Member
+    public @Nullable String getWindowTitle() {
+        return mWindowTitle;
+    }
+
+    /**
+     * Carries additional logging and debugging information when enabled.
+     */
+    @DataClass.Generated.Member
+    public @NonNull ArrayList<String> getMessages() {
+        return mMessages;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "ScrollCaptureResponse { " +
+                "description = " + mDescription + ", " +
+                "connection = " + mConnection + ", " +
+                "windowBounds = " + mWindowBounds + ", " +
+                "boundsInWindow = " + mBoundsInWindow + ", " +
+                "windowTitle = " + mWindowTitle + ", " +
+                "messages = " + mMessages +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mConnection != null) flg |= 0x2;
+        if (mWindowBounds != null) flg |= 0x4;
+        if (mBoundsInWindow != null) flg |= 0x8;
+        if (mWindowTitle != null) flg |= 0x10;
+        dest.writeByte(flg);
+        dest.writeString(mDescription);
+        if (mConnection != null) dest.writeStrongInterface(mConnection);
+        if (mWindowBounds != null) dest.writeTypedObject(mWindowBounds, flags);
+        if (mBoundsInWindow != null) dest.writeTypedObject(mBoundsInWindow, flags);
+        if (mWindowTitle != null) dest.writeString(mWindowTitle);
+        dest.writeStringList(mMessages);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    protected ScrollCaptureResponse(@NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        String description = in.readString();
+        IScrollCaptureConnection connection = (flg & 0x2) == 0 ? null : IScrollCaptureConnection.Stub.asInterface(in.readStrongBinder());
+        Rect windowBounds = (flg & 0x4) == 0 ? null : (Rect) in.readTypedObject(Rect.CREATOR);
+        Rect boundsInWindow = (flg & 0x8) == 0 ? null : (Rect) in.readTypedObject(Rect.CREATOR);
+        String windowTitle = (flg & 0x10) == 0 ? null : in.readString();
+        ArrayList<String> messages = new ArrayList<>();
+        in.readStringList(messages);
+
+        this.mDescription = description;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mDescription);
+        this.mConnection = connection;
+        this.mWindowBounds = windowBounds;
+        this.mBoundsInWindow = boundsInWindow;
+        this.mWindowTitle = windowTitle;
+        this.mMessages = messages;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mMessages);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<ScrollCaptureResponse> CREATOR
+            = new Parcelable.Creator<ScrollCaptureResponse>() {
+        @Override
+        public ScrollCaptureResponse[] newArray(int size) {
+            return new ScrollCaptureResponse[size];
+        }
+
+        @Override
+        public ScrollCaptureResponse createFromParcel(@NonNull android.os.Parcel in) {
+            return new ScrollCaptureResponse(in);
+        }
+    };
+
+    /**
+     * A builder for {@link ScrollCaptureResponse}
+     */
+    @SuppressWarnings("WeakerAccess")
+    @DataClass.Generated.Member
+    public static class Builder {
+
+        private @NonNull String mDescription;
+        private @Nullable IScrollCaptureConnection mConnection;
+        private @Nullable Rect mWindowBounds;
+        private @Nullable Rect mBoundsInWindow;
+        private @Nullable String mWindowTitle;
+        private @NonNull ArrayList<String> mMessages;
+
+        private long mBuilderFieldsSet = 0L;
+
+        public Builder() {
+        }
+
+        /**
+         * Developer-facing human readable description of the result.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setDescription(@NonNull String value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x1;
+            mDescription = value;
+            return this;
+        }
+
+        /**
+         * The active connection for a successful result.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setConnection(@Nullable IScrollCaptureConnection value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x2;
+            mConnection = value;
+            return this;
+        }
+
+        /**
+         * The bounds of the window within the display
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setWindowBounds(@NonNull Rect value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x4;
+            mWindowBounds = value;
+            return this;
+        }
+
+        /**
+         * The bounds of the scrolling content, in window space.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setBoundsInWindow(@NonNull Rect value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x8;
+            mBoundsInWindow = value;
+            return this;
+        }
+
+        /**
+         * The current window title.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setWindowTitle(@NonNull String value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x10;
+            mWindowTitle = value;
+            return this;
+        }
+
+        /**
+         * Carries additional logging and debugging information when enabled.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setMessages(@NonNull ArrayList<String> value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x20;
+            mMessages = value;
+            return this;
+        }
+
+        /** @see #setMessages */
+        @DataClass.Generated.Member
+        public @NonNull Builder addMessage(@NonNull String value) {
+            if (mMessages == null) setMessages(new ArrayList<>());
+            mMessages.add(value);
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        public @NonNull ScrollCaptureResponse build() {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x40; // Mark builder used
+
+            if ((mBuilderFieldsSet & 0x1) == 0) {
+                mDescription = "";
+            }
+            if ((mBuilderFieldsSet & 0x2) == 0) {
+                mConnection = null;
+            }
+            if ((mBuilderFieldsSet & 0x4) == 0) {
+                mWindowBounds = null;
+            }
+            if ((mBuilderFieldsSet & 0x8) == 0) {
+                mBoundsInWindow = null;
+            }
+            if ((mBuilderFieldsSet & 0x10) == 0) {
+                mWindowTitle = null;
+            }
+            if ((mBuilderFieldsSet & 0x20) == 0) {
+                mMessages = new ArrayList<>();
+            }
+            ScrollCaptureResponse o = new ScrollCaptureResponse(
+                    mDescription,
+                    mConnection,
+                    mWindowBounds,
+                    mBoundsInWindow,
+                    mWindowTitle,
+                    mMessages);
+            return o;
+        }
+
+        private void checkNotUsed() {
+            if ((mBuilderFieldsSet & 0x40) != 0) {
+                throw new IllegalStateException(
+                        "This Builder should not be reused. Use a new Builder instance instead");
+            }
+        }
+    }
+
+    @DataClass.Generated(
+            time = 1614833185795L,
+            codegenVersion = "1.0.22",
+            sourceFile = "frameworks/base/core/java/android/view/ScrollCaptureResponse.java",
+            inputSignatures = "private @android.annotation.NonNull java.lang.String mDescription\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.MaySetToNull android.view.IScrollCaptureConnection mConnection\nprivate @android.annotation.Nullable android.graphics.Rect mWindowBounds\nprivate @android.annotation.Nullable android.graphics.Rect mBoundsInWindow\nprivate @android.annotation.Nullable java.lang.String mWindowTitle\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"message\") java.util.ArrayList<java.lang.String> mMessages\npublic  boolean isConnected()\npublic  void close()\nclass ScrollCaptureResponse extends java.lang.Object implements [android.os.Parcelable]\[email protected](genToString=true, genGetters=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/android/view/ScrollCaptureSearchResults.java b/android/view/ScrollCaptureSearchResults.java
new file mode 100644
index 0000000..3469b9d
--- /dev/null
+++ b/android/view/ScrollCaptureSearchResults.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.annotation.UiThread;
+import android.graphics.Rect;
+import android.os.CancellationSignal;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Collects nodes in the view hierarchy which have been identified as scrollable content.
+ *
+ * @hide
+ */
+@UiThread
+public final class ScrollCaptureSearchResults {
+    private final Executor mExecutor;
+    private final List<ScrollCaptureTarget> mTargets;
+    private final CancellationSignal mCancel;
+
+    private Runnable mOnCompleteListener;
+    private int mCompleted;
+    private boolean mComplete = true;
+
+    public ScrollCaptureSearchResults(Executor executor) {
+        mExecutor = executor;
+        mTargets = new ArrayList<>();
+        mCancel = new CancellationSignal();
+    }
+
+    // Public
+
+    /**
+     * Add the given target to the results.
+     *
+     * @param target the target to consider
+     */
+    public void addTarget(@NonNull ScrollCaptureTarget target) {
+        requireNonNull(target);
+
+        mTargets.add(target);
+        mComplete = false;
+        final ScrollCaptureCallback callback = target.getCallback();
+        final Consumer<Rect> consumer = new SearchRequest(target);
+
+        // Defer so the view hierarchy scan completes first
+        mExecutor.execute(
+                () -> callback.onScrollCaptureSearch(mCancel, consumer));
+    }
+
+    public boolean isComplete() {
+        return mComplete;
+    }
+
+    /**
+     * Provides a callback to be invoked as soon as all responses have been received from all
+     * targets to this point.
+     *
+     * @param onComplete listener to add
+     */
+    public void setOnCompleteListener(Runnable onComplete) {
+        if (mComplete) {
+            onComplete.run();
+        } else {
+            mOnCompleteListener = onComplete;
+        }
+    }
+
+    /**
+     * Indicates whether the search results are empty.
+     *
+     * @return true if no targets have been added
+     */
+    public boolean isEmpty() {
+        return mTargets.isEmpty();
+    }
+
+    /**
+     * Force the results to complete now, cancelling any pending requests and calling a complete
+     * listener if provided.
+     */
+    public void finish() {
+        if (!mComplete) {
+            mCancel.cancel();
+            signalComplete();
+        }
+    }
+
+    private void signalComplete() {
+        mComplete = true;
+        mTargets.sort(PRIORITY_ORDER);
+        if (mOnCompleteListener != null) {
+            mOnCompleteListener.run();
+            mOnCompleteListener = null;
+        }
+    }
+
+    @VisibleForTesting
+    public List<ScrollCaptureTarget> getTargets() {
+        return new ArrayList<>(mTargets);
+    }
+
+    /**
+     * Get the top ranked result out of all completed requests.
+     *
+     * @return the top ranked result
+     */
+    public ScrollCaptureTarget getTopResult() {
+        ScrollCaptureTarget target = mTargets.isEmpty() ? null : mTargets.get(0);
+        return target != null && target.getScrollBounds() != null ? target : null;
+    }
+
+    private class SearchRequest implements Consumer<Rect> {
+        private ScrollCaptureTarget mTarget;
+
+        SearchRequest(ScrollCaptureTarget target) {
+            mTarget = target;
+        }
+
+        @Override
+        public void accept(Rect scrollBounds) {
+            if (mTarget == null || mCancel.isCanceled()) {
+                return;
+            }
+            mExecutor.execute(() -> consume(scrollBounds));
+        }
+
+        private void consume(Rect scrollBounds) {
+            if (mTarget == null || mCancel.isCanceled()) {
+                return;
+            }
+            if (!nullOrEmpty(scrollBounds)) {
+                mTarget.setScrollBounds(scrollBounds);
+                mTarget.updatePositionInWindow();
+            }
+            mCompleted++;
+            mTarget = null;
+
+            // All done?
+            if (mCompleted == mTargets.size()) {
+                signalComplete();
+            }
+        }
+    }
+
+    private static final int AFTER = 1;
+    private static final int BEFORE = -1;
+    private static final int EQUAL = 0;
+
+    static final Comparator<ScrollCaptureTarget> PRIORITY_ORDER = (a, b) -> {
+        if (a == null && b == null) {
+            return 0;
+        } else if (a == null || b == null) {
+            return (a == null) ? 1 : -1;
+        }
+
+        boolean emptyScrollBoundsA = nullOrEmpty(a.getScrollBounds());
+        boolean emptyScrollBoundsB = nullOrEmpty(b.getScrollBounds());
+        if (emptyScrollBoundsA || emptyScrollBoundsB) {
+            if (emptyScrollBoundsA && emptyScrollBoundsB) {
+                return EQUAL;
+            }
+            // Prefer the one with a non-empty scroll bounds
+            if (emptyScrollBoundsA) {
+                return AFTER;
+            }
+            return BEFORE;
+        }
+
+        final View viewA = a.getContainingView();
+        final View viewB = b.getContainingView();
+
+        // Prefer any view with scrollCaptureHint="INCLUDE", over one without
+        // This is an escape hatch for the next rule (descendants first)
+        boolean hintIncludeA = hasIncludeHint(viewA);
+        boolean hintIncludeB = hasIncludeHint(viewB);
+        if (hintIncludeA != hintIncludeB) {
+            return (hintIncludeA) ? BEFORE : AFTER;
+        }
+        // If the views are relatives, prefer the descendant. This allows implementations to
+        // leverage nested scrolling APIs by interacting with the innermost scrollable view (as
+        // would happen with touch input).
+        if (isDescendant(viewA, viewB)) {
+            return BEFORE;
+        }
+        if (isDescendant(viewB, viewA)) {
+            return AFTER;
+        }
+
+        // finally, prefer one with larger scroll bounds
+        int scrollAreaA = area(a.getScrollBounds());
+        int scrollAreaB = area(b.getScrollBounds());
+        return (scrollAreaA >= scrollAreaB) ? BEFORE : AFTER;
+    };
+
+    private static int area(Rect r) {
+        return r.width() * r.height();
+    }
+
+    private static boolean nullOrEmpty(Rect r) {
+        return r == null || r.isEmpty();
+    }
+
+    private static boolean hasIncludeHint(View view) {
+        return (view.getScrollCaptureHint() & View.SCROLL_CAPTURE_HINT_INCLUDE) != 0;
+    }
+
+    /**
+     * Determines if {@code otherView} is a descendant of {@code view}.
+     *
+     * @param view      a view
+     * @param otherView another view
+     * @return true if {@code view} is an ancestor of {@code otherView}
+     */
+    private static boolean isDescendant(@NonNull View view, @NonNull View otherView) {
+        if (view == otherView) {
+            return false;
+        }
+        ViewParent otherParent = otherView.getParent();
+        while (otherParent != view && otherParent != null) {
+            otherParent = otherParent.getParent();
+        }
+        return otherParent == view;
+    }
+
+    void dump(IndentingPrintWriter writer) {
+        writer.println("results:");
+        writer.increaseIndent();
+        writer.println("complete: " + isComplete());
+        writer.println("cancelled: " + mCancel.isCanceled());
+        writer.println("targets:");
+        writer.increaseIndent();
+        if (isEmpty()) {
+            writer.println("None");
+        } else {
+            for (int i = 0; i < mTargets.size(); i++) {
+                writer.println("[" + i + "]");
+                writer.increaseIndent();
+                mTargets.get(i).dump(writer);
+                writer.decreaseIndent();
+            }
+            writer.decreaseIndent();
+        }
+        writer.decreaseIndent();
+    }
+}
diff --git a/android/view/ScrollCaptureSession.java b/android/view/ScrollCaptureSession.java
new file mode 100644
index 0000000..748e7ea
--- /dev/null
+++ b/android/view/ScrollCaptureSession.java
@@ -0,0 +1,85 @@
+/*
+ * 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.view;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.graphics.Point;
+import android.graphics.Rect;
+
+/**
+ * A session represents the scope of interaction between a {@link ScrollCaptureCallback} and the
+ * system during an active scroll capture operation.
+ */
+public class ScrollCaptureSession {
+
+    private final Surface mSurface;
+    private final Rect mScrollBounds;
+    private final Point mPositionInWindow;
+
+    /**
+     * Constructs a new session instance.
+     *
+     * @param surface the surface to consume generated images
+     * @param scrollBounds the bounds of the capture area within the containing view
+     * @param positionInWindow the offset of scrollBounds within the window
+     */
+    public ScrollCaptureSession(@NonNull Surface surface, @NonNull Rect scrollBounds,
+            @NonNull Point positionInWindow) {
+        mSurface = requireNonNull(surface);
+        mScrollBounds = requireNonNull(scrollBounds);
+        mPositionInWindow = requireNonNull(positionInWindow);
+    }
+
+    /**
+     * Returns a
+     * <a href="https://source.android.com/devices/graphics/arch-bq-gralloc">BufferQueue</a> in the
+     * form of a {@link Surface} for transfer of image buffers.
+     * <p>
+     * The surface is guaranteed to remain {@link Surface#isValid() valid} until the session
+     * {@link ScrollCaptureCallback#onScrollCaptureEnd(Runnable) ends}.
+     *
+     * @return the surface for transferring image buffers
+     * @throws IllegalStateException if the session has been closed
+     */
+    @NonNull
+    public Surface getSurface() {
+        return mSurface;
+    }
+
+    /**
+     * Returns the {@code scroll bounds}, as provided by
+     * {@link ScrollCaptureCallback#onScrollCaptureSearch}.
+     *
+     * @return the area of scrolling content within the containing view
+     */
+    @NonNull
+    public Rect getScrollBounds() {
+        return mScrollBounds;
+    }
+
+    /**
+     * Returns the offset of {@code scroll bounds} within the window.
+     *
+     * @return the area of scrolling content within the containing view
+     */
+    @NonNull
+    public Point getPositionInWindow() {
+        return mPositionInWindow;
+    }
+}
diff --git a/android/view/ScrollCaptureTarget.java b/android/view/ScrollCaptureTarget.java
new file mode 100644
index 0000000..a8bb037
--- /dev/null
+++ b/android/view/ScrollCaptureTarget.java
@@ -0,0 +1,150 @@
+/*
+ * 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.view;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UiThread;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.CancellationSignal;
+
+import java.io.PrintWriter;
+import java.util.function.Consumer;
+
+/**
+ * A target collects the set of contextual information for a ScrollCaptureHandler discovered during
+ * a {@link View#dispatchScrollCaptureSearch scroll capture search}.
+ */
+public final class ScrollCaptureTarget {
+    private final View mContainingView;
+    private final ScrollCaptureCallback mCallback;
+    private final Rect mLocalVisibleRect;
+    private final Point mPositionInWindow;
+    private final int mHint;
+    private Rect mScrollBounds;
+
+    private final int[] mTmpIntArr = new int[2];
+
+    public ScrollCaptureTarget(@NonNull View scrollTarget, @NonNull Rect localVisibleRect,
+            @NonNull Point positionInWindow, @NonNull ScrollCaptureCallback callback) {
+        mContainingView = requireNonNull(scrollTarget);
+        mHint = mContainingView.getScrollCaptureHint();
+        mCallback = requireNonNull(callback);
+        mLocalVisibleRect = requireNonNull(localVisibleRect);
+        mPositionInWindow = requireNonNull(positionInWindow);
+    }
+
+    /**
+     * @return the hint that the {@code containing view} had during the scroll capture search
+     * @see View#getScrollCaptureHint()
+     */
+    @View.ScrollCaptureHint
+    public int getHint() {
+        return mHint;
+    }
+
+    /** @return the {@link ScrollCaptureCallback} for this target */
+    @NonNull
+    public ScrollCaptureCallback getCallback() {
+        return mCallback;
+    }
+
+    /** @return the {@code containing view} for this {@link ScrollCaptureCallback callback} */
+    @NonNull
+    public View getContainingView() {
+        return mContainingView;
+    }
+
+    /**
+     * Returns the visible bounds of the containing view.
+     *
+     * @return the visible bounds of the {@code containing view} in view-local coordinates
+     */
+    @NonNull
+    public Rect getLocalVisibleRect() {
+        return mLocalVisibleRect;
+    }
+
+    /** @return the position of the visible bounds of the containing view within the window */
+    @NonNull
+    public Point getPositionInWindow() {
+        return mPositionInWindow;
+    }
+
+    /**
+     * @return the {@code scroll bounds} for this {@link ScrollCaptureCallback callback}
+     *
+     * @see ScrollCaptureCallback#onScrollCaptureSearch(CancellationSignal, Consumer)
+     */
+    @Nullable
+    public Rect getScrollBounds() {
+        return mScrollBounds;
+    }
+
+    /**
+     * Sets the scroll bounds rect to the intersection of provided rect and the current bounds of
+     * the {@code containing view}.
+     */
+    public void setScrollBounds(@Nullable Rect scrollBounds) {
+        mScrollBounds = Rect.copyOrNull(scrollBounds);
+        if (mScrollBounds == null) {
+            return;
+        }
+        if (!mScrollBounds.intersect(0, 0,
+                mContainingView.getWidth(), mContainingView.getHeight())) {
+            mScrollBounds.setEmpty();
+        }
+    }
+
+    /**
+     * Refresh the local visible bounds and its offset within the window, based on the current
+     * state of the {@code containing view}.
+     */
+    @UiThread
+    public void updatePositionInWindow() {
+        mContainingView.getLocationInWindow(mTmpIntArr);
+        mPositionInWindow.x = mTmpIntArr[0];
+        mPositionInWindow.y = mTmpIntArr[1];
+    }
+
+    public String toString() {
+        return "ScrollCaptureTarget{" + "view=" + mContainingView
+                + ", callback=" + mCallback
+                + ", scrollBounds=" + mScrollBounds
+                + ", localVisibleRect=" + mLocalVisibleRect
+                + ", positionInWindow=" + mPositionInWindow
+                + "}";
+    }
+
+    void dump(@NonNull PrintWriter writer) {
+        View view = getContainingView();
+        writer.println("view: " + view);
+        writer.println("hint: " + mHint);
+        writer.println("callback: " + mCallback);
+        writer.println("scrollBounds: "
+                + (mScrollBounds == null ? "null" : mScrollBounds.toShortString()));
+        Point inWindow = getPositionInWindow();
+        writer.println("positionInWindow: "
+                + ((inWindow == null) ? "null" : "[" + inWindow.x + "," + inWindow.y + "]"));
+        Rect localVisible = getLocalVisibleRect();
+        writer.println("localVisibleRect: "
+                + (localVisible == null ? "null" : localVisible.toShortString()));
+    }
+}
diff --git a/android/view/SearchEvent.java b/android/view/SearchEvent.java
new file mode 100644
index 0000000..72b5e4b
--- /dev/null
+++ b/android/view/SearchEvent.java
@@ -0,0 +1,38 @@
+/*
+ * 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.view;
+
+/**
+ * Class that contains information about an event that triggers a search.
+ */
+public class SearchEvent {
+
+    private InputDevice mInputDevice;
+
+    /** Create a new search event. */
+    public SearchEvent(InputDevice inputDevice) {
+        mInputDevice = inputDevice;
+    }
+
+    /**
+     * Returns the {@link InputDevice} that triggered the search.
+     * @return InputDevice the InputDevice that triggered the search.
+     */
+    public InputDevice getInputDevice() {
+        return mInputDevice;
+    }
+}
diff --git a/android/view/ShadowPainter.java b/android/view/ShadowPainter.java
new file mode 100644
index 0000000..788c6c3
--- /dev/null
+++ b/android/view/ShadowPainter.java
@@ -0,0 +1,424 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.NonNull;
+
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferInt;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.imageio.ImageIO;
+
+public class ShadowPainter {
+
+    /**
+     * Adds a drop shadow to a semi-transparent image (of an arbitrary shape) and returns it as a
+     * new image. This method attempts to mimic the same visual characteristics as the rectangular
+     * shadow painting methods in this class, {@link #createRectangularDropShadow(java.awt.image.BufferedImage)}
+     * and {@link #createSmallRectangularDropShadow(java.awt.image.BufferedImage)}.
+     * <p/>
+     * If shadowSize is less or equals to 1, no shadow will be painted and the source image will be
+     * returned instead.
+     *
+     * @param source the source image
+     * @param shadowSize the size of the shadow, normally {@link #SHADOW_SIZE or {@link
+     * #SMALL_SHADOW_SIZE}}
+     * @param alpha alpha value to apply to the shadow
+     *
+     * @return an image with the shadow painted in or the source image if shadowSize <= 1
+     */
+    @NonNull
+    public static BufferedImage createDropShadow(BufferedImage source, int shadowSize, float
+            alpha) {
+        shadowSize /= 2; // make shadow size have the same meaning as in the other shadow paint methods in this class
+
+        return createDropShadow(source, shadowSize, 0.7f * alpha, 0);
+    }
+
+    /**
+     * Creates a drop shadow of a given image and returns a new image which shows the input image on
+     * top of its drop shadow.
+     * <p/>
+     * <b>NOTE: If the shape is rectangular and opaque, consider using {@link
+     * #drawRectangleShadow(Graphics2D, int, int, int, int)} instead.</b>
+     *
+     * @param source the source image to be shadowed
+     * @param shadowSize the size of the shadow in pixels
+     * @param shadowOpacity the opacity of the shadow, with 0=transparent and 1=opaque
+     * @param shadowRgb the RGB int to use for the shadow color
+     *
+     * @return a new image with the source image on top of its shadow when shadowSize > 0 or the
+     * source image otherwise
+     */
+    @SuppressWarnings({"SuspiciousNameCombination", "UnnecessaryLocalVariable"})  // Imported code
+    public static BufferedImage createDropShadow(BufferedImage source, int shadowSize,
+            float shadowOpacity, int shadowRgb) {
+        if (shadowSize <= 0) {
+            return source;
+        }
+
+        // This code is based on
+        //      http://www.jroller.com/gfx/entry/non_rectangular_shadow
+
+        BufferedImage image;
+        int width = source.getWidth();
+        int height = source.getHeight();
+        image = new BufferedImage(width + SHADOW_SIZE, height + SHADOW_SIZE,
+                BufferedImage.TYPE_INT_ARGB);
+
+        Graphics2D g2 = image.createGraphics();
+        g2.drawImage(image, shadowSize, shadowSize, null);
+
+        int dstWidth = image.getWidth();
+        int dstHeight = image.getHeight();
+
+        int left = (shadowSize - 1) >> 1;
+        int right = shadowSize - left;
+        int xStart = left;
+        int xStop = dstWidth - right;
+        int yStart = left;
+        int yStop = dstHeight - right;
+
+        shadowRgb &= 0x00FFFFFF;
+
+        int[] aHistory = new int[shadowSize];
+        int historyIdx;
+
+        int aSum;
+
+        int[] dataBuffer = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
+        int lastPixelOffset = right * dstWidth;
+        float sumDivider = shadowOpacity / shadowSize;
+
+        // horizontal pass
+        for (int y = 0, bufferOffset = 0; y < dstHeight; y++, bufferOffset = y * dstWidth) {
+            aSum = 0;
+            historyIdx = 0;
+            for (int x = 0; x < shadowSize; x++, bufferOffset++) {
+                int a = dataBuffer[bufferOffset] >>> 24;
+                aHistory[x] = a;
+                aSum += a;
+            }
+
+            bufferOffset -= right;
+
+            for (int x = xStart; x < xStop; x++, bufferOffset++) {
+                int a = (int) (aSum * sumDivider);
+                dataBuffer[bufferOffset] = a << 24 | shadowRgb;
+
+                // subtract the oldest pixel from the sum
+                aSum -= aHistory[historyIdx];
+
+                // get the latest pixel
+                a = dataBuffer[bufferOffset + right] >>> 24;
+                aHistory[historyIdx] = a;
+                aSum += a;
+
+                if (++historyIdx >= shadowSize) {
+                    historyIdx -= shadowSize;
+                }
+            }
+        }
+        // vertical pass
+        for (int x = 0, bufferOffset = 0; x < dstWidth; x++, bufferOffset = x) {
+            aSum = 0;
+            historyIdx = 0;
+            for (int y = 0; y < shadowSize; y++, bufferOffset += dstWidth) {
+                int a = dataBuffer[bufferOffset] >>> 24;
+                aHistory[y] = a;
+                aSum += a;
+            }
+
+            bufferOffset -= lastPixelOffset;
+
+            for (int y = yStart; y < yStop; y++, bufferOffset += dstWidth) {
+                int a = (int) (aSum * sumDivider);
+                dataBuffer[bufferOffset] = a << 24 | shadowRgb;
+
+                // subtract the oldest pixel from the sum
+                aSum -= aHistory[historyIdx];
+
+                // get the latest pixel
+                a = dataBuffer[bufferOffset + lastPixelOffset] >>> 24;
+                aHistory[historyIdx] = a;
+                aSum += a;
+
+                if (++historyIdx >= shadowSize) {
+                    historyIdx -= shadowSize;
+                }
+            }
+        }
+
+        g2.drawImage(source, null, 0, 0);
+        g2.dispose();
+
+        return image;
+    }
+
+    /**
+     * Draws a rectangular drop shadow (of size {@link #SHADOW_SIZE} by {@link #SHADOW_SIZE} around
+     * the given source and returns a new image with both combined
+     *
+     * @param source the source image
+     *
+     * @return the source image with a drop shadow on the bottom and right
+     */
+    @SuppressWarnings("UnusedDeclaration")
+    public static BufferedImage createRectangularDropShadow(BufferedImage source) {
+        int type = source.getType();
+        if (type == BufferedImage.TYPE_CUSTOM) {
+            type = BufferedImage.TYPE_INT_ARGB;
+        }
+
+        int width = source.getWidth();
+        int height = source.getHeight();
+        BufferedImage image;
+        image = new BufferedImage(width + SHADOW_SIZE, height + SHADOW_SIZE, type);
+        Graphics2D g = image.createGraphics();
+        g.drawImage(source, 0, 0, null);
+        drawRectangleShadow(image, 0, 0, width, height);
+        g.dispose();
+
+        return image;
+    }
+
+    /**
+     * Draws a small rectangular drop shadow (of size {@link #SMALL_SHADOW_SIZE} by {@link
+     * #SMALL_SHADOW_SIZE} around the given source and returns a new image with both combined
+     *
+     * @param source the source image
+     *
+     * @return the source image with a drop shadow on the bottom and right
+     */
+    @SuppressWarnings("UnusedDeclaration")
+    public static BufferedImage createSmallRectangularDropShadow(BufferedImage source) {
+        int type = source.getType();
+        if (type == BufferedImage.TYPE_CUSTOM) {
+            type = BufferedImage.TYPE_INT_ARGB;
+        }
+
+        int width = source.getWidth();
+        int height = source.getHeight();
+
+        BufferedImage image;
+        image = new BufferedImage(width + SMALL_SHADOW_SIZE, height + SMALL_SHADOW_SIZE, type);
+
+        Graphics2D g = image.createGraphics();
+        g.drawImage(source, 0, 0, null);
+        drawSmallRectangleShadow(image, 0, 0, width, height);
+        g.dispose();
+
+        return image;
+    }
+
+    /**
+     * Draws a drop shadow for the given rectangle into the given context. It will not draw anything
+     * if the rectangle is smaller than a minimum determined by the assets used to draw the shadow
+     * graphics. The size of the shadow is {@link #SHADOW_SIZE}.
+     *
+     * @param image the image to draw the shadow into
+     * @param x the left coordinate of the left hand side of the rectangle
+     * @param y the top coordinate of the top of the rectangle
+     * @param width the width of the rectangle
+     * @param height the height of the rectangle
+     */
+    public static void drawRectangleShadow(BufferedImage image,
+            int x, int y, int width, int height) {
+        Graphics2D gc = image.createGraphics();
+        try {
+            drawRectangleShadow(gc, x, y, width, height);
+        } finally {
+            gc.dispose();
+        }
+    }
+
+    /**
+     * Draws a small drop shadow for the given rectangle into the given context. It will not draw
+     * anything if the rectangle is smaller than a minimum determined by the assets used to draw the
+     * shadow graphics. The size of the shadow is {@link #SMALL_SHADOW_SIZE}.
+     *
+     * @param image the image to draw the shadow into
+     * @param x the left coordinate of the left hand side of the rectangle
+     * @param y the top coordinate of the top of the rectangle
+     * @param width the width of the rectangle
+     * @param height the height of the rectangle
+     */
+    public static void drawSmallRectangleShadow(BufferedImage image,
+            int x, int y, int width, int height) {
+        Graphics2D gc = image.createGraphics();
+        try {
+            drawSmallRectangleShadow(gc, x, y, width, height);
+        } finally {
+            gc.dispose();
+        }
+    }
+
+    /**
+     * The width and height of the drop shadow painted by
+     * {@link #drawRectangleShadow(Graphics2D, int, int, int, int)}
+     */
+    public static final int SHADOW_SIZE = 20; // DO NOT EDIT. This corresponds to bitmap graphics
+
+    /**
+     * The width and height of the drop shadow painted by
+     * {@link #drawSmallRectangleShadow(Graphics2D, int, int, int, int)}
+     */
+    public static final int SMALL_SHADOW_SIZE = 10; // DO NOT EDIT. Corresponds to bitmap graphics
+
+    /**
+     * Draws a drop shadow for the given rectangle into the given context. It will not draw anything
+     * if the rectangle is smaller than a minimum determined by the assets used to draw the shadow
+     * graphics.
+     *
+     * @param gc the graphics context to draw into
+     * @param x the left coordinate of the left hand side of the rectangle
+     * @param y the top coordinate of the top of the rectangle
+     * @param width the width of the rectangle
+     * @param height the height of the rectangle
+     */
+    public static void drawRectangleShadow(Graphics2D gc, int x, int y, int width, int height) {
+        assert ShadowBottomLeft != null;
+        assert ShadowBottomRight.getWidth(null) == SHADOW_SIZE;
+        assert ShadowBottomRight.getHeight(null) == SHADOW_SIZE;
+
+        int blWidth = ShadowBottomLeft.getWidth(null);
+        int trHeight = ShadowTopRight.getHeight(null);
+        if (width < blWidth) {
+            return;
+        }
+        if (height < trHeight) {
+            return;
+        }
+
+        gc.drawImage(ShadowBottomLeft, x - ShadowBottomLeft.getWidth(null), y + height, null);
+        gc.drawImage(ShadowBottomRight, x + width, y + height, null);
+        gc.drawImage(ShadowTopRight, x + width, y, null);
+        gc.drawImage(ShadowTopLeft, x - ShadowTopLeft.getWidth(null), y, null);
+        gc.drawImage(ShadowBottom,
+                x, y + height, x + width, y + height + ShadowBottom.getHeight(null),
+                0, 0, ShadowBottom.getWidth(null), ShadowBottom.getHeight(null), null);
+        gc.drawImage(ShadowRight,
+                x + width, y + ShadowTopRight.getHeight(null), x + width + ShadowRight.getWidth(null), y + height,
+                0, 0, ShadowRight.getWidth(null), ShadowRight.getHeight(null), null);
+        gc.drawImage(ShadowLeft,
+                x - ShadowLeft.getWidth(null), y + ShadowTopLeft.getHeight(null), x, y + height,
+                0, 0, ShadowLeft.getWidth(null), ShadowLeft.getHeight(null), null);
+    }
+
+    /**
+     * Draws a small drop shadow for the given rectangle into the given context. It will not draw
+     * anything if the rectangle is smaller than a minimum determined by the assets used to draw the
+     * shadow graphics.
+     * <p/>
+     *
+     * @param gc the graphics context to draw into
+     * @param x the left coordinate of the left hand side of the rectangle
+     * @param y the top coordinate of the top of the rectangle
+     * @param width the width of the rectangle
+     * @param height the height of the rectangle
+     */
+    public static void drawSmallRectangleShadow(Graphics2D gc, int x, int y, int width,
+            int height) {
+        assert Shadow2BottomLeft != null;
+        assert Shadow2TopRight != null;
+        assert Shadow2BottomRight.getWidth(null) == SMALL_SHADOW_SIZE;
+        assert Shadow2BottomRight.getHeight(null) == SMALL_SHADOW_SIZE;
+
+        int blWidth = Shadow2BottomLeft.getWidth(null);
+        int trHeight = Shadow2TopRight.getHeight(null);
+        if (width < blWidth) {
+            return;
+        }
+        if (height < trHeight) {
+            return;
+        }
+
+        gc.drawImage(Shadow2BottomLeft, x - Shadow2BottomLeft.getWidth(null), y + height, null);
+        gc.drawImage(Shadow2BottomRight, x + width, y + height, null);
+        gc.drawImage(Shadow2TopRight, x + width, y, null);
+        gc.drawImage(Shadow2TopLeft, x - Shadow2TopLeft.getWidth(null), y, null);
+        gc.drawImage(Shadow2Bottom,
+                x, y + height, x + width, y + height + Shadow2Bottom.getHeight(null),
+                0, 0, Shadow2Bottom.getWidth(null), Shadow2Bottom.getHeight(null), null);
+        gc.drawImage(Shadow2Right,
+                x + width, y + Shadow2TopRight.getHeight(null), x + width + Shadow2Right.getWidth(null), y + height,
+                0, 0, Shadow2Right.getWidth(null), Shadow2Right.getHeight(null), null);
+        gc.drawImage(Shadow2Left,
+                x - Shadow2Left.getWidth(null), y + Shadow2TopLeft.getHeight(null), x, y + height,
+                0, 0, Shadow2Left.getWidth(null), Shadow2Left.getHeight(null), null);
+    }
+
+    private static Image loadIcon(String name) {
+        InputStream inputStream = ShadowPainter.class.getResourceAsStream(name);
+        if (inputStream == null) {
+            throw new RuntimeException("Unable to load image for shadow: " + name);
+        }
+        try {
+            return ImageIO.read(inputStream);
+        } catch (IOException e) {
+            throw new RuntimeException("Unable to load image for shadow:" + name, e);
+        } finally {
+            try {
+                inputStream.close();
+            } catch (IOException e) {
+                // ignore.
+            }
+        }
+    }
+
+    // Shadow graphics. This was generated by creating a drop shadow in
+    // Gimp, using the parameters x offset=10, y offset=10, blur radius=10,
+    // (for the small drop shadows x offset=10, y offset=10, blur radius=10)
+    // color=black, and opacity=51. These values attempt to make a shadow
+    // that is legible both for dark and light themes, on top of the
+    // canvas background (rgb(150,150,150). Darker shadows would tend to
+    // blend into the foreground for a dark holo screen, and lighter shadows
+    // would be hard to spot on the canvas background. If you make adjustments,
+    // make sure to check the shadow with both dark and light themes.
+    //
+    // After making the graphics, I cut out the top right, bottom left
+    // and bottom right corners as 20x20 images, and these are reproduced by
+    // painting them in the corresponding places in the target graphics context.
+    // I then grabbed a single horizontal gradient line from the middle of the
+    // right edge,and a single vertical gradient line from the bottom. These
+    // are then painted scaled/stretched in the target to fill the gaps between
+    // the three corner images.
+    //
+    // Filenames: bl=bottom left, b=bottom, br=bottom right, r=right, tr=top right
+
+    // Normal Drop Shadow
+    private static final Image ShadowBottom = loadIcon("/icons/shadow-b.png");
+    private static final Image ShadowBottomLeft = loadIcon("/icons/shadow-bl.png");
+    private static final Image ShadowBottomRight = loadIcon("/icons/shadow-br.png");
+    private static final Image ShadowRight = loadIcon("/icons/shadow-r.png");
+    private static final Image ShadowTopRight = loadIcon("/icons/shadow-tr.png");
+    private static final Image ShadowTopLeft = loadIcon("/icons/shadow-tl.png");
+    private static final Image ShadowLeft = loadIcon("/icons/shadow-l.png");
+
+    // Small Drop Shadow
+    private static final Image Shadow2Bottom = loadIcon("/icons/shadow2-b.png");
+    private static final Image Shadow2BottomLeft = loadIcon("/icons/shadow2-bl.png");
+    private static final Image Shadow2BottomRight = loadIcon("/icons/shadow2-br.png");
+    private static final Image Shadow2Right = loadIcon("/icons/shadow2-r.png");
+    private static final Image Shadow2TopRight = loadIcon("/icons/shadow2-tr.png");
+    private static final Image Shadow2TopLeft = loadIcon("/icons/shadow2-tl.png");
+    private static final Image Shadow2Left = loadIcon("/icons/shadow2-l.png");
+}
diff --git a/android/view/SoundEffectConstants.java b/android/view/SoundEffectConstants.java
new file mode 100644
index 0000000..bd86a47
--- /dev/null
+++ b/android/view/SoundEffectConstants.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.IntDef;
+import android.media.AudioManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Random;
+
+/**
+ * Constants to be used to play sound effects via {@link View#playSoundEffect(int)}
+ */
+public class SoundEffectConstants {
+
+    private SoundEffectConstants() {}
+    private static final Random NAVIGATION_REPEAT_RANDOMIZER = new Random();
+    private static int sLastNavigationRepeatSoundEffectId = -1;
+
+    public static final int CLICK = 0;
+
+    /** Effect id for a navigation left */
+    public static final int NAVIGATION_LEFT = 1;
+    /** Effect id for a navigation up */
+    public static final int NAVIGATION_UP = 2;
+    /** Effect id for a navigation right */
+    public static final int NAVIGATION_RIGHT = 3;
+    /** Effect id for a navigation down */
+    public static final int NAVIGATION_DOWN = 4;
+    /** Effect id for a repeatedly triggered navigation left, e.g. due to long pressing a button */
+    public static final int NAVIGATION_REPEAT_LEFT = 5;
+    /** Effect id for a repeatedly triggered navigation up, e.g. due to long pressing a button */
+    public static final int NAVIGATION_REPEAT_UP = 6;
+    /** Effect id for a repeatedly triggered navigation right, e.g. due to long pressing a button */
+    public static final int NAVIGATION_REPEAT_RIGHT = 7;
+    /** Effect id for a repeatedly triggered navigation down, e.g. due to long pressing a button */
+    public static final int NAVIGATION_REPEAT_DOWN = 8;
+
+    /** @hide */
+    @IntDef(value = {
+            CLICK,
+            NAVIGATION_LEFT,
+            NAVIGATION_UP,
+            NAVIGATION_RIGHT,
+            NAVIGATION_DOWN,
+            NAVIGATION_REPEAT_LEFT,
+            NAVIGATION_REPEAT_UP,
+            NAVIGATION_REPEAT_RIGHT,
+            NAVIGATION_REPEAT_DOWN
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SoundEffect {}
+
+    /** @hide */
+    @IntDef(prefix = { "NAVIGATION_" }, value = {
+            NAVIGATION_LEFT,
+            NAVIGATION_UP,
+            NAVIGATION_RIGHT,
+            NAVIGATION_DOWN,
+            NAVIGATION_REPEAT_LEFT,
+            NAVIGATION_REPEAT_UP,
+            NAVIGATION_REPEAT_RIGHT,
+            NAVIGATION_REPEAT_DOWN
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface NavigationSoundEffect {}
+
+    /**
+     * Get the sonification constant for the focus directions.
+     * @param direction The direction of the focus.
+     * @return The appropriate sonification constant.
+     * @throws {@link IllegalArgumentException} when the passed direction is not one of the
+     *     documented values.
+     */
+    public static int getContantForFocusDirection(@View.FocusDirection int direction) {
+        switch (direction) {
+            case View.FOCUS_RIGHT:
+                return SoundEffectConstants.NAVIGATION_RIGHT;
+            case View.FOCUS_FORWARD:
+            case View.FOCUS_DOWN:
+                return SoundEffectConstants.NAVIGATION_DOWN;
+            case View.FOCUS_LEFT:
+                return SoundEffectConstants.NAVIGATION_LEFT;
+            case View.FOCUS_BACKWARD:
+            case View.FOCUS_UP:
+                return SoundEffectConstants.NAVIGATION_UP;
+        }
+        throw new IllegalArgumentException("direction must be one of "
+                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, FOCUS_FORWARD, FOCUS_BACKWARD}.");
+    }
+
+    /**
+     * Get the sonification constant for the focus directions
+     * @param direction The direction of the focus.
+     * @param repeating True if the user long-presses a direction
+     * @return The appropriate sonification constant
+     * @throws IllegalArgumentException when the passed direction is not one of the
+     *      documented values.
+     */
+    public static @NavigationSoundEffect int getConstantForFocusDirection(
+            @View.FocusDirection int direction, boolean repeating) {
+        if (repeating) {
+            switch (direction) {
+                case View.FOCUS_RIGHT:
+                    return SoundEffectConstants.NAVIGATION_REPEAT_RIGHT;
+                case View.FOCUS_FORWARD:
+                case View.FOCUS_DOWN:
+                    return SoundEffectConstants.NAVIGATION_REPEAT_DOWN;
+                case View.FOCUS_LEFT:
+                    return SoundEffectConstants.NAVIGATION_REPEAT_LEFT;
+                case View.FOCUS_BACKWARD:
+                case View.FOCUS_UP:
+                    return SoundEffectConstants.NAVIGATION_REPEAT_UP;
+            }
+            throw new IllegalArgumentException("direction must be one of {FOCUS_UP, FOCUS_DOWN, "
+                    + "FOCUS_LEFT, FOCUS_RIGHT, FOCUS_FORWARD, FOCUS_BACKWARD}.");
+        } else {
+            return getContantForFocusDirection(direction);
+        }
+    }
+
+    /**
+     * @param effectId any of the effect ids defined in {@link SoundEffectConstants}
+     * @return true if the given effect id is a navigation repeat one
+     * @hide
+     */
+    @VisibleForTesting(visibility = Visibility.PACKAGE)
+    public static boolean isNavigationRepeat(@NavigationSoundEffect int effectId) {
+        return effectId == SoundEffectConstants.NAVIGATION_REPEAT_DOWN
+                || effectId == SoundEffectConstants.NAVIGATION_REPEAT_LEFT
+                || effectId == SoundEffectConstants.NAVIGATION_REPEAT_RIGHT
+                || effectId == SoundEffectConstants.NAVIGATION_REPEAT_UP;
+    }
+
+    /**
+     * @return The next navigation repeat sound effect id, chosen at random in a non-repeating
+     * fashion
+     * @hide
+     */
+    @VisibleForTesting(visibility = Visibility.PACKAGE)
+    public static int nextNavigationRepeatSoundEffectId() {
+        int next = NAVIGATION_REPEAT_RANDOMIZER.nextInt(
+                AudioManager.NUM_NAVIGATION_REPEAT_SOUND_EFFECTS - 1);
+        if (next >= sLastNavigationRepeatSoundEffectId) {
+            next++;
+        }
+        sLastNavigationRepeatSoundEffectId = next;
+        return AudioManager.getNthNavigationRepeatSoundEffect(next);
+    }
+}
diff --git a/android/view/SubMenu.java b/android/view/SubMenu.java
new file mode 100644
index 0000000..38662b0
--- /dev/null
+++ b/android/view/SubMenu.java
@@ -0,0 +1,111 @@
+/*
+ * 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.view;
+
+import android.annotation.DrawableRes;
+import android.annotation.StringRes;
+import android.graphics.drawable.Drawable;
+
+/**
+ * Subclass of {@link Menu} for sub menus.
+ * <p>
+ * Sub menus do not support item icons, or nested sub menus.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For information about creating menus, read the
+ * <a href="{@docRoot}guide/topics/ui/menus.html">Menus</a> developer guide.</p>
+ * </div>
+ */
+
+public interface SubMenu extends Menu {
+    /**
+     * Sets the submenu header's title to the title given in <var>titleRes</var>
+     * resource identifier.
+     * 
+     * @param titleRes The string resource identifier used for the title.
+     * @return This SubMenu so additional setters can be called.
+     */
+    public SubMenu setHeaderTitle(@StringRes int titleRes);
+
+    /**
+     * Sets the submenu header's title to the title given in <var>title</var>.
+     * 
+     * @param title The character sequence used for the title.
+     * @return This SubMenu so additional setters can be called.
+     */
+    public SubMenu setHeaderTitle(CharSequence title);
+    
+    /**
+     * Sets the submenu header's icon to the icon given in <var>iconRes</var>
+     * resource id.
+     * 
+     * @param iconRes The resource identifier used for the icon.
+     * @return This SubMenu so additional setters can be called.
+     */
+    public SubMenu setHeaderIcon(@DrawableRes int iconRes);
+
+    /**
+     * Sets the submenu header's icon to the icon given in <var>icon</var>
+     * {@link Drawable}.
+     * 
+     * @param icon The {@link Drawable} used for the icon.
+     * @return This SubMenu so additional setters can be called.
+     */
+    public SubMenu setHeaderIcon(Drawable icon);
+    
+    /**
+     * Sets the header of the submenu to the {@link View} given in
+     * <var>view</var>. This replaces the header title and icon (and those
+     * replace this).
+     * 
+     * @param view The {@link View} used for the header.
+     * @return This SubMenu so additional setters can be called.
+     */
+    public SubMenu setHeaderView(View view);
+    
+    /**
+     * Clears the header of the submenu.
+     */
+    public void clearHeader();
+    
+    /**
+     * Change the icon associated with this submenu's item in its parent menu.
+     * 
+     * @see MenuItem#setIcon(int)
+     * @param iconRes The new icon (as a resource ID) to be displayed.
+     * @return This SubMenu so additional setters can be called.
+     */
+    public SubMenu setIcon(@DrawableRes int iconRes);
+    
+    /**
+     * Change the icon associated with this submenu's item in its parent menu.
+     * 
+     * @see MenuItem#setIcon(Drawable)
+     * @param icon The new icon (as a Drawable) to be displayed.
+     * @return This SubMenu so additional setters can be called.
+     */
+    public SubMenu setIcon(Drawable icon);
+    
+    /**
+     * Gets the {@link MenuItem} that represents this submenu in the parent
+     * menu.  Use this for setting additional item attributes.
+     * 
+     * @return The {@link MenuItem} that launches the submenu when invoked.
+     */
+    public MenuItem getItem();
+}
diff --git a/android/view/Surface.java b/android/view/Surface.java
new file mode 100644
index 0000000..ff2d2eb
--- /dev/null
+++ b/android/view/Surface.java
@@ -0,0 +1,1120 @@
+/*
+ * 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.view;
+
+import static android.system.OsConstants.EINVAL;
+
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.ActivityInfo;
+import android.content.res.CompatibilityInfo.Translator;
+import android.graphics.BLASTBufferQueue;
+import android.graphics.Canvas;
+import android.graphics.ColorSpace;
+import android.graphics.HardwareRenderer;
+import android.graphics.Matrix;
+import android.graphics.RecordingCanvas;
+import android.graphics.Rect;
+import android.graphics.RenderNode;
+import android.graphics.SurfaceTexture;
+import android.hardware.HardwareBuffer;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import dalvik.system.CloseGuard;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Handle onto a raw buffer that is being managed by the screen compositor.
+ *
+ * <p>A Surface is generally created by or from a consumer of image buffers (such as a
+ * {@link android.graphics.SurfaceTexture}, {@link android.media.MediaRecorder}, or
+ * {@link android.renderscript.Allocation}), and is handed to some kind of producer (such as
+ * {@link android.opengl.EGL14#eglCreateWindowSurface(android.opengl.EGLDisplay,android.opengl.EGLConfig,java.lang.Object,int[],int) OpenGL},
+ * {@link android.media.MediaPlayer#setSurface MediaPlayer}, or
+ * {@link android.hardware.camera2.CameraDevice#createCaptureSession CameraDevice}) to draw
+ * into.</p>
+ *
+ * <p><strong>Note:</strong> A Surface acts like a
+ * {@link java.lang.ref.WeakReference weak reference} to the consumer it is associated with. By
+ * itself it will not keep its parent consumer from being reclaimed.</p>
+ */
+public class Surface implements Parcelable {
+    private static final String TAG = "Surface";
+
+    private static native long nativeCreateFromSurfaceTexture(SurfaceTexture surfaceTexture)
+            throws OutOfResourcesException;
+
+    private static native long nativeCreateFromSurfaceControl(long surfaceControlNativeObject);
+    private static native long nativeGetFromSurfaceControl(long surfaceObject,
+            long surfaceControlNativeObject);
+    private static native long nativeGetFromBlastBufferQueue(long surfaceObject,
+                                                             long blastBufferQueueNativeObject);
+
+    private static native long nativeLockCanvas(long nativeObject, Canvas canvas, Rect dirty)
+            throws OutOfResourcesException;
+    private static native void nativeUnlockCanvasAndPost(long nativeObject, Canvas canvas);
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private static native void nativeRelease(long nativeObject);
+    private static native boolean nativeIsValid(long nativeObject);
+    private static native boolean nativeIsConsumerRunningBehind(long nativeObject);
+    private static native long nativeReadFromParcel(long nativeObject, Parcel source);
+    private static native void nativeWriteToParcel(long nativeObject, Parcel dest);
+
+    private static native void nativeAllocateBuffers(long nativeObject);
+
+    private static native int nativeGetWidth(long nativeObject);
+    private static native int nativeGetHeight(long nativeObject);
+
+    private static native long nativeGetNextFrameNumber(long nativeObject);
+    private static native int nativeSetScalingMode(long nativeObject, int scalingMode);
+    private static native int nativeForceScopedDisconnect(long nativeObject);
+    private static native int nativeAttachAndQueueBufferWithColorSpace(long nativeObject,
+            HardwareBuffer buffer, int colorSpaceId);
+
+    private static native int nativeSetSharedBufferModeEnabled(long nativeObject, boolean enabled);
+    private static native int nativeSetAutoRefreshEnabled(long nativeObject, boolean enabled);
+
+    private static native int nativeSetFrameRate(
+            long nativeObject, float frameRate, int compatibility, int changeFrameRateStrategy);
+
+    public static final @android.annotation.NonNull Parcelable.Creator<Surface> CREATOR =
+            new Parcelable.Creator<Surface>() {
+        @Override
+        public Surface createFromParcel(Parcel source) {
+            try {
+                Surface s = new Surface();
+                s.readFromParcel(source);
+                return s;
+            } catch (Exception e) {
+                Log.e(TAG, "Exception creating surface from parcel", e);
+                return null;
+            }
+        }
+
+        @Override
+        public Surface[] newArray(int size) {
+            return new Surface[size];
+        }
+    };
+
+    private final CloseGuard mCloseGuard = CloseGuard.get();
+
+    // Guarded state.
+    @UnsupportedAppUsage
+    final Object mLock = new Object(); // protects the native state
+    @UnsupportedAppUsage
+    private String mName;
+    @UnsupportedAppUsage
+    long mNativeObject; // package scope only for SurfaceControl access
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private long mLockedObject;
+    private int mGenerationId; // incremented each time mNativeObject changes
+    private final Canvas mCanvas = new CompatibleCanvas();
+
+    // A matrix to scale the matrix set by application. This is set to null for
+    // non compatibility mode.
+    private Matrix mCompatibleMatrix;
+
+    private HwuiContext mHwuiContext;
+
+    private boolean mIsSingleBuffered;
+    private boolean mIsSharedBufferModeEnabled;
+    private boolean mIsAutoRefreshEnabled;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "SCALING_MODE_" }, value = {
+            SCALING_MODE_FREEZE,
+            SCALING_MODE_SCALE_TO_WINDOW,
+            SCALING_MODE_SCALE_CROP,
+            SCALING_MODE_NO_SCALE_CROP
+    })
+    public @interface ScalingMode {}
+    // From system/window.h
+    /** @hide */
+    public static final int SCALING_MODE_FREEZE = 0;
+    /** @hide */
+    public static final int SCALING_MODE_SCALE_TO_WINDOW = 1;
+    /** @hide */
+    public static final int SCALING_MODE_SCALE_CROP = 2;
+    /** @hide */
+    public static final int SCALING_MODE_NO_SCALE_CROP = 3;
+
+    /** @hide */
+    @IntDef(prefix = { "ROTATION_" }, value = {
+            ROTATION_0,
+            ROTATION_90,
+            ROTATION_180,
+            ROTATION_270
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Rotation {}
+
+    /**
+     * Rotation constant: 0 degree rotation (natural orientation)
+     */
+    public static final int ROTATION_0 = 0;
+
+    /**
+     * Rotation constant: 90 degree rotation.
+     */
+    public static final int ROTATION_90 = 1;
+
+    /**
+     * Rotation constant: 180 degree rotation.
+     */
+    public static final int ROTATION_180 = 2;
+
+    /**
+     * Rotation constant: 270 degree rotation.
+     */
+    public static final int ROTATION_270 = 3;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"FRAME_RATE_COMPATIBILITY_"},
+            value = {FRAME_RATE_COMPATIBILITY_DEFAULT, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE})
+    public @interface FrameRateCompatibility {}
+
+    // From native_window.h. Keep these in sync.
+    /**
+     * There are no inherent restrictions on the frame rate of this surface. When the
+     * system selects a frame rate other than what the app requested, the app will be able
+     * to run at the system frame rate without requiring pull down. This value should be
+     * used when displaying game content, UIs, and anything that isn't video.
+     */
+    public static final int FRAME_RATE_COMPATIBILITY_DEFAULT = 0;
+
+    /**
+     * This surface is being used to display content with an inherently fixed frame rate,
+     * e.g. a video that has a specific frame rate. When the system selects a frame rate
+     * other than what the app requested, the app will need to do pull down or use some
+     * other technique to adapt to the system's frame rate. The user experience is likely
+     * to be worse (e.g. more frame stuttering) than it would be if the system had chosen
+     * the app's requested frame rate. This value should be used for video content.
+     */
+    public static final int FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1;
+
+    /**
+     * This surface belongs to an app on the High Refresh Rate Deny list, and needs the display
+     * to operate at the exact frame rate.
+     *
+     * This is used internally by the platform and should not be used by apps.
+     * @hide
+     */
+    public static final int FRAME_RATE_COMPATIBILITY_EXACT = 100;
+
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CHANGE_FRAME_RATE_"},
+            value = {CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, CHANGE_FRAME_RATE_ALWAYS})
+    public @interface ChangeFrameRateStrategy {}
+
+    /**
+     * Change the frame rate only if the transition is going to be seamless.
+     */
+    public static final int CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS = 0;
+
+    /**
+     * Change the frame rate even if the transition is going to be non-seamless, i.e. with visual
+     * interruptions for the user. Non-seamless switches might be used when the benefit of matching
+     * the content's frame rate outweighs the cost of the transition, for example when
+     * displaying long-running video content.
+     */
+    public static final int CHANGE_FRAME_RATE_ALWAYS = 1;
+
+    /**
+     * Create an empty surface, which will later be filled in by readFromParcel().
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public Surface() {
+    }
+
+    /**
+     * Create a Surface associated with a given {@link SurfaceControl}. Buffers submitted to this
+     * surface will be displayed by the system compositor according to the parameters
+     * specified by the control. Multiple surfaces may be constructed from one SurfaceControl,
+     * but only one can be connected (e.g. have an active EGL context) at a time.
+     *
+     * @param from The SurfaceControl to associate this Surface with
+     */
+    public Surface(@NonNull SurfaceControl from) {
+        copyFrom(from);
+    }
+
+    /**
+     * Create Surface from a {@link SurfaceTexture}.
+     *
+     * Images drawn to the Surface will be made available to the {@link
+     * SurfaceTexture}, which can attach them to an OpenGL ES texture via {@link
+     * SurfaceTexture#updateTexImage}.
+     *
+     * Please note that holding onto the Surface created here is not enough to
+     * keep the provided SurfaceTexture from being reclaimed.  In that sense,
+     * the Surface will act like a
+     * {@link java.lang.ref.WeakReference weak reference} to the SurfaceTexture.
+     *
+     * @param surfaceTexture The {@link SurfaceTexture} that is updated by this
+     * Surface.
+     * @throws OutOfResourcesException if the surface could not be created.
+     */
+    public Surface(SurfaceTexture surfaceTexture) {
+        if (surfaceTexture == null) {
+            throw new IllegalArgumentException("surfaceTexture must not be null");
+        }
+        mIsSingleBuffered = surfaceTexture.isSingleBuffered();
+        synchronized (mLock) {
+            mName = surfaceTexture.toString();
+            setNativeObjectLocked(nativeCreateFromSurfaceTexture(surfaceTexture));
+        }
+    }
+
+    /* called from android_view_Surface_createFromIGraphicBufferProducer() */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private Surface(long nativeObject) {
+        synchronized (mLock) {
+            setNativeObjectLocked(nativeObject);
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mCloseGuard != null) {
+                mCloseGuard.warnIfOpen();
+            }
+            release();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Release the local reference to the server-side surface.
+     * Always call release() when you're done with a Surface.
+     * This will make the surface invalid.
+     */
+    public void release() {
+        synchronized (mLock) {
+            if (mHwuiContext != null) {
+                mHwuiContext.destroy();
+                mHwuiContext = null;
+            }
+            if (mNativeObject != 0) {
+                nativeRelease(mNativeObject);
+                setNativeObjectLocked(0);
+            }
+        }
+    }
+
+    /**
+     * Free all server-side state associated with this surface and
+     * release this object's reference.  This method can only be
+     * called from the process that created the service.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void destroy() {
+        release();
+    }
+
+    /**
+     * Destroys the HwuiContext without completely
+     * releasing the Surface.
+     * @hide
+     */
+    public void hwuiDestroy() {
+        if (mHwuiContext != null) {
+            mHwuiContext.destroy();
+            mHwuiContext = null;
+        }
+    }
+
+    /**
+     * Returns true if this object holds a valid surface.
+     *
+     * @return True if it holds a physical surface, so lockCanvas() will succeed.
+     * Otherwise returns false.
+     */
+    public boolean isValid() {
+        synchronized (mLock) {
+            if (mNativeObject == 0) return false;
+            return nativeIsValid(mNativeObject);
+        }
+    }
+
+    /**
+     * Gets the generation number of this surface, incremented each time
+     * the native surface contained within this object changes.
+     *
+     * @return The current generation number.
+     * @hide
+     */
+    public int getGenerationId() {
+        synchronized (mLock) {
+            return mGenerationId;
+        }
+    }
+
+    /**
+     * Returns the next frame number which will be dequeued for rendering.
+     * Intended for use with SurfaceFlinger's deferred transactions API.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public long getNextFrameNumber() {
+        synchronized (mLock) {
+            checkNotReleasedLocked();
+            return nativeGetNextFrameNumber(mNativeObject);
+        }
+    }
+
+    /**
+     * Returns true if the consumer of this Surface is running behind the producer.
+     *
+     * @return True if the consumer is more than one buffer ahead of the producer.
+     * @hide
+     */
+    public boolean isConsumerRunningBehind() {
+        synchronized (mLock) {
+            checkNotReleasedLocked();
+            return nativeIsConsumerRunningBehind(mNativeObject);
+        }
+    }
+
+    /**
+     * Gets a {@link Canvas} for drawing into this surface.
+     *
+     * After drawing into the provided {@link Canvas}, the caller must
+     * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
+     *
+     * @param inOutDirty A rectangle that represents the dirty region that the caller wants
+     * to redraw.  This function may choose to expand the dirty rectangle if for example
+     * the surface has been resized or if the previous contents of the surface were
+     * not available.  The caller must redraw the entire dirty region as represented
+     * by the contents of the inOutDirty rectangle upon return from this function.
+     * The caller may also pass <code>null</code> instead, in the case where the
+     * entire surface should be redrawn.
+     * @return A canvas for drawing into the surface.
+     *
+     * @throws IllegalArgumentException If the inOutDirty rectangle is not valid.
+     * @throws OutOfResourcesException If the canvas cannot be locked.
+     */
+    public Canvas lockCanvas(Rect inOutDirty)
+            throws Surface.OutOfResourcesException, IllegalArgumentException {
+        synchronized (mLock) {
+            checkNotReleasedLocked();
+            if (mLockedObject != 0) {
+                // Ideally, nativeLockCanvas() would throw in this situation and prevent the
+                // double-lock, but that won't happen if mNativeObject was updated.  We can't
+                // abandon the old mLockedObject because it might still be in use, so instead
+                // we just refuse to re-lock the Surface.
+                throw new IllegalArgumentException("Surface was already locked");
+            }
+            mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
+            return mCanvas;
+        }
+    }
+
+    /**
+     * Posts the new contents of the {@link Canvas} to the surface and
+     * releases the {@link Canvas}.
+     *
+     * @param canvas The canvas previously obtained from {@link #lockCanvas}.
+     */
+    public void unlockCanvasAndPost(Canvas canvas) {
+        synchronized (mLock) {
+            checkNotReleasedLocked();
+
+            if (mHwuiContext != null) {
+                mHwuiContext.unlockAndPost(canvas);
+            } else {
+                unlockSwCanvasAndPost(canvas);
+            }
+        }
+    }
+
+    private void unlockSwCanvasAndPost(Canvas canvas) {
+        if (canvas != mCanvas) {
+            throw new IllegalArgumentException("canvas object must be the same instance that "
+                    + "was previously returned by lockCanvas");
+        }
+        if (mNativeObject != mLockedObject) {
+            Log.w(TAG, "WARNING: Surface's mNativeObject (0x" +
+                    Long.toHexString(mNativeObject) + ") != mLockedObject (0x" +
+                    Long.toHexString(mLockedObject) +")");
+        }
+        if (mLockedObject == 0) {
+            throw new IllegalStateException("Surface was not locked");
+        }
+        try {
+            nativeUnlockCanvasAndPost(mLockedObject, canvas);
+        } finally {
+            nativeRelease(mLockedObject);
+            mLockedObject = 0;
+        }
+    }
+
+    /**
+     * Gets a {@link Canvas} for drawing into this surface.
+     *
+     * After drawing into the provided {@link Canvas}, the caller must
+     * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
+     *
+     * Unlike {@link #lockCanvas(Rect)} this will return a hardware-accelerated
+     * canvas. See the <a href="{@docRoot}guide/topics/graphics/hardware-accel.html#unsupported">
+     * unsupported drawing operations</a> for a list of what is and isn't
+     * supported in a hardware-accelerated canvas. It is also required to
+     * fully cover the surface every time {@link #lockHardwareCanvas()} is
+     * called as the buffer is not preserved between frames. Partial updates
+     * are not supported.
+     *
+     * @return A canvas for drawing into the surface.
+     *
+     * @throws IllegalStateException If the canvas cannot be locked.
+     */
+    public Canvas lockHardwareCanvas() {
+        synchronized (mLock) {
+            checkNotReleasedLocked();
+            if (mHwuiContext == null) {
+                mHwuiContext = new HwuiContext(false);
+            }
+            return mHwuiContext.lockCanvas(
+                    nativeGetWidth(mNativeObject),
+                    nativeGetHeight(mNativeObject));
+        }
+    }
+
+    /**
+     * Gets a {@link Canvas} for drawing into this surface that supports wide color gamut.
+     *
+     * After drawing into the provided {@link Canvas}, the caller must
+     * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
+     *
+     * Unlike {@link #lockCanvas(Rect)} and {@link #lockHardwareCanvas()},
+     * this will return a hardware-accelerated canvas that supports wide color gamut.
+     * See the <a href="{@docRoot}guide/topics/graphics/hardware-accel.html#unsupported">
+     * unsupported drawing operations</a> for a list of what is and isn't
+     * supported in a hardware-accelerated canvas. It is also required to
+     * fully cover the surface every time {@link #lockHardwareCanvas()} is
+     * called as the buffer is not preserved between frames. Partial updates
+     * are not supported.
+     *
+     * @return A canvas for drawing into the surface.
+     *
+     * @throws IllegalStateException If the canvas cannot be locked.
+     *
+     * @hide
+     */
+    public Canvas lockHardwareWideColorGamutCanvas() {
+        synchronized (mLock) {
+            checkNotReleasedLocked();
+            if (mHwuiContext != null && !mHwuiContext.isWideColorGamut()) {
+                mHwuiContext.destroy();
+                mHwuiContext = null;
+            }
+            if (mHwuiContext == null) {
+                mHwuiContext = new HwuiContext(true);
+            }
+            return mHwuiContext.lockCanvas(
+                    nativeGetWidth(mNativeObject),
+                    nativeGetHeight(mNativeObject));
+        }
+    }
+
+    /**
+     * @deprecated This API has been removed and is not supported.  Do not use.
+     */
+    @Deprecated
+    public void unlockCanvas(Canvas canvas) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Sets the translator used to scale canvas's width/height in compatibility
+     * mode.
+     */
+    void setCompatibilityTranslator(Translator translator) {
+        if (translator != null) {
+            float appScale = translator.applicationScale;
+            mCompatibleMatrix = new Matrix();
+            mCompatibleMatrix.setScale(appScale, appScale);
+        }
+    }
+
+    private void updateNativeObject(long newNativeObject) {
+        synchronized (mLock) {
+            if (newNativeObject == mNativeObject) {
+                return;
+            }
+            if (mNativeObject != 0) {
+                nativeRelease(mNativeObject);
+            }
+            setNativeObjectLocked(newNativeObject);
+        }
+    }
+
+    /**
+     * Copy another surface to this one.  This surface now holds a reference
+     * to the same data as the original surface, and is -not- the owner.
+     * This is for use by the window manager when returning a window surface
+     * back from a client, converting it from the representation being managed
+     * by the window manager to the representation the client uses to draw
+     * in to it.
+     *
+     * @param other {@link SurfaceControl} to copy from.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void copyFrom(SurfaceControl other) {
+        if (other == null) {
+            throw new IllegalArgumentException("other must not be null");
+        }
+
+        long surfaceControlPtr = other.mNativeObject;
+        if (surfaceControlPtr == 0) {
+            throw new NullPointerException(
+                    "null SurfaceControl native object. Are you using a released SurfaceControl?");
+        }
+        long newNativeObject = nativeGetFromSurfaceControl(mNativeObject, surfaceControlPtr);
+        updateNativeObject(newNativeObject);
+    }
+
+    /**
+     * Update the surface if the BLASTBufferQueue IGraphicBufferProducer is different from this
+     * surface's IGraphicBufferProducer.
+     *
+     * @param queue {@link BLASTBufferQueue} to copy from.
+     * @hide
+     */
+    public void copyFrom(BLASTBufferQueue queue) {
+        if (queue == null) {
+            throw new IllegalArgumentException("queue must not be null");
+        }
+
+        long blastBufferQueuePtr = queue.mNativeObject;
+        if (blastBufferQueuePtr == 0) {
+            throw new NullPointerException("Null BLASTBufferQueue native object");
+        }
+        long newNativeObject = nativeGetFromBlastBufferQueue(mNativeObject, blastBufferQueuePtr);
+        updateNativeObject(newNativeObject);
+    }
+
+    /**
+     * Gets a reference a surface created from this one.  This surface now holds a reference
+     * to the same data as the original surface, and is -not- the owner.
+     * This is for use by the window manager when returning a window surface
+     * back from a client, converting it from the representation being managed
+     * by the window manager to the representation the client uses to draw
+     * in to it.
+     *
+     * @param other {@link SurfaceControl} to create surface from.
+     *
+     * @hide
+     */
+    public void createFrom(SurfaceControl other) {
+        if (other == null) {
+            throw new IllegalArgumentException("other must not be null");
+        }
+
+        long surfaceControlPtr = other.mNativeObject;
+        if (surfaceControlPtr == 0) {
+            throw new NullPointerException(
+                    "null SurfaceControl native object. Are you using a released SurfaceControl?");
+        }
+        long newNativeObject = nativeCreateFromSurfaceControl(surfaceControlPtr);
+
+        synchronized (mLock) {
+            if (mNativeObject != 0) {
+                nativeRelease(mNativeObject);
+            }
+            setNativeObjectLocked(newNativeObject);
+        }
+    }
+
+    /**
+     * This is intended to be used by {@link SurfaceView#updateWindow} only.
+     * @param other access is not thread safe
+     * @hide
+     * @deprecated
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public void transferFrom(Surface other) {
+        if (other == null) {
+            throw new IllegalArgumentException("other must not be null");
+        }
+        if (other != this) {
+            final long newPtr;
+            synchronized (other.mLock) {
+                newPtr = other.mNativeObject;
+                other.setNativeObjectLocked(0);
+            }
+
+            synchronized (mLock) {
+                if (mNativeObject != 0) {
+                    nativeRelease(mNativeObject);
+                }
+                setNativeObjectLocked(newPtr);
+            }
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public void readFromParcel(Parcel source) {
+        if (source == null) {
+            throw new IllegalArgumentException("source must not be null");
+        }
+
+        synchronized (mLock) {
+            // nativeReadFromParcel() will either return mNativeObject, or
+            // create a new native Surface and return it after reducing
+            // the reference count on mNativeObject.  Either way, it is
+            // not necessary to call nativeRelease() here.
+            // NOTE: This must be kept synchronized with the native parceling code
+            // in frameworks/native/libs/Surface.cpp
+            mName = source.readString();
+            mIsSingleBuffered = source.readInt() != 0;
+            setNativeObjectLocked(nativeReadFromParcel(mNativeObject, source));
+        }
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        if (dest == null) {
+            throw new IllegalArgumentException("dest must not be null");
+        }
+        synchronized (mLock) {
+            // NOTE: This must be kept synchronized with the native parceling code
+            // in frameworks/native/libs/Surface.cpp
+            dest.writeString(mName);
+            dest.writeInt(mIsSingleBuffered ? 1 : 0);
+            nativeWriteToParcel(mNativeObject, dest);
+        }
+        if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) {
+            release();
+        }
+    }
+
+    @Override
+    public String toString() {
+        synchronized (mLock) {
+            return "Surface(name=" + mName + ")/@0x" +
+                    Integer.toHexString(System.identityHashCode(this));
+        }
+    }
+
+    private void setNativeObjectLocked(long ptr) {
+        if (mNativeObject != ptr) {
+            if (mNativeObject == 0 && ptr != 0) {
+                mCloseGuard.open("release");
+            } else if (mNativeObject != 0 && ptr == 0) {
+                mCloseGuard.close();
+            }
+            mNativeObject = ptr;
+            mGenerationId += 1;
+            if (mHwuiContext != null) {
+                mHwuiContext.updateSurface();
+            }
+        }
+    }
+
+    private void checkNotReleasedLocked() {
+        if (mNativeObject == 0) {
+            throw new IllegalStateException("Surface has already been released.");
+        }
+    }
+
+    /**
+     * Allocate buffers ahead of time to avoid allocation delays during rendering
+     * @hide
+     */
+    public void allocateBuffers() {
+        synchronized (mLock) {
+            checkNotReleasedLocked();
+            nativeAllocateBuffers(mNativeObject);
+        }
+    }
+
+    /**
+     * Set the scaling mode to be used for this surfaces buffers
+     * @hide
+     */
+     public void setScalingMode(@ScalingMode int scalingMode) {
+        synchronized (mLock) {
+            checkNotReleasedLocked();
+            int err = nativeSetScalingMode(mNativeObject, scalingMode);
+            if (err != 0) {
+                throw new IllegalArgumentException("Invalid scaling mode: " + scalingMode);
+            }
+        }
+    }
+
+    void forceScopedDisconnect() {
+        synchronized (mLock) {
+            checkNotReleasedLocked();
+            int err = nativeForceScopedDisconnect(mNativeObject);
+            if (err != 0) {
+                throw new RuntimeException("Failed to disconnect Surface instance (bad object?)");
+            }
+        }
+    }
+
+    /**
+     * Transfer ownership of buffer with a color space and present it on the Surface.
+     * The supported color spaces are SRGB and Display P3, other color spaces will be
+     * treated as SRGB.
+     * @hide
+     */
+    public void attachAndQueueBufferWithColorSpace(HardwareBuffer buffer, ColorSpace colorSpace) {
+        synchronized (mLock) {
+            checkNotReleasedLocked();
+            if (colorSpace == null) {
+                colorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
+            }
+            int err = nativeAttachAndQueueBufferWithColorSpace(mNativeObject, buffer,
+                    colorSpace.getId());
+            if (err != 0) {
+                throw new RuntimeException(
+                        "Failed to attach and queue buffer to Surface (bad object?), "
+                        + "native error: " + err);
+            }
+        }
+    }
+
+    /**
+     * Returns whether or not this Surface is backed by a single-buffered SurfaceTexture
+     * @hide
+     */
+    public boolean isSingleBuffered() {
+        return mIsSingleBuffered;
+    }
+
+    /**
+     * <p>The shared buffer mode allows both the application and the surface compositor
+     * (SurfaceFlinger) to concurrently access this surface's buffer. While the
+     * application is still required to issue a present request
+     * (see {@link #unlockCanvasAndPost(Canvas)}) to the compositor when an update is required,
+     * the compositor may trigger an update at any time. Since the surface's buffer is shared
+     * between the application and the compositor, updates triggered by the compositor may
+     * cause visible tearing.</p>
+     *
+     * <p>The shared buffer mode can be used with
+     * {@link #setAutoRefreshEnabled(boolean) auto-refresh} to avoid the overhead of
+     * issuing present requests.</p>
+     *
+     * <p>If the application uses the shared buffer mode to reduce latency, it is
+     * recommended to use software rendering (see {@link #lockCanvas(Rect)} to ensure
+     * the graphics workloads are not affected by other applications and/or the system
+     * using the GPU. When using software rendering, the application should update the
+     * smallest possible region of the surface required.</p>
+     *
+     * <p class="note">The shared buffer mode might not be supported by the underlying
+     * hardware. Enabling shared buffer mode on hardware that does not support it will
+     * not yield an error but the application will not benefit from lower latency (and
+     * tearing will not be visible).</p>
+     *
+     * <p class="note">Depending on how many and what kind of surfaces are visible, the
+     * surface compositor may need to copy the shared buffer before it is displayed. When
+     * this happens, the latency benefits of shared buffer mode will be reduced.</p>
+     *
+     * @param enabled True to enable the shared buffer mode on this surface, false otherwise
+     *
+     * @see #isSharedBufferModeEnabled()
+     * @see #setAutoRefreshEnabled(boolean)
+     *
+     * @hide
+     */
+    public void setSharedBufferModeEnabled(boolean enabled) {
+        if (mIsSharedBufferModeEnabled != enabled) {
+            int error = nativeSetSharedBufferModeEnabled(mNativeObject, enabled);
+            if (error != 0) {
+                throw new RuntimeException(
+                        "Failed to set shared buffer mode on Surface (bad object?)");
+            } else {
+                mIsSharedBufferModeEnabled = enabled;
+            }
+        }
+    }
+
+    /**
+     * @return True if shared buffer mode is enabled on this surface, false otherwise
+     *
+     * @see #setSharedBufferModeEnabled(boolean)
+     *
+     * @hide
+     */
+    public boolean isSharedBufferModeEnabled() {
+        return mIsSharedBufferModeEnabled;
+    }
+
+    /**
+     * <p>When auto-refresh is enabled, the surface compositor (SurfaceFlinger)
+     * automatically updates the display on a regular refresh cycle. The application
+     * can continue to issue present requests but it is not required. Enabling
+     * auto-refresh may result in visible tearing.</p>
+     *
+     * <p>Auto-refresh has no effect if the {@link #setSharedBufferModeEnabled(boolean)
+     * shared buffer mode} is not enabled.</p>
+     *
+     * <p>Because auto-refresh will trigger continuous updates of the display, it is
+     * recommended to turn it on only when necessary. For example, in a drawing/painting
+     * application auto-refresh should be enabled on finger/pen down and disabled on
+     * finger/pen up.</p>
+     *
+     * @param enabled True to enable auto-refresh on this surface, false otherwise
+     *
+     * @see #isAutoRefreshEnabled()
+     * @see #setSharedBufferModeEnabled(boolean)
+     *
+     * @hide
+     */
+    public void setAutoRefreshEnabled(boolean enabled) {
+        if (mIsAutoRefreshEnabled != enabled) {
+            int error = nativeSetAutoRefreshEnabled(mNativeObject, enabled);
+            if (error != 0) {
+                throw new RuntimeException("Failed to set auto refresh on Surface (bad object?)");
+            } else {
+                mIsAutoRefreshEnabled = enabled;
+            }
+        }
+    }
+
+    /**
+     * @return True if auto-refresh is enabled on this surface, false otherwise
+     *
+     * @hide
+     */
+    public boolean isAutoRefreshEnabled() {
+        return mIsAutoRefreshEnabled;
+    }
+
+    /**
+     * Sets the intended frame rate for this surface.
+     *
+     * <p>On devices that are capable of running the display at different refresh rates,
+     * the system may choose a display refresh rate to better match this surface's frame
+     * rate. Usage of this API won't introduce frame rate throttling, or affect other
+     * aspects of the application's frame production pipeline. However, because the system
+     * may change the display refresh rate, calls to this function may result in changes
+     * to Choreographer callback timings, and changes to the time interval at which the
+     * system releases buffers back to the application.</p>
+     *
+     * <p>Note that this only has an effect for surfaces presented on the display. If this
+     * surface is consumed by something other than the system compositor, e.g. a media
+     * codec, this call has no effect.</p>
+     *
+     * @param frameRate The intended frame rate of this surface, in frames per second. 0
+     * is a special value that indicates the app will accept the system's choice for the
+     * display frame rate, which is the default behavior if this function isn't
+     * called. The <code>frameRate</code> parameter does <em>not</em> need to be a valid refresh
+     * rate for this device's display - e.g., it's fine to pass 30fps to a device that can only run
+     * the display at 60fps.
+     *
+     * @param compatibility The frame rate compatibility of this surface. The
+     * compatibility value may influence the system's choice of display frame rate.
+     * This parameter is ignored when <code>frameRate</code> is 0.
+     *
+     * @param changeFrameRateStrategy Whether display refresh rate transitions caused by this
+     * surface should be seamless. A seamless transition is one that doesn't have any visual
+     * interruptions, such as a black screen for a second or two. This parameter is ignored when
+     * <code>frameRate</code> is 0.
+     *
+     * @throws IllegalArgumentException If <code>frameRate</code>, <code>compatibility</code> or
+     * <code>changeFrameRateStrategy</code> are invalid.
+     */
+    public void setFrameRate(@FloatRange(from = 0.0) float frameRate,
+            @FrameRateCompatibility int compatibility,
+            @ChangeFrameRateStrategy int changeFrameRateStrategy) {
+        synchronized (mLock) {
+            checkNotReleasedLocked();
+            int error = nativeSetFrameRate(mNativeObject, frameRate, compatibility,
+                    changeFrameRateStrategy);
+            if (error == -EINVAL) {
+                throw new IllegalArgumentException("Invalid argument to Surface.setFrameRate()");
+            } else if (error != 0) {
+                throw new RuntimeException("Failed to set frame rate on Surface");
+            }
+        }
+    }
+
+    /**
+     * Sets the intended frame rate for this surface. Any switching of refresh rates is
+     * most probably going to be seamless.
+     *
+     * @see #setFrameRate(float, int, int)
+     */
+    public void setFrameRate(
+            @FloatRange(from = 0.0) float frameRate, @FrameRateCompatibility int compatibility) {
+        setFrameRate(frameRate, compatibility, CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+    }
+
+    /**
+     * Exception thrown when a Canvas couldn't be locked with {@link Surface#lockCanvas}, or
+     * when a SurfaceTexture could not successfully be allocated.
+     */
+    @SuppressWarnings("serial")
+    public static class OutOfResourcesException extends RuntimeException {
+        public OutOfResourcesException() {
+        }
+        public OutOfResourcesException(String name) {
+            super(name);
+        }
+    }
+
+    /**
+     * Returns a human readable representation of a rotation.
+     *
+     * @param rotation The rotation.
+     * @return The rotation symbolic name.
+     *
+     * @hide
+     */
+    public static String rotationToString(int rotation) {
+        switch (rotation) {
+            case Surface.ROTATION_0: {
+                return "ROTATION_0";
+            }
+            case Surface.ROTATION_90: {
+                return "ROTATION_90";
+            }
+            case Surface.ROTATION_180: {
+                return "ROTATION_180";
+            }
+            case Surface.ROTATION_270: {
+                return "ROTATION_270";
+            }
+            default: {
+                return Integer.toString(rotation);
+            }
+        }
+    }
+
+    /**
+     * A Canvas class that can handle the compatibility mode.
+     * This does two things differently.
+     * <ul>
+     * <li>Returns the width and height of the target metrics, rather than
+     * native. For example, the canvas returns 320x480 even if an app is running
+     * in WVGA high density.
+     * <li>Scales the matrix in setMatrix by the application scale, except if
+     * the matrix looks like obtained from getMatrix. This is a hack to handle
+     * the case that an application uses getMatrix to keep the original matrix,
+     * set matrix of its own, then set the original matrix back. There is no
+     * perfect solution that works for all cases, and there are a lot of cases
+     * that this model does not work, but we hope this works for many apps.
+     * </ul>
+     */
+    private final class CompatibleCanvas extends Canvas {
+        // A temp matrix to remember what an application obtained via {@link getMatrix}
+        private Matrix mOrigMatrix = null;
+
+        @Override
+        public void setMatrix(Matrix matrix) {
+            if (mCompatibleMatrix == null || mOrigMatrix == null || mOrigMatrix.equals(matrix)) {
+                // don't scale the matrix if it's not compatibility mode, or
+                // the matrix was obtained from getMatrix.
+                super.setMatrix(matrix);
+            } else {
+                Matrix m = new Matrix(mCompatibleMatrix);
+                m.preConcat(matrix);
+                super.setMatrix(m);
+            }
+        }
+
+        @SuppressWarnings("deprecation")
+        @Override
+        public void getMatrix(Matrix m) {
+            super.getMatrix(m);
+            if (mOrigMatrix == null) {
+                mOrigMatrix = new Matrix();
+            }
+            mOrigMatrix.set(m);
+        }
+    }
+
+    private final class HwuiContext {
+        private final RenderNode mRenderNode;
+        private HardwareRenderer mHardwareRenderer;
+        private RecordingCanvas mCanvas;
+        private final boolean mIsWideColorGamut;
+
+        HwuiContext(boolean isWideColorGamut) {
+            mRenderNode = RenderNode.create("HwuiCanvas", null);
+            mRenderNode.setClipToBounds(false);
+            mRenderNode.setForceDarkAllowed(false);
+            mIsWideColorGamut = isWideColorGamut;
+
+            mHardwareRenderer = new HardwareRenderer();
+            mHardwareRenderer.setContentRoot(mRenderNode);
+            mHardwareRenderer.setSurface(Surface.this, true);
+            mHardwareRenderer.setColorMode(
+                    isWideColorGamut
+                            ? ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT
+                            : ActivityInfo.COLOR_MODE_DEFAULT);
+            mHardwareRenderer.setLightSourceAlpha(0.0f, 0.0f);
+            mHardwareRenderer.setLightSourceGeometry(0.0f, 0.0f, 0.0f, 0.0f);
+        }
+
+        Canvas lockCanvas(int width, int height) {
+            if (mCanvas != null) {
+                throw new IllegalStateException("Surface was already locked!");
+            }
+            mCanvas = mRenderNode.beginRecording(width, height);
+            return mCanvas;
+        }
+
+        void unlockAndPost(Canvas canvas) {
+            if (canvas != mCanvas) {
+                throw new IllegalArgumentException("canvas object must be the same instance that "
+                        + "was previously returned by lockCanvas");
+            }
+            mRenderNode.endRecording();
+            mCanvas = null;
+            mHardwareRenderer.createRenderRequest()
+                    .setVsyncTime(System.nanoTime())
+                    .syncAndDraw();
+        }
+
+        void updateSurface() {
+            mHardwareRenderer.setSurface(Surface.this, true);
+        }
+
+        void destroy() {
+            mHardwareRenderer.destroy();
+        }
+
+        boolean isWideColorGamut() {
+            return mIsWideColorGamut;
+        }
+    }
+}
diff --git a/android/view/SurfaceControl.java b/android/view/SurfaceControl.java
new file mode 100644
index 0000000..8143cf9
--- /dev/null
+++ b/android/view/SurfaceControl.java
@@ -0,0 +1,3633 @@
+/*
+ * 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.view;
+
+import static android.graphics.Matrix.MSCALE_X;
+import static android.graphics.Matrix.MSCALE_Y;
+import static android.graphics.Matrix.MSKEW_X;
+import static android.graphics.Matrix.MSKEW_Y;
+import static android.graphics.Matrix.MTRANS_X;
+import static android.graphics.Matrix.MTRANS_Y;
+import static android.view.SurfaceControlProto.HASH_CODE;
+import static android.view.SurfaceControlProto.NAME;
+
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.Size;
+import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
+import android.graphics.GraphicBuffer;
+import android.graphics.Matrix;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.hardware.HardwareBuffer;
+import android.hardware.display.DeviceProductInfo;
+import android.hardware.display.DisplayedContentSample;
+import android.hardware.display.DisplayedContentSamplingAttributes;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseIntArray;
+import android.util.proto.ProtoOutputStream;
+import android.view.Surface.OutOfResourcesException;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.VirtualRefBasePtr;
+
+import dalvik.system.CloseGuard;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.io.Closeable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Handle to an on-screen Surface managed by the system compositor. The SurfaceControl is
+ * a combination of a buffer source, and metadata about how to display the buffers.
+ * By constructing a {@link Surface} from this SurfaceControl you can submit buffers to be
+ * composited. Using {@link SurfaceControl.Transaction} you can manipulate various
+ * properties of how the buffer will be displayed on-screen. SurfaceControl's are
+ * arranged into a scene-graph like hierarchy, and as such any SurfaceControl may have
+ * a parent. Geometric properties like transform, crop, and Z-ordering will be inherited
+ * from the parent, as if the child were content in the parents buffer stream.
+ */
+public final class SurfaceControl implements Parcelable {
+    private static final String TAG = "SurfaceControl";
+
+    private static native long nativeCreate(SurfaceSession session, String name,
+            int w, int h, int format, int flags, long parentObject, Parcel metadata)
+            throws OutOfResourcesException;
+    private static native long nativeReadFromParcel(Parcel in);
+    private static native long nativeCopyFromSurfaceControl(long nativeObject);
+    private static native void nativeWriteToParcel(long nativeObject, Parcel out);
+    private static native void nativeRelease(long nativeObject);
+    private static native void nativeDisconnect(long nativeObject);
+    private static native void nativeUpdateDefaultBufferSize(long nativeObject, int width, int height);
+    private static native int nativeCaptureDisplay(DisplayCaptureArgs captureArgs,
+            ScreenCaptureListener captureListener);
+    private static native int nativeCaptureLayers(LayerCaptureArgs captureArgs,
+            ScreenCaptureListener captureListener);
+    private static native long nativeMirrorSurface(long mirrorOfObject);
+    private static native long nativeCreateTransaction();
+    private static native long nativeGetNativeTransactionFinalizer();
+    private static native void nativeApplyTransaction(long transactionObj, boolean sync);
+    private static native void nativeMergeTransaction(long transactionObj,
+            long otherTransactionObj);
+    private static native void nativeClearTransaction(long transactionObj);
+    private static native void nativeSetAnimationTransaction(long transactionObj);
+    private static native void nativeSetEarlyWakeupStart(long transactionObj);
+    private static native void nativeSetEarlyWakeupEnd(long transactionObj);
+
+    private static native void nativeSetLayer(long transactionObj, long nativeObject, int zorder);
+    private static native void nativeSetRelativeLayer(long transactionObj, long nativeObject,
+            long relativeToObject, int zorder);
+    private static native void nativeSetPosition(long transactionObj, long nativeObject,
+            float x, float y);
+    private static native void nativeSetSize(long transactionObj, long nativeObject, int w, int h);
+    private static native void nativeSetTransparentRegionHint(long transactionObj,
+            long nativeObject, Region region);
+    private static native void nativeSetAlpha(long transactionObj, long nativeObject, float alpha);
+    private static native void nativeSetMatrix(long transactionObj, long nativeObject,
+            float dsdx, float dtdx,
+            float dtdy, float dsdy);
+    private static native void nativeSetColorTransform(long transactionObj, long nativeObject,
+            float[] matrix, float[] translation);
+    private static native void nativeSetColorSpaceAgnostic(long transactionObj, long nativeObject,
+            boolean agnostic);
+    private static native void nativeSetGeometry(long transactionObj, long nativeObject,
+            Rect sourceCrop, Rect dest, long orientation);
+    private static native void nativeSetColor(long transactionObj, long nativeObject, float[] color);
+    private static native void nativeSetFlags(long transactionObj, long nativeObject,
+            int flags, int mask);
+    private static native void nativeSetFrameRateSelectionPriority(long transactionObj,
+            long nativeObject, int priority);
+    private static native void nativeSetWindowCrop(long transactionObj, long nativeObject,
+            int l, int t, int r, int b);
+    private static native void nativeSetCornerRadius(long transactionObj, long nativeObject,
+            float cornerRadius);
+    private static native void nativeSetBackgroundBlurRadius(long transactionObj, long nativeObject,
+            int blurRadius);
+    private static native void nativeSetLayerStack(long transactionObj, long nativeObject,
+            int layerStack);
+    private static native void nativeSetBlurRegions(long transactionObj, long nativeObj,
+            float[][] regions, int length);
+    private static native void nativeSetStretchEffect(long transactionObj, long nativeObj,
+            float width, float height, float vecX, float vecY,
+            float maxStretchAmountX, float maxStretchAmountY, float childRelativeLeft,
+            float childRelativeTop, float childRelativeRight, float childRelativeBottom);
+    private static native void nativeSetTrustedOverlay(long transactionObj, long nativeObject,
+            boolean isTrustedOverlay);
+
+    private static native boolean nativeClearContentFrameStats(long nativeObject);
+    private static native boolean nativeGetContentFrameStats(long nativeObject, WindowContentFrameStats outStats);
+    private static native boolean nativeClearAnimationFrameStats();
+    private static native boolean nativeGetAnimationFrameStats(WindowAnimationFrameStats outStats);
+
+    private static native long[] nativeGetPhysicalDisplayIds();
+    private static native IBinder nativeGetPhysicalDisplayToken(long physicalDisplayId);
+    private static native IBinder nativeCreateDisplay(String name, boolean secure);
+    private static native void nativeDestroyDisplay(IBinder displayToken);
+    private static native void nativeSetDisplaySurface(long transactionObj,
+            IBinder displayToken, long nativeSurfaceObject);
+    private static native void nativeSetDisplayLayerStack(long transactionObj,
+            IBinder displayToken, int layerStack);
+    private static native void nativeSetDisplayProjection(long transactionObj,
+            IBinder displayToken, int orientation,
+            int l, int t, int r, int b,
+            int L, int T, int R, int B);
+    private static native void nativeSetDisplaySize(long transactionObj, IBinder displayToken,
+            int width, int height);
+    private static native StaticDisplayInfo nativeGetStaticDisplayInfo(IBinder displayToken);
+    private static native DynamicDisplayInfo nativeGetDynamicDisplayInfo(IBinder displayToken);
+    private static native DisplayedContentSamplingAttributes
+            nativeGetDisplayedContentSamplingAttributes(IBinder displayToken);
+    private static native boolean nativeSetDisplayedContentSamplingEnabled(IBinder displayToken,
+            boolean enable, int componentMask, int maxFrames);
+    private static native DisplayedContentSample nativeGetDisplayedContentSample(
+            IBinder displayToken, long numFrames, long timestamp);
+    private static native boolean nativeSetDesiredDisplayModeSpecs(IBinder displayToken,
+            DesiredDisplayModeSpecs desiredDisplayModeSpecs);
+    private static native DesiredDisplayModeSpecs
+            nativeGetDesiredDisplayModeSpecs(IBinder displayToken);
+    private static native DisplayPrimaries nativeGetDisplayNativePrimaries(
+            IBinder displayToken);
+    private static native int[] nativeGetCompositionDataspaces();
+    private static native boolean nativeSetActiveColorMode(IBinder displayToken,
+            int colorMode);
+    private static native void nativeSetAutoLowLatencyMode(IBinder displayToken, boolean on);
+    private static native void nativeSetGameContentType(IBinder displayToken, boolean on);
+    private static native void nativeSetDisplayPowerMode(
+            IBinder displayToken, int mode);
+    private static native void nativeReparent(long transactionObj, long nativeObject,
+            long newParentNativeObject);
+    private static native void nativeSetBuffer(long transactionObj, long nativeObject,
+            GraphicBuffer buffer);
+    private static native void nativeSetColorSpace(long transactionObj, long nativeObject,
+            int colorSpace);
+
+    private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes);
+
+    private static native void nativeSetInputWindowInfo(long transactionObj, long nativeObject,
+            InputWindowHandle handle);
+
+    private static native boolean nativeGetProtectedContentSupport();
+    private static native void nativeSetMetadata(long transactionObj, long nativeObject, int key,
+            Parcel data);
+    private static native void nativeSyncInputWindows(long transactionObj);
+    private static native boolean nativeGetDisplayBrightnessSupport(IBinder displayToken);
+    private static native boolean nativeSetDisplayBrightness(IBinder displayToken,
+            float sdrBrightness, float sdrBrightnessNits, float displayBrightness,
+            float displayBrightnessNits);
+    private static native long nativeReadTransactionFromParcel(Parcel in);
+    private static native void nativeWriteTransactionToParcel(long nativeObject, Parcel out);
+    private static native void nativeSetShadowRadius(long transactionObj, long nativeObject,
+            float shadowRadius);
+    private static native void nativeSetGlobalShadowSettings(@Size(4) float[] ambientColor,
+            @Size(4) float[] spotColor, float lightPosY, float lightPosZ, float lightRadius);
+
+    private static native void nativeSetFrameRate(long transactionObj, long nativeObject,
+            float frameRate, int compatibility, int changeFrameRateStrategy);
+    private static native long nativeGetHandle(long nativeObject);
+
+    private static native long nativeAcquireFrameRateFlexibilityToken();
+    private static native void nativeReleaseFrameRateFlexibilityToken(long token);
+    private static native void nativeSetFixedTransformHint(long transactionObj, long nativeObject,
+            int transformHint);
+    private static native void nativeSetFocusedWindow(long transactionObj, IBinder toToken,
+            String windowName, IBinder focusedToken, String focusedWindowName, int displayId);
+    private static native void nativeSetFrameTimelineVsync(long transactionObj,
+            long frameTimelineVsyncId);
+    private static native void nativeAddJankDataListener(long nativeListener,
+            long nativeSurfaceControl);
+    private static native void nativeRemoveJankDataListener(long nativeListener);
+    private static native long nativeCreateJankDataListenerWrapper(OnJankDataListener listener);
+    private static native int nativeGetGPUContextPriority();
+    private static native void nativeSetTransformHint(long nativeObject, int transformHint);
+    private static native int nativeGetTransformHint(long nativeObject);
+
+    @Nullable
+    @GuardedBy("mLock")
+    private ArrayList<OnReparentListener> mReparentListeners;
+
+    /**
+     * Listener to observe surface reparenting.
+     *
+     * @hide
+     */
+    public interface OnReparentListener {
+
+        /**
+         * Callback for reparenting surfaces.
+         *
+         * Important: You should only interact with the provided surface control
+         * only if you have a contract with its owner to avoid them closing it
+         * under you or vise versa.
+         *
+         * @param transaction The transaction that would commit reparenting.
+         * @param parent The future parent surface.
+         */
+        void onReparent(@NonNull Transaction transaction, @Nullable SurfaceControl parent);
+    }
+
+    /**
+     * Jank information to be fed back via {@link OnJankDataListener}.
+     * @hide
+     */
+    public static class JankData {
+
+        /** @hide */
+        @IntDef(flag = true, value = {JANK_NONE,
+                DISPLAY_HAL,
+                JANK_SURFACEFLINGER_DEADLINE_MISSED,
+                JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED,
+                JANK_APP_DEADLINE_MISSED,
+                PREDICTION_ERROR,
+                SURFACE_FLINGER_SCHEDULING})
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface JankType {}
+
+        // Needs to be kept in sync with frameworks/native/libs/gui/include/gui/JankInfo.h
+
+        // No Jank
+        public static final int JANK_NONE = 0x0;
+
+        // Jank not related to SurfaceFlinger or the App
+        public static final int DISPLAY_HAL = 0x1;
+        // SF took too long on the CPU
+        public static final int JANK_SURFACEFLINGER_DEADLINE_MISSED = 0x2;
+        // SF took too long on the GPU
+        public static final int JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED = 0x4;
+        // Either App or GPU took too long on the frame
+        public static final int JANK_APP_DEADLINE_MISSED = 0x8;
+        // Predictions live for 120ms, if prediction is expired for a frame, there is definitely a
+        // jank
+        // associated with the App if this is for a SurfaceFrame, and SF for a DisplayFrame.
+        public static final int PREDICTION_ERROR = 0x10;
+        // Latching a buffer early might cause an early present of the frame
+        public static final int SURFACE_FLINGER_SCHEDULING = 0x20;
+        // A buffer is said to be stuffed if it was expected to be presented on a vsync but was
+        // presented later because the previous buffer was presented in its expected vsync. This
+        // usually happens if there is an unexpectedly long frame causing the rest of the buffers
+        // to enter a stuffed state.
+        public static final int BUFFER_STUFFING = 0x40;
+        // Jank due to unknown reasons.
+        public static final int UNKNOWN = 0x80;
+
+        public JankData(long frameVsyncId, @JankType int jankType) {
+            this.frameVsyncId = frameVsyncId;
+            this.jankType = jankType;
+        }
+
+        public final long frameVsyncId;
+        public final @JankType int jankType;
+    }
+
+    /**
+     * Listener interface to be informed about SurfaceFlinger's jank classification for a specific
+     * surface.
+     *
+     * @see JankData
+     * @see #addJankDataListener
+     * @hide
+     */
+    public static abstract class OnJankDataListener {
+        private final VirtualRefBasePtr mNativePtr;
+
+        public OnJankDataListener() {
+            mNativePtr = new VirtualRefBasePtr(nativeCreateJankDataListenerWrapper(this));
+        }
+
+        /**
+         * Called when new jank classifications are available.
+         */
+        public abstract void onJankDataAvailable(JankData[] jankStats);
+    }
+
+    private final CloseGuard mCloseGuard = CloseGuard.get();
+    private String mName;
+
+     /**
+     * @hide
+     */
+    public long mNativeObject;
+    private long mNativeHandle;
+
+    // TODO: Move width/height to native and fix locking through out.
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private int mWidth;
+    @GuardedBy("mLock")
+    private int mHeight;
+
+    private int mTransformHint;
+
+    private WeakReference<View> mLocalOwnerView;
+
+    static GlobalTransactionWrapper sGlobalTransaction;
+    static long sTransactionNestCount = 0;
+
+    /**
+     * Adds a reparenting listener.
+     *
+     * @param listener The listener.
+     * @return Whether listener was added.
+     *
+     * @hide
+     */
+    public boolean addOnReparentListener(@NonNull OnReparentListener listener) {
+        synchronized (mLock) {
+            if (mReparentListeners == null) {
+                mReparentListeners = new ArrayList<>(1);
+            }
+            return mReparentListeners.add(listener);
+        }
+    }
+
+    /**
+     * Removes a reparenting listener.
+     *
+     * @param listener The listener.
+     * @return Whether listener was removed.
+     *
+     * @hide
+     */
+    public boolean removeOnReparentListener(@NonNull OnReparentListener listener) {
+        synchronized (mLock) {
+            final boolean removed = mReparentListeners.remove(listener);
+            if (mReparentListeners.isEmpty()) {
+                mReparentListeners = null;
+            }
+            return removed;
+        }
+    }
+
+    /* flags used in constructor (keep in sync with ISurfaceComposerClient.h) */
+
+    /**
+     * Surface creation flag: Surface is created hidden
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public static final int HIDDEN = 0x00000004;
+
+    /**
+     * Surface creation flag: Skip this layer and its children when taking a screenshot. This
+     * also includes mirroring and screen recording, so the layers with flag SKIP_SCREENSHOT
+     * will not be included on non primary displays.
+     * @hide
+     */
+    public static final int SKIP_SCREENSHOT = 0x00000040;
+
+    /**
+     * Surface creation flag: Special measures will be taken to disallow the surface's content to
+     * be copied. In particular, screenshots and secondary, non-secure displays will render black
+     * content instead of the surface content.
+     *
+     * @see #createDisplay(String, boolean)
+     * @hide
+     */
+    public static final int SECURE = 0x00000080;
+
+
+    /**
+     * Queue up BufferStateLayer buffers instead of dropping the oldest buffer when this flag is
+     * set. This blocks the client until all the buffers have been presented. If the buffers
+     * have presentation timestamps, then we may drop buffers.
+     * @hide
+     */
+    public static final int ENABLE_BACKPRESSURE = 0x00000100;
+
+    /**
+     * Surface creation flag: Creates a surface where color components are interpreted
+     * as "non pre-multiplied" by their alpha channel. Of course this flag is
+     * meaningless for surfaces without an alpha channel. By default
+     * surfaces are pre-multiplied, which means that each color component is
+     * already multiplied by its alpha value. In this case the blending
+     * equation used is:
+     * <p>
+     *    <code>DEST = SRC + DEST * (1-SRC_ALPHA)</code>
+     * <p>
+     * By contrast, non pre-multiplied surfaces use the following equation:
+     * <p>
+     *    <code>DEST = SRC * SRC_ALPHA * DEST * (1-SRC_ALPHA)</code>
+     * <p>
+     * pre-multiplied surfaces must always be used if transparent pixels are
+     * composited on top of each-other into the surface. A pre-multiplied
+     * surface can never lower the value of the alpha component of a given
+     * pixel.
+     * <p>
+     * In some rare situations, a non pre-multiplied surface is preferable.
+     * @hide
+     */
+    public static final int NON_PREMULTIPLIED = 0x00000100;
+
+    /**
+     * Surface creation flag: Indicates that the surface must be considered opaque,
+     * even if its pixel format contains an alpha channel. This can be useful if an
+     * application needs full RGBA 8888 support for instance but will
+     * still draw every pixel opaque.
+     * <p>
+     * This flag is ignored if setAlpha() is used to make the surface non-opaque.
+     * Combined effects are (assuming a buffer format with an alpha channel):
+     * <ul>
+     * <li>OPAQUE + alpha(1.0) == opaque composition
+     * <li>OPAQUE + alpha(0.x) == blended composition
+     * <li>!OPAQUE + alpha(1.0) == blended composition
+     * <li>!OPAQUE + alpha(0.x) == blended composition
+     * </ul>
+     * If the underlying buffer lacks an alpha channel, the OPAQUE flag is effectively
+     * set automatically.
+     * @hide
+     */
+    public static final int OPAQUE = 0x00000400;
+
+    /**
+     * Surface creation flag: Application requires a hardware-protected path to an
+     * external display sink. If a hardware-protected path is not available,
+     * then this surface will not be displayed on the external sink.
+     *
+     * @hide
+     */
+    public static final int PROTECTED_APP = 0x00000800;
+
+    // 0x1000 is reserved for an independent DRM protected flag in framework
+
+    /**
+     * Surface creation flag: Window represents a cursor glyph.
+     * @hide
+     */
+    public static final int CURSOR_WINDOW = 0x00002000;
+
+    /**
+     * Surface creation flag: Indicates the effect layer will not have a color fill on
+     * creation.
+     *
+     * @hide
+     */
+    public static final int NO_COLOR_FILL = 0x00004000;
+
+    /**
+     * Surface creation flag: Creates a normal surface.
+     * This is the default.
+     *
+     * @hide
+     */
+    public static final int FX_SURFACE_NORMAL   = 0x00000000;
+
+    /**
+     * Surface creation flag: Creates a effect surface which
+     * represents a solid color and or shadows.
+     *
+     * @hide
+     */
+    public static final int FX_SURFACE_EFFECT = 0x00020000;
+
+    /**
+     * Surface creation flag: Creates a container surface.
+     * This surface will have no buffers and will only be used
+     * as a container for other surfaces, or for its InputInfo.
+     * @hide
+     */
+    public static final int FX_SURFACE_CONTAINER = 0x00080000;
+
+    /**
+     * @hide
+     */
+    public static final int FX_SURFACE_BLAST = 0x00040000;
+
+    /**
+     * Mask used for FX values above.
+     *
+     * @hide
+     */
+    public static final int FX_SURFACE_MASK = 0x000F0000;
+
+    /* flags used with setFlags() (keep in sync with ISurfaceComposer.h) */
+
+    /**
+     * Surface flag: Hide the surface.
+     * Equivalent to calling hide().
+     * Updates the value set during Surface creation (see {@link #HIDDEN}).
+     */
+    private static final int SURFACE_HIDDEN = 0x01;
+
+    /**
+     * Surface flag: composite without blending when possible.
+     * Updates the value set during Surface creation (see {@link #OPAQUE}).
+     */
+    private static final int SURFACE_OPAQUE = 0x02;
+
+    // Display power modes.
+    /**
+     * Display power mode off: used while blanking the screen.
+     * Use only with {@link SurfaceControl#setDisplayPowerMode}.
+     * @hide
+     */
+    public static final int POWER_MODE_OFF = 0;
+
+    /**
+     * Display power mode doze: used while putting the screen into low power mode.
+     * Use only with {@link SurfaceControl#setDisplayPowerMode}.
+     * @hide
+     */
+    public static final int POWER_MODE_DOZE = 1;
+
+    /**
+     * Display power mode normal: used while unblanking the screen.
+     * Use only with {@link SurfaceControl#setDisplayPowerMode}.
+     * @hide
+     */
+    public static final int POWER_MODE_NORMAL = 2;
+
+    /**
+     * Display power mode doze: used while putting the screen into a suspended
+     * low power mode.  Use only with {@link SurfaceControl#setDisplayPowerMode}.
+     * @hide
+     */
+    public static final int POWER_MODE_DOZE_SUSPEND = 3;
+
+    /**
+     * Display power mode on: used while putting the screen into a suspended
+     * full power mode.  Use only with {@link SurfaceControl#setDisplayPowerMode}.
+     * @hide
+     */
+    public static final int POWER_MODE_ON_SUSPEND = 4;
+
+    /**
+     * internal representation of how to interpret pixel value, used only to convert to ColorSpace.
+     */
+    private static final int INTERNAL_DATASPACE_SRGB = 142671872;
+    private static final int INTERNAL_DATASPACE_DISPLAY_P3 = 143261696;
+    private static final int INTERNAL_DATASPACE_SCRGB = 411107328;
+
+    private void assignNativeObject(long nativeObject, String callsite) {
+        if (mNativeObject != 0) {
+            release();
+        }
+        if (nativeObject != 0) {
+            mCloseGuard.openWithCallSite("release", callsite);
+        }
+        mNativeObject = nativeObject;
+        mNativeHandle = mNativeObject != 0 ? nativeGetHandle(nativeObject) : 0;
+    }
+
+    /**
+     * @hide
+     */
+    public void copyFrom(@NonNull SurfaceControl other, String callsite) {
+        mName = other.mName;
+        mWidth = other.mWidth;
+        mHeight = other.mHeight;
+        mLocalOwnerView = other.mLocalOwnerView;
+        assignNativeObject(nativeCopyFromSurfaceControl(other.mNativeObject), callsite);
+    }
+
+    /**
+     * owner UID.
+     * @hide
+     */
+    public static final int METADATA_OWNER_UID = 1;
+
+    /**
+     * Window type as per {@link WindowManager.LayoutParams}.
+     * @hide
+     */
+    public static final int METADATA_WINDOW_TYPE = 2;
+
+    /**
+     * Task id to allow association between surfaces and task.
+     * @hide
+     */
+    public static final int METADATA_TASK_ID = 3;
+
+    /**
+     * The style of mouse cursor and hotspot.
+     * @hide
+     */
+    public static final int METADATA_MOUSE_CURSOR = 4;
+
+    /**
+     * Accessibility ID to allow association between surfaces and accessibility tree.
+     * @hide
+     */
+    public static final int METADATA_ACCESSIBILITY_ID = 5;
+
+    /**
+     * owner PID.
+     * @hide
+     */
+    public static final int METADATA_OWNER_PID = 6;
+
+    /**
+     * game mode for the layer - used for metrics
+     * @hide
+     */
+    public static final int METADATA_GAME_MODE = 8;
+
+    /**
+     * A wrapper around HardwareBuffer that contains extra information about how to
+     * interpret the screenshot HardwareBuffer.
+     *
+     * @hide
+     */
+    public static class ScreenshotHardwareBuffer {
+        private final HardwareBuffer mHardwareBuffer;
+        private final ColorSpace mColorSpace;
+        private final boolean mContainsSecureLayers;
+
+        public ScreenshotHardwareBuffer(HardwareBuffer hardwareBuffer, ColorSpace colorSpace,
+                boolean containsSecureLayers) {
+            mHardwareBuffer = hardwareBuffer;
+            mColorSpace = colorSpace;
+            mContainsSecureLayers = containsSecureLayers;
+        }
+
+       /**
+        * Create ScreenshotHardwareBuffer from an existing HardwareBuffer object.
+        * @param hardwareBuffer The existing HardwareBuffer object
+        * @param namedColorSpace Integer value of a named color space {@link ColorSpace.Named}
+        * @param containsSecureLayers Indicates whether this graphic buffer contains captured
+        *                             contents
+        *        of secure layers, in which case the screenshot should not be persisted.
+        */
+        private static ScreenshotHardwareBuffer createFromNative(HardwareBuffer hardwareBuffer,
+                int namedColorSpace, boolean containsSecureLayers) {
+            ColorSpace colorSpace = ColorSpace.get(ColorSpace.Named.values()[namedColorSpace]);
+            return new ScreenshotHardwareBuffer(hardwareBuffer, colorSpace, containsSecureLayers);
+        }
+
+        public ColorSpace getColorSpace() {
+            return mColorSpace;
+        }
+
+        public HardwareBuffer getHardwareBuffer() {
+            return mHardwareBuffer;
+        }
+
+        public boolean containsSecureLayers() {
+            return mContainsSecureLayers;
+        }
+
+        /**
+         * Copy content of ScreenshotHardwareBuffer into a hardware bitmap and return it.
+         * Note: If you want to modify the Bitmap in software, you will need to copy the Bitmap
+         * into
+         * a software Bitmap using {@link Bitmap#copy(Bitmap.Config, boolean)}
+         *
+         * CAVEAT: This can be extremely slow; avoid use unless absolutely necessary; prefer to
+         * directly
+         * use the {@link HardwareBuffer} directly.
+         *
+         * @return Bitmap generated from the {@link HardwareBuffer}
+         */
+        public Bitmap asBitmap() {
+            if (mHardwareBuffer == null) {
+                Log.w(TAG, "Failed to take screenshot. Null screenshot object");
+                return null;
+            }
+            return Bitmap.wrapHardwareBuffer(mHardwareBuffer, mColorSpace);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public interface ScreenCaptureListener {
+        /**
+         * The callback invoked when the screen capture is complete.
+         * @param hardwareBuffer Data containing info about the screen capture.
+         */
+        void onScreenCaptureComplete(ScreenshotHardwareBuffer hardwareBuffer);
+    }
+
+    private static class SyncScreenCaptureListener implements ScreenCaptureListener {
+        private static final int SCREENSHOT_WAIT_TIME_S = 1;
+        private ScreenshotHardwareBuffer mScreenshotHardwareBuffer;
+        private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
+
+        @Override
+        public void onScreenCaptureComplete(ScreenshotHardwareBuffer hardwareBuffer) {
+            mScreenshotHardwareBuffer = hardwareBuffer;
+            mCountDownLatch.countDown();
+        }
+
+        private ScreenshotHardwareBuffer waitForScreenshot() {
+            try {
+                mCountDownLatch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS);
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to wait for screen capture result", e);
+            }
+
+            return mScreenshotHardwareBuffer;
+        }
+    }
+
+    /**
+     * A common arguments class used for various screenshot requests. This contains arguments that
+     * are shared between {@link DisplayCaptureArgs} and {@link LayerCaptureArgs}
+     * @hide
+     */
+    private abstract static class CaptureArgs {
+        private final int mPixelFormat;
+        private final Rect mSourceCrop = new Rect();
+        private final float mFrameScaleX;
+        private final float mFrameScaleY;
+        private final boolean mCaptureSecureLayers;
+        private final boolean mAllowProtected;
+        private final long mUid;
+        private final boolean mGrayscale;
+
+        private CaptureArgs(Builder<? extends Builder<?>> builder) {
+            mPixelFormat = builder.mPixelFormat;
+            mSourceCrop.set(builder.mSourceCrop);
+            mFrameScaleX = builder.mFrameScaleX;
+            mFrameScaleY = builder.mFrameScaleY;
+            mCaptureSecureLayers = builder.mCaptureSecureLayers;
+            mAllowProtected = builder.mAllowProtected;
+            mUid = builder.mUid;
+            mGrayscale = builder.mGrayscale;
+        }
+
+        /**
+         * The Builder class used to construct {@link CaptureArgs}
+         *
+         * @param <T> A builder that extends {@link Builder}
+         */
+        abstract static class Builder<T extends Builder<T>> {
+            private int mPixelFormat = PixelFormat.RGBA_8888;
+            private final Rect mSourceCrop = new Rect();
+            private float mFrameScaleX = 1;
+            private float mFrameScaleY = 1;
+            private boolean mCaptureSecureLayers;
+            private boolean mAllowProtected;
+            private long mUid = -1;
+            private boolean mGrayscale;
+
+            /**
+             * The desired pixel format of the returned buffer.
+             */
+            public T setPixelFormat(int pixelFormat) {
+                mPixelFormat = pixelFormat;
+                return getThis();
+            }
+
+            /**
+             * The portion of the screen to capture into the buffer. Caller may pass  in
+             * 'new Rect()' if no cropping is desired.
+             */
+            public T setSourceCrop(Rect sourceCrop) {
+                mSourceCrop.set(sourceCrop);
+                return getThis();
+            }
+
+            /**
+             * The desired scale of the returned buffer. The raw screen will be scaled up/down.
+             */
+            public T setFrameScale(float frameScale) {
+                mFrameScaleX = frameScale;
+                mFrameScaleY = frameScale;
+                return getThis();
+            }
+
+            /**
+             * The desired scale of the returned buffer, allowing separate values for x and y scale.
+             * The raw screen will be scaled up/down.
+             */
+            public T setFrameScale(float frameScaleX, float frameScaleY) {
+                mFrameScaleX = frameScaleX;
+                mFrameScaleY = frameScaleY;
+                return getThis();
+            }
+
+            /**
+             * Whether to allow the screenshot of secure layers. Warning: This should only be done
+             * if the content will be placed in a secure SurfaceControl.
+             *
+             * @see ScreenshotHardwareBuffer#containsSecureLayers()
+             */
+            public T setCaptureSecureLayers(boolean captureSecureLayers) {
+                mCaptureSecureLayers = captureSecureLayers;
+                return getThis();
+            }
+
+            /**
+             * Whether to allow the screenshot of protected (DRM) content. Warning: The screenshot
+             * cannot be read in unprotected space.
+             *
+             * @see HardwareBuffer#USAGE_PROTECTED_CONTENT
+             */
+            public T setAllowProtected(boolean allowProtected) {
+                mAllowProtected = allowProtected;
+                return getThis();
+            }
+
+            /**
+             * Set the uid of the content that should be screenshot. The code will skip any surfaces
+             * that don't belong to the specified uid.
+             */
+            public T setUid(long uid) {
+                mUid = uid;
+                return getThis();
+            }
+
+            /**
+             * Set whether the screenshot should use grayscale or not.
+             */
+            public T setGrayscale(boolean grayscale) {
+                mGrayscale = grayscale;
+                return getThis();
+            }
+
+            /**
+             * Each sub class should return itself to allow the builder to chain properly
+             */
+            abstract T getThis();
+        }
+    }
+
+    /**
+     * The arguments class used to make display capture requests.
+     *
+     * @see #nativeCaptureDisplay(DisplayCaptureArgs, ScreenCaptureListener)
+     * @hide
+     */
+    public static class DisplayCaptureArgs extends CaptureArgs {
+        private final IBinder mDisplayToken;
+        private final int mWidth;
+        private final int mHeight;
+        private final boolean mUseIdentityTransform;
+
+        private DisplayCaptureArgs(Builder builder) {
+            super(builder);
+            mDisplayToken = builder.mDisplayToken;
+            mWidth = builder.mWidth;
+            mHeight = builder.mHeight;
+            mUseIdentityTransform = builder.mUseIdentityTransform;
+        }
+
+        /**
+         * The Builder class used to construct {@link DisplayCaptureArgs}
+         */
+        public static class Builder extends CaptureArgs.Builder<Builder> {
+            private IBinder mDisplayToken;
+            private int mWidth;
+            private int mHeight;
+            private boolean mUseIdentityTransform;
+
+            /**
+             * Construct a new {@link LayerCaptureArgs} with the set parameters. The builder
+             * remains valid.
+             */
+            public DisplayCaptureArgs build() {
+                if (mDisplayToken == null) {
+                    throw new IllegalStateException(
+                            "Can't take screenshot with null display token");
+                }
+                return new DisplayCaptureArgs(this);
+            }
+
+            public Builder(IBinder displayToken) {
+                setDisplayToken(displayToken);
+            }
+
+            /**
+             * The display to take the screenshot of.
+             */
+            public Builder setDisplayToken(IBinder displayToken) {
+                mDisplayToken = displayToken;
+                return this;
+            }
+
+            /**
+             * Set the desired size of the returned buffer. The raw screen  will be  scaled down to
+             * this size
+             *
+             * @param width  The desired width of the returned buffer. Caller may pass in 0 if no
+             *               scaling is desired.
+             * @param height The desired height of the returned buffer. Caller may pass in 0 if no
+             *               scaling is desired.
+             */
+            public Builder setSize(int width, int height) {
+                mWidth = width;
+                mHeight = height;
+                return this;
+            }
+
+            /**
+             * Replace the rotation transform of the display with the identity transformation while
+             * taking the screenshot. This ensures the screenshot is taken in the ROTATION_0
+             * orientation. Set this value to false if the screenshot should be taken in the
+             * current screen orientation.
+             */
+            public Builder setUseIdentityTransform(boolean useIdentityTransform) {
+                mUseIdentityTransform = useIdentityTransform;
+                return this;
+            }
+
+            @Override
+            Builder getThis() {
+                return this;
+            }
+        }
+    }
+
+    /**
+     * The arguments class used to make layer capture requests.
+     *
+     * @see #nativeCaptureLayers(LayerCaptureArgs, ScreenCaptureListener)
+     * @hide
+     */
+    public static class LayerCaptureArgs extends CaptureArgs {
+        private final long mNativeLayer;
+        private final long[] mNativeExcludeLayers;
+        private final boolean mChildrenOnly;
+
+        private LayerCaptureArgs(Builder builder) {
+            super(builder);
+            mChildrenOnly = builder.mChildrenOnly;
+            mNativeLayer = builder.mLayer.mNativeObject;
+            if (builder.mExcludeLayers != null) {
+                mNativeExcludeLayers = new long[builder.mExcludeLayers.length];
+                for (int i = 0; i < builder.mExcludeLayers.length; i++) {
+                    mNativeExcludeLayers[i] = builder.mExcludeLayers[i].mNativeObject;
+                }
+            } else {
+                mNativeExcludeLayers = null;
+            }
+        }
+
+        /**
+         * The Builder class used to construct {@link LayerCaptureArgs}
+         */
+        public static class Builder extends CaptureArgs.Builder<Builder> {
+            private SurfaceControl mLayer;
+            private SurfaceControl[] mExcludeLayers;
+            private boolean mChildrenOnly = true;
+
+            /**
+             * Construct a new {@link LayerCaptureArgs} with the set parameters. The builder
+             * remains valid.
+             */
+            public LayerCaptureArgs build() {
+                if (mLayer == null) {
+                    throw new IllegalStateException(
+                            "Can't take screenshot with null layer");
+                }
+                return new LayerCaptureArgs(this);
+            }
+
+            public Builder(SurfaceControl layer) {
+                setLayer(layer);
+            }
+
+            /**
+             * The root layer to capture.
+             */
+            public Builder setLayer(SurfaceControl layer) {
+                mLayer = layer;
+                return this;
+            }
+
+
+            /**
+             * An array of layer handles to exclude.
+             */
+            public Builder setExcludeLayers(@Nullable SurfaceControl[] excludeLayers) {
+                mExcludeLayers = excludeLayers;
+                return this;
+            }
+
+            /**
+             * Whether to include the layer itself in the screenshot or just the children and their
+             * descendants.
+             */
+            public Builder setChildrenOnly(boolean childrenOnly) {
+                mChildrenOnly = childrenOnly;
+                return this;
+            }
+
+            @Override
+            Builder getThis() {
+                return this;
+            }
+
+        }
+    }
+
+    /**
+     * Builder class for {@link SurfaceControl} objects.
+     *
+     * By default the surface will be hidden, and have "unset" bounds, meaning it can
+     * be as large as the bounds of its parent if a buffer or child so requires.
+     *
+     * It is necessary to set at least a name via {@link Builder#setName}
+     */
+    public static class Builder {
+        private SurfaceSession mSession;
+        private int mFlags = HIDDEN;
+        private int mWidth;
+        private int mHeight;
+        private int mFormat = PixelFormat.OPAQUE;
+        private String mName;
+        private WeakReference<View> mLocalOwnerView;
+        private SurfaceControl mParent;
+        private SparseIntArray mMetadata;
+        private String mCallsite = "SurfaceControl.Builder";
+
+        /**
+         * Begin building a SurfaceControl with a given {@link SurfaceSession}.
+         *
+         * @param session The {@link SurfaceSession} with which to eventually construct the surface.
+         * @hide
+         */
+        public Builder(SurfaceSession session) {
+            mSession = session;
+        }
+
+        /**
+         * Begin building a SurfaceControl.
+         */
+        public Builder() {
+        }
+
+        /**
+         * Construct a new {@link SurfaceControl} with the set parameters. The builder
+         * remains valid.
+         */
+        @NonNull
+        public SurfaceControl build() {
+            if (mWidth < 0 || mHeight < 0) {
+                throw new IllegalStateException(
+                        "width and height must be positive or unset");
+            }
+            if ((mWidth > 0 || mHeight > 0) && (isEffectLayer() || isContainerLayer())) {
+                throw new IllegalStateException(
+                        "Only buffer layers can set a valid buffer size.");
+            }
+
+            if ((mFlags & FX_SURFACE_MASK) == FX_SURFACE_NORMAL) {
+                setBLASTLayer();
+            }
+
+            return new SurfaceControl(
+                    mSession, mName, mWidth, mHeight, mFormat, mFlags, mParent, mMetadata,
+                    mLocalOwnerView, mCallsite);
+        }
+
+        /**
+         * Set a debugging-name for the SurfaceControl.
+         *
+         * @param name A name to identify the Surface in debugging.
+         */
+        @NonNull
+        public Builder setName(@NonNull String name) {
+            mName = name;
+            return this;
+        }
+
+        /**
+         * Set the local owner view for the surface. This view is only
+         * valid in the same process and is not transferred in an IPC.
+         *
+         * Note: This is used for cases where we want to know the view
+         * that manages the surface control while intercepting reparenting.
+         * A specific example is InlineContentView which exposes is surface
+         * control for reparenting as a way to implement clipping of several
+         * InlineContentView instances within a certain area.
+         *
+         * @param view The owner view.
+         * @return This builder.
+         *
+         * @hide
+         */
+        @NonNull
+        public Builder setLocalOwnerView(@NonNull View view) {
+            mLocalOwnerView = new WeakReference<>(view);
+            return this;
+        }
+
+        /**
+         * Set the initial size of the controlled surface's buffers in pixels.
+         *
+         * @param width The buffer width in pixels.
+         * @param height The buffer height in pixels.
+         */
+        @NonNull
+        public Builder setBufferSize(@IntRange(from = 0) int width,
+                @IntRange(from = 0) int height) {
+            if (width < 0 || height < 0) {
+                throw new IllegalArgumentException(
+                        "width and height must be positive");
+            }
+            mWidth = width;
+            mHeight = height;
+            // set this as a buffer layer since we are specifying a buffer size.
+            return setFlags(FX_SURFACE_NORMAL, FX_SURFACE_MASK);
+        }
+
+        private void unsetBufferSize() {
+            mWidth = 0;
+            mHeight = 0;
+        }
+
+        /**
+         * Set the pixel format of the controlled surface's buffers, using constants from
+         * {@link android.graphics.PixelFormat}.
+         */
+        @NonNull
+        public Builder setFormat(@PixelFormat.Format int format) {
+            mFormat = format;
+            return this;
+        }
+
+        /**
+         * Specify if the app requires a hardware-protected path to
+         * an external display sync. If protected content is enabled, but
+         * such a path is not available, then the controlled Surface will
+         * not be displayed.
+         *
+         * @param protectedContent Whether to require a protected sink.
+         * @hide
+         */
+        @NonNull
+        public Builder setProtected(boolean protectedContent) {
+            if (protectedContent) {
+                mFlags |= PROTECTED_APP;
+            } else {
+                mFlags &= ~PROTECTED_APP;
+            }
+            return this;
+        }
+
+        /**
+         * Specify whether the Surface contains secure content. If true, the system
+         * will prevent the surfaces content from being copied by another process. In
+         * particular screenshots and VNC servers will be disabled. This is however
+         * not a complete prevention of readback as {@link #setProtected}.
+         * @hide
+         */
+        @NonNull
+        public Builder setSecure(boolean secure) {
+            if (secure) {
+                mFlags |= SECURE;
+            } else {
+                mFlags &= ~SECURE;
+            }
+            return this;
+        }
+
+        /**
+         * Indicates whether the surface must be considered opaque,
+         * even if its pixel format is set to translucent. This can be useful if an
+         * application needs full RGBA 8888 support for instance but will
+         * still draw every pixel opaque.
+         * <p>
+         * This flag only determines whether opacity will be sampled from the alpha channel.
+         * Plane-alpha from calls to setAlpha() can still result in blended composition
+         * regardless of the opaque setting.
+         *
+         * Combined effects are (assuming a buffer format with an alpha channel):
+         * <ul>
+         * <li>OPAQUE + alpha(1.0) == opaque composition
+         * <li>OPAQUE + alpha(0.x) == blended composition
+         * <li>OPAQUE + alpha(0.0) == no composition
+         * <li>!OPAQUE + alpha(1.0) == blended composition
+         * <li>!OPAQUE + alpha(0.x) == blended composition
+         * <li>!OPAQUE + alpha(0.0) == no composition
+         * </ul>
+         * If the underlying buffer lacks an alpha channel, it is as if setOpaque(true)
+         * were set automatically.
+         * @param opaque Whether the Surface is OPAQUE.
+         */
+        @NonNull
+        public Builder setOpaque(boolean opaque) {
+            if (opaque) {
+                mFlags |= OPAQUE;
+            } else {
+                mFlags &= ~OPAQUE;
+            }
+            return this;
+        }
+
+        /**
+         * Set the initial visibility for the SurfaceControl.
+         *
+         * @param hidden Whether the Surface is initially HIDDEN.
+         * @hide
+         */
+        @NonNull
+        public Builder setHidden(boolean hidden) {
+            if (hidden) {
+                mFlags |= HIDDEN;
+            } else {
+                mFlags &= ~HIDDEN;
+            }
+            return this;
+        }
+
+        /**
+         * Set a parent surface for our new SurfaceControl.
+         *
+         * Child surfaces are constrained to the onscreen region of their parent.
+         * Furthermore they stack relatively in Z order, and inherit the transformation
+         * of the parent.
+         *
+         * @param parent The parent control.
+         */
+        @NonNull
+        public Builder setParent(@Nullable SurfaceControl parent) {
+            mParent = parent;
+            return this;
+        }
+
+        /**
+         * Sets a metadata int.
+         *
+         * @param key metadata key
+         * @param data associated data
+         * @hide
+         */
+        public Builder setMetadata(int key, int data) {
+            if (mMetadata == null) {
+                mMetadata = new SparseIntArray();
+            }
+            mMetadata.put(key, data);
+            return this;
+        }
+
+        /**
+         * Indicate whether an 'EffectLayer' is to be constructed.
+         *
+         * An effect layer behaves like a container layer by default but it can support
+         * color fill, shadows and/or blur. These layers will not have an associated buffer.
+         * When created, this layer has no effects set and will be transparent but the caller
+         * can render an effect by calling:
+         *  - {@link Transaction#setColor(SurfaceControl, float[])}
+         *  - {@link Transaction#setBackgroundBlurRadius(SurfaceControl, int)}
+         *  - {@link Transaction#setShadowRadius(SurfaceControl, float)}
+         *
+         * @hide
+         */
+        public Builder setEffectLayer() {
+            mFlags |= NO_COLOR_FILL;
+            unsetBufferSize();
+            return setFlags(FX_SURFACE_EFFECT, FX_SURFACE_MASK);
+        }
+
+        /**
+         * A convenience function to create an effect layer with a default color fill
+         * applied to it. Currently that color is black.
+         *
+         * @hide
+         */
+        public Builder setColorLayer() {
+            unsetBufferSize();
+            return setFlags(FX_SURFACE_EFFECT, FX_SURFACE_MASK);
+        }
+
+        private boolean isEffectLayer() {
+            return  (mFlags & FX_SURFACE_EFFECT) == FX_SURFACE_EFFECT;
+        }
+
+        /**
+         * @hide
+         */
+        public Builder setBLASTLayer() {
+            return setFlags(FX_SURFACE_BLAST, FX_SURFACE_MASK);
+        }
+
+        /**
+         * Indicates whether a 'ContainerLayer' is to be constructed.
+         *
+         * Container layers will not be rendered in any fashion and instead are used
+         * as a parent of renderable layers.
+         *
+         * @hide
+         */
+        public Builder setContainerLayer() {
+            unsetBufferSize();
+            return setFlags(FX_SURFACE_CONTAINER, FX_SURFACE_MASK);
+        }
+
+        private boolean isContainerLayer() {
+            return  (mFlags & FX_SURFACE_CONTAINER) == FX_SURFACE_CONTAINER;
+        }
+
+        /**
+         * Set 'Surface creation flags' such as {@link #HIDDEN}, {@link #SECURE}.
+         *
+         * TODO: Finish conversion to individual builder methods?
+         * @param flags The combined flags
+         * @hide
+         */
+        public Builder setFlags(int flags) {
+            mFlags = flags;
+            return this;
+        }
+
+        /**
+         * Sets the callsite this SurfaceControl is constructed from.
+         *
+         * @param callsite String uniquely identifying callsite that created this object. Used for
+         *                 leakage tracking.
+         * @hide
+         */
+        public Builder setCallsite(String callsite) {
+            mCallsite = callsite;
+            return this;
+        }
+
+        private Builder setFlags(int flags, int mask) {
+            mFlags = (mFlags & ~mask) | flags;
+            return this;
+        }
+    }
+
+    /**
+     * Create a surface with a name.
+     * <p>
+     * The surface creation flags specify what kind of surface to create and
+     * certain options such as whether the surface can be assumed to be opaque
+     * and whether it should be initially hidden.  Surfaces should always be
+     * created with the {@link #HIDDEN} flag set to ensure that they are not
+     * made visible prematurely before all of the surface's properties have been
+     * configured.
+     * <p>
+     * Good practice is to first create the surface with the {@link #HIDDEN} flag
+     * specified, open a transaction, set the surface layer, layer stack, alpha,
+     * and position, call {@link Transaction#show(SurfaceControl)} if appropriate, and close the
+     * transaction.
+     * <p>
+     * Bounds of the surface is determined by its crop and its buffer size. If the
+     * surface has no buffer or crop, the surface is boundless and only constrained
+     * by the size of its parent bounds.
+     *
+     * @param session  The surface session, must not be null.
+     * @param name     The surface name, must not be null.
+     * @param w        The surface initial width.
+     * @param h        The surface initial height.
+     * @param flags    The surface creation flags.
+     * @param metadata Initial metadata.
+     * @param callsite String uniquely identifying callsite that created this object. Used for
+     *                 leakage tracking.
+     * @throws throws OutOfResourcesException If the SurfaceControl cannot be created.
+     */
+    private SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags,
+            SurfaceControl parent, SparseIntArray metadata, WeakReference<View> localOwnerView,
+            String callsite)
+                    throws OutOfResourcesException, IllegalArgumentException {
+        if (name == null) {
+            throw new IllegalArgumentException("name must not be null");
+        }
+
+        mName = name;
+        mWidth = w;
+        mHeight = h;
+        mLocalOwnerView = localOwnerView;
+        Parcel metaParcel = Parcel.obtain();
+        try {
+            if (metadata != null && metadata.size() > 0) {
+                metaParcel.writeInt(metadata.size());
+                for (int i = 0; i < metadata.size(); ++i) {
+                    metaParcel.writeInt(metadata.keyAt(i));
+                    metaParcel.writeByteArray(
+                            ByteBuffer.allocate(4).order(ByteOrder.nativeOrder())
+                                    .putInt(metadata.valueAt(i)).array());
+                }
+                metaParcel.setDataPosition(0);
+            }
+            mNativeObject = nativeCreate(session, name, w, h, format, flags,
+                    parent != null ? parent.mNativeObject : 0, metaParcel);
+        } finally {
+            metaParcel.recycle();
+        }
+        if (mNativeObject == 0) {
+            throw new OutOfResourcesException(
+                    "Couldn't allocate SurfaceControl native object");
+        }
+        mNativeHandle = nativeGetHandle(mNativeObject);
+        mCloseGuard.openWithCallSite("release", callsite);
+    }
+
+    /**
+     * Copy constructor. Creates a new native object pointing to the same surface as {@code other}.
+     *
+     * @param other The object to copy the surface from.
+     * @param callsite String uniquely identifying callsite that created this object. Used for
+     *                 leakage tracking.
+     * @hide
+     */
+    @TestApi
+    public SurfaceControl(@NonNull SurfaceControl other, @NonNull String callsite) {
+        copyFrom(other, callsite);
+    }
+
+    private SurfaceControl(Parcel in) {
+        readFromParcel(in);
+    }
+
+    /**
+     * @hide
+     */
+    public SurfaceControl() {
+    }
+
+    public void readFromParcel(Parcel in) {
+        if (in == null) {
+            throw new IllegalArgumentException("source must not be null");
+        }
+
+        mName = in.readString8();
+        mWidth = in.readInt();
+        mHeight = in.readInt();
+
+        long object = 0;
+        if (in.readInt() != 0) {
+            object = nativeReadFromParcel(in);
+        }
+        assignNativeObject(object, "readFromParcel");
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString8(mName);
+        dest.writeInt(mWidth);
+        dest.writeInt(mHeight);
+        if (mNativeObject == 0) {
+            dest.writeInt(0);
+        } else {
+            dest.writeInt(1);
+        }
+        nativeWriteToParcel(mNativeObject, dest);
+
+        if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) {
+            release();
+        }
+    }
+
+    /**
+     * Checks whether two {@link SurfaceControl} objects represent the same surface.
+     *
+     * @param other The other object to check
+     * @return {@code true} if these two {@link SurfaceControl} objects represent the same surface.
+     * @hide
+     */
+    @TestApi
+    public boolean isSameSurface(@NonNull SurfaceControl other) {
+        return other.mNativeHandle == mNativeHandle;
+    }
+
+    /**
+     * Write to a protocol buffer output stream. Protocol buffer message definition is at {@link
+     * android.view.SurfaceControlProto}.
+     *
+     * @param proto Stream to write the SurfaceControl object to.
+     * @param fieldId Field Id of the SurfaceControl as defined in the parent message.
+     * @hide
+     */
+    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(HASH_CODE, System.identityHashCode(this));
+        proto.write(NAME, mName);
+        proto.end(token);
+    }
+
+    public static final @android.annotation.NonNull Creator<SurfaceControl> CREATOR
+            = new Creator<SurfaceControl>() {
+        public SurfaceControl createFromParcel(Parcel in) {
+            return new SurfaceControl(in);
+        }
+
+        public SurfaceControl[] newArray(int size) {
+            return new SurfaceControl[size];
+        }
+    };
+
+    /**
+     * @hide
+     */
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mCloseGuard != null) {
+                mCloseGuard.warnIfOpen();
+            }
+            if (mNativeObject != 0) {
+                nativeRelease(mNativeObject);
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Release the local reference to the server-side surface. The surface
+     * may continue to exist on-screen as long as its parent continues
+     * to exist. To explicitly remove a surface from the screen use
+     * {@link Transaction#reparent} with a null-parent. After release,
+     * {@link #isValid} will return false and other methods will throw
+     * an exception.
+     *
+     * Always call release() when you're done with a SurfaceControl.
+     */
+    public void release() {
+        if (mNativeObject != 0) {
+            nativeRelease(mNativeObject);
+            mNativeObject = 0;
+            mNativeHandle = 0;
+            mCloseGuard.close();
+        }
+    }
+
+    /**
+     * Disconnect any client still connected to the surface.
+     * @hide
+     */
+    public void disconnect() {
+        if (mNativeObject != 0) {
+            nativeDisconnect(mNativeObject);
+        }
+    }
+
+    private void checkNotReleased() {
+        if (mNativeObject == 0) throw new NullPointerException(
+                "Invalid " + this + ", mNativeObject is null. Have you called release() already?");
+    }
+
+    /**
+     * Check whether this instance points to a valid layer with the system-compositor. For
+     * example this may be false if construction failed, or the layer was released
+     * ({@link #release}).
+     *
+     * @return Whether this SurfaceControl is valid.
+     */
+    public boolean isValid() {
+        return mNativeObject != 0;
+    }
+
+    /*
+     * set surface parameters.
+     * needs to be inside open/closeTransaction block
+     */
+
+    /** start a transaction
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static void openTransaction() {
+        synchronized (SurfaceControl.class) {
+            if (sGlobalTransaction == null) {
+                sGlobalTransaction = new GlobalTransactionWrapper();
+            }
+            synchronized(SurfaceControl.class) {
+                sTransactionNestCount++;
+            }
+        }
+    }
+
+    /**
+     * Merge the supplied transaction in to the deprecated "global" transaction.
+     * This clears the supplied transaction in an identical fashion to {@link Transaction#merge}.
+     * <p>
+     * This is a utility for interop with legacy-code and will go away with the Global Transaction.
+     * @hide
+     */
+    @Deprecated
+    public static void mergeToGlobalTransaction(Transaction t) {
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.merge(t);
+        }
+    }
+
+    /** end a transaction
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static void closeTransaction() {
+        synchronized(SurfaceControl.class) {
+            if (sTransactionNestCount == 0) {
+                Log.e(TAG,
+                        "Call to SurfaceControl.closeTransaction without matching openTransaction");
+            } else if (--sTransactionNestCount > 0) {
+                return;
+            }
+            sGlobalTransaction.applyGlobalTransaction(false);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public boolean clearContentFrameStats() {
+        checkNotReleased();
+        return nativeClearContentFrameStats(mNativeObject);
+    }
+
+    /**
+     * @hide
+     */
+    public boolean getContentFrameStats(WindowContentFrameStats outStats) {
+        checkNotReleased();
+        return nativeGetContentFrameStats(mNativeObject, outStats);
+    }
+
+    /**
+     * @hide
+     */
+    public static boolean clearAnimationFrameStats() {
+        return nativeClearAnimationFrameStats();
+    }
+
+    /**
+     * @hide
+     */
+    public static boolean getAnimationFrameStats(WindowAnimationFrameStats outStats) {
+        return nativeGetAnimationFrameStats(outStats);
+    }
+
+    /**
+     * @hide
+     */
+    public int getWidth() {
+        synchronized (mLock) {
+            return mWidth;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public int getHeight() {
+        synchronized (mLock) {
+            return mHeight;
+        }
+    }
+
+    /**
+     * Gets the local view that owns this surface.
+     *
+     * @return The owner view.
+     *
+     * @hide
+     */
+    public @Nullable View getLocalOwnerView() {
+        return (mLocalOwnerView != null) ? mLocalOwnerView.get() : null;
+    }
+
+    @Override
+    public String toString() {
+        return "Surface(name=" + mName + ")/@0x" +
+                Integer.toHexString(System.identityHashCode(this));
+    }
+
+    /**
+     * Immutable information about physical display.
+     *
+     * @hide
+     */
+    public static final class StaticDisplayInfo {
+        public boolean isInternal;
+        public float density;
+        public boolean secure;
+        public DeviceProductInfo deviceProductInfo;
+
+        @Override
+        public String toString() {
+            return "StaticDisplayInfo{isInternal=" + isInternal
+                    + ", density=" + density
+                    + ", secure=" + secure
+                    + ", deviceProductInfo=" + deviceProductInfo + "}";
+        }
+
+        @Override
+        public boolean equals(@Nullable Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            StaticDisplayInfo that = (StaticDisplayInfo) o;
+            return isInternal == that.isInternal
+                    && density == that.density
+                    && secure == that.secure
+                    && Objects.equals(deviceProductInfo, that.deviceProductInfo);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(isInternal, density, secure, deviceProductInfo);
+        }
+    }
+
+    /**
+     * Dynamic information about physical display.
+     *
+     * @hide
+     */
+    public static final class DynamicDisplayInfo {
+        public DisplayMode[] supportedDisplayModes;
+        public int activeDisplayModeId;
+
+        public int[] supportedColorModes;
+        public int activeColorMode;
+
+        public Display.HdrCapabilities hdrCapabilities;
+
+        public boolean autoLowLatencyModeSupported;
+        public boolean gameContentTypeSupported;
+
+        @Override
+        public String toString() {
+            return "DynamicDisplayInfo{"
+                    + "supportedDisplayModes=" + Arrays.toString(supportedDisplayModes)
+                    + ", activeDisplayModeId=" + activeDisplayModeId
+                    + ", supportedColorModes=" + Arrays.toString(supportedColorModes)
+                    + ", activeColorMode=" + activeColorMode
+                    + ", hdrCapabilities=" + hdrCapabilities
+                    + ", autoLowLatencyModeSupported=" + autoLowLatencyModeSupported
+                    + ", gameContentTypeSupported" + gameContentTypeSupported + "}";
+        }
+
+        @Override
+        public boolean equals(@Nullable Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            DynamicDisplayInfo that = (DynamicDisplayInfo) o;
+            return Arrays.equals(supportedDisplayModes, that.supportedDisplayModes)
+                && activeDisplayModeId == that.activeDisplayModeId
+                && Arrays.equals(supportedColorModes, that.supportedColorModes)
+                && activeColorMode == that.activeColorMode
+                && Objects.equals(hdrCapabilities, that.hdrCapabilities);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(supportedDisplayModes, activeDisplayModeId, activeDisplayModeId,
+                    activeColorMode, hdrCapabilities);
+        }
+    }
+
+    /**
+     * Configuration supported by physical display.
+     *
+     * @hide
+     */
+    public static final class DisplayMode {
+        /**
+         * Invalid display config id.
+         */
+        public static final int INVALID_DISPLAY_MODE_ID = -1;
+
+        public int id;
+        public int width;
+        public int height;
+        public float xDpi;
+        public float yDpi;
+
+        public float refreshRate;
+        public long appVsyncOffsetNanos;
+        public long presentationDeadlineNanos;
+
+        /**
+         * The config group ID this config is associated to.
+         * Configs in the same group are similar from vendor's perspective and switching between
+         * configs within the same group can be done seamlessly in most cases.
+         * @see: [email protected]::IComposerClient::Attribute::CONFIG_GROUP
+         */
+        public int group;
+
+        @Override
+        public String toString() {
+            return "DisplayMode{id=" + id
+                    + ", width=" + width
+                    + ", height=" + height
+                    + ", xDpi=" + xDpi
+                    + ", yDpi=" + yDpi
+                    + ", refreshRate=" + refreshRate
+                    + ", appVsyncOffsetNanos=" + appVsyncOffsetNanos
+                    + ", presentationDeadlineNanos=" + presentationDeadlineNanos
+                    + ", group=" + group + "}";
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            DisplayMode that = (DisplayMode) o;
+            return id == that.id
+                    && width == that.width
+                    && height == that.height
+                    && Float.compare(that.xDpi, xDpi) == 0
+                    && Float.compare(that.yDpi, yDpi) == 0
+                    && Float.compare(that.refreshRate, refreshRate) == 0
+                    && appVsyncOffsetNanos == that.appVsyncOffsetNanos
+                    && presentationDeadlineNanos == that.presentationDeadlineNanos
+                    && group == that.group;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(id, width, height, xDpi, yDpi, refreshRate, appVsyncOffsetNanos,
+                    presentationDeadlineNanos, group);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public static void setDisplayPowerMode(IBinder displayToken, int mode) {
+        if (displayToken == null) {
+            throw new IllegalArgumentException("displayToken must not be null");
+        }
+        nativeSetDisplayPowerMode(displayToken, mode);
+    }
+
+    /**
+     * @hide
+     */
+    public static StaticDisplayInfo getStaticDisplayInfo(IBinder displayToken) {
+        if (displayToken == null) {
+            throw new IllegalArgumentException("displayToken must not be null");
+        }
+        return nativeGetStaticDisplayInfo(displayToken);
+    }
+
+    /**
+     * @hide
+     */
+    public static DynamicDisplayInfo getDynamicDisplayInfo(IBinder displayToken) {
+        if (displayToken == null) {
+            throw new IllegalArgumentException("displayToken must not be null");
+        }
+        return nativeGetDynamicDisplayInfo(displayToken);
+    }
+
+    /**
+     * @hide
+     */
+    public static DisplayedContentSamplingAttributes getDisplayedContentSamplingAttributes(
+            IBinder displayToken) {
+        if (displayToken == null) {
+            throw new IllegalArgumentException("displayToken must not be null");
+        }
+        return nativeGetDisplayedContentSamplingAttributes(displayToken);
+    }
+
+    /**
+     * @hide
+     */
+    public static boolean setDisplayedContentSamplingEnabled(
+            IBinder displayToken, boolean enable, int componentMask, int maxFrames) {
+        if (displayToken == null) {
+            throw new IllegalArgumentException("displayToken must not be null");
+        }
+        final int maxColorComponents = 4;
+        if ((componentMask >> maxColorComponents) != 0) {
+            throw new IllegalArgumentException("invalid componentMask when enabling sampling");
+        }
+        return nativeSetDisplayedContentSamplingEnabled(
+                displayToken, enable, componentMask, maxFrames);
+    }
+
+    /**
+     * @hide
+     */
+    public static DisplayedContentSample getDisplayedContentSample(
+            IBinder displayToken, long maxFrames, long timestamp) {
+        if (displayToken == null) {
+            throw new IllegalArgumentException("displayToken must not be null");
+        }
+        return nativeGetDisplayedContentSample(displayToken, maxFrames, timestamp);
+    }
+
+
+    /**
+     * Contains information about desired display configuration.
+     *
+     * @hide
+     */
+    public static final class DesiredDisplayModeSpecs {
+        public int defaultMode;
+        /**
+         * The primary refresh rate range represents display manager's general guidance on the
+         * display configs surface flinger will consider when switching refresh rates. Unless
+         * surface flinger has a specific reason to do otherwise, it will stay within this range.
+         */
+        public float primaryRefreshRateMin;
+        public float primaryRefreshRateMax;
+        /**
+         * The app request refresh rate range allows surface flinger to consider more display
+         * configs when switching refresh rates. Although surface flinger will generally stay within
+         * the primary range, specific considerations, such as layer frame rate settings specified
+         * via the setFrameRate() api, may cause surface flinger to go outside the primary
+         * range. Surface flinger never goes outside the app request range. The app request range
+         * will be greater than or equal to the primary refresh rate range, never smaller.
+         */
+        public float appRequestRefreshRateMin;
+        public float appRequestRefreshRateMax;
+
+        /**
+         * If true this will allow switching between modes in different display configuration
+         * groups. This way the user may see visual interruptions when the display mode changes.
+         */
+        public boolean allowGroupSwitching;
+
+        public DesiredDisplayModeSpecs() {}
+
+        public DesiredDisplayModeSpecs(DesiredDisplayModeSpecs other) {
+            copyFrom(other);
+        }
+
+        public DesiredDisplayModeSpecs(int defaultMode, boolean allowGroupSwitching,
+                float primaryRefreshRateMin, float primaryRefreshRateMax,
+                float appRequestRefreshRateMin, float appRequestRefreshRateMax) {
+            this.defaultMode = defaultMode;
+            this.allowGroupSwitching = allowGroupSwitching;
+            this.primaryRefreshRateMin = primaryRefreshRateMin;
+            this.primaryRefreshRateMax = primaryRefreshRateMax;
+            this.appRequestRefreshRateMin = appRequestRefreshRateMin;
+            this.appRequestRefreshRateMax = appRequestRefreshRateMax;
+        }
+
+        @Override
+        public boolean equals(@Nullable Object o) {
+            return o instanceof DesiredDisplayModeSpecs && equals((DesiredDisplayModeSpecs) o);
+        }
+
+        /**
+         * Tests for equality.
+         */
+        public boolean equals(DesiredDisplayModeSpecs other) {
+            return other != null && defaultMode == other.defaultMode
+                    && primaryRefreshRateMin == other.primaryRefreshRateMin
+                    && primaryRefreshRateMax == other.primaryRefreshRateMax
+                    && appRequestRefreshRateMin == other.appRequestRefreshRateMin
+                    && appRequestRefreshRateMax == other.appRequestRefreshRateMax;
+        }
+
+        @Override
+        public int hashCode() {
+            return 0; // don't care
+        }
+
+        /**
+         * Copies the supplied object's values to this object.
+         */
+        public void copyFrom(DesiredDisplayModeSpecs other) {
+            defaultMode = other.defaultMode;
+            primaryRefreshRateMin = other.primaryRefreshRateMin;
+            primaryRefreshRateMax = other.primaryRefreshRateMax;
+            appRequestRefreshRateMin = other.appRequestRefreshRateMin;
+            appRequestRefreshRateMax = other.appRequestRefreshRateMax;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("defaultConfig=%d primaryRefreshRateRange=[%.0f %.0f]"
+                            + " appRequestRefreshRateRange=[%.0f %.0f]",
+                    defaultMode, primaryRefreshRateMin, primaryRefreshRateMax,
+                    appRequestRefreshRateMin, appRequestRefreshRateMax);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public static boolean setDesiredDisplayModeSpecs(IBinder displayToken,
+            DesiredDisplayModeSpecs desiredDisplayModeSpecs) {
+        if (displayToken == null) {
+            throw new IllegalArgumentException("displayToken must not be null");
+        }
+        if (desiredDisplayModeSpecs == null) {
+            throw new IllegalArgumentException("desiredDisplayModeSpecs must not be null");
+        }
+        if (desiredDisplayModeSpecs.defaultMode < 0) {
+            throw new IllegalArgumentException("defaultMode must be non-negative");
+        }
+
+        return nativeSetDesiredDisplayModeSpecs(displayToken, desiredDisplayModeSpecs);
+    }
+
+    /**
+     * @hide
+     */
+    public static DesiredDisplayModeSpecs getDesiredDisplayModeSpecs(
+            IBinder displayToken) {
+        if (displayToken == null) {
+            throw new IllegalArgumentException("displayToken must not be null");
+        }
+
+        return nativeGetDesiredDisplayModeSpecs(displayToken);
+    }
+
+    /**
+     * Color coordinates in CIE1931 XYZ color space
+     *
+     * @hide
+     */
+    public static final class CieXyz {
+        /**
+         * @hide
+         */
+        public float X;
+
+        /**
+         * @hide
+         */
+        public float Y;
+
+        /**
+         * @hide
+         */
+        public float Z;
+    }
+
+    /**
+     * Contains a display's color primaries
+     *
+     * @hide
+     */
+    public static final class DisplayPrimaries {
+        /**
+         * @hide
+         */
+        public CieXyz red;
+
+        /**
+         * @hide
+         */
+        public CieXyz green;
+
+        /**
+         * @hide
+         */
+        public CieXyz blue;
+
+        /**
+         * @hide
+         */
+        public CieXyz white;
+
+        /**
+         * @hide
+         */
+        public DisplayPrimaries() {
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public static DisplayPrimaries getDisplayNativePrimaries(
+            IBinder displayToken) {
+        if (displayToken == null) {
+            throw new IllegalArgumentException("displayToken must not be null");
+        }
+
+        return nativeGetDisplayNativePrimaries(displayToken);
+    }
+
+    /**
+     * @hide
+     */
+    public static boolean setActiveColorMode(IBinder displayToken, int colorMode) {
+        if (displayToken == null) {
+            throw new IllegalArgumentException("displayToken must not be null");
+        }
+        return nativeSetActiveColorMode(displayToken, colorMode);
+    }
+
+    /**
+     * Returns an array of color spaces with 2 elements. The first color space is the
+     * default color space and second one is wide color gamut color space.
+     * @hide
+     */
+    public static ColorSpace[] getCompositionColorSpaces() {
+        int[] dataspaces = nativeGetCompositionDataspaces();
+        ColorSpace srgb = ColorSpace.get(ColorSpace.Named.SRGB);
+        ColorSpace[] colorSpaces = { srgb, srgb };
+        if (dataspaces.length == 2) {
+            for (int i = 0; i < 2; ++i) {
+                switch(dataspaces[i]) {
+                    case INTERNAL_DATASPACE_DISPLAY_P3:
+                        colorSpaces[i] = ColorSpace.get(ColorSpace.Named.DISPLAY_P3);
+                        break;
+                    case INTERNAL_DATASPACE_SCRGB:
+                        colorSpaces[i] = ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB);
+                        break;
+                    case INTERNAL_DATASPACE_SRGB:
+                    // Other dataspace is not recognized, use SRGB color space instead,
+                    // the default value of the array is already SRGB, thus do nothing.
+                    default:
+                        break;
+                }
+            }
+        }
+        return colorSpaces;
+    }
+
+    /**
+     * @hide
+     */
+    public static void setAutoLowLatencyMode(IBinder displayToken, boolean on) {
+        if (displayToken == null) {
+            throw new IllegalArgumentException("displayToken must not be null");
+        }
+
+        nativeSetAutoLowLatencyMode(displayToken, on);
+    }
+
+    /**
+     * @hide
+     */
+    public static void setGameContentType(IBinder displayToken, boolean on) {
+        if (displayToken == null) {
+            throw new IllegalArgumentException("displayToken must not be null");
+        }
+
+        nativeSetGameContentType(displayToken, on);
+    }
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static void setDisplayProjection(IBinder displayToken,
+            int orientation, Rect layerStackRect, Rect displayRect) {
+        synchronized (SurfaceControl.class) {
+            sGlobalTransaction.setDisplayProjection(displayToken, orientation,
+                    layerStackRect, displayRect);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static void setDisplayLayerStack(IBinder displayToken, int layerStack) {
+        synchronized (SurfaceControl.class) {
+            sGlobalTransaction.setDisplayLayerStack(displayToken, layerStack);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static void setDisplaySurface(IBinder displayToken, Surface surface) {
+        synchronized (SurfaceControl.class) {
+            sGlobalTransaction.setDisplaySurface(displayToken, surface);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public static void setDisplaySize(IBinder displayToken, int width, int height) {
+        synchronized (SurfaceControl.class) {
+            sGlobalTransaction.setDisplaySize(displayToken, width, height);
+        }
+    }
+
+    /**
+     * Overrides HDR modes for a display device.
+     *
+     * If the caller does not have ACCESS_SURFACE_FLINGER permission, this will throw a Security
+     * Exception.
+     * @hide
+     */
+    @TestApi
+    public static void overrideHdrTypes(@NonNull IBinder displayToken, @NonNull int[] modes) {
+        nativeOverrideHdrTypes(displayToken, modes);
+    }
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static IBinder createDisplay(String name, boolean secure) {
+        if (name == null) {
+            throw new IllegalArgumentException("name must not be null");
+        }
+        return nativeCreateDisplay(name, secure);
+    }
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static void destroyDisplay(IBinder displayToken) {
+        if (displayToken == null) {
+            throw new IllegalArgumentException("displayToken must not be null");
+        }
+        nativeDestroyDisplay(displayToken);
+    }
+
+    /**
+     * @hide
+     */
+    public static long[] getPhysicalDisplayIds() {
+        return nativeGetPhysicalDisplayIds();
+    }
+
+    /**
+     * @hide
+     */
+    public static IBinder getPhysicalDisplayToken(long physicalDisplayId) {
+        return nativeGetPhysicalDisplayToken(physicalDisplayId);
+    }
+
+    /**
+     * TODO(b/116025192): Remove this stopgap once framework is display-agnostic.
+     *
+     * @hide
+     */
+    @TestApi
+    @NonNull
+    public static IBinder getInternalDisplayToken() {
+        final long[] physicalDisplayIds = getPhysicalDisplayIds();
+        if (physicalDisplayIds.length == 0) {
+            return null;
+        }
+        return getPhysicalDisplayToken(physicalDisplayIds[0]);
+    }
+
+    /**
+     * @param captureArgs Arguments about how to take the screenshot
+     * @param captureListener A listener to receive the screenshot callback
+     * @hide
+     */
+    public static int captureDisplay(@NonNull DisplayCaptureArgs captureArgs,
+            @NonNull ScreenCaptureListener captureListener) {
+        return nativeCaptureDisplay(captureArgs, captureListener);
+    }
+
+    /**
+     * Captures all the surfaces in a display and returns a {@link ScreenshotHardwareBuffer} with
+     * the content.
+     *
+     * @hide
+     */
+    public static ScreenshotHardwareBuffer captureDisplay(DisplayCaptureArgs captureArgs) {
+        SyncScreenCaptureListener screenCaptureListener = new SyncScreenCaptureListener();
+
+        int status = captureDisplay(captureArgs, screenCaptureListener);
+        if (status != 0) {
+            return null;
+        }
+
+        return screenCaptureListener.waitForScreenshot();
+    }
+
+    /**
+     * Captures a layer and its children and returns a {@link HardwareBuffer} with the content.
+     *
+     * @param layer            The root layer to capture.
+     * @param sourceCrop       The portion of the root surface to capture; caller may pass in 'new
+     *                         Rect()' or null if no cropping is desired. If the root layer does not
+     *                         have a buffer or a crop set, then a non-empty source crop must be
+     *                         specified.
+     * @param frameScale       The desired scale of the returned buffer; the raw
+     *                         screen will be scaled up/down.
+     *
+     * @return Returns a HardwareBuffer that contains the layer capture.
+     * @hide
+     */
+    public static ScreenshotHardwareBuffer captureLayers(SurfaceControl layer, Rect sourceCrop,
+            float frameScale) {
+        return captureLayers(layer, sourceCrop, frameScale, PixelFormat.RGBA_8888);
+    }
+
+    /**
+     * Captures a layer and its children and returns a {@link HardwareBuffer} with the content.
+     *
+     * @param layer            The root layer to capture.
+     * @param sourceCrop       The portion of the root surface to capture; caller may pass in 'new
+     *                         Rect()' or null if no cropping is desired. If the root layer does not
+     *                         have a buffer or a crop set, then a non-empty source crop must be
+     *                         specified.
+     * @param frameScale       The desired scale of the returned buffer; the raw
+     *                         screen will be scaled up/down.
+     * @param format           The desired pixel format of the returned buffer.
+     *
+     * @return Returns a HardwareBuffer that contains the layer capture.
+     * @hide
+     */
+    public static ScreenshotHardwareBuffer captureLayers(SurfaceControl layer, Rect sourceCrop,
+            float frameScale, int format) {
+        LayerCaptureArgs captureArgs = new LayerCaptureArgs.Builder(layer)
+                .setSourceCrop(sourceCrop)
+                .setFrameScale(frameScale)
+                .setPixelFormat(format)
+                .build();
+
+        return captureLayers(captureArgs);
+    }
+
+    /**
+     * @hide
+     */
+    public static ScreenshotHardwareBuffer captureLayers(LayerCaptureArgs captureArgs) {
+        SyncScreenCaptureListener screenCaptureListener = new SyncScreenCaptureListener();
+
+        int status = captureLayers(captureArgs, screenCaptureListener);
+        if (status != 0) {
+            return null;
+        }
+
+        return screenCaptureListener.waitForScreenshot();
+    }
+
+    /**
+     * Like {@link #captureLayers(SurfaceControl, Rect, float, int)} but with an array of layer
+     * handles to exclude.
+     * @hide
+     */
+    public static ScreenshotHardwareBuffer captureLayersExcluding(SurfaceControl layer,
+            Rect sourceCrop, float frameScale, int format, SurfaceControl[] exclude) {
+        LayerCaptureArgs captureArgs = new LayerCaptureArgs.Builder(layer)
+                .setSourceCrop(sourceCrop)
+                .setFrameScale(frameScale)
+                .setPixelFormat(format)
+                .setExcludeLayers(exclude)
+                .build();
+
+        return captureLayers(captureArgs);
+    }
+
+    /**
+     * @param captureArgs Arguments about how to take the screenshot
+     * @param captureListener A listener to receive the screenshot callback
+     * @hide
+     */
+    public static int captureLayers(@NonNull LayerCaptureArgs captureArgs,
+            @NonNull ScreenCaptureListener captureListener) {
+        return nativeCaptureLayers(captureArgs, captureListener);
+    }
+
+    /**
+     * Returns whether protected content is supported in GPU composition.
+     * @hide
+     */
+    public static boolean getProtectedContentSupport() {
+        return nativeGetProtectedContentSupport();
+    }
+
+    /**
+     * Returns whether brightness operations are supported on a display.
+     *
+     * @param displayToken
+     *      The token for the display.
+     *
+     * @return Whether brightness operations are supported on the display.
+     *
+     * @hide
+     */
+    public static boolean getDisplayBrightnessSupport(IBinder displayToken) {
+        return nativeGetDisplayBrightnessSupport(displayToken);
+    }
+
+    /**
+     * Sets the brightness of a display.
+     *
+     * @param displayToken
+     *      The token for the display whose brightness is set.
+     * @param brightness
+     *      A number between 0.0f (minimum brightness) and 1.0f (maximum brightness), or -1.0f to
+     *      turn the backlight off.
+     *
+     * @return Whether the method succeeded or not.
+     *
+     * @throws IllegalArgumentException if:
+     *      - displayToken is null;
+     *      - brightness is NaN or greater than 1.0f.
+     *
+     * @hide
+     */
+    public static boolean setDisplayBrightness(IBinder displayToken, float brightness) {
+        return setDisplayBrightness(displayToken, brightness, -1, brightness, -1);
+    }
+
+    /**
+     * Sets the brightness of a display.
+     *
+     * @param displayToken
+     *      The token for the display whose brightness is set.
+     * @param sdrBrightness
+     *      A number between 0.0f (minimum brightness) and 1.0f (maximum brightness), or -1.0f to
+     *      turn the backlight off. Specifies the desired brightness of SDR content.
+     * @param sdrBrightnessNits
+     *      The value of sdrBrightness converted to calibrated nits. -1 if this isn't available.
+     * @param displayBrightness
+     *     A number between 0.0f (minimum brightness) and 1.0f (maximum brightness), or
+     *     -1.0f to turn the backlight off. Specifies the desired brightness of the display itself,
+     *     used directly for HDR content.
+     * @param displayBrightnessNits
+     *      The value of displayBrightness converted to calibrated nits. -1 if this isn't
+     *      available.
+     *
+     * @return Whether the method succeeded or not.
+     *
+     * @throws IllegalArgumentException if:
+     *      - displayToken is null;
+     *      - brightness is NaN or greater than 1.0f.
+     *
+     * @hide
+     */
+    public static boolean setDisplayBrightness(IBinder displayToken, float sdrBrightness,
+            float sdrBrightnessNits, float displayBrightness, float displayBrightnessNits) {
+        Objects.requireNonNull(displayToken);
+        if (Float.isNaN(displayBrightness) || displayBrightness > 1.0f
+                || (displayBrightness < 0.0f && displayBrightness != -1.0f)) {
+            throw new IllegalArgumentException("displayBrightness must be a number between 0.0f "
+                    + " and 1.0f, or -1 to turn the backlight off: " + displayBrightness);
+        }
+        if (Float.isNaN(sdrBrightness) || sdrBrightness > 1.0f
+                || (sdrBrightness < 0.0f && sdrBrightness != -1.0f)) {
+            throw new IllegalArgumentException("sdrBrightness must be a number between 0.0f "
+                    + "and 1.0f, or -1 to turn the backlight off: " + sdrBrightness);
+        }
+        return nativeSetDisplayBrightness(displayToken, sdrBrightness, sdrBrightnessNits,
+                displayBrightness, displayBrightnessNits);
+    }
+
+    /**
+     * Creates a mirrored hierarchy for the mirrorOf {@link SurfaceControl}
+     *
+     * Real Hierarchy    Mirror
+     *                     SC (value that's returned)
+     *                      |
+     *      A               A'
+     *      |               |
+     *      B               B'
+     *
+     * @param mirrorOf The root of the hierarchy that should be mirrored.
+     * @return A SurfaceControl that's the parent of the root of the mirrored hierarchy.
+     *
+     * @hide
+     */
+    public static SurfaceControl mirrorSurface(SurfaceControl mirrorOf) {
+        long nativeObj = nativeMirrorSurface(mirrorOf.mNativeObject);
+        SurfaceControl sc = new SurfaceControl();
+        sc.assignNativeObject(nativeObj, "mirrorSurface");
+        return sc;
+    }
+
+    private static void validateColorArg(@Size(4) float[] color) {
+        final String msg = "Color must be specified as a float array with"
+                + " four values to represent r, g, b, a in range [0..1]";
+        if (color.length != 4) {
+            throw new IllegalArgumentException(msg);
+        }
+        for (float c:color) {
+            if ((c < 0.f) || (c > 1.f)) {
+                throw new IllegalArgumentException(msg);
+            }
+        }
+    }
+
+    /**
+     * Sets the global configuration for all the shadows drawn by SurfaceFlinger. Shadow follows
+     * material design guidelines.
+     *
+     * @param ambientColor Color applied to the ambient shadow. The alpha is premultiplied. A
+     *                     float array with four values to represent r, g, b, a in range [0..1]
+     * @param spotColor Color applied to the spot shadow. The alpha is premultiplied. The position
+     *                  of the spot shadow depends on the light position. A float array with
+     *                  four values to represent r, g, b, a in range [0..1]
+     * @param lightPosY Y axis position of the light used to cast the spot shadow in pixels.
+     * @param lightPosZ Z axis position of the light used to cast the spot shadow in pixels. The X
+     *                  axis position is set to the display width / 2.
+     * @param lightRadius Radius of the light casting the shadow in pixels.
+     *[
+     * @hide
+     */
+    public static void setGlobalShadowSettings(@Size(4) float[] ambientColor,
+            @Size(4) float[] spotColor, float lightPosY, float lightPosZ, float lightRadius) {
+        validateColorArg(ambientColor);
+        validateColorArg(spotColor);
+        nativeSetGlobalShadowSettings(ambientColor, spotColor, lightPosY, lightPosZ, lightRadius);
+    }
+
+    /**
+     * Adds a callback to be informed about SF's jank classification for a specific surface.
+     * @hide
+     */
+    public static void addJankDataListener(OnJankDataListener listener, SurfaceControl surface) {
+        nativeAddJankDataListener(listener.mNativePtr.get(), surface.mNativeObject);
+    }
+
+    /**
+     * Removes a jank callback previously added with {@link #addJankDataListener}
+     * @hide
+     */
+    public static void removeJankDataListener(OnJankDataListener listener) {
+        nativeRemoveJankDataListener(listener.mNativePtr.get());
+    }
+
+    /**
+     * Return GPU Context priority that is set in SurfaceFlinger's Render Engine.
+     * @hide
+     */
+    public static int getGPUContextPriority() {
+        return nativeGetGPUContextPriority();
+    }
+
+    /**
+     * An atomic set of changes to a set of SurfaceControl.
+     */
+    public static class Transaction implements Closeable, Parcelable {
+        /**
+         * @hide
+         */
+        public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+                Transaction.class.getClassLoader(),
+                nativeGetNativeTransactionFinalizer(), 512);
+        /**
+         * @hide
+         */
+        public long mNativeObject;
+
+        private final ArrayMap<SurfaceControl, Point> mResizedSurfaces = new ArrayMap<>();
+        private final ArrayMap<SurfaceControl, SurfaceControl> mReparentedSurfaces =
+                 new ArrayMap<>();
+
+        Runnable mFreeNativeResources;
+        private static final float[] INVALID_COLOR = {-1, -1, -1};
+
+        /**
+         * @hide
+         */
+        protected void checkPreconditions(SurfaceControl sc) {
+            sc.checkNotReleased();
+        }
+
+        /**
+         * Open a new transaction object. The transaction may be filed with commands to
+         * manipulate {@link SurfaceControl} instances, and then applied atomically with
+         * {@link #apply}. Eventually the user should invoke {@link #close}, when the object
+         * is no longer required. Note however that re-using a transaction after a call to apply
+         * is allowed as a convenience.
+         */
+        public Transaction() {
+            mNativeObject = nativeCreateTransaction();
+            mFreeNativeResources
+                = sRegistry.registerNativeAllocation(this, mNativeObject);
+        }
+
+        private Transaction(Parcel in) {
+            readFromParcel(in);
+        }
+
+        /**
+         * Apply the transaction, clearing it's state, and making it usable
+         * as a new transaction.
+         */
+        public void apply() {
+            apply(false);
+        }
+
+        /**
+         * Clear the transaction object, without applying it.
+         *
+         * @hide
+         */
+        public void clear() {
+            mResizedSurfaces.clear();
+            mReparentedSurfaces.clear();
+            if (mNativeObject != 0) {
+                nativeClearTransaction(mNativeObject);
+            }
+        }
+
+        /**
+         * Release the native transaction object, without applying it.
+         */
+        @Override
+        public void close() {
+            mResizedSurfaces.clear();
+            mReparentedSurfaces.clear();
+            mFreeNativeResources.run();
+            mNativeObject = 0;
+        }
+
+        /**
+         * Jankier version of apply. Avoid use (b/28068298).
+         * @hide
+         */
+        public void apply(boolean sync) {
+            applyResizedSurfaces();
+            notifyReparentedSurfaces();
+            nativeApplyTransaction(mNativeObject, sync);
+        }
+
+        /**
+         * @hide
+         */
+        protected void applyResizedSurfaces() {
+            for (int i = mResizedSurfaces.size() - 1; i >= 0; i--) {
+                final Point size = mResizedSurfaces.valueAt(i);
+                final SurfaceControl surfaceControl = mResizedSurfaces.keyAt(i);
+                synchronized (surfaceControl.mLock) {
+                    surfaceControl.resize(size.x, size.y);
+                }
+            }
+            mResizedSurfaces.clear();
+        }
+
+        /**
+         * @hide
+         */
+        protected void notifyReparentedSurfaces() {
+            final int reparentCount = mReparentedSurfaces.size();
+            for (int i = reparentCount - 1; i >= 0; i--) {
+                final SurfaceControl child = mReparentedSurfaces.keyAt(i);
+                synchronized (child.mLock) {
+                    final int listenerCount = (child.mReparentListeners != null)
+                            ? child.mReparentListeners.size() : 0;
+                    for (int j = 0; j < listenerCount; j++) {
+                        final OnReparentListener listener = child.mReparentListeners.get(j);
+                        listener.onReparent(this, mReparentedSurfaces.valueAt(i));
+                    }
+                    mReparentedSurfaces.removeAt(i);
+                }
+            }
+        }
+
+        /**
+         * Toggle the visibility of a given Layer and it's sub-tree.
+         *
+         * @param sc The SurfaceControl for which to set the visibility
+         * @param visible The new visibility
+         * @return This transaction object.
+         */
+        @NonNull
+        public Transaction setVisibility(@NonNull SurfaceControl sc, boolean visible) {
+            checkPreconditions(sc);
+            if (visible) {
+                return show(sc);
+            } else {
+                return hide(sc);
+            }
+        }
+
+        /**
+         * This information is passed to SurfaceFlinger to decide which window should have a
+         * priority when deciding about the refresh rate of the display. All windows have the
+         * lowest priority by default.
+         * @hide
+         */
+        @NonNull
+        public Transaction setFrameRateSelectionPriority(@NonNull SurfaceControl sc, int priority) {
+            sc.checkNotReleased();
+            nativeSetFrameRateSelectionPriority(mNativeObject, sc.mNativeObject, priority);
+            return this;
+        }
+
+        /**
+         * Request that a given surface and it's sub-tree be shown.
+         *
+         * @param sc The surface to show.
+         * @return This transaction.
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public Transaction show(SurfaceControl sc) {
+            checkPreconditions(sc);
+            nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SURFACE_HIDDEN);
+            return this;
+        }
+
+        /**
+         * Request that a given surface and it's sub-tree be hidden.
+         *
+         * @param sc The surface to hidden.
+         * @return This transaction.
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public Transaction hide(SurfaceControl sc) {
+            checkPreconditions(sc);
+            nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_HIDDEN, SURFACE_HIDDEN);
+            return this;
+        }
+
+        /**
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public Transaction setPosition(SurfaceControl sc, float x, float y) {
+            checkPreconditions(sc);
+            nativeSetPosition(mNativeObject, sc.mNativeObject, x, y);
+            return this;
+        }
+
+        /**
+         * Set the default buffer size for the SurfaceControl, if there is a
+         * {@link Surface} associated with the control, then
+         * this will be the default size for buffers dequeued from it.
+         * @param sc The surface to set the buffer size for.
+         * @param w The default width
+         * @param h The default height
+         * @return This Transaction
+         */
+        @NonNull
+        public Transaction setBufferSize(@NonNull SurfaceControl sc,
+                @IntRange(from = 0) int w, @IntRange(from = 0) int h) {
+            checkPreconditions(sc);
+            mResizedSurfaces.put(sc, new Point(w, h));
+            nativeSetSize(mNativeObject, sc.mNativeObject, w, h);
+            return this;
+        }
+
+        /**
+         * Provide the graphic producer a transform hint if the layer and its children are
+         * in an orientation different from the display's orientation. The caller is responsible
+         * for clearing this transform hint if the layer is no longer in a fixed orientation.
+         *
+         * The transform hint is used to prevent allocating a buffer of different size when a
+         * layer is rotated. The producer can choose to consume the hint and allocate the buffer
+         * with the same size.
+         *
+         * @return This Transaction.
+         * @hide
+         */
+        @NonNull
+        public Transaction setFixedTransformHint(@NonNull SurfaceControl sc,
+                       @Surface.Rotation int transformHint) {
+            checkPreconditions(sc);
+            nativeSetFixedTransformHint(mNativeObject, sc.mNativeObject, transformHint);
+            return this;
+        }
+
+        /**
+         * Clearing any transform hint if set on this layer.
+         *
+         * @return This Transaction.
+         * @hide
+         */
+        @NonNull
+        public Transaction unsetFixedTransformHint(@NonNull SurfaceControl sc) {
+            checkPreconditions(sc);
+            nativeSetFixedTransformHint(mNativeObject, sc.mNativeObject, -1/* INVALID_ROTATION */);
+            return this;
+        }
+
+        /**
+         * Set the Z-order for a given SurfaceControl, relative to it's siblings.
+         * If two siblings share the same Z order the ordering is undefined. Surfaces
+         * with a negative Z will be placed below the parent surface.
+         *
+         * @param sc The SurfaceControl to set the Z order on
+         * @param z The Z-order
+         * @return This Transaction.
+         */
+        @NonNull
+        public Transaction setLayer(@NonNull SurfaceControl sc,
+                @IntRange(from = Integer.MIN_VALUE, to = Integer.MAX_VALUE) int z) {
+            checkPreconditions(sc);
+            nativeSetLayer(mNativeObject, sc.mNativeObject, z);
+            return this;
+        }
+
+        /**
+         * @hide
+         */
+        public Transaction setRelativeLayer(SurfaceControl sc, SurfaceControl relativeTo, int z) {
+            checkPreconditions(sc);
+            nativeSetRelativeLayer(mNativeObject, sc.mNativeObject, relativeTo.mNativeObject, z);
+            return this;
+        }
+
+        /**
+         * @hide
+         */
+        public Transaction setTransparentRegionHint(SurfaceControl sc, Region transparentRegion) {
+            checkPreconditions(sc);
+            nativeSetTransparentRegionHint(mNativeObject,
+                    sc.mNativeObject, transparentRegion);
+            return this;
+        }
+
+        /**
+         * Set the alpha for a given surface. If the alpha is non-zero the SurfaceControl
+         * will be blended with the Surfaces under it according to the specified ratio.
+         *
+         * @param sc The given SurfaceControl.
+         * @param alpha The alpha to set.
+         */
+        @NonNull
+        public Transaction setAlpha(@NonNull SurfaceControl sc,
+                @FloatRange(from = 0.0, to = 1.0) float alpha) {
+            checkPreconditions(sc);
+            nativeSetAlpha(mNativeObject, sc.mNativeObject, alpha);
+            return this;
+        }
+
+        /**
+         * @hide
+         */
+        public Transaction setInputWindowInfo(SurfaceControl sc, InputWindowHandle handle) {
+            checkPreconditions(sc);
+            nativeSetInputWindowInfo(mNativeObject, sc.mNativeObject, handle);
+            return this;
+        }
+
+        /**
+         * Waits until any changes to input windows have been sent from SurfaceFlinger to
+         * InputFlinger before returning.
+         *
+         * @hide
+         */
+        public Transaction syncInputWindows() {
+            nativeSyncInputWindows(mNativeObject);
+            return this;
+        }
+
+        /**
+         * Specify how the buffer associated with this Surface is mapped in to the
+         * parent coordinate space. The source frame will be scaled to fit the destination
+         * frame, after being rotated according to the orientation parameter.
+         *
+         * @param sc The SurfaceControl to specify the geometry of
+         * @param sourceCrop The source rectangle in buffer space. Or null for the entire buffer.
+         * @param destFrame The destination rectangle in parent space. Or null for the source frame.
+         * @param orientation The buffer rotation
+         * @return This transaction object.
+         */
+        @NonNull
+        public Transaction setGeometry(@NonNull SurfaceControl sc, @Nullable Rect sourceCrop,
+                @Nullable Rect destFrame, @Surface.Rotation int orientation) {
+            checkPreconditions(sc);
+            nativeSetGeometry(mNativeObject, sc.mNativeObject, sourceCrop, destFrame, orientation);
+            return this;
+        }
+
+        /**
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public Transaction setMatrix(SurfaceControl sc,
+                float dsdx, float dtdx, float dtdy, float dsdy) {
+            checkPreconditions(sc);
+            nativeSetMatrix(mNativeObject, sc.mNativeObject,
+                    dsdx, dtdx, dtdy, dsdy);
+            return this;
+        }
+
+        /**
+         * Sets the transform and position of a {@link SurfaceControl} from a 3x3 transformation
+         * matrix.
+         *
+         * @param sc     SurfaceControl to set matrix of
+         * @param matrix The matrix to apply.
+         * @param float9 An array of 9 floats to be used to extract the values from the matrix.
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public Transaction setMatrix(SurfaceControl sc, Matrix matrix, float[] float9) {
+            matrix.getValues(float9);
+            setMatrix(sc, float9[MSCALE_X], float9[MSKEW_Y],
+                    float9[MSKEW_X], float9[MSCALE_Y]);
+            setPosition(sc, float9[MTRANS_X], float9[MTRANS_Y]);
+            return this;
+        }
+
+        /**
+         * Sets the color transform for the Surface.
+         *
+         * @param sc          SurfaceControl to set color transform of
+         * @param matrix      A float array with 9 values represents a 3x3 transform matrix
+         * @param translation A float array with 3 values represents a translation vector
+         * @hide
+         */
+        public Transaction setColorTransform(SurfaceControl sc, @Size(9) float[] matrix,
+                @Size(3) float[] translation) {
+            checkPreconditions(sc);
+            nativeSetColorTransform(mNativeObject, sc.mNativeObject, matrix, translation);
+            return this;
+        }
+
+        /**
+         * Sets the Surface to be color space agnostic. If a surface is color space agnostic,
+         * the color can be interpreted in any color space.
+         * @param agnostic A boolean to indicate whether the surface is color space agnostic
+         * @hide
+         */
+        public Transaction setColorSpaceAgnostic(SurfaceControl sc, boolean agnostic) {
+            checkPreconditions(sc);
+            nativeSetColorSpaceAgnostic(mNativeObject, sc.mNativeObject, agnostic);
+            return this;
+        }
+
+        /**
+         * Bounds the surface and its children to the bounds specified. Size of the surface will be
+         * ignored and only the crop and buffer size will be used to determine the bounds of the
+         * surface. If no crop is specified and the surface has no buffer, the surface bounds is
+         * only constrained by the size of its parent bounds.
+         *
+         * @param sc   SurfaceControl to set crop of.
+         * @param crop Bounds of the crop to apply.
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public Transaction setWindowCrop(SurfaceControl sc, Rect crop) {
+            checkPreconditions(sc);
+            if (crop != null) {
+                nativeSetWindowCrop(mNativeObject, sc.mNativeObject,
+                        crop.left, crop.top, crop.right, crop.bottom);
+            } else {
+                nativeSetWindowCrop(mNativeObject, sc.mNativeObject, 0, 0, 0, 0);
+            }
+
+            return this;
+        }
+
+        /**
+         * Same as {@link Transaction#setWindowCrop(SurfaceControl, Rect)} but sets the crop rect
+         * top left at 0, 0.
+         *
+         * @param sc     SurfaceControl to set crop of.
+         * @param width  width of crop rect
+         * @param height height of crop rect
+         * @hide
+         */
+        public Transaction setWindowCrop(SurfaceControl sc, int width, int height) {
+            checkPreconditions(sc);
+            nativeSetWindowCrop(mNativeObject, sc.mNativeObject, 0, 0, width, height);
+            return this;
+        }
+
+        /**
+         * Sets the corner radius of a {@link SurfaceControl}.
+         * @param sc SurfaceControl
+         * @param cornerRadius Corner radius in pixels.
+         * @return Itself.
+         * @hide
+         */
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        public Transaction setCornerRadius(SurfaceControl sc, float cornerRadius) {
+            checkPreconditions(sc);
+            nativeSetCornerRadius(mNativeObject, sc.mNativeObject, cornerRadius);
+
+            return this;
+        }
+
+        /**
+         * Sets the background blur radius of the {@link SurfaceControl}.
+         *
+         * @param sc SurfaceControl.
+         * @param radius Blur radius in pixels.
+         * @return itself.
+         * @hide
+         */
+        public Transaction setBackgroundBlurRadius(SurfaceControl sc, int radius) {
+            checkPreconditions(sc);
+            nativeSetBackgroundBlurRadius(mNativeObject, sc.mNativeObject, radius);
+            return this;
+        }
+
+        /**
+         * Specify what regions should be blurred on the {@link SurfaceControl}.
+         *
+         * @param sc SurfaceControl.
+         * @param regions List of regions that will have blurs.
+         * @return itself.
+         * @see BlurRegion#toFloatArray()
+         * @hide
+         */
+        public Transaction setBlurRegions(SurfaceControl sc, float[][] regions) {
+            checkPreconditions(sc);
+            nativeSetBlurRegions(mNativeObject, sc.mNativeObject, regions, regions.length);
+            return this;
+        }
+
+        /**
+         * @hide
+         */
+        public Transaction setStretchEffect(SurfaceControl sc, float width, float height,
+                float vecX, float vecY, float maxStretchAmountX,
+                float maxStretchAmountY, float childRelativeLeft, float childRelativeTop, float childRelativeRight,
+                float childRelativeBottom) {
+            checkPreconditions(sc);
+            nativeSetStretchEffect(mNativeObject, sc.mNativeObject, width, height,
+                    vecX, vecY, maxStretchAmountX, maxStretchAmountY, childRelativeLeft, childRelativeTop,
+                    childRelativeRight, childRelativeBottom);
+            return this;
+        }
+
+        /**
+         * @hide
+         */
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O)
+        public Transaction setLayerStack(SurfaceControl sc, int layerStack) {
+            checkPreconditions(sc);
+            nativeSetLayerStack(mNativeObject, sc.mNativeObject, layerStack);
+            return this;
+        }
+
+        /**
+         * Re-parents a given layer to a new parent. Children inherit transform (position, scaling)
+         * crop, visibility, and Z-ordering from their parents, as if the children were pixels within the
+         * parent Surface.
+         *
+         * @param sc The SurfaceControl to reparent
+         * @param newParent The new parent for the given control.
+         * @return This Transaction
+         */
+        @NonNull
+        public Transaction reparent(@NonNull SurfaceControl sc,
+                @Nullable SurfaceControl newParent) {
+            checkPreconditions(sc);
+            long otherObject = 0;
+            if (newParent != null) {
+                newParent.checkNotReleased();
+                otherObject = newParent.mNativeObject;
+            }
+            nativeReparent(mNativeObject, sc.mNativeObject, otherObject);
+            mReparentedSurfaces.put(sc, newParent);
+            return this;
+        }
+
+        /**
+         * Fills the surface with the specified color.
+         * @param color A float array with three values to represent r, g, b in range [0..1]. An
+         * invalid color will remove the color fill.
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public Transaction setColor(SurfaceControl sc, @Size(3) float[] color) {
+            checkPreconditions(sc);
+            nativeSetColor(mNativeObject, sc.mNativeObject, color);
+            return this;
+        }
+
+        /**
+         * Removes color fill.
+        * @hide
+        */
+        public Transaction unsetColor(SurfaceControl sc) {
+            checkPreconditions(sc);
+            nativeSetColor(mNativeObject, sc.mNativeObject, INVALID_COLOR);
+            return this;
+        }
+
+        /**
+         * Sets the security of the surface.  Setting the flag is equivalent to creating the
+         * Surface with the {@link #SECURE} flag.
+         * @hide
+         */
+        public Transaction setSecure(SurfaceControl sc, boolean isSecure) {
+            checkPreconditions(sc);
+            if (isSecure) {
+                nativeSetFlags(mNativeObject, sc.mNativeObject, SECURE, SECURE);
+            } else {
+                nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SECURE);
+            }
+            return this;
+        }
+
+        /**
+         * Sets the opacity of the surface.  Setting the flag is equivalent to creating the
+         * Surface with the {@link #OPAQUE} flag.
+         * @hide
+         */
+        public Transaction setOpaque(SurfaceControl sc, boolean isOpaque) {
+            checkPreconditions(sc);
+            if (isOpaque) {
+                nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_OPAQUE, SURFACE_OPAQUE);
+            } else {
+                nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SURFACE_OPAQUE);
+            }
+            return this;
+        }
+
+        /**
+         * @hide
+         */
+        public Transaction setDisplaySurface(IBinder displayToken, Surface surface) {
+            if (displayToken == null) {
+                throw new IllegalArgumentException("displayToken must not be null");
+            }
+
+            if (surface != null) {
+                synchronized (surface.mLock) {
+                    nativeSetDisplaySurface(mNativeObject, displayToken, surface.mNativeObject);
+                }
+            } else {
+                nativeSetDisplaySurface(mNativeObject, displayToken, 0);
+            }
+            return this;
+        }
+
+        /**
+         * @hide
+         */
+        public Transaction setDisplayLayerStack(IBinder displayToken, int layerStack) {
+            if (displayToken == null) {
+                throw new IllegalArgumentException("displayToken must not be null");
+            }
+            nativeSetDisplayLayerStack(mNativeObject, displayToken, layerStack);
+            return this;
+        }
+
+        /**
+         * @hide
+         */
+        public Transaction setDisplayProjection(IBinder displayToken,
+                int orientation, Rect layerStackRect, Rect displayRect) {
+            if (displayToken == null) {
+                throw new IllegalArgumentException("displayToken must not be null");
+            }
+            if (layerStackRect == null) {
+                throw new IllegalArgumentException("layerStackRect must not be null");
+            }
+            if (displayRect == null) {
+                throw new IllegalArgumentException("displayRect must not be null");
+            }
+            nativeSetDisplayProjection(mNativeObject, displayToken, orientation,
+                    layerStackRect.left, layerStackRect.top, layerStackRect.right, layerStackRect.bottom,
+                    displayRect.left, displayRect.top, displayRect.right, displayRect.bottom);
+            return this;
+        }
+
+        /**
+         * @hide
+         */
+        public Transaction setDisplaySize(IBinder displayToken, int width, int height) {
+            if (displayToken == null) {
+                throw new IllegalArgumentException("displayToken must not be null");
+            }
+            if (width <= 0 || height <= 0) {
+                throw new IllegalArgumentException("width and height must be positive");
+            }
+
+            nativeSetDisplaySize(mNativeObject, displayToken, width, height);
+            return this;
+        }
+
+        /** flag the transaction as an animation
+         * @hide
+         */
+        public Transaction setAnimationTransaction() {
+            nativeSetAnimationTransaction(mNativeObject);
+            return this;
+        }
+
+         /**
+          * Provides a hint to SurfaceFlinger to change its offset so that SurfaceFlinger wakes up
+          * earlier to compose surfaces. The caller should use this as a hint to SurfaceFlinger
+          * when the scene is complex enough to use GPU composition. The hint will remain active
+          * until until the client calls {@link Transaction#setEarlyWakeupEnd}.
+          *
+          * @hide
+          */
+        public Transaction setEarlyWakeupStart() {
+            nativeSetEarlyWakeupStart(mNativeObject);
+            return this;
+        }
+
+        /**
+         * Removes the early wake up hint set by {@link Transaction#setEarlyWakeupStart}.
+         *
+         * @hide
+         */
+        public Transaction setEarlyWakeupEnd() {
+            nativeSetEarlyWakeupEnd(mNativeObject);
+            return this;
+        }
+
+        /**
+         * Sets an arbitrary piece of metadata on the surface. This is a helper for int data.
+         * @hide
+         */
+        public Transaction setMetadata(SurfaceControl sc, int key, int data) {
+            Parcel parcel = Parcel.obtain();
+            parcel.writeInt(data);
+            try {
+                setMetadata(sc, key, parcel);
+            } finally {
+                parcel.recycle();
+            }
+            return this;
+        }
+
+        /**
+         * Sets an arbitrary piece of metadata on the surface.
+         * @hide
+         */
+        public Transaction setMetadata(SurfaceControl sc, int key, Parcel data) {
+            checkPreconditions(sc);
+            nativeSetMetadata(mNativeObject, sc.mNativeObject, key, data);
+            return this;
+        }
+
+         /**
+          * Draws shadows of length {@code shadowRadius} around the surface {@link SurfaceControl}.
+          * If the length is 0.0f then the shadows will not be drawn.
+          *
+          * Shadows are drawn around the screen bounds, these are the post transformed cropped
+          * bounds. They can draw over their parent bounds and will be occluded by layers with a
+          * higher z-order. The shadows will respect the surface's corner radius if the
+          * rounded corner bounds (transformed source bounds) are within the screen bounds.
+          *
+          * A shadow will only be drawn on buffer and color layers. If the radius is applied on a
+          * container layer, it will be passed down the hierarchy to be applied on buffer and color
+          * layers but not its children. A scenario where this is useful is when SystemUI animates
+          * a task by controlling a leash to it, can draw a shadow around the app surface by
+          * setting a shadow on the leash. This is similar to how rounded corners are set.
+          *
+          * @hide
+          */
+        public Transaction setShadowRadius(SurfaceControl sc, float shadowRadius) {
+            checkPreconditions(sc);
+            nativeSetShadowRadius(mNativeObject, sc.mNativeObject, shadowRadius);
+            return this;
+        }
+
+        /**
+         * Sets the intended frame rate for this surface. Any switching of refresh rates is
+         * most probably going to be seamless.
+         *
+         * @see #setFrameRate(SurfaceControl, float, int, int)
+         */
+        @NonNull
+        public Transaction setFrameRate(@NonNull SurfaceControl sc,
+                @FloatRange(from = 0.0) float frameRate,
+                @Surface.FrameRateCompatibility int compatibility) {
+            return setFrameRate(sc, frameRate, compatibility,
+                    Surface.CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+        }
+
+        /**
+         * Sets the intended frame rate for the surface {@link SurfaceControl}.
+         * <p>
+         * On devices that are capable of running the display at different refresh rates, the system
+         * may choose a display refresh rate to better match this surface's frame rate. Usage of
+         * this API won't directly affect the application's frame production pipeline. However,
+         * because the system may change the display refresh rate, calls to this function may result
+         * in changes to Choreographer callback timings, and changes to the time interval at which
+         * the system releases buffers back to the application.
+         * <p>
+         * Note that this only has an effect for surfaces presented on the display. If this
+         * surface is consumed by something other than the system compositor, e.g. a media
+         * codec, this call has no effect.
+         *
+         * @param sc The SurfaceControl to specify the frame rate of.
+         * @param frameRate The intended frame rate for this surface, in frames per second. 0 is a
+         *                  special value that indicates the app will accept the system's choice for
+         *                  the display frame rate, which is the default behavior if this function
+         *                  isn't called. The <code>frameRate</code> param does <em>not</em> need
+         *                  to be a valid refresh rate for this device's display - e.g., it's fine
+         *                  to pass 30fps to a device that can only run the display at 60fps.
+         * @param compatibility The frame rate compatibility of this surface. The compatibility
+         *                      value may influence the system's choice of display frame rate.
+         *                      This parameter is ignored when <code>frameRate</code> is 0.
+         * @param changeFrameRateStrategy Whether display refresh rate transitions caused by this
+         *                                surface should be seamless. A seamless transition is one
+         *                                that doesn't have any visual interruptions, such as a
+         *                                black screen for a second or two. This parameter is
+         *                                ignored when <code>frameRate</code> is 0.
+         * @return This transaction object.
+         */
+        @NonNull
+        public Transaction setFrameRate(@NonNull SurfaceControl sc,
+                @FloatRange(from = 0.0) float frameRate,
+                @Surface.FrameRateCompatibility int compatibility,
+                @Surface.ChangeFrameRateStrategy int changeFrameRateStrategy) {
+            checkPreconditions(sc);
+            nativeSetFrameRate(mNativeObject, sc.mNativeObject, frameRate, compatibility,
+                    changeFrameRateStrategy);
+            return this;
+        }
+
+        /**
+         * Sets focus on the window identified by the input {@code token} if the window is focusable
+         * otherwise the request is dropped.
+         *
+         * If the window is not visible, the request will be queued until the window becomes
+         * visible or the request is overrriden by another request. The currently focused window
+         * will lose focus immediately. This is to send the newly focused window any focus
+         * dispatched events that occur while it is completing its first draw.
+         *
+         * @hide
+         */
+        public Transaction setFocusedWindow(@NonNull IBinder token, String windowName,
+                int displayId) {
+            nativeSetFocusedWindow(mNativeObject, token,  windowName,
+                    null /* focusedToken */, null /* focusedWindowName */, displayId);
+            return this;
+        }
+
+        /**
+         * Set focus on the window identified by the input {@code token} if the window identified by
+         * the input {@code focusedToken} is currently focused. If the {@code focusedToken} does not
+         * have focus, the request is dropped.
+         *
+         * This is used by forward focus transfer requests from clients that host embedded windows,
+         * and want to transfer focus to/from them.
+         *
+         * @hide
+         */
+        public Transaction requestFocusTransfer(@NonNull IBinder token,
+                                                String windowName,
+                                                @NonNull IBinder focusedToken,
+                                                String focusedWindowName,
+                                                int displayId) {
+            nativeSetFocusedWindow(mNativeObject, token, windowName, focusedToken,
+                    focusedWindowName, displayId);
+            return this;
+        }
+
+        /**
+         * Adds or removes the flag SKIP_SCREENSHOT of the surface.  Setting the flag is equivalent
+         * to creating the Surface with the {@link #SKIP_SCREENSHOT} flag.
+         *
+         * @hide
+         */
+        public Transaction setSkipScreenshot(SurfaceControl sc, boolean skipScrenshot) {
+            checkPreconditions(sc);
+            if (skipScrenshot) {
+                nativeSetFlags(mNativeObject, sc.mNativeObject, SKIP_SCREENSHOT, SKIP_SCREENSHOT);
+            } else {
+                nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SKIP_SCREENSHOT);
+            }
+            return this;
+        }
+
+        /**
+         * Set a buffer for a SurfaceControl. This can only be used for SurfaceControls that were
+         * created as type {@link #FX_SURFACE_BLAST}
+         *
+         * @hide
+         */
+        public Transaction setBuffer(SurfaceControl sc, GraphicBuffer buffer) {
+            checkPreconditions(sc);
+            nativeSetBuffer(mNativeObject, sc.mNativeObject, buffer);
+            return this;
+        }
+
+        /**
+         * Set the color space for the SurfaceControl. The supported color spaces are SRGB
+         * and Display P3, other color spaces will be treated as SRGB. This can only be used for
+         * SurfaceControls that were created as type {@link #FX_SURFACE_BLAST}
+         *
+         * @hide
+         */
+        public Transaction setColorSpace(SurfaceControl sc, ColorSpace colorSpace) {
+            checkPreconditions(sc);
+            nativeSetColorSpace(mNativeObject, sc.mNativeObject, colorSpace.getId());
+            return this;
+        }
+
+        /**
+         * Sets the trusted overlay state on this SurfaceControl and it is inherited to all the
+         * children. The caller must hold the ACCESS_SURFACE_FLINGER permission.
+         * @hide
+         */
+        public Transaction setTrustedOverlay(SurfaceControl sc, boolean isTrustedOverlay) {
+            checkPreconditions(sc);
+            nativeSetTrustedOverlay(mNativeObject, sc.mNativeObject, isTrustedOverlay);
+            return this;
+        }
+
+         /**
+         * Merge the other transaction into this transaction, clearing the
+         * other transaction as if it had been applied.
+         *
+         * @param other The transaction to merge in to this one.
+         * @return This transaction.
+         */
+        @NonNull
+        public Transaction merge(@NonNull Transaction other) {
+            if (this == other) {
+                return this;
+            }
+            mResizedSurfaces.putAll(other.mResizedSurfaces);
+            other.mResizedSurfaces.clear();
+            mReparentedSurfaces.putAll(other.mReparentedSurfaces);
+            other.mReparentedSurfaces.clear();
+            nativeMergeTransaction(mNativeObject, other.mNativeObject);
+            return this;
+        }
+
+        /**
+         * Equivalent to reparent with a null parent, in that it removes
+         * the SurfaceControl from the scene, but it also releases
+         * the local resources (by calling {@link SurfaceControl#release})
+         * after this method returns, {@link SurfaceControl#isValid} will return
+         * false for the argument.
+         *
+         * @param sc The surface to remove and release.
+         * @return This transaction
+         * @hide
+         */
+        @NonNull
+        public Transaction remove(@NonNull SurfaceControl sc) {
+            reparent(sc, null);
+            sc.release();
+            return this;
+        }
+
+        /**
+         * Sets the frame timeline vsync id received from choreographer
+         * {@link Choreographer#getVsyncId()} that corresponds to the transaction submitted on that
+         * surface control.
+         *
+         * @hide
+         */
+        @NonNull
+        public Transaction setFrameTimelineVsync(long frameTimelineVsyncId) {
+            nativeSetFrameTimelineVsync(mNativeObject, frameTimelineVsyncId);
+            return this;
+        }
+
+        /**
+         * Writes the transaction to parcel, clearing the transaction as if it had been applied so
+         * it can be used to store future transactions. It's the responsibility of the parcel
+         * reader to apply the original transaction.
+         *
+         * @param dest parcel to write the transaction to
+         * @param flags
+         */
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, @WriteFlags int flags) {
+            if (mNativeObject == 0) {
+                dest.writeInt(0);
+                return;
+            }
+
+            dest.writeInt(1);
+            nativeWriteTransactionToParcel(mNativeObject, dest);
+            if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) {
+                nativeClearTransaction(mNativeObject);
+            }
+        }
+
+        private void readFromParcel(Parcel in) {
+            mNativeObject = 0;
+            if (in.readInt() != 0) {
+                mNativeObject = nativeReadTransactionFromParcel(in);
+                mFreeNativeResources = sRegistry.registerNativeAllocation(this, mNativeObject);
+            }
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        public static final @NonNull Creator<Transaction> CREATOR = new Creator<Transaction>() {
+                    @Override
+                    public Transaction createFromParcel(Parcel in) {
+                        return new Transaction(in);
+                    }
+                    @Override
+                    public Transaction[] newArray(int size) {
+                        return new Transaction[size];
+                    }
+                };
+    }
+
+    /**
+     * A debugging utility subclass of SurfaceControl.Transaction. At construction
+     * you can pass in a monitor object, and all the other methods will throw an exception
+     * if the monitor is not held when they are called.
+     * @hide
+     */
+    public static class LockDebuggingTransaction extends SurfaceControl.Transaction {
+        Object mMonitor;
+
+        public LockDebuggingTransaction(Object o) {
+            mMonitor = o;
+        }
+
+        @Override
+        protected void checkPreconditions(SurfaceControl sc) {
+            super.checkPreconditions(sc);
+            if (!Thread.holdsLock(mMonitor)) {
+                throw new RuntimeException(
+                        "Unlocked access to synchronized SurfaceControl.Transaction");
+            }
+        }
+    }
+
+    /**
+     * As part of eliminating usage of the global Transaction we expose
+     * a SurfaceControl.getGlobalTransaction function. However calling
+     * apply on this global transaction (rather than using closeTransaction)
+     * would be very dangerous. So for the global transaction we use this
+     * subclass of Transaction where the normal apply throws an exception.
+     */
+    private static class GlobalTransactionWrapper extends SurfaceControl.Transaction {
+        void applyGlobalTransaction(boolean sync) {
+            applyResizedSurfaces();
+            notifyReparentedSurfaces();
+            nativeApplyTransaction(mNativeObject, sync);
+        }
+
+        @Override
+        public void apply(boolean sync) {
+            throw new RuntimeException("Global transaction must be applied from closeTransaction");
+        }
+    }
+
+    /**
+     * Acquire a frame rate flexibility token, which allows surface flinger to freely switch display
+     * frame rates. This is used by CTS tests to put the device in a consistent state. See
+     * ISurfaceComposer::acquireFrameRateFlexibilityToken(). The caller must have the
+     * ACCESS_SURFACE_FLINGER permission, or else the call will fail, returning 0.
+     * @hide
+     */
+    @TestApi
+    public static long acquireFrameRateFlexibilityToken() {
+        return nativeAcquireFrameRateFlexibilityToken();
+    }
+
+    /**
+     * Release a frame rate flexibility token.
+     * @hide
+     */
+    @TestApi
+    public static void releaseFrameRateFlexibilityToken(long token) {
+        nativeReleaseFrameRateFlexibilityToken(token);
+    }
+
+    /**
+     * This is a refactoring utility function to enable lower levels of code to be refactored
+     * from using the global transaction (and instead use a passed in Transaction) without
+     * having to refactor the higher levels at the same time.
+     * The returned global transaction can't be applied, it must be applied from closeTransaction
+     * Unless you are working on removing Global Transaction usage in the WindowManager, this
+     * probably isn't a good function to use.
+     * @hide
+     */
+    public static Transaction getGlobalTransaction() {
+        return sGlobalTransaction;
+    }
+
+    /**
+     * @hide
+     */
+    public void resize(int w, int h) {
+        mWidth = w;
+        mHeight = h;
+        nativeUpdateDefaultBufferSize(mNativeObject, w, h);
+    }
+
+    /**
+     * @hide
+     */
+    public int getTransformHint() {
+        checkNotReleased();
+        return nativeGetTransformHint(mNativeObject);
+    }
+
+    /**
+     * Update the transform hint of current SurfaceControl. Only affect if type is
+     * {@link #FX_SURFACE_BLAST}
+     *
+     * The transform hint is used to prevent allocating a buffer of different size when a
+     * layer is rotated. The producer can choose to consume the hint and allocate the buffer
+     * with the same size.
+     * @hide
+     */
+    public void setTransformHint(@Surface.Rotation int transformHint) {
+        nativeSetTransformHint(mNativeObject, transformHint);
+    }
+}
diff --git a/android/view/SurfaceControlFpsListener.java b/android/view/SurfaceControlFpsListener.java
new file mode 100644
index 0000000..20a511a
--- /dev/null
+++ b/android/view/SurfaceControlFpsListener.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.NonNull;
+
+/**
+ * Listener for sampling the frames per second for a SurfaceControl and its children.
+ * This should only be used by a system component that needs to listen to a SurfaceControl's
+ * tree's FPS when it is not actively submitting transactions for that SurfaceControl.
+ * Otherwise, ASurfaceTransaction_OnComplete callbacks should be used.
+ *
+ * @hide
+ */
+public abstract class SurfaceControlFpsListener {
+    private long mNativeListener;
+
+    public SurfaceControlFpsListener() {
+        mNativeListener = nativeCreate(this);
+    }
+
+    protected void destroy() {
+        if (mNativeListener == 0) {
+            return;
+        }
+        unregister();
+        nativeDestroy(mNativeListener);
+        mNativeListener = 0;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            destroy();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Reports the fps from the registered SurfaceControl
+     */
+    public abstract void onFpsReported(float fps);
+
+    /**
+     * Registers the sampling listener for a particular task ID
+     */
+    public void register(int taskId) {
+        if (mNativeListener == 0) {
+            return;
+        }
+
+        nativeRegister(mNativeListener, taskId);
+    }
+
+    /**
+     * Unregisters the sampling listener.
+     */
+    public void unregister() {
+        if (mNativeListener == 0) {
+            return;
+        }
+        nativeUnregister(mNativeListener);
+    }
+
+    /**
+     * Dispatch the collected sample.
+     *
+     * Called from native code on a binder thread.
+     */
+    private static void dispatchOnFpsReported(
+            @NonNull SurfaceControlFpsListener listener, float fps) {
+        listener.onFpsReported(fps);
+    }
+
+    private static native long nativeCreate(SurfaceControlFpsListener thiz);
+    private static native void nativeDestroy(long ptr);
+    private static native void nativeRegister(long ptr, int taskId);
+    private static native void nativeUnregister(long ptr);
+}
diff --git a/android/view/SurfaceControlHdrLayerInfoListener.java b/android/view/SurfaceControlHdrLayerInfoListener.java
new file mode 100644
index 0000000..13d68d0
--- /dev/null
+++ b/android/view/SurfaceControlHdrLayerInfoListener.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.RequiresPermission;
+import android.os.IBinder;
+import android.util.ArrayMap;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.util.Objects;
+
+/**
+ * Allows for the monitoring of layers with HDR content
+ *
+ * @hide */
+public abstract class SurfaceControlHdrLayerInfoListener {
+    private static final NativeAllocationRegistry sRegistry =
+            NativeAllocationRegistry.createMalloced(
+                    SurfaceControlHdrLayerInfoListener.class.getClassLoader(), nGetDestructor());
+
+    /**
+     * Callback when the HDR information about the given display has changed
+     *
+     * @param displayToken The display this callback is about
+     * @param numberOfHdrLayers How many HDR layers are visible on the display
+     * @param maxW The width of the HDR layer with the largest area
+     * @param maxH The height of the HDR layer with the largest area
+     * @param flags Additional metadata flags, currently always 0
+     *              TODO(b/182312559): Add some flags
+     *
+     * @hide */
+    public abstract void onHdrInfoChanged(IBinder displayToken, int numberOfHdrLayers,
+            int maxW, int maxH, int flags);
+
+    /**
+     * Registers this as an HDR info listener on the provided display
+     * @param displayToken
+     */
+    @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+    public void register(IBinder displayToken) {
+        Objects.requireNonNull(displayToken);
+        synchronized (this) {
+            if (mRegisteredListeners.containsKey(displayToken)) {
+                return;
+            }
+            long nativePtr = nRegister(displayToken);
+            Runnable destructor = sRegistry.registerNativeAllocation(this, nativePtr);
+            mRegisteredListeners.put(displayToken, destructor);
+        }
+    }
+
+    /**
+     * Unregisters this as an HDR info listener on the provided display
+     * @param displayToken
+     */
+    @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+    public void unregister(IBinder displayToken) {
+        Objects.requireNonNull(displayToken);
+        final Runnable destructor;
+        synchronized (this) {
+            destructor = mRegisteredListeners.remove(displayToken);
+        }
+        if (destructor != null) {
+            destructor.run();
+        }
+    }
+
+    /**
+     * Unregisters this on all previously registered displays
+     */
+    @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+    public void unregisterAll() {
+        final ArrayMap<IBinder, Runnable> toDestroy;
+        synchronized (this) {
+            toDestroy = mRegisteredListeners;
+            mRegisteredListeners = new ArrayMap<>();
+        }
+        for (Runnable destructor : toDestroy.values()) {
+            destructor.run();
+        }
+    }
+
+    private ArrayMap<IBinder, Runnable> mRegisteredListeners = new ArrayMap<>();
+
+    private static native long nGetDestructor();
+    private native long nRegister(IBinder displayToken);
+}
diff --git a/android/view/SurfaceControlViewHost.java b/android/view/SurfaceControlViewHost.java
new file mode 100644
index 0000000..11b161a
--- /dev/null
+++ b/android/view/SurfaceControlViewHost.java
@@ -0,0 +1,324 @@
+/*
+ * 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.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.accessibility.IAccessibilityEmbeddedConnection;
+
+import java.util.Objects;
+
+/**
+ * Utility class for adding a View hierarchy to a {@link SurfaceControl}. The View hierarchy
+ * will render in to a root SurfaceControl, and receive input based on the SurfaceControl's
+ * placement on-screen. The primary usage of this class is to embed a View hierarchy from
+ * one process in to another. After the SurfaceControlViewHost has been set up in the embedded
+ * content provider, we can send the {@link SurfaceControlViewHost.SurfacePackage}
+ * to the host process. The host process can then attach the hierarchy to a SurfaceView within
+ * its own by calling
+ * {@link SurfaceView#setChildSurfacePackage}.
+ */
+public class SurfaceControlViewHost {
+    private final ViewRootImpl mViewRoot;
+    private WindowlessWindowManager mWm;
+
+    private SurfaceControl mSurfaceControl;
+    private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
+
+    /**
+     * Package encapsulating a Surface hierarchy which contains interactive view
+     * elements. It's expected to get this object from
+     * {@link SurfaceControlViewHost#getSurfacePackage} afterwards it can be embedded within
+     * a SurfaceView by calling {@link SurfaceView#setChildSurfacePackage}.
+     *
+     * Note that each {@link SurfacePackage} must be released by calling
+     * {@link SurfacePackage#release}. However, if you use the recommended flow,
+     *  the framework will automatically handle the lifetime for you.
+     *
+     * 1. When sending the package to the remote process, return it from an AIDL method
+     * or manually use FLAG_WRITE_RETURN_VALUE in writeToParcel. This will automatically
+     * release the package in the local process.
+     * 2. In the remote process, consume the package using SurfaceView. This way the
+     * SurfaceView will take over the lifetime and call {@link SurfacePackage#release}
+     * for the user.
+     *
+     * One final note: The {@link SurfacePackage} lifetime is totally de-coupled
+     * from the lifetime of the underlying {@link SurfaceControlViewHost}. Regardless
+     * of the lifetime of the package the user should still call
+     * {@link SurfaceControlViewHost#release} when finished.
+     */
+    public static final class SurfacePackage implements Parcelable {
+        private SurfaceControl mSurfaceControl;
+        private final IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
+        private final IBinder mInputToken;
+
+        SurfacePackage(SurfaceControl sc, IAccessibilityEmbeddedConnection connection,
+                       IBinder inputToken) {
+            mSurfaceControl = sc;
+            mAccessibilityEmbeddedConnection = connection;
+            mInputToken = inputToken;
+        }
+
+        /**
+         * Constructs a copy of {@code SurfacePackage} with an independent lifetime.
+         *
+         * The caller can use this to create an independent copy in situations where ownership of
+         * the {@code SurfacePackage} would be transferred elsewhere, such as attaching to a
+         * {@code SurfaceView}, returning as {@code Binder} result value, etc. The caller is
+         * responsible for releasing this copy when its done.
+         *
+         * @param other {@code SurfacePackage} to create a copy of.
+         */
+        public SurfacePackage(@NonNull SurfacePackage other) {
+            SurfaceControl otherSurfaceControl = other.mSurfaceControl;
+            if (otherSurfaceControl != null && otherSurfaceControl.isValid()) {
+                mSurfaceControl = new SurfaceControl();
+                mSurfaceControl.copyFrom(otherSurfaceControl, "SurfacePackage");
+            }
+            mAccessibilityEmbeddedConnection = other.mAccessibilityEmbeddedConnection;
+            mInputToken = other.mInputToken;
+        }
+
+        private SurfacePackage(Parcel in) {
+            mSurfaceControl = new SurfaceControl();
+            mSurfaceControl.readFromParcel(in);
+            mAccessibilityEmbeddedConnection = IAccessibilityEmbeddedConnection.Stub.asInterface(
+                    in.readStrongBinder());
+            mInputToken = in.readStrongBinder();
+        }
+
+        /**
+         * Use {@link SurfaceView#setChildSurfacePackage} or manually fix
+         * accessibility (see SurfaceView implementation).
+         * @hide
+         */
+        public @NonNull SurfaceControl getSurfaceControl() {
+            return mSurfaceControl;
+        }
+
+        /**
+         * Gets an accessibility embedded connection interface for this SurfaceControlViewHost.
+         *
+         * @return {@link IAccessibilityEmbeddedConnection} interface.
+         * @hide
+         */
+        public IAccessibilityEmbeddedConnection getAccessibilityEmbeddedConnection() {
+            return mAccessibilityEmbeddedConnection;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel out, int flags) {
+            mSurfaceControl.writeToParcel(out, flags);
+            out.writeStrongBinder(mAccessibilityEmbeddedConnection.asBinder());
+            out.writeStrongBinder(mInputToken);
+        }
+
+        /**
+         * Release the {@link SurfaceControl} associated with this package.
+         * It's not necessary to call this if you pass the package to
+         * {@link SurfaceView#setChildSurfacePackage} as {@link SurfaceView} will
+         * take ownership in that case.
+         */
+        public void release() {
+            if (mSurfaceControl != null) {
+                mSurfaceControl.release();
+             }
+             mSurfaceControl = null;
+        }
+
+        /**
+         * Returns an input token used which can be used to request focus on the embedded surface.
+         *
+         * @hide
+         */
+        public IBinder getInputToken() {
+            return mInputToken;
+        }
+
+        public static final @NonNull Creator<SurfacePackage> CREATOR
+             = new Creator<SurfacePackage>() {
+                     public SurfacePackage createFromParcel(Parcel in) {
+                         return new SurfacePackage(in);
+                     }
+                     public SurfacePackage[] newArray(int size) {
+                         return new SurfacePackage[size];
+                     }
+             };
+    }
+
+    /** @hide */
+    public SurfaceControlViewHost(@NonNull Context c, @NonNull Display d,
+            @NonNull WindowlessWindowManager wwm) {
+        this(c, d, wwm, false /* useSfChoreographer */);
+    }
+
+    /** @hide */
+    public SurfaceControlViewHost(@NonNull Context c, @NonNull Display d,
+            @NonNull WindowlessWindowManager wwm, boolean useSfChoreographer) {
+        mWm = wwm;
+        mViewRoot = new ViewRootImpl(c, d, mWm, useSfChoreographer);
+        mAccessibilityEmbeddedConnection = mViewRoot.getAccessibilityEmbeddedConnection();
+    }
+
+    /**
+     * Construct a new SurfaceControlViewHost. The root Surface will be
+     * allocated internally and is accessible via getSurfacePackage().
+     *
+     * The {@param hostToken} parameter, primarily used for ANR reporting,
+     * must be obtained from whomever will be hosting the embedded hierarchy.
+     * It's accessible from {@link SurfaceView#getHostToken}.
+     *
+     * @param context The Context object for your activity or application.
+     * @param display The Display the hierarchy will be placed on.
+     * @param hostToken The host token, as discussed above.
+     */
+    public SurfaceControlViewHost(@NonNull Context context, @NonNull Display display,
+            @Nullable IBinder hostToken) {
+        mSurfaceControl = new SurfaceControl.Builder()
+                .setContainerLayer()
+                .setName("SurfaceControlViewHost")
+                .setCallsite("SurfaceControlViewHost")
+                .build();
+        mWm = new WindowlessWindowManager(context.getResources().getConfiguration(),
+                mSurfaceControl, hostToken);
+        mViewRoot = new ViewRootImpl(context, display, mWm);
+        mAccessibilityEmbeddedConnection = mViewRoot.getAccessibilityEmbeddedConnection();
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    protected void finalize() throws Throwable {
+        // We aren't on the UI thread here so we need to pass false to
+        // doDie
+        mViewRoot.die(false /* immediate */);
+    }
+
+
+    /**
+     * Return a SurfacePackage for the root SurfaceControl of the embedded hierarchy.
+     * Rather than be directly reparented using {@link SurfaceControl.Transaction} this
+     * SurfacePackage should be passed to {@link SurfaceView#setChildSurfacePackage}
+     * which will not only reparent the Surface, but ensure the accessibility hierarchies
+     * are linked.
+     */
+    public @Nullable SurfacePackage getSurfacePackage() {
+        if (mSurfaceControl != null && mAccessibilityEmbeddedConnection != null) {
+            return new SurfacePackage(mSurfaceControl, mAccessibilityEmbeddedConnection,
+                    mViewRoot.getInputToken());
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Set the root view of the SurfaceControlViewHost. This view will render in to
+     * the SurfaceControl, and receive input based on the SurfaceControls positioning on
+     * screen. It will be laid as if it were in a window of the passed in width and height.
+     *
+     * @param view The View to add
+     * @param width The width to layout the View within, in pixels.
+     * @param height The height to layout the View within, in pixels.
+     */
+    public void setView(@NonNull View view, int width, int height) {
+        final WindowManager.LayoutParams lp =
+                new WindowManager.LayoutParams(width, height,
+                        WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.TRANSPARENT);
+        setView(view, lp);
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    public void setView(@NonNull View view, @NonNull WindowManager.LayoutParams attrs) {
+        Objects.requireNonNull(view);
+        attrs.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+        view.setLayoutParams(attrs);
+        mViewRoot.setView(view, attrs, null);
+    }
+
+    /**
+     * @return The view passed to setView, or null if none has been passed.
+     */
+    public @Nullable View getView() {
+        return mViewRoot.getView();
+    }
+
+    /**
+     * @return the ViewRootImpl wrapped by this host.
+     * @hide
+     */
+    public IWindow getWindowToken() {
+        return mViewRoot.mWindow;
+    }
+
+    /**
+     * @return the WindowlessWindowManager instance that this host is attached to.
+     * @hide
+     */
+    public @NonNull WindowlessWindowManager getWindowlessWM() {
+        return mWm;
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    public void relayout(WindowManager.LayoutParams attrs) {
+        mViewRoot.setLayoutParams(attrs, false);
+        mViewRoot.setReportNextDraw();
+        mWm.setCompletionCallback(mViewRoot.mWindow.asBinder(), (SurfaceControl.Transaction t) -> {
+            t.apply();
+        });
+    }
+
+    /**
+     * Modify the size of the root view.
+     *
+     * @param width Width in pixels
+     * @param height Height in pixels
+     */
+    public void relayout(int width, int height) {
+        final WindowManager.LayoutParams lp =
+                new WindowManager.LayoutParams(width, height,
+                        WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.TRANSPARENT);
+        relayout(lp);
+    }
+
+    /**
+     * Trigger the tear down of the embedded view hierarchy and release the SurfaceControl.
+     * This will result in onDispatchedFromWindow being dispatched to the embedded view hierarchy
+     * and render the object unusable.
+     */
+    public void release() {
+        // ViewRoot will release mSurfaceControl for us.
+        mViewRoot.die(true /* immediate */);
+    }
+}
diff --git a/android/view/SurfaceControl_Delegate.java b/android/view/SurfaceControl_Delegate.java
new file mode 100644
index 0000000..24838b2
--- /dev/null
+++ b/android/view/SurfaceControl_Delegate.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import libcore.util.NativeAllocationRegistry_Delegate;
+
+public class SurfaceControl_Delegate {
+
+    // ---- delegate manager ----
+    private static final DelegateManager<SurfaceControl_Delegate> sManager =
+            new DelegateManager<>(SurfaceControl_Delegate.class);
+    private static long sFinalizer = -1;
+
+    @LayoutlibDelegate
+    /*package*/ static long nativeCreateTransaction() {
+        return sManager.addNewDelegate(new SurfaceControl_Delegate());
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nativeGetNativeTransactionFinalizer() {
+        synchronized (SurfaceControl_Delegate.class) {
+            if (sFinalizer == -1) {
+                sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(sManager::removeJavaReferenceFor);
+            }
+        }
+        return sFinalizer;
+    }
+}
diff --git a/android/view/SurfaceHolder.java b/android/view/SurfaceHolder.java
new file mode 100644
index 0000000..c5d45c3
--- /dev/null
+++ b/android/view/SurfaceHolder.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.graphics.Canvas;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+
+/**
+ * Abstract interface to someone holding a display surface.  Allows you to
+ * control the surface size and format, edit the pixels in the surface, and
+ * monitor changes to the surface.  This interface is typically available
+ * through the {@link SurfaceView} class.
+ *
+ * <p>When using this interface from a thread other than the one running
+ * its {@link SurfaceView}, you will want to carefully read the
+ * methods
+ * {@link #lockCanvas} and {@link Callback#surfaceCreated Callback.surfaceCreated()}.
+ */
+public interface SurfaceHolder {
+
+    /** @deprecated this is ignored, this value is set automatically when needed. */
+    @Deprecated
+    public static final int SURFACE_TYPE_NORMAL = 0;
+    /** @deprecated this is ignored, this value is set automatically when needed. */
+    @Deprecated
+    public static final int SURFACE_TYPE_HARDWARE = 1;
+    /** @deprecated this is ignored, this value is set automatically when needed. */
+    @Deprecated
+    public static final int SURFACE_TYPE_GPU = 2;
+    /** @deprecated this is ignored, this value is set automatically when needed. */
+    @Deprecated
+    public static final int SURFACE_TYPE_PUSH_BUFFERS = 3;
+
+    /**
+     * Exception that is thrown from {@link #lockCanvas} when called on a Surface
+     * whose type is SURFACE_TYPE_PUSH_BUFFERS.
+     */
+    public static class BadSurfaceTypeException extends RuntimeException {
+        public BadSurfaceTypeException() {
+        }
+
+        public BadSurfaceTypeException(String name) {
+            super(name);
+        }
+    }
+
+    /**
+     * A client may implement this interface to receive information about
+     * changes to the surface.  When used with a {@link SurfaceView}, the
+     * Surface being held is only available between calls to
+     * {@link #surfaceCreated(SurfaceHolder)} and
+     * {@link #surfaceDestroyed(SurfaceHolder)}.  The Callback is set with
+     * {@link SurfaceHolder#addCallback SurfaceHolder.addCallback} method.
+     */
+    public interface Callback {
+        /**
+         * This is called immediately after the surface is first created.
+         * Implementations of this should start up whatever rendering code
+         * they desire.  Note that only one thread can ever draw into
+         * a {@link Surface}, so you should not draw into the Surface here
+         * if your normal rendering will be in another thread.
+         *
+         * @param holder The SurfaceHolder whose surface is being created.
+         */
+        void surfaceCreated(@NonNull SurfaceHolder holder);
+
+        /**
+         * This is called immediately after any structural changes (format or
+         * size) have been made to the surface.  You should at this point update
+         * the imagery in the surface.  This method is always called at least
+         * once, after {@link #surfaceCreated}.
+         *
+         * @param holder The SurfaceHolder whose surface has changed.
+         * @param format The new {@link PixelFormat} of the surface.
+         * @param width The new width of the surface.
+         * @param height The new height of the surface.
+         */
+        void surfaceChanged(@NonNull SurfaceHolder holder, @PixelFormat.Format int format,
+                @IntRange(from = 0) int width, @IntRange(from = 0) int height);
+
+        /**
+         * This is called immediately before a surface is being destroyed. After
+         * returning from this call, you should no longer try to access this
+         * surface.  If you have a rendering thread that directly accesses
+         * the surface, you must ensure that thread is no longer touching the
+         * Surface before returning from this function.
+         *
+         * @param holder The SurfaceHolder whose surface is being destroyed.
+         */
+        void surfaceDestroyed(@NonNull SurfaceHolder holder);
+    }
+
+    /**
+     * Additional callbacks that can be received for {@link Callback}.
+     */
+    public interface Callback2 extends Callback {
+        /**
+         * Called when the application needs to redraw the content of its
+         * surface, after it is resized or for some other reason.  By not
+         * returning from here until the redraw is complete, you can ensure that
+         * the user will not see your surface in a bad state (at its new
+         * size before it has been correctly drawn that way).  This will
+         * typically be preceeded by a call to {@link #surfaceChanged}.
+         *
+         * As of O, {@link #surfaceRedrawNeededAsync} may be implemented
+         * to provide a non-blocking implementation. If {@link #surfaceRedrawNeededAsync}
+         * is not implemented, then this will be called instead.
+         *
+         * @param holder The SurfaceHolder whose surface has changed.
+         */
+        void surfaceRedrawNeeded(@NonNull SurfaceHolder holder);
+
+        /**
+         * An alternative to surfaceRedrawNeeded where it is not required to block
+         * until the redraw is complete. You should initiate the redraw, and return,
+         * later invoking drawingFinished when your redraw is complete.
+         *
+         * This can be useful to avoid blocking your main application thread on rendering.
+         *
+         * As of O, if this is implemented {@link #surfaceRedrawNeeded} will not be called.
+         * However it is still recommended to implement {@link #surfaceRedrawNeeded} for
+         * compatibility with older versions of the platform.
+         *
+         * @param holder The SurfaceHolder which needs redrawing.
+         * @param drawingFinished A runnable to signal completion. This may be invoked
+         * from any thread.
+         *
+         */
+        default void surfaceRedrawNeededAsync(@NonNull SurfaceHolder holder,
+                @NonNull Runnable drawingFinished) {
+            surfaceRedrawNeeded(holder);
+            drawingFinished.run();
+        }
+    }
+
+    /**
+     * Add a Callback interface for this holder.  There can several Callback
+     * interfaces associated with a holder.
+     *
+     * @param callback The new Callback interface.
+     */
+    public void addCallback(Callback callback);
+
+    /**
+     * Removes a previously added Callback interface from this holder.
+     *
+     * @param callback The Callback interface to remove.
+     */
+    public void removeCallback(Callback callback);
+
+    /**
+     * Use this method to find out if the surface is in the process of being
+     * created from Callback methods. This is intended to be used with
+     * {@link Callback#surfaceChanged}.
+     *
+     * @return true if the surface is in the process of being created.
+     */
+    public boolean isCreating();
+
+    /**
+     * Sets the surface's type.
+     *
+     * @deprecated this is ignored, this value is set automatically when needed.
+     */
+    @Deprecated
+    public void setType(int type);
+
+    /**
+     * Make the surface a fixed size.  It will never change from this size.
+     * When working with a {@link SurfaceView}, this must be called from the
+     * same thread running the SurfaceView's window.
+     *
+     * @param width The surface's width.
+     * @param height The surface's height.
+     */
+    public void setFixedSize(int width, int height);
+
+    /**
+     * Allow the surface to resized based on layout of its container (this is
+     * the default).  When this is enabled, you should monitor
+     * {@link Callback#surfaceChanged} for changes to the size of the surface.
+     * When working with a {@link SurfaceView}, this must be called from the
+     * same thread running the SurfaceView's window.
+     */
+    public void setSizeFromLayout();
+
+    /**
+     * Set the desired PixelFormat of the surface.  The default is OPAQUE.
+     * When working with a {@link SurfaceView}, this must be called from the
+     * same thread running the SurfaceView's window.
+     *
+     * @param format A constant from PixelFormat.
+     *
+     * @see android.graphics.PixelFormat
+     */
+    public void setFormat(int format);
+
+    /**
+     * Enable or disable option to keep the screen turned on while this
+     * surface is displayed.  The default is false, allowing it to turn off.
+     * This is safe to call from any thread.
+     *
+     * @param screenOn Set to true to force the screen to stay on, false
+     * to allow it to turn off.
+     */
+    public void setKeepScreenOn(boolean screenOn);
+
+    /**
+     * Start editing the pixels in the surface.  The returned Canvas can be used
+     * to draw into the surface's bitmap.  A null is returned if the surface has
+     * not been created or otherwise cannot be edited.  You will usually need
+     * to implement {@link Callback#surfaceCreated Callback.surfaceCreated}
+     * to find out when the Surface is available for use.
+     *
+     * <p>The content of the Surface is never preserved between unlockCanvas() and
+     * lockCanvas(), for this reason, every pixel within the Surface area
+     * must be written. The only exception to this rule is when a dirty
+     * rectangle is specified, in which case, non-dirty pixels will be
+     * preserved.
+     *
+     * <p>If you call this repeatedly when the Surface is not ready (before
+     * {@link Callback#surfaceCreated Callback.surfaceCreated} or after
+     * {@link Callback#surfaceDestroyed Callback.surfaceDestroyed}), your calls
+     * will be throttled to a slow rate in order to avoid consuming CPU.
+     *
+     * <p>If null is not returned, this function internally holds a lock until
+     * the corresponding {@link #unlockCanvasAndPost} call, preventing
+     * {@link SurfaceView} from creating, destroying, or modifying the surface
+     * while it is being drawn.  This can be more convenient than accessing
+     * the Surface directly, as you do not need to do special synchronization
+     * with a drawing thread in {@link Callback#surfaceDestroyed
+     * Callback.surfaceDestroyed}.
+     *
+     * @return Canvas Use to draw into the surface.
+     */
+    public Canvas lockCanvas();
+
+
+    /**
+     * Just like {@link #lockCanvas()} but allows specification of a dirty rectangle.
+     * Every
+     * pixel within that rectangle must be written; however pixels outside
+     * the dirty rectangle will be preserved by the next call to lockCanvas().
+     *
+     * @see android.view.SurfaceHolder#lockCanvas
+     *
+     * @param dirty Area of the Surface that will be modified.
+     * @return Canvas Use to draw into the surface.
+     */
+    public Canvas lockCanvas(Rect dirty);
+
+    /**
+     * <p>Just like {@link #lockCanvas()} but the returned canvas is hardware-accelerated.
+     *
+     * <p>See the <a href="{@docRoot}guide/topics/graphics/hardware-accel.html#unsupported">
+     * unsupported drawing operations</a> for a list of what is and isn't
+     * supported in a hardware-accelerated canvas.
+     *
+     * @return Canvas Use to draw into the surface.
+     * @throws IllegalStateException If the canvas cannot be locked.
+     */
+    default Canvas lockHardwareCanvas() {
+        throw new IllegalStateException("This SurfaceHolder doesn't support lockHardwareCanvas");
+    }
+
+    /**
+     * Finish editing pixels in the surface.  After this call, the surface's
+     * current pixels will be shown on the screen, but its content is lost,
+     * in particular there is no guarantee that the content of the Surface
+     * will remain unchanged when lockCanvas() is called again.
+     *
+     * @see #lockCanvas()
+     *
+     * @param canvas The Canvas previously returned by lockCanvas().
+     */
+    public void unlockCanvasAndPost(Canvas canvas);
+
+    /**
+     * Retrieve the current size of the surface.  Note: do not modify the
+     * returned Rect.  This is only safe to call from the thread of
+     * {@link SurfaceView}'s window, or while inside of
+     * {@link #lockCanvas()}.
+     *
+     * @return Rect The surface's dimensions.  The left and top are always 0.
+     */
+    public Rect getSurfaceFrame();
+
+    /**
+     * Direct access to the surface object.  The Surface may not always be
+     * available -- for example when using a {@link SurfaceView} the holder's
+     * Surface is not created until the view has been attached to the window
+     * manager and performed a layout in order to determine the dimensions
+     * and screen position of the Surface.    You will thus usually need
+     * to implement {@link Callback#surfaceCreated Callback.surfaceCreated}
+     * to find out when the Surface is available for use.
+     *
+     * <p>Note that if you directly access the Surface from another thread,
+     * it is critical that you correctly implement
+     * {@link Callback#surfaceCreated Callback.surfaceCreated} and
+     * {@link Callback#surfaceDestroyed Callback.surfaceDestroyed} to ensure
+     * that thread only accesses the Surface while it is valid, and that the
+     * Surface does not get destroyed while the thread is using it.
+     *
+     * <p>This method is intended to be used by frameworks which often need
+     * direct access to the Surface object (usually to pass it to native code).
+     *
+     * @return Surface The surface.
+     */
+    public Surface getSurface();
+}
diff --git a/android/view/SurfaceSession.java b/android/view/SurfaceSession.java
new file mode 100644
index 0000000..20f0598
--- /dev/null
+++ b/android/view/SurfaceSession.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+
+/**
+ * An instance of this class represents a connection to the surface
+ * flinger, from which you can create one or more Surface instances that will
+ * be composited to the screen.
+ * {@hide}
+ */
+public final class SurfaceSession {
+    // Note: This field is accessed by native code.
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private long mNativeClient; // SurfaceComposerClient*
+
+    private static native long nativeCreate();
+    private static native void nativeDestroy(long ptr);
+
+    /** Create a new connection with the surface flinger. */
+    @UnsupportedAppUsage
+    public SurfaceSession() {
+        mNativeClient = nativeCreate();
+    }
+
+    /* no user serviceable parts here ... */
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            kill();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Remove the reference to the native Session object. The native object may still exist if
+     * there are other references to it, but it cannot be accessed from this Java object anymore.
+     */
+    @UnsupportedAppUsage
+    public void kill() {
+        if (mNativeClient != 0) {
+            nativeDestroy(mNativeClient);
+            mNativeClient = 0;
+        }
+    }
+}
+
diff --git a/android/view/SurfaceView.java b/android/view/SurfaceView.java
new file mode 100644
index 0000000..ebb2af4
--- /dev/null
+++ b/android/view/SurfaceView.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import com.android.layoutlib.bridge.MockView;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.util.AttributeSet;
+
+/**
+ * Mock version of the SurfaceView.
+ * Only non override public methods from the real SurfaceView have been added in there.
+ * Methods that take an unknown class as parameter or as return object, have been removed for now.
+ *
+ * TODO: generate automatically.
+ *
+ */
+public class SurfaceView extends MockView {
+
+    public SurfaceView(Context context) {
+        this(context, null);
+    }
+
+    public SurfaceView(Context context, AttributeSet attrs) {
+        this(context, attrs , 0);
+    }
+
+    public SurfaceView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    public boolean gatherTransparentRegion(Region region) {
+      return false;
+    }
+
+    public void setZOrderMediaOverlay(boolean isMediaOverlay) {
+    }
+
+    public void setZOrderOnTop(boolean onTop) {
+    }
+
+    public void setSecure(boolean isSecure) {
+    }
+
+    public SurfaceHolder getHolder() {
+        return mSurfaceHolder;
+    }
+
+    private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
+
+        @Override
+        public boolean isCreating() {
+            return false;
+        }
+
+        @Override
+        public void addCallback(Callback callback) {
+        }
+
+        @Override
+        public void removeCallback(Callback callback) {
+        }
+
+        @Override
+        public void setFixedSize(int width, int height) {
+        }
+
+        @Override
+        public void setSizeFromLayout() {
+        }
+
+        @Override
+        public void setFormat(int format) {
+        }
+
+        @Override
+        public void setType(int type) {
+        }
+
+        @Override
+        public void setKeepScreenOn(boolean screenOn) {
+        }
+
+        @Override
+        public Canvas lockCanvas() {
+            return null;
+        }
+
+        @Override
+        public Canvas lockCanvas(Rect dirty) {
+            return null;
+        }
+
+        @Override
+        public void unlockCanvasAndPost(Canvas canvas) {
+        }
+
+        @Override
+        public Surface getSurface() {
+            return null;
+        }
+
+        @Override
+        public Rect getSurfaceFrame() {
+            return null;
+        }
+    };
+}
+
diff --git a/android/view/SyncRtSurfaceTransactionApplier.java b/android/view/SyncRtSurfaceTransactionApplier.java
new file mode 100644
index 0000000..3e21103
--- /dev/null
+++ b/android/view/SyncRtSurfaceTransactionApplier.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.view.SurfaceControl.Transaction;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.function.Consumer;
+
+/**
+ * Helper class to apply surface transactions in sync with RenderThread.
+ * @hide
+ */
+public class SyncRtSurfaceTransactionApplier {
+
+    public static final int FLAG_ALL = 0xffffffff;
+    public static final int FLAG_ALPHA = 1;
+    public static final int FLAG_MATRIX = 1 << 1;
+    public static final int FLAG_WINDOW_CROP = 1 << 2;
+    public static final int FLAG_LAYER = 1 << 3;
+    public static final int FLAG_CORNER_RADIUS = 1 << 4;
+    public static final int FLAG_BACKGROUND_BLUR_RADIUS = 1 << 5;
+    public static final int FLAG_VISIBILITY = 1 << 6;
+    public static final int FLAG_TRANSACTION = 1 << 7;
+
+    private SurfaceControl mTargetSc;
+    private final ViewRootImpl mTargetViewRootImpl;
+    private final float[] mTmpFloat9 = new float[9];
+
+    /**
+     * @param targetView The view in the surface that acts as synchronization anchor.
+     */
+    public SyncRtSurfaceTransactionApplier(View targetView) {
+        mTargetViewRootImpl = targetView != null ? targetView.getViewRootImpl() : null;
+    }
+
+    /**
+     * Schedules applying surface parameters on the next frame.
+     *
+     * @param params The surface parameters to apply.
+     */
+    public void scheduleApply(final SurfaceParams... params) {
+        if (mTargetViewRootImpl == null) {
+            return;
+        }
+        mTargetSc = mTargetViewRootImpl.getSurfaceControl();
+        final Transaction t = new Transaction();
+        applyParams(t, params);
+
+        mTargetViewRootImpl.registerRtFrameCallback(frame -> {
+            if (mTargetSc == null || !mTargetSc.isValid()) {
+                return;
+            }
+            applyTransaction(t, frame);
+        });
+
+        // Make sure a frame gets scheduled.
+        mTargetViewRootImpl.getView().invalidate();
+    }
+
+    /**
+     * Applies surface parameters on the next frame.
+     * @param t transaction to apply all parameters in.
+     * @param frame frame to synchronize to. Set -1 when sync is not required.
+     * @param params The surface parameters to apply.
+     */
+     void applyParams(Transaction t, final SurfaceParams... params) {
+        for (int i = params.length - 1; i >= 0; i--) {
+            SurfaceParams surfaceParams = params[i];
+            SurfaceControl surface = surfaceParams.surface;
+            applyParams(t, surfaceParams, mTmpFloat9);
+        }
+    }
+
+    void applyTransaction(Transaction t, long frame) {
+        if (mTargetViewRootImpl != null) {
+            mTargetViewRootImpl.mergeWithNextTransaction(t, frame);
+        } else {
+            t.apply();
+        }
+    }
+
+    public static void applyParams(Transaction t, SurfaceParams params, float[] tmpFloat9) {
+        if ((params.flags & FLAG_TRANSACTION) != 0) {
+            t.merge(params.mergeTransaction);
+        }
+
+        if ((params.flags & FLAG_MATRIX) != 0) {
+            t.setMatrix(params.surface, params.matrix, tmpFloat9);
+        }
+        if ((params.flags & FLAG_WINDOW_CROP) != 0) {
+            t.setWindowCrop(params.surface, params.windowCrop);
+        }
+        if ((params.flags & FLAG_ALPHA) != 0) {
+            t.setAlpha(params.surface, params.alpha);
+        }
+        if ((params.flags & FLAG_LAYER) != 0) {
+            t.setLayer(params.surface, params.layer);
+        }
+        if ((params.flags & FLAG_CORNER_RADIUS) != 0) {
+            t.setCornerRadius(params.surface, params.cornerRadius);
+        }
+        if ((params.flags & FLAG_BACKGROUND_BLUR_RADIUS) != 0) {
+            t.setBackgroundBlurRadius(params.surface, params.backgroundBlurRadius);
+        }
+        if ((params.flags & FLAG_VISIBILITY) != 0) {
+            if (params.visible) {
+                t.show(params.surface);
+            } else {
+                t.hide(params.surface);
+            }
+        }
+    }
+
+    /**
+     * Creates an instance of SyncRtSurfaceTransactionApplier, deferring until the target view is
+     * attached if necessary.
+     */
+    public static void create(final View targetView,
+            final Consumer<SyncRtSurfaceTransactionApplier> callback) {
+        if (targetView == null) {
+            // No target view, no applier
+            callback.accept(null);
+        } else if (targetView.getViewRootImpl() != null) {
+            // Already attached, we're good to go
+            callback.accept(new SyncRtSurfaceTransactionApplier(targetView));
+        } else {
+            // Haven't been attached before we can get the view root
+            targetView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+                @Override
+                public void onViewAttachedToWindow(View v) {
+                    targetView.removeOnAttachStateChangeListener(this);
+                    callback.accept(new SyncRtSurfaceTransactionApplier(targetView));
+                }
+
+                @Override
+                public void onViewDetachedFromWindow(View v) {
+                    // Do nothing
+                }
+            });
+        }
+    }
+
+    public static class SurfaceParams {
+
+        public static class Builder {
+            final SurfaceControl surface;
+            int flags;
+            float alpha;
+            float cornerRadius;
+            int backgroundBlurRadius;
+            Matrix matrix;
+            Rect windowCrop;
+            int layer;
+            boolean visible;
+            Transaction mergeTransaction;
+
+            /**
+             * @param surface The surface to modify.
+             */
+            public Builder(SurfaceControl surface) {
+                this.surface = surface;
+            }
+
+            /**
+             * @param alpha The alpha value to apply to the surface.
+             * @return this Builder
+             */
+            public Builder withAlpha(float alpha) {
+                this.alpha = alpha;
+                flags |= FLAG_ALPHA;
+                return this;
+            }
+
+            /**
+             * @param matrix The matrix to apply to the surface.
+             * @return this Builder
+             */
+            public Builder withMatrix(Matrix matrix) {
+                this.matrix = new Matrix(matrix);
+                flags |= FLAG_MATRIX;
+                return this;
+            }
+
+            /**
+             * @param windowCrop The window crop to apply to the surface.
+             * @return this Builder
+             */
+            public Builder withWindowCrop(Rect windowCrop) {
+                this.windowCrop = new Rect(windowCrop);
+                flags |= FLAG_WINDOW_CROP;
+                return this;
+            }
+
+            /**
+             * @param layer The layer to assign the surface.
+             * @return this Builder
+             */
+            public Builder withLayer(int layer) {
+                this.layer = layer;
+                flags |= FLAG_LAYER;
+                return this;
+            }
+
+            /**
+             * @param radius the Radius for rounded corners to apply to the surface.
+             * @return this Builder
+             */
+            public Builder withCornerRadius(float radius) {
+                this.cornerRadius = radius;
+                flags |= FLAG_CORNER_RADIUS;
+                return this;
+            }
+
+            /**
+             * @param radius the Radius for blur to apply to the background surfaces.
+             * @return this Builder
+             */
+            public Builder withBackgroundBlur(int radius) {
+                this.backgroundBlurRadius = radius;
+                flags |= FLAG_BACKGROUND_BLUR_RADIUS;
+                return this;
+            }
+
+            /**
+             * @param visible The visibility to apply to the surface.
+             * @return this Builder
+             */
+            public Builder withVisibility(boolean visible) {
+                this.visible = visible;
+                flags |= FLAG_VISIBILITY;
+                return this;
+            }
+
+            /**
+             * @param mergeTransaction The transaction to apply to the surface. Note this is applied
+             *                         first before all the other properties.
+             * @return this Builder
+             */
+            public Builder withMergeTransaction(Transaction mergeTransaction) {
+                this.mergeTransaction = mergeTransaction;
+                flags |= FLAG_TRANSACTION;
+                return this;
+            }
+
+            /**
+             * @return a new SurfaceParams instance
+             */
+            public SurfaceParams build() {
+                return new SurfaceParams(surface, flags, alpha, matrix, windowCrop, layer,
+                        cornerRadius, backgroundBlurRadius, visible, mergeTransaction);
+            }
+        }
+
+        private SurfaceParams(SurfaceControl surface, int params, float alpha, Matrix matrix,
+                Rect windowCrop, int layer, float cornerRadius,
+                int backgroundBlurRadius, boolean visible, Transaction mergeTransaction) {
+            this.flags = params;
+            this.surface = surface;
+            this.alpha = alpha;
+            this.matrix = matrix;
+            this.windowCrop = windowCrop;
+            this.layer = layer;
+            this.cornerRadius = cornerRadius;
+            this.backgroundBlurRadius = backgroundBlurRadius;
+            this.visible = visible;
+            this.mergeTransaction = mergeTransaction;
+        }
+
+        private final int flags;
+
+        @VisibleForTesting
+        public final SurfaceControl surface;
+
+        @VisibleForTesting
+        public final float alpha;
+
+        @VisibleForTesting
+        public final float cornerRadius;
+
+        @VisibleForTesting
+        public final int backgroundBlurRadius;
+
+        @VisibleForTesting
+        public final Matrix matrix;
+
+        @VisibleForTesting
+        public final Rect windowCrop;
+
+        @VisibleForTesting
+        public final int layer;
+
+        public final boolean visible;
+
+        public final Transaction mergeTransaction;
+    }
+}
diff --git a/android/view/TextureView.java b/android/view/TextureView.java
new file mode 100644
index 0000000..fc0ec4c
--- /dev/null
+++ b/android/view/TextureView.java
@@ -0,0 +1,864 @@
+/*
+ * 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.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.RecordingCanvas;
+import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
+import android.graphics.TextureLayer;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.util.Log;
+
+/**
+ * <p>A TextureView can be used to display a content stream. Such a content
+ * stream can for instance be a video or an OpenGL scene. The content stream
+ * can come from the application's process as well as a remote process.</p>
+ *
+ * <p>TextureView can only be used in a hardware accelerated window. When
+ * rendered in software, TextureView will draw nothing.</p>
+ *
+ * <p>Unlike {@link SurfaceView}, TextureView does not create a separate
+ * window but behaves as a regular View. This key difference allows a
+ * TextureView to be moved, transformed, animated, etc. For instance, you
+ * can make a TextureView semi-translucent by calling
+ * <code>myView.setAlpha(0.5f)</code>.</p>
+ *
+ * <p>Using a TextureView is simple: all you need to do is get its
+ * {@link SurfaceTexture}. The {@link SurfaceTexture} can then be used to
+ * render content. The following example demonstrates how to render the
+ * camera preview into a TextureView:</p>
+ *
+ * <pre>
+ *  public class LiveCameraActivity extends Activity implements TextureView.SurfaceTextureListener {
+ *      private Camera mCamera;
+ *      private TextureView mTextureView;
+ *
+ *      protected void onCreate(Bundle savedInstanceState) {
+ *          super.onCreate(savedInstanceState);
+ *
+ *          mTextureView = new TextureView(this);
+ *          mTextureView.setSurfaceTextureListener(this);
+ *
+ *          setContentView(mTextureView);
+ *      }
+ *
+ *      public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+ *          mCamera = Camera.open();
+ *
+ *          try {
+ *              mCamera.setPreviewTexture(surface);
+ *              mCamera.startPreview();
+ *          } catch (IOException ioe) {
+ *              // Something bad happened
+ *          }
+ *      }
+ *
+ *      public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+ *          // Ignored, Camera does all the work for us
+ *      }
+ *
+ *      public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+ *          mCamera.stopPreview();
+ *          mCamera.release();
+ *          return true;
+ *      }
+ *
+ *      public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+ *          // Invoked every time there's a new Camera preview frame
+ *      }
+ *  }
+ * </pre>
+ *
+ * <p>A TextureView's SurfaceTexture can be obtained either by invoking
+ * {@link #getSurfaceTexture()} or by using a {@link SurfaceTextureListener}.
+ * It is important to know that a SurfaceTexture is available only after the
+ * TextureView is attached to a window (and {@link #onAttachedToWindow()} has
+ * been invoked.) It is therefore highly recommended you use a listener to
+ * be notified when the SurfaceTexture becomes available.</p>
+ *
+ * <p>It is important to note that only one producer can use the TextureView.
+ * For instance, if you use a TextureView to display the camera preview, you
+ * cannot use {@link #lockCanvas()} to draw onto the TextureView at the same
+ * time.</p>
+ *
+ * @see SurfaceView
+ * @see SurfaceTexture
+ */
+public class TextureView extends View {
+    private static final String LOG_TAG = "TextureView";
+
+    @UnsupportedAppUsage
+    private TextureLayer mLayer;
+    @UnsupportedAppUsage
+    private SurfaceTexture mSurface;
+    private SurfaceTextureListener mListener;
+    private boolean mHadSurface;
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private boolean mOpaque = true;
+
+    private final Matrix mMatrix = new Matrix();
+    private boolean mMatrixChanged;
+
+    private final Object[] mLock = new Object[0];
+    private boolean mUpdateLayer;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private boolean mUpdateSurface;
+
+    private Canvas mCanvas;
+    private int mSaveCount;
+
+    private final Object[] mNativeWindowLock = new Object[0];
+    // Set by native code, do not write!
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private long mNativeWindow;
+
+    /**
+     * Creates a new TextureView.
+     *
+     * @param context The context to associate this view with.
+     */
+    public TextureView(@NonNull Context context) {
+        super(context);
+    }
+
+    /**
+     * Creates a new TextureView.
+     *
+     * @param context The context to associate this view with.
+     * @param attrs The attributes of the XML tag that is inflating the view.
+     */
+    public TextureView(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    /**
+     * Creates a new TextureView.
+     *
+     * @param context The context to associate this view with.
+     * @param attrs The attributes of the XML tag that is inflating the view.
+     * @param defStyleAttr An attribute in the current theme that contains a
+     *        reference to a style resource that supplies default values for
+     *        the view. Can be 0 to not look for defaults.
+     */
+    public TextureView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    /**
+     * Creates a new TextureView.
+     *
+     * @param context The context to associate this view with.
+     * @param attrs The attributes of the XML tag that is inflating the view.
+     * @param defStyleAttr An attribute in the current theme that contains a
+     *        reference to a style resource that supplies default values for
+     *        the view. Can be 0 to not look for defaults.
+     * @param defStyleRes A resource identifier of a style resource that
+     *        supplies default values for the view, used only if
+     *        defStyleAttr is 0 or can not be found in the theme. Can be 0
+     *        to not look for defaults.
+     */
+    public TextureView(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isOpaque() {
+        return mOpaque;
+    }
+
+    /**
+     * Indicates whether the content of this TextureView is opaque. The
+     * content is assumed to be opaque by default.
+     *
+     * @param opaque True if the content of this TextureView is opaque,
+     *               false otherwise
+     */
+    public void setOpaque(boolean opaque) {
+        if (opaque != mOpaque) {
+            mOpaque = opaque;
+            if (mLayer != null) {
+                updateLayerAndInvalidate();
+            }
+        }
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        if (!isHardwareAccelerated()) {
+            Log.w(LOG_TAG, "A TextureView or a subclass can only be "
+                    + "used with hardware acceleration enabled.");
+        }
+
+        if (mHadSurface) {
+            invalidate(true);
+            mHadSurface = false;
+        }
+    }
+
+    /** @hide */
+    @Override
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    protected void onDetachedFromWindowInternal() {
+        destroyHardwareLayer();
+        releaseSurfaceTexture();
+        super.onDetachedFromWindowInternal();
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    @UnsupportedAppUsage
+    protected void destroyHardwareResources() {
+        super.destroyHardwareResources();
+        destroyHardwareLayer();
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private void destroyHardwareLayer() {
+        if (mLayer != null) {
+            mLayer.detachSurfaceTexture();
+            mLayer.close();
+            mLayer = null;
+            mMatrixChanged = true;
+        }
+    }
+
+    private void releaseSurfaceTexture() {
+        if (mSurface != null) {
+            boolean shouldRelease = true;
+
+            if (mListener != null) {
+                shouldRelease = mListener.onSurfaceTextureDestroyed(mSurface);
+            }
+
+            synchronized (mNativeWindowLock) {
+                nDestroyNativeWindow();
+            }
+
+            if (shouldRelease) {
+                mSurface.release();
+            }
+            mSurface = null;
+            mHadSurface = true;
+        }
+    }
+
+    /**
+     * The layer type of a TextureView is ignored since a TextureView is always
+     * considered to act as a hardware layer. The optional paint supplied to this
+     * method will however be taken into account when rendering the content of
+     * this TextureView.
+     *
+     * @param layerType The type of layer to use with this view, must be one of
+     *        {@link #LAYER_TYPE_NONE}, {@link #LAYER_TYPE_SOFTWARE} or
+     *        {@link #LAYER_TYPE_HARDWARE}
+     * @param paint The paint used to compose the layer. This argument is optional
+     *        and can be null. It is ignored when the layer type is
+     *        {@link #LAYER_TYPE_NONE}
+     */
+    @Override
+    public void setLayerType(int layerType, @Nullable Paint paint) {
+        setLayerPaint(paint);
+    }
+
+    @Override
+    public void setLayerPaint(@Nullable Paint paint) {
+        if (paint != mLayerPaint) {
+            mLayerPaint = paint;
+            invalidate();
+        }
+    }
+
+    /**
+     * Always returns {@link #LAYER_TYPE_HARDWARE}.
+     */
+    @Override
+    public int getLayerType() {
+        return LAYER_TYPE_HARDWARE;
+    }
+
+    /**
+     * Calling this method has no effect.
+     */
+    @Override
+    public void buildLayer() {
+    }
+
+    @Override
+    public void setForeground(Drawable foreground) {
+        if (foreground != null && !sTextureViewIgnoresDrawableSetters) {
+            throw new UnsupportedOperationException(
+                    "TextureView doesn't support displaying a foreground drawable");
+        }
+    }
+
+    @Override
+    public void setBackgroundDrawable(Drawable background) {
+        if (background != null && !sTextureViewIgnoresDrawableSetters) {
+            throw new UnsupportedOperationException(
+                    "TextureView doesn't support displaying a background drawable");
+        }
+    }
+
+    /**
+     * Subclasses of TextureView cannot do their own rendering
+     * with the {@link Canvas} object.
+     *
+     * @param canvas The Canvas to which the View is rendered.
+     */
+    @Override
+    public final void draw(Canvas canvas) {
+        // NOTE: Maintain this carefully (see View#draw)
+        mPrivateFlags = (mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
+
+        /* Simplify drawing to guarantee the layer is the only thing drawn - so e.g. no background,
+        scrolling, or fading edges. This guarantees all drawing is in the layer, so drawing
+        properties (alpha, layer paint) affect all of the content of a TextureView. */
+
+        if (canvas.isHardwareAccelerated()) {
+            RecordingCanvas recordingCanvas = (RecordingCanvas) canvas;
+
+            TextureLayer layer = getTextureLayer();
+            if (layer != null) {
+                applyUpdate();
+                applyTransformMatrix();
+
+                mLayer.setLayerPaint(mLayerPaint); // ensure layer paint is up to date
+                recordingCanvas.drawTextureLayer(layer);
+            }
+        }
+    }
+
+    /**
+     * Subclasses of TextureView cannot do their own rendering
+     * with the {@link Canvas} object.
+     *
+     * @param canvas The Canvas to which the View is rendered.
+     */
+    @Override
+    protected final void onDraw(Canvas canvas) {
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+        if (mSurface != null) {
+            mSurface.setDefaultBufferSize(getWidth(), getHeight());
+            updateLayer();
+            if (mListener != null) {
+                mListener.onSurfaceTextureSizeChanged(mSurface, getWidth(), getHeight());
+            }
+        }
+    }
+
+    TextureLayer getTextureLayer() {
+        if (mLayer == null) {
+            if (mAttachInfo == null || mAttachInfo.mThreadedRenderer == null) {
+                return null;
+            }
+
+            mLayer = mAttachInfo.mThreadedRenderer.createTextureLayer();
+            boolean createNewSurface = (mSurface == null);
+            if (createNewSurface) {
+                // Create a new SurfaceTexture for the layer.
+                mSurface = new SurfaceTexture(false);
+                nCreateNativeWindow(mSurface);
+            }
+            mLayer.setSurfaceTexture(mSurface);
+            mSurface.setDefaultBufferSize(getWidth(), getHeight());
+            mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler);
+
+            if (mListener != null && createNewSurface) {
+                mListener.onSurfaceTextureAvailable(mSurface, getWidth(), getHeight());
+            }
+            mLayer.setLayerPaint(mLayerPaint);
+        }
+
+        if (mUpdateSurface) {
+            // Someone has requested that we use a specific SurfaceTexture, so
+            // tell mLayer about it and set the SurfaceTexture to use the
+            // current view size.
+            mUpdateSurface = false;
+
+            // Since we are updating the layer, force an update to ensure its
+            // parameters are correct (width, height, transform, etc.)
+            updateLayer();
+            mMatrixChanged = true;
+
+            mLayer.setSurfaceTexture(mSurface);
+            mSurface.setDefaultBufferSize(getWidth(), getHeight());
+        }
+
+        return mLayer;
+    }
+
+    @Override
+    protected void onVisibilityChanged(View changedView, int visibility) {
+        super.onVisibilityChanged(changedView, visibility);
+
+        if (mSurface != null) {
+            // When the view becomes invisible, stop updating it, it's a waste of CPU
+            // To cancel updates, the easiest thing to do is simply to remove the
+            // updates listener
+            if (visibility == VISIBLE) {
+                if (mLayer != null) {
+                    mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler);
+                }
+                updateLayerAndInvalidate();
+            } else {
+                mSurface.setOnFrameAvailableListener(null);
+            }
+        }
+    }
+
+    private void updateLayer() {
+        synchronized (mLock) {
+            mUpdateLayer = true;
+        }
+    }
+
+    private void updateLayerAndInvalidate() {
+        synchronized (mLock) {
+            mUpdateLayer = true;
+        }
+        invalidate();
+    }
+
+    private void applyUpdate() {
+        if (mLayer == null) {
+            return;
+        }
+
+        synchronized (mLock) {
+            if (mUpdateLayer) {
+                mUpdateLayer = false;
+            } else {
+                return;
+            }
+        }
+
+        mLayer.prepare(getWidth(), getHeight(), mOpaque);
+        mLayer.updateSurfaceTexture();
+
+        if (mListener != null) {
+            mListener.onSurfaceTextureUpdated(mSurface);
+        }
+    }
+
+    /**
+     * <p>Sets the transform to associate with this texture view.
+     * The specified transform applies to the underlying surface
+     * texture and does not affect the size or position of the view
+     * itself, only of its content.</p>
+     *
+     * <p>Some transforms might prevent the content from drawing
+     * all the pixels contained within this view's bounds. In such
+     * situations, make sure this texture view is not marked opaque.</p>
+     *
+     * @param transform The transform to apply to the content of
+     *        this view. If null the transform will be set to identity.
+     *
+     * @see #getTransform(android.graphics.Matrix)
+     * @see #isOpaque()
+     * @see #setOpaque(boolean)
+     */
+    public void setTransform(@Nullable Matrix transform) {
+        mMatrix.set(transform);
+        mMatrixChanged = true;
+        invalidateParentIfNeeded();
+    }
+
+    /**
+     * Returns the transform associated with this texture view.
+     *
+     * @param transform The {@link Matrix} in which to copy the current
+     *        transform. Can be null.
+     *
+     * @return The specified matrix if not null or a new {@link Matrix}
+     *         instance otherwise.
+     *
+     * @see #setTransform(android.graphics.Matrix)
+     */
+    public @NonNull Matrix getTransform(@Nullable Matrix transform) {
+        if (transform == null) {
+            transform = new Matrix();
+        }
+
+        transform.set(mMatrix);
+
+        return transform;
+    }
+
+    private void applyTransformMatrix() {
+        if (mMatrixChanged && mLayer != null) {
+            mLayer.setTransform(mMatrix);
+            mMatrixChanged = false;
+        }
+    }
+
+    /**
+     * <p>Returns a {@link android.graphics.Bitmap} representation of the content
+     * of the associated surface texture. If the surface texture is not available,
+     * this method returns null.</p>
+     *
+     * <p>The bitmap returned by this method uses the {@link Bitmap.Config#ARGB_8888}
+     * pixel format and its dimensions are the same as this view's.</p>
+     *
+     * <p><strong>Do not</strong> invoke this method from a drawing method
+     * ({@link #onDraw(android.graphics.Canvas)} for instance).</p>
+     *
+     * <p>If an error occurs during the copy, an empty bitmap will be returned.</p>
+     *
+     * @return A valid {@link Bitmap.Config#ARGB_8888} bitmap, or null if the surface
+     *         texture is not available or the width &lt;= 0 or the height &lt;= 0
+     *
+     * @see #isAvailable()
+     * @see #getBitmap(android.graphics.Bitmap)
+     * @see #getBitmap(int, int)
+     */
+    public @Nullable Bitmap getBitmap() {
+        return getBitmap(getWidth(), getHeight());
+    }
+
+    /**
+     * <p>Returns a {@link android.graphics.Bitmap} representation of the content
+     * of the associated surface texture. If the surface texture is not available,
+     * this method returns null.</p>
+     *
+     * <p>The bitmap returned by this method uses the {@link Bitmap.Config#ARGB_8888}
+     * pixel format.</p>
+     *
+     * <p><strong>Do not</strong> invoke this method from a drawing method
+     * ({@link #onDraw(android.graphics.Canvas)} for instance).</p>
+     *
+     * <p>If an error occurs during the copy, an empty bitmap will be returned.</p>
+     *
+     * @param width The width of the bitmap to create
+     * @param height The height of the bitmap to create
+     *
+     * @return A valid {@link Bitmap.Config#ARGB_8888} bitmap, or null if the surface
+     *         texture is not available or width is &lt;= 0 or height is &lt;= 0
+     *
+     * @see #isAvailable()
+     * @see #getBitmap(android.graphics.Bitmap)
+     * @see #getBitmap()
+     */
+    public @Nullable Bitmap getBitmap(int width, int height) {
+        if (isAvailable() && width > 0 && height > 0) {
+            return getBitmap(Bitmap.createBitmap(getResources().getDisplayMetrics(),
+                    width, height, Bitmap.Config.ARGB_8888));
+        }
+        return null;
+    }
+
+    /**
+     * <p>Copies the content of this view's surface texture into the specified
+     * bitmap. If the surface texture is not available, the copy is not executed.
+     * The content of the surface texture will be scaled to fit exactly inside
+     * the specified bitmap.</p>
+     *
+     * <p><strong>Do not</strong> invoke this method from a drawing method
+     * ({@link #onDraw(android.graphics.Canvas)} for instance).</p>
+     *
+     * <p>If an error occurs, the bitmap is left unchanged.</p>
+     *
+     * @param bitmap The bitmap to copy the content of the surface texture into,
+     *               cannot be null, all configurations are supported
+     *
+     * @return The bitmap specified as a parameter
+     *
+     * @see #isAvailable()
+     * @see #getBitmap(int, int)
+     * @see #getBitmap()
+     *
+     * @throws IllegalStateException if the hardware rendering context cannot be
+     *         acquired to capture the bitmap
+     */
+    public @NonNull Bitmap getBitmap(@NonNull Bitmap bitmap) {
+        if (bitmap != null && isAvailable()) {
+            applyUpdate();
+            applyTransformMatrix();
+
+            // This case can happen if the app invokes setSurfaceTexture() before
+            // we are able to create the hardware layer. We can safely initialize
+            // the layer here thanks to the validate() call at the beginning of
+            // this method
+            if (mLayer == null && mUpdateSurface) {
+                getTextureLayer();
+            }
+
+            if (mLayer != null) {
+                mLayer.copyInto(bitmap);
+            }
+        }
+        return bitmap;
+    }
+
+    /**
+     * Returns true if the {@link SurfaceTexture} associated with this
+     * TextureView is available for rendering. When this method returns
+     * true, {@link #getSurfaceTexture()} returns a valid surface texture.
+     */
+    public boolean isAvailable() {
+        return mSurface != null;
+    }
+
+    /**
+     * <p>Start editing the pixels in the surface.  The returned Canvas can be used
+     * to draw into the surface's bitmap.  A null is returned if the surface has
+     * not been created or otherwise cannot be edited. You will usually need
+     * to implement
+     * {@link SurfaceTextureListener#onSurfaceTextureAvailable(android.graphics.SurfaceTexture, int, int)}
+     * to find out when the Surface is available for use.</p>
+     *
+     * <p>The content of the Surface is never preserved between unlockCanvas()
+     * and lockCanvas(), for this reason, every pixel within the Surface area
+     * must be written. The only exception to this rule is when a dirty
+     * rectangle is specified, in which case, non-dirty pixels will be
+     * preserved.</p>
+     *
+     * <p>This method can only be used if the underlying surface is not already
+     * owned by another producer. For instance, if the TextureView is being used
+     * to render the camera's preview you cannot invoke this method.</p>
+     *
+     * @return A Canvas used to draw into the surface, or null if the surface cannot be locked for
+     * drawing (see {@link #isAvailable()}).
+     *
+     * @see #lockCanvas(android.graphics.Rect)
+     * @see #unlockCanvasAndPost(android.graphics.Canvas)
+     */
+    public @Nullable Canvas lockCanvas() {
+        return lockCanvas(null);
+    }
+
+    /**
+     * Just like {@link #lockCanvas()} but allows specification of a dirty
+     * rectangle. Every pixel within that rectangle must be written; however
+     * pixels outside the dirty rectangle will be preserved by the next call
+     * to lockCanvas().
+     *
+     * This method can return null if the underlying surface texture is not
+     * available (see {@link #isAvailable()} or if the surface texture is
+     * already connected to an image producer (for instance: the camera,
+     * OpenGL, a media player, etc.)
+     *
+     * @param dirty Area of the surface that will be modified. If null the area of the entire
+     *              surface is used.
+
+     * @return A Canvas used to draw into the surface, or null if the surface cannot be locked for
+     * drawing (see {@link #isAvailable()}).
+     *
+     * @see #lockCanvas()
+     * @see #unlockCanvasAndPost(android.graphics.Canvas)
+     * @see #isAvailable()
+     */
+    public @Nullable Canvas lockCanvas(@Nullable Rect dirty) {
+        if (!isAvailable()) return null;
+
+        if (mCanvas == null) {
+            mCanvas = new Canvas();
+        }
+
+        synchronized (mNativeWindowLock) {
+            if (!nLockCanvas(mNativeWindow, mCanvas, dirty)) {
+                return null;
+            }
+        }
+        mSaveCount = mCanvas.save();
+
+        return mCanvas;
+    }
+
+    /**
+     * Finish editing pixels in the surface. After this call, the surface's
+     * current pixels will be shown on the screen, but its content is lost,
+     * in particular there is no guarantee that the content of the Surface
+     * will remain unchanged when lockCanvas() is called again.
+     *
+     * @param canvas The Canvas previously returned by lockCanvas()
+     *
+     * @see #lockCanvas()
+     * @see #lockCanvas(android.graphics.Rect)
+     */
+    public void unlockCanvasAndPost(@NonNull Canvas canvas) {
+        if (mCanvas != null && canvas == mCanvas) {
+            canvas.restoreToCount(mSaveCount);
+            mSaveCount = 0;
+
+            synchronized (mNativeWindowLock) {
+                nUnlockCanvasAndPost(mNativeWindow, mCanvas);
+            }
+        }
+    }
+
+    /**
+     * Returns the {@link SurfaceTexture} used by this view. This method
+     * may return null if the view is not attached to a window or if the surface
+     * texture has not been initialized yet.
+     *
+     * @see #isAvailable()
+     */
+    public @Nullable SurfaceTexture getSurfaceTexture() {
+        return mSurface;
+    }
+
+    /**
+     * Set the {@link SurfaceTexture} for this view to use. If a {@link
+     * SurfaceTexture} is already being used by this view, it is immediately
+     * released and not usable any more.  The {@link
+     * SurfaceTextureListener#onSurfaceTextureDestroyed} callback is <b>not</b>
+     * called for the previous {@link SurfaceTexture}.  Similarly, the {@link
+     * SurfaceTextureListener#onSurfaceTextureAvailable} callback is <b>not</b>
+     * called for the {@link SurfaceTexture} passed to setSurfaceTexture.
+     *
+     * The {@link SurfaceTexture} object must be detached from all OpenGL ES
+     * contexts prior to calling this method.
+     *
+     * @param surfaceTexture The {@link SurfaceTexture} that the view should use.
+     * @see SurfaceTexture#detachFromGLContext()
+     */
+    public void setSurfaceTexture(@NonNull SurfaceTexture surfaceTexture) {
+        if (surfaceTexture == null) {
+            throw new NullPointerException("surfaceTexture must not be null");
+        }
+        if (surfaceTexture == mSurface) {
+            throw new IllegalArgumentException("Trying to setSurfaceTexture to " +
+                    "the same SurfaceTexture that's already set.");
+        }
+        if (surfaceTexture.isReleased()) {
+            throw new IllegalArgumentException("Cannot setSurfaceTexture to a " +
+                    "released SurfaceTexture");
+        }
+        if (mSurface != null) {
+            nDestroyNativeWindow();
+            mSurface.release();
+        }
+        mSurface = surfaceTexture;
+        nCreateNativeWindow(mSurface);
+
+        /*
+         * If the view is visible and we already made a layer, update the
+         * listener in the new surface to use the existing listener in the view.
+         * Otherwise this will be called when the view becomes visible or the
+         * layer is created
+         */
+        if (((mViewFlags & VISIBILITY_MASK) == VISIBLE) && mLayer != null) {
+            mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler);
+        }
+        mUpdateSurface = true;
+        invalidateParentIfNeeded();
+    }
+
+    /**
+     * Returns the {@link SurfaceTextureListener} currently associated with this
+     * texture view.
+     *
+     * @see #setSurfaceTextureListener(android.view.TextureView.SurfaceTextureListener)
+     * @see SurfaceTextureListener
+     */
+    public @Nullable SurfaceTextureListener getSurfaceTextureListener() {
+        return mListener;
+    }
+
+    /**
+     * Sets the {@link SurfaceTextureListener} used to listen to surface
+     * texture events.
+     *
+     * @see #getSurfaceTextureListener()
+     * @see SurfaceTextureListener
+     */
+    public void setSurfaceTextureListener(@Nullable SurfaceTextureListener listener) {
+        mListener = listener;
+    }
+
+    @UnsupportedAppUsage
+    private final SurfaceTexture.OnFrameAvailableListener mUpdateListener =
+            surfaceTexture -> {
+                updateLayer();
+                invalidate();
+            };
+
+    /**
+     * This listener can be used to be notified when the surface texture
+     * associated with this texture view is available.
+     */
+    public interface SurfaceTextureListener {
+        /**
+         * Invoked when a {@link TextureView}'s SurfaceTexture is ready for use.
+         *
+         * @param surface The surface returned by
+         *                {@link android.view.TextureView#getSurfaceTexture()}
+         * @param width The width of the surface
+         * @param height The height of the surface
+         */
+        void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height);
+
+        /**
+         * Invoked when the {@link SurfaceTexture}'s buffers size changed.
+         *
+         * @param surface The surface returned by
+         *                {@link android.view.TextureView#getSurfaceTexture()}
+         * @param width The new width of the surface
+         * @param height The new height of the surface
+         */
+        void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height);
+
+        /**
+         * Invoked when the specified {@link SurfaceTexture} is about to be destroyed.
+         * If returns true, no rendering should happen inside the surface texture after this method
+         * is invoked. If returns false, the client needs to call {@link SurfaceTexture#release()}.
+         * Most applications should return true.
+         *
+         * @param surface The surface about to be destroyed
+         */
+        boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface);
+
+        /**
+         * Invoked when the specified {@link SurfaceTexture} is updated through
+         * {@link SurfaceTexture#updateTexImage()}.
+         *
+         * @param surface The surface just updated
+         */
+        void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface);
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private native void nCreateNativeWindow(SurfaceTexture surface);
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private native void nDestroyNativeWindow();
+
+    private static native boolean nLockCanvas(long nativeWindow, Canvas canvas, Rect dirty);
+    private static native void nUnlockCanvasAndPost(long nativeWindow, Canvas canvas);
+}
diff --git a/android/view/ThreadedRenderer.java b/android/view/ThreadedRenderer.java
new file mode 100644
index 0000000..d839e35
--- /dev/null
+++ b/android/view/ThreadedRenderer.java
@@ -0,0 +1,707 @@
+/*
+ * 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.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.FrameInfo;
+import android.graphics.HardwareRenderer;
+import android.graphics.Picture;
+import android.graphics.Point;
+import android.graphics.RecordingCanvas;
+import android.graphics.Rect;
+import android.graphics.RenderNode;
+import android.os.Trace;
+import android.util.Log;
+import android.view.Surface.OutOfResourcesException;
+import android.view.View.AttachInfo;
+import android.view.animation.AnimationUtils;
+
+import com.android.internal.R;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Threaded renderer that proxies the rendering to a render thread. Most calls
+ * are currently synchronous.
+ *
+ * The UI thread can block on the RenderThread, but RenderThread must never
+ * block on the UI thread.
+ *
+ * ThreadedRenderer creates an instance of RenderProxy. RenderProxy in turn creates
+ * and manages a CanvasContext on the RenderThread. The CanvasContext is fully managed
+ * by the lifecycle of the RenderProxy.
+ *
+ * Note that although currently the EGL context & surfaces are created & managed
+ * by the render thread, the goal is to move that into a shared structure that can
+ * be managed by both threads. EGLSurface creation & deletion should ideally be
+ * done on the UI thread and not the RenderThread to avoid stalling the
+ * RenderThread with surface buffer allocation.
+ *
+ * @hide
+ */
+public final class ThreadedRenderer extends HardwareRenderer {
+    /**
+     * System property used to enable or disable threaded rendering profiling.
+     * The default value of this property is assumed to be false.
+     *
+     * When profiling is enabled, the adb shell dumpsys gfxinfo command will
+     * output extra information about the time taken to execute by the last
+     * frames.
+     *
+     * Possible values:
+     * "true", to enable profiling
+     * "visual_bars", to enable profiling and visualize the results on screen
+     * "false", to disable profiling
+     *
+     * @see #PROFILE_PROPERTY_VISUALIZE_BARS
+     *
+     * @hide
+     */
+    public static final String PROFILE_PROPERTY = "debug.hwui.profile";
+
+    /**
+     * Value for {@link #PROFILE_PROPERTY}. When the property is set to this
+     * value, profiling data will be visualized on screen as a bar chart.
+     *
+     * @hide
+     */
+    public static final String PROFILE_PROPERTY_VISUALIZE_BARS = "visual_bars";
+
+    /**
+     * System property used to specify the number of frames to be used
+     * when doing threaded rendering profiling.
+     * The default value of this property is #PROFILE_MAX_FRAMES.
+     *
+     * When profiling is enabled, the adb shell dumpsys gfxinfo command will
+     * output extra information about the time taken to execute by the last
+     * frames.
+     *
+     * Possible values:
+     * "60", to set the limit of frames to 60
+     */
+    static final String PROFILE_MAXFRAMES_PROPERTY = "debug.hwui.profile.maxframes";
+
+    /**
+     * System property used to debug EGL configuration choice.
+     *
+     * Possible values:
+     * "choice", print the chosen configuration only
+     * "all", print all possible configurations
+     */
+    static final String PRINT_CONFIG_PROPERTY = "debug.hwui.print_config";
+
+    /**
+     * Turn on to draw dirty regions every other frame.
+     *
+     * Possible values:
+     * "true", to enable dirty regions debugging
+     * "false", to disable dirty regions debugging
+     *
+     * @hide
+     */
+    public static final String DEBUG_DIRTY_REGIONS_PROPERTY = "debug.hwui.show_dirty_regions";
+
+    /**
+     * Turn on to flash hardware layers when they update.
+     *
+     * Possible values:
+     * "true", to enable hardware layers updates debugging
+     * "false", to disable hardware layers updates debugging
+     *
+     * @hide
+     */
+    public static final String DEBUG_SHOW_LAYERS_UPDATES_PROPERTY =
+            "debug.hwui.show_layers_updates";
+
+    /**
+     * Controls overdraw debugging.
+     *
+     * Possible values:
+     * "false", to disable overdraw debugging
+     * "show", to show overdraw areas on screen
+     * "count", to display an overdraw counter
+     *
+     * @hide
+     */
+    public static final String DEBUG_OVERDRAW_PROPERTY = "debug.hwui.overdraw";
+
+    /**
+     * Value for {@link #DEBUG_OVERDRAW_PROPERTY}. When the property is set to this
+     * value, overdraw will be shown on screen by coloring pixels.
+     *
+     * @hide
+     */
+    public static final String OVERDRAW_PROPERTY_SHOW = "show";
+
+    /**
+     * Turn on to debug non-rectangular clip operations.
+     *
+     * Possible values:
+     * "hide", to disable this debug mode
+     * "highlight", highlight drawing commands tested against a non-rectangular clip
+     * "stencil", renders the clip region on screen when set
+     *
+     * @hide
+     */
+    public static final String DEBUG_SHOW_NON_RECTANGULAR_CLIP_PROPERTY =
+            "debug.hwui.show_non_rect_clip";
+
+    /**
+     * Sets the FPS devisor to lower the FPS.
+     *
+     * Sets a positive integer as a divisor. 1 (the default value) menas the full FPS, and 2
+     * means half the full FPS.
+     *
+     *
+     * @hide
+     */
+    public static final String DEBUG_FPS_DIVISOR = "debug.hwui.fps_divisor";
+
+    /**
+     * Forces smart-dark to be always on.
+     * @hide
+     */
+    public static final String DEBUG_FORCE_DARK = "debug.hwui.force_dark";
+
+    public static int EGL_CONTEXT_PRIORITY_HIGH_IMG = 0x3101;
+    public static int EGL_CONTEXT_PRIORITY_MEDIUM_IMG = 0x3102;
+    public static int EGL_CONTEXT_PRIORITY_LOW_IMG = 0x3103;
+
+    /**
+     * Further threaded renderer disabling for the system process.
+     *
+     * @hide
+     */
+    public static boolean sRendererEnabled = true;
+
+    public static boolean sTrimForeground = false;
+
+    /**
+     * Controls whether or not the renderer should aggressively trim
+     * memory. Note that this must not be set for any process that uses
+     * WebView! This should be only used by system_process or similar
+     * that do not go into the background.
+     */
+    public static void enableForegroundTrimming() {
+        sTrimForeground = true;
+    }
+
+    /**
+     * Initialize HWUI for being in a system process like system_server
+     * Should not be called in non-system processes
+     */
+    public static void initForSystemProcess() {
+        // The system process on low-memory devices do not get to use hardware
+        // accelerated drawing, since this can add too much overhead to the
+        // process.
+        if (!ActivityManager.isHighEndGfx()) {
+            sRendererEnabled = false;
+        } else {
+            enableForegroundTrimming();
+        }
+    }
+
+    /**
+     * Creates a threaded renderer using OpenGL.
+     *
+     * @param translucent True if the surface is translucent, false otherwise
+     *
+     * @return A threaded renderer backed by OpenGL.
+     */
+    public static ThreadedRenderer create(Context context, boolean translucent, String name) {
+        return new ThreadedRenderer(context, translucent, name);
+    }
+
+    private static final String[] VISUALIZERS = {
+        PROFILE_PROPERTY_VISUALIZE_BARS,
+    };
+
+    // Size of the rendered content.
+    private int mWidth, mHeight;
+
+    // Actual size of the drawing surface.
+    private int mSurfaceWidth, mSurfaceHeight;
+
+    // Insets between the drawing surface and rendered content. These are
+    // applied as translation when updating the root render node.
+    private int mInsetTop, mInsetLeft;
+
+    // Light properties specified by the theme.
+    private final float mLightY;
+    private final float mLightZ;
+    private final float mLightRadius;
+
+    private boolean mInitialized = false;
+    private boolean mRootNodeNeedsUpdate;
+
+    private boolean mEnabled;
+    private boolean mRequested = true;
+
+    @Nullable
+    private ArrayList<FrameDrawingCallback> mNextRtFrameCallbacks;
+
+    ThreadedRenderer(Context context, boolean translucent, String name) {
+        super();
+        setName(name);
+        setOpaque(!translucent);
+
+        final TypedArray a = context.obtainStyledAttributes(null, R.styleable.Lighting, 0, 0);
+        mLightY = a.getDimension(R.styleable.Lighting_lightY, 0);
+        mLightZ = a.getDimension(R.styleable.Lighting_lightZ, 0);
+        mLightRadius = a.getDimension(R.styleable.Lighting_lightRadius, 0);
+        float ambientShadowAlpha = a.getFloat(R.styleable.Lighting_ambientShadowAlpha, 0);
+        float spotShadowAlpha = a.getFloat(R.styleable.Lighting_spotShadowAlpha, 0);
+        a.recycle();
+        setLightSourceAlpha(ambientShadowAlpha, spotShadowAlpha);
+    }
+
+    @Override
+    public void destroy() {
+        mInitialized = false;
+        updateEnabledState(null);
+        super.destroy();
+    }
+
+    /**
+     * Indicates whether threaded rendering is currently enabled.
+     *
+     * @return True if threaded rendering  is in use, false otherwise.
+     */
+    boolean isEnabled() {
+        return mEnabled;
+    }
+
+    /**
+     * Indicates whether threaded rendering  is currently enabled.
+     *
+     * @param enabled True if the threaded renderer is in use, false otherwise.
+     */
+    void setEnabled(boolean enabled) {
+        mEnabled = enabled;
+    }
+
+    /**
+     * Indicates whether threaded rendering is currently request but not
+     * necessarily enabled yet.
+     *
+     * @return True if requested, false otherwise.
+     */
+    boolean isRequested() {
+        return mRequested;
+    }
+
+    /**
+     * Indicates whether threaded rendering is currently requested but not
+     * necessarily enabled yet.
+     */
+    void setRequested(boolean requested) {
+        mRequested = requested;
+    }
+
+    private void updateEnabledState(Surface surface) {
+        if (surface == null || !surface.isValid()) {
+            setEnabled(false);
+        } else {
+            setEnabled(mInitialized);
+        }
+    }
+
+    /**
+     * Initializes the threaded renderer for the specified surface.
+     *
+     * @param surface The surface to render
+     *
+     * @return True if the initialization was successful, false otherwise.
+     */
+    boolean initialize(Surface surface) throws OutOfResourcesException {
+        boolean status = !mInitialized;
+        mInitialized = true;
+        updateEnabledState(surface);
+        setSurface(surface);
+        return status;
+    }
+
+    /**
+     * Initializes the threaded renderer for the specified surface and setup the
+     * renderer for drawing, if needed. This is invoked when the ViewAncestor has
+     * potentially lost the threaded renderer. The threaded renderer should be
+     * reinitialized and setup when the render {@link #isRequested()} and
+     * {@link #isEnabled()}.
+     *
+     * @param width The width of the drawing surface.
+     * @param height The height of the drawing surface.
+     * @param attachInfo Information about the window.
+     * @param surface The surface to render
+     * @param surfaceInsets The drawing surface insets to apply
+     *
+     * @return true if the surface was initialized, false otherwise. Returning
+     *         false might mean that the surface was already initialized.
+     */
+    boolean initializeIfNeeded(int width, int height, View.AttachInfo attachInfo,
+            Surface surface, Rect surfaceInsets) throws OutOfResourcesException {
+        if (isRequested()) {
+            // We lost the gl context, so recreate it.
+            if (!isEnabled()) {
+                if (initialize(surface)) {
+                    setup(width, height, attachInfo, surfaceInsets);
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Updates the threaded renderer for the specified surface.
+     *
+     * @param surface The surface to render
+     */
+    void updateSurface(Surface surface) throws OutOfResourcesException {
+        updateEnabledState(surface);
+        setSurface(surface);
+    }
+
+    @Override
+    public void setSurface(Surface surface) {
+        // TODO: Do we ever pass a non-null but isValid() = false surface?
+        // This is here to be super conservative for ViewRootImpl
+        if (surface != null && surface.isValid()) {
+            super.setSurface(surface);
+        } else {
+            super.setSurface(null);
+        }
+    }
+
+    /**
+     * Registers a callback to be executed when the next frame is being drawn on RenderThread. This
+     * callback will be executed on a RenderThread worker thread, and only used for the next frame
+     * and thus it will only fire once.
+     *
+     * @param callback The callback to register.
+     */
+    void registerRtFrameCallback(@NonNull FrameDrawingCallback callback) {
+        if (mNextRtFrameCallbacks == null) {
+            mNextRtFrameCallbacks = new ArrayList<>();
+        }
+        mNextRtFrameCallbacks.add(callback);
+    }
+
+    /**
+     * Destroys all hardware rendering resources associated with the specified
+     * view hierarchy.
+     *
+     * @param view The root of the view hierarchy
+     */
+    void destroyHardwareResources(View view) {
+        destroyResources(view);
+        clearContent();
+    }
+
+    private static void destroyResources(View view) {
+        view.destroyHardwareResources();
+    }
+
+    /**
+     * Sets up the renderer for drawing.
+     *
+     * @param width The width of the drawing surface.
+     * @param height The height of the drawing surface.
+     * @param attachInfo Information about the window.
+     * @param surfaceInsets The drawing surface insets to apply
+     */
+    void setup(int width, int height, AttachInfo attachInfo, Rect surfaceInsets) {
+        mWidth = width;
+        mHeight = height;
+
+        if (surfaceInsets != null && (surfaceInsets.left != 0 || surfaceInsets.right != 0
+                || surfaceInsets.top != 0 || surfaceInsets.bottom != 0)) {
+            mInsetLeft = surfaceInsets.left;
+            mInsetTop = surfaceInsets.top;
+            mSurfaceWidth = width + mInsetLeft + surfaceInsets.right;
+            mSurfaceHeight = height + mInsetTop + surfaceInsets.bottom;
+
+            // If the surface has insets, it can't be opaque.
+            setOpaque(false);
+        } else {
+            mInsetLeft = 0;
+            mInsetTop = 0;
+            mSurfaceWidth = width;
+            mSurfaceHeight = height;
+        }
+
+        mRootNode.setLeftTopRightBottom(-mInsetLeft, -mInsetTop, mSurfaceWidth, mSurfaceHeight);
+
+        setLightCenter(attachInfo);
+    }
+
+    /**
+     * Updates the light position based on the position of the window.
+     *
+     * @param attachInfo Information about the window.
+     */
+    void setLightCenter(AttachInfo attachInfo) {
+        // Adjust light position for window offsets.
+        final Point displaySize = attachInfo.mPoint;
+        attachInfo.mDisplay.getRealSize(displaySize);
+        final float lightX = displaySize.x / 2f - attachInfo.mWindowLeft;
+        final float lightY = mLightY - attachInfo.mWindowTop;
+        setLightSourceGeometry(lightX, lightY, mLightZ, mLightRadius);
+    }
+
+    /**
+     * Gets the current width of the surface. This is the width that the surface
+     * was last set to in a call to {@link #setup(int, int, View.AttachInfo, Rect)}.
+     *
+     * @return the current width of the surface
+     */
+    int getWidth() {
+        return mWidth;
+    }
+
+    /**
+     * Gets the current height of the surface. This is the height that the surface
+     * was last set to in a call to {@link #setup(int, int, View.AttachInfo, Rect)}.
+     *
+     * @return the current width of the surface
+     */
+    int getHeight() {
+        return mHeight;
+    }
+
+    /**
+     * Outputs extra debugging information in the specified file descriptor.
+     */
+    void dumpGfxInfo(PrintWriter pw, FileDescriptor fd, String[] args) {
+        pw.flush();
+        // If there's no arguments, eg 'dumpsys gfxinfo', then dump everything.
+        // If there's a targetted package, eg 'dumpsys gfxinfo com.android.systemui', then only
+        // dump the summary information
+        int flags = (args == null || args.length == 0) ? FLAG_DUMP_ALL : 0;
+        for (int i = 0; i < args.length; i++) {
+            switch (args[i]) {
+                case "framestats":
+                    flags |= FLAG_DUMP_FRAMESTATS;
+                    break;
+                case "reset":
+                    flags |= FLAG_DUMP_RESET;
+                    break;
+                case "-a": // magic option passed when dumping a bugreport.
+                    flags = FLAG_DUMP_ALL;
+                    break;
+            }
+        }
+        dumpProfileInfo(fd, flags);
+    }
+
+    Picture captureRenderingCommands() {
+        return null;
+    }
+
+    @Override
+    public boolean loadSystemProperties() {
+        boolean changed = super.loadSystemProperties();
+        if (changed) {
+            invalidateRoot();
+        }
+        return changed;
+    }
+
+    private void updateViewTreeDisplayList(View view) {
+        view.mPrivateFlags |= View.PFLAG_DRAWN;
+        view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
+                == View.PFLAG_INVALIDATED;
+        view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
+        view.updateDisplayListIfDirty();
+        view.mRecreateDisplayList = false;
+    }
+
+    private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
+        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Record View#draw()");
+        updateViewTreeDisplayList(view);
+
+        // Consume and set the frame callback after we dispatch draw to the view above, but before
+        // onPostDraw below which may reset the callback for the next frame.  This ensures that
+        // updates to the frame callback during scroll handling will also apply in this frame.
+        if (mNextRtFrameCallbacks != null) {
+            final ArrayList<FrameDrawingCallback> frameCallbacks = mNextRtFrameCallbacks;
+            mNextRtFrameCallbacks = null;
+            setFrameCallback(frame -> {
+                for (int i = 0; i < frameCallbacks.size(); ++i) {
+                    frameCallbacks.get(i).onFrameDraw(frame);
+                }
+            });
+        }
+
+        if (mRootNodeNeedsUpdate || !mRootNode.hasDisplayList()) {
+            RecordingCanvas canvas = mRootNode.beginRecording(mSurfaceWidth, mSurfaceHeight);
+            try {
+                final int saveCount = canvas.save();
+                canvas.translate(mInsetLeft, mInsetTop);
+                callbacks.onPreDraw(canvas);
+
+                canvas.enableZ();
+                canvas.drawRenderNode(view.updateDisplayListIfDirty());
+                canvas.disableZ();
+
+                callbacks.onPostDraw(canvas);
+                canvas.restoreToCount(saveCount);
+                mRootNodeNeedsUpdate = false;
+            } finally {
+                mRootNode.endRecording();
+            }
+        }
+        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+    }
+
+    /**
+     * Interface used to receive callbacks whenever a view is drawn by
+     * a threaded renderer instance.
+     */
+    interface DrawCallbacks {
+        /**
+         * Invoked before a view is drawn by a threaded renderer.
+         * This method can be used to apply transformations to the
+         * canvas but no drawing command should be issued.
+         *
+         * @param canvas The Canvas used to render the view.
+         */
+        void onPreDraw(RecordingCanvas canvas);
+
+        /**
+         * Invoked after a view is drawn by a threaded renderer.
+         * It is safe to invoke drawing commands from this method.
+         *
+         * @param canvas The Canvas used to render the view.
+         */
+        void onPostDraw(RecordingCanvas canvas);
+    }
+
+    /**
+     *  Indicates that the content drawn by DrawCallbacks needs to
+     *  be updated, which will be done by the next call to draw()
+     */
+    void invalidateRoot() {
+        mRootNodeNeedsUpdate = true;
+    }
+
+    /**
+     * Draws the specified view.
+     *
+     * @param view The view to draw.
+     * @param attachInfo AttachInfo tied to the specified view.
+     */
+    void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
+        attachInfo.mViewRootImpl.mViewFrameInfo.markDrawStart();
+
+        updateRootDisplayList(view, callbacks);
+
+        // register animating rendernodes which started animating prior to renderer
+        // creation, which is typical for animators started prior to first draw
+        if (attachInfo.mPendingAnimatingRenderNodes != null) {
+            final int count = attachInfo.mPendingAnimatingRenderNodes.size();
+            for (int i = 0; i < count; i++) {
+                registerAnimatingRenderNode(
+                        attachInfo.mPendingAnimatingRenderNodes.get(i));
+            }
+            attachInfo.mPendingAnimatingRenderNodes.clear();
+            // We don't need this anymore as subsequent calls to
+            // ViewRootImpl#attachRenderNodeAnimator will go directly to us.
+            attachInfo.mPendingAnimatingRenderNodes = null;
+        }
+
+        final FrameInfo frameInfo = attachInfo.mViewRootImpl.getUpdatedFrameInfo();
+
+        int syncResult = syncAndDrawFrame(frameInfo);
+        if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {
+            Log.w("OpenGLRenderer", "Surface lost, forcing relayout");
+            // We lost our surface. For a relayout next frame which should give us a new
+            // surface from WindowManager, which hopefully will work.
+            attachInfo.mViewRootImpl.mForceNextWindowRelayout = true;
+            attachInfo.mViewRootImpl.requestLayout();
+        }
+        if ((syncResult & SYNC_REDRAW_REQUESTED) != 0) {
+            attachInfo.mViewRootImpl.invalidate();
+        }
+    }
+
+    /** The root of everything */
+    public @NonNull RenderNode getRootNode() {
+        return mRootNode;
+    }
+
+    /**
+     * Basic synchronous renderer. Currently only used to render the Magnifier, so use with care.
+     * TODO: deduplicate against ThreadedRenderer.
+     *
+     * @hide
+     */
+    public static class SimpleRenderer extends HardwareRenderer {
+        private final float mLightY, mLightZ, mLightRadius;
+
+        public SimpleRenderer(final Context context, final String name, final Surface surface) {
+            super();
+            setName(name);
+            setOpaque(false);
+            setSurface(surface);
+            final TypedArray a = context.obtainStyledAttributes(null, R.styleable.Lighting, 0, 0);
+            mLightY = a.getDimension(R.styleable.Lighting_lightY, 0);
+            mLightZ = a.getDimension(R.styleable.Lighting_lightZ, 0);
+            mLightRadius = a.getDimension(R.styleable.Lighting_lightRadius, 0);
+            final float ambientShadowAlpha = a.getFloat(R.styleable.Lighting_ambientShadowAlpha, 0);
+            final float spotShadowAlpha = a.getFloat(R.styleable.Lighting_spotShadowAlpha, 0);
+            a.recycle();
+            setLightSourceAlpha(ambientShadowAlpha, spotShadowAlpha);
+        }
+
+        /**
+         * Set the light center.
+         */
+        public void setLightCenter(final Display display,
+                final int windowLeft, final int windowTop) {
+            // Adjust light position for window offsets.
+            final Point displaySize = new Point();
+            display.getRealSize(displaySize);
+            final float lightX = displaySize.x / 2f - windowLeft;
+            final float lightY = mLightY - windowTop;
+
+            setLightSourceGeometry(lightX, lightY, mLightZ, mLightRadius);
+        }
+
+        public RenderNode getRootNode() {
+            return mRootNode;
+        }
+
+        /**
+         * Draw the surface.
+         */
+        public void draw(final FrameDrawingCallback callback) {
+            final long vsync = AnimationUtils.currentAnimationTimeMillis() * 1000000L;
+            if (callback != null) {
+                setFrameCallback(callback);
+            }
+            createRenderRequest()
+                    .setVsyncTime(vsync)
+                    .syncAndDraw();
+        }
+    }
+}
diff --git a/android/view/TouchDelegate.java b/android/view/TouchDelegate.java
new file mode 100644
index 0000000..de0f9e5
--- /dev/null
+++ b/android/view/TouchDelegate.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.util.ArrayMap;
+import android.view.accessibility.AccessibilityNodeInfo.TouchDelegateInfo;
+
+/**
+ * Helper class to handle situations where you want a view to have a larger touch area than its
+ * actual view bounds. The view whose touch area is changed is called the delegate view. This
+ * class should be used by an ancestor of the delegate. To use a TouchDelegate, first create an
+ * instance that specifies the bounds that should be mapped to the delegate and the delegate
+ * view itself.
+ * <p>
+ * The ancestor should then forward all of its touch events received in its
+ * {@link android.view.View#onTouchEvent(MotionEvent)} to {@link #onTouchEvent(MotionEvent)}.
+ * </p>
+ */
+public class TouchDelegate {
+
+    /**
+     * View that should receive forwarded touch events
+     */
+    private View mDelegateView;
+
+    /**
+     * Bounds in local coordinates of the containing view that should be mapped to the delegate
+     * view. This rect is used for initial hit testing.
+     */
+    private Rect mBounds;
+
+    /**
+     * mBounds inflated to include some slop. This rect is to track whether the motion events
+     * should be considered to be within the delegate view.
+     */
+    private Rect mSlopBounds;
+
+    /**
+     * True if the delegate had been targeted on a down event (intersected mBounds).
+     */
+    @UnsupportedAppUsage
+    private boolean mDelegateTargeted;
+
+    /**
+     * The touchable region of the View extends above its actual extent.
+     */
+    public static final int ABOVE = 1;
+
+    /**
+     * The touchable region of the View extends below its actual extent.
+     */
+    public static final int BELOW = 2;
+
+    /**
+     * The touchable region of the View extends to the left of its actual extent.
+     */
+    public static final int TO_LEFT = 4;
+
+    /**
+     * The touchable region of the View extends to the right of its actual extent.
+     */
+    public static final int TO_RIGHT = 8;
+
+    private int mSlop;
+
+    /**
+     * Touch delegate information for accessibility
+     */
+    private TouchDelegateInfo mTouchDelegateInfo;
+
+    /**
+     * Constructor
+     *
+     * @param bounds Bounds in local coordinates of the containing view that should be mapped to
+     *        the delegate view
+     * @param delegateView The view that should receive motion events
+     */
+    public TouchDelegate(Rect bounds, View delegateView) {
+        mBounds = bounds;
+
+        mSlop = ViewConfiguration.get(delegateView.getContext()).getScaledTouchSlop();
+        mSlopBounds = new Rect(bounds);
+        mSlopBounds.inset(-mSlop, -mSlop);
+        mDelegateView = delegateView;
+    }
+
+    /**
+     * Forward touch events to the delegate view if the event is within the bounds
+     * specified in the constructor.
+     *
+     * @param event The touch event to forward
+     * @return True if the event was consumed by the delegate, false otherwise.
+     */
+    public boolean onTouchEvent(@NonNull MotionEvent event) {
+        int x = (int)event.getX();
+        int y = (int)event.getY();
+        boolean sendToDelegate = false;
+        boolean hit = true;
+        boolean handled = false;
+
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                mDelegateTargeted = mBounds.contains(x, y);
+                sendToDelegate = mDelegateTargeted;
+                break;
+            case MotionEvent.ACTION_POINTER_DOWN:
+            case MotionEvent.ACTION_POINTER_UP:
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_MOVE:
+                sendToDelegate = mDelegateTargeted;
+                if (sendToDelegate) {
+                    Rect slopBounds = mSlopBounds;
+                    if (!slopBounds.contains(x, y)) {
+                        hit = false;
+                    }
+                }
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                sendToDelegate = mDelegateTargeted;
+                mDelegateTargeted = false;
+                break;
+        }
+        if (sendToDelegate) {
+            if (hit) {
+                // Offset event coordinates to be inside the target view
+                event.setLocation(mDelegateView.getWidth() / 2, mDelegateView.getHeight() / 2);
+            } else {
+                // Offset event coordinates to be outside the target view (in case it does
+                // something like tracking pressed state)
+                int slop = mSlop;
+                event.setLocation(-(slop * 2), -(slop * 2));
+            }
+            handled = mDelegateView.dispatchTouchEvent(event);
+        }
+        return handled;
+    }
+
+    /**
+     * Forward hover events to the delegate view if the event is within the bounds
+     * specified in the constructor and touch exploration is enabled.
+     *
+     * <p>This method is provided for accessibility purposes so touch exploration, which is
+     * commonly used by screen readers, can properly place accessibility focus on views that
+     * use touch delegates. Therefore, touch exploration must be enabled for hover events
+     * to be dispatched through the delegate.</p>
+     *
+     * @param event The hover event to forward
+     * @return True if the event was consumed by the delegate, false otherwise.
+     *
+     * @see android.view.accessibility.AccessibilityManager#isTouchExplorationEnabled
+     */
+    public boolean onTouchExplorationHoverEvent(@NonNull MotionEvent event) {
+        if (mBounds == null) {
+            return false;
+        }
+
+        final int x = (int) event.getX();
+        final int y = (int) event.getY();
+        boolean hit = true;
+        boolean handled = false;
+
+        final boolean isInbound = mBounds.contains(x, y);
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_HOVER_ENTER:
+                mDelegateTargeted = isInbound;
+                break;
+            case MotionEvent.ACTION_HOVER_MOVE:
+                if (isInbound) {
+                    mDelegateTargeted = true;
+                } else {
+                    // delegated previously
+                    if (mDelegateTargeted && !mSlopBounds.contains(x, y)) {
+                        hit = false;
+                    }
+                }
+                break;
+            case MotionEvent.ACTION_HOVER_EXIT:
+                mDelegateTargeted = true;
+                break;
+        }
+        if (mDelegateTargeted) {
+            if (hit) {
+                event.setLocation(mDelegateView.getWidth() / 2, mDelegateView.getHeight() / 2);
+            } else {
+                mDelegateTargeted = false;
+            }
+            handled = mDelegateView.dispatchHoverEvent(event);
+        }
+        return handled;
+    }
+
+    /**
+     * Return a {@link TouchDelegateInfo} mapping from regions (in view coordinates) to
+     * delegated views for accessibility usage.
+     *
+     * @return A TouchDelegateInfo.
+     */
+    @NonNull
+    public TouchDelegateInfo getTouchDelegateInfo() {
+        if (mTouchDelegateInfo == null) {
+            final ArrayMap<Region, View> targetMap = new ArrayMap<>(1);
+            Rect bounds = mBounds;
+            if (bounds == null) {
+                bounds = new Rect();
+            }
+            targetMap.put(new Region(bounds), mDelegateView);
+            mTouchDelegateInfo = new TouchDelegateInfo(targetMap);
+        }
+        return mTouchDelegateInfo;
+    }
+}
diff --git a/android/view/TunnelModeEnabledListener.java b/android/view/TunnelModeEnabledListener.java
new file mode 100644
index 0000000..c158da9
--- /dev/null
+++ b/android/view/TunnelModeEnabledListener.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Listens for tunnel mode enabled/disabled events from SurfaceFlinger.
+ * {@hide}
+ */
+public abstract class TunnelModeEnabledListener {
+
+    private long mNativeListener;
+    private final Executor mExecutor;
+
+    public TunnelModeEnabledListener(Executor executor) {
+        mExecutor = executor;
+        mNativeListener = nativeCreate(this);
+    }
+
+    /**
+     * Destroys the listener.
+     */
+    public void destroy() {
+        if (mNativeListener == 0) {
+            return;
+        }
+        unregister(this);
+        nativeDestroy(mNativeListener);
+        mNativeListener = 0;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            destroy();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Reports when tunnel mode has been enabled/disabled.
+     */
+    public abstract void onTunnelModeEnabledChanged(boolean tunnelModeEnabled);
+
+    /**
+     * Registers a listener.
+     */
+    public static void register(TunnelModeEnabledListener listener) {
+        if (listener.mNativeListener == 0) {
+            return;
+        }
+        nativeRegister(listener.mNativeListener);
+    }
+
+    /**
+     * Unregisters a listener.
+     */
+    public static void unregister(TunnelModeEnabledListener listener) {
+        if (listener.mNativeListener == 0) {
+            return;
+        }
+        nativeUnregister(listener.mNativeListener);
+    }
+
+    /**
+     * Dispatch tunnel mode enabled.
+     *
+     * Called from native code on a binder thread.
+     */
+    @VisibleForTesting
+    public static void dispatchOnTunnelModeEnabledChanged(TunnelModeEnabledListener listener,
+            boolean tunnelModeEnabled) {
+        listener.mExecutor.execute(() -> listener.onTunnelModeEnabledChanged(tunnelModeEnabled));
+    }
+
+    private static native long nativeCreate(TunnelModeEnabledListener thiz);
+    private static native void nativeDestroy(long ptr);
+    private static native void nativeRegister(long ptr);
+    private static native void nativeUnregister(long ptr);
+}
diff --git a/android/view/VelocityTracker.java b/android/view/VelocityTracker.java
new file mode 100644
index 0000000..e1c4305
--- /dev/null
+++ b/android/view/VelocityTracker.java
@@ -0,0 +1,507 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.IntDef;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.SystemProperties;
+import android.util.ArrayMap;
+import android.util.Pools.SynchronizedPool;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Map;
+
+/**
+ * Helper for tracking the velocity of touch events, for implementing
+ * flinging and other such gestures.
+ *
+ * Use {@link #obtain} to retrieve a new instance of the class when you are going
+ * to begin tracking.  Put the motion events you receive into it with
+ * {@link #addMovement(MotionEvent)}.  When you want to determine the velocity call
+ * {@link #computeCurrentVelocity(int)} and then call {@link #getXVelocity(int)}
+ * and {@link #getYVelocity(int)} to retrieve the velocity for each pointer id.
+ */
+public final class VelocityTracker {
+    private static final SynchronizedPool<VelocityTracker> sPool =
+            new SynchronizedPool<VelocityTracker>(2);
+
+    private static final int ACTIVE_POINTER_ID = -1;
+
+    /**
+     * Velocity Tracker Strategy: Invalid.
+     *
+     * @hide
+     */
+    public static final int VELOCITY_TRACKER_STRATEGY_DEFAULT = -1;
+
+    /**
+     * Velocity Tracker Strategy: Impulse.
+     * Physical model of pushing an object.  Quality: VERY GOOD.
+     * Works with duplicate coordinates, unclean finger liftoff.
+     *
+     * @hide
+     */
+    public static final int VELOCITY_TRACKER_STRATEGY_IMPULSE = 0;
+
+    /**
+     * Velocity Tracker Strategy: LSQ1.
+     * 1st order least squares.  Quality: POOR.
+     * Frequently underfits the touch data especially when the finger accelerates
+     * or changes direction.  Often underestimates velocity.  The direction
+     * is overly influenced by historical touch points.
+     *
+     * @hide
+     */
+    public static final int VELOCITY_TRACKER_STRATEGY_LSQ1 = 1;
+
+    /**
+     * Velocity Tracker Strategy: LSQ2.
+     * 2nd order least squares.  Quality: VERY GOOD.
+     * Pretty much ideal, but can be confused by certain kinds of touch data,
+     * particularly if the panel has a tendency to generate delayed,
+     * duplicate or jittery touch coordinates when the finger is released.
+     *
+     * @hide
+     */
+    public static final int VELOCITY_TRACKER_STRATEGY_LSQ2 = 2;
+
+    /**
+     * Velocity Tracker Strategy: LSQ3.
+     * 3rd order least squares.  Quality: UNUSABLE.
+     * Frequently overfits the touch data yielding wildly divergent estimates
+     * of the velocity when the finger is released.
+     *
+     * @hide
+     */
+    public static final int VELOCITY_TRACKER_STRATEGY_LSQ3 = 3;
+
+    /**
+     * Velocity Tracker Strategy: WLSQ2_DELTA.
+     * 2nd order weighted least squares, delta weighting.  Quality: EXPERIMENTAL
+     *
+     * @hide
+     */
+    public static final int VELOCITY_TRACKER_STRATEGY_WLSQ2_DELTA = 4;
+
+    /**
+     * Velocity Tracker Strategy: WLSQ2_CENTRAL.
+     * 2nd order weighted least squares, central weighting.  Quality: EXPERIMENTAL
+     *
+     * @hide
+     */
+    public static final int VELOCITY_TRACKER_STRATEGY_WLSQ2_CENTRAL = 5;
+
+    /**
+     * Velocity Tracker Strategy: WLSQ2_RECENT.
+     * 2nd order weighted least squares, recent weighting.  Quality: EXPERIMENTAL
+     *
+     * @hide
+     */
+    public static final int VELOCITY_TRACKER_STRATEGY_WLSQ2_RECENT = 6;
+
+    /**
+     * Velocity Tracker Strategy: INT1.
+     * 1st order integrating filter.  Quality: GOOD.
+     * Not as good as 'lsq2' because it cannot estimate acceleration but it is
+     * more tolerant of errors.  Like 'lsq1', this strategy tends to underestimate
+     * the velocity of a fling but this strategy tends to respond to changes in
+     * direction more quickly and accurately.
+     *
+     * @hide
+     */
+    public static final int VELOCITY_TRACKER_STRATEGY_INT1 = 7;
+
+    /**
+     * Velocity Tracker Strategy: INT2.
+     * 2nd order integrating filter.  Quality: EXPERIMENTAL.
+     * For comparison purposes only.  Unlike 'int1' this strategy can compensate
+     * for acceleration but it typically overestimates the effect.
+     *
+     * @hide
+     */
+    public static final int VELOCITY_TRACKER_STRATEGY_INT2 = 8;
+
+    /**
+     * Velocity Tracker Strategy: Legacy.
+     * Legacy velocity tracker algorithm.  Quality: POOR.
+     * For comparison purposes only.  This algorithm is strongly influenced by
+     * old data points, consistently underestimates velocity and takes a very long
+     * time to adjust to changes in direction.
+     *
+     * @hide
+     */
+    public static final int VELOCITY_TRACKER_STRATEGY_LEGACY = 9;
+
+
+    /**
+     * Velocity Tracker Strategy look up table.
+     */
+    private static final Map<String, Integer> STRATEGIES = new ArrayMap<>();
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"VELOCITY_TRACKER_STRATEGY_"}, value = {
+            VELOCITY_TRACKER_STRATEGY_DEFAULT,
+            VELOCITY_TRACKER_STRATEGY_IMPULSE,
+            VELOCITY_TRACKER_STRATEGY_LSQ1,
+            VELOCITY_TRACKER_STRATEGY_LSQ2,
+            VELOCITY_TRACKER_STRATEGY_LSQ3,
+            VELOCITY_TRACKER_STRATEGY_WLSQ2_DELTA,
+            VELOCITY_TRACKER_STRATEGY_WLSQ2_CENTRAL,
+            VELOCITY_TRACKER_STRATEGY_WLSQ2_RECENT,
+            VELOCITY_TRACKER_STRATEGY_INT1,
+            VELOCITY_TRACKER_STRATEGY_INT2,
+            VELOCITY_TRACKER_STRATEGY_LEGACY
+    })
+    public @interface VelocityTrackerStrategy {}
+
+    private long mPtr;
+    @VelocityTrackerStrategy
+    private final int mStrategy;
+
+    private static native long nativeInitialize(int strategy);
+    private static native void nativeDispose(long ptr);
+    private static native void nativeClear(long ptr);
+    private static native void nativeAddMovement(long ptr, MotionEvent event);
+    private static native void nativeComputeCurrentVelocity(long ptr, int units, float maxVelocity);
+    private static native float nativeGetXVelocity(long ptr, int id);
+    private static native float nativeGetYVelocity(long ptr, int id);
+    private static native boolean nativeGetEstimator(long ptr, int id, Estimator outEstimator);
+
+    static {
+        // Strategy string and IDs mapping lookup.
+        STRATEGIES.put("impulse", VELOCITY_TRACKER_STRATEGY_IMPULSE);
+        STRATEGIES.put("lsq1", VELOCITY_TRACKER_STRATEGY_LSQ1);
+        STRATEGIES.put("lsq2", VELOCITY_TRACKER_STRATEGY_LSQ2);
+        STRATEGIES.put("lsq3", VELOCITY_TRACKER_STRATEGY_LSQ3);
+        STRATEGIES.put("wlsq2-delta", VELOCITY_TRACKER_STRATEGY_WLSQ2_DELTA);
+        STRATEGIES.put("wlsq2-central", VELOCITY_TRACKER_STRATEGY_WLSQ2_CENTRAL);
+        STRATEGIES.put("wlsq2-recent", VELOCITY_TRACKER_STRATEGY_WLSQ2_RECENT);
+        STRATEGIES.put("int1", VELOCITY_TRACKER_STRATEGY_INT1);
+        STRATEGIES.put("int2", VELOCITY_TRACKER_STRATEGY_INT2);
+        STRATEGIES.put("legacy", VELOCITY_TRACKER_STRATEGY_LEGACY);
+    }
+
+    /**
+     * Return a strategy ID from string.
+     */
+    private static int toStrategyId(String strStrategy) {
+        if (STRATEGIES.containsKey(strStrategy)) {
+            return STRATEGIES.get(strStrategy);
+        }
+        return VELOCITY_TRACKER_STRATEGY_DEFAULT;
+    }
+
+    /**
+     * Retrieve a new VelocityTracker object to watch the velocity of a
+     * motion.  Be sure to call {@link #recycle} when done.  You should
+     * generally only maintain an active object while tracking a movement,
+     * so that the VelocityTracker can be re-used elsewhere.
+     *
+     * @return Returns a new VelocityTracker.
+     */
+    static public VelocityTracker obtain() {
+        VelocityTracker instance = sPool.acquire();
+        return (instance != null) ? instance
+                : new VelocityTracker(VELOCITY_TRACKER_STRATEGY_DEFAULT);
+    }
+
+    /**
+     * Obtains a velocity tracker with the specified strategy as string.
+     * For testing and comparison purposes only.
+     * @deprecated Use {@link obtain(int strategy)} instead.
+     *
+     * @param strategy The strategy, or null to use the default.
+     * @return The velocity tracker.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @Deprecated
+    public static VelocityTracker obtain(String strategy) {
+        if (strategy == null) {
+            return obtain();
+        }
+        return new VelocityTracker(toStrategyId(strategy));
+    }
+
+    /**
+     * Obtains a velocity tracker with the specified strategy.
+     * For testing and comparison purposes only.
+     *
+     * @param strategy The strategy Id, VELOCITY_TRACKER_STRATEGY_DEFAULT to use the default.
+     * @return The velocity tracker.
+     *
+     * @hide
+     */
+    public static VelocityTracker obtain(int strategy) {
+        return new VelocityTracker(strategy);
+    }
+
+    /**
+     * Return a VelocityTracker object back to be re-used by others.  You must
+     * not touch the object after calling this function.
+     */
+    public void recycle() {
+        if (mStrategy == VELOCITY_TRACKER_STRATEGY_DEFAULT) {
+            clear();
+            sPool.release(this);
+        }
+    }
+
+    /**
+     * Return strategy Id of VelocityTracker object.
+     * @return The velocity tracker strategy Id.
+     *
+     * @hide
+     */
+    public int getStrategyId() {
+        return mStrategy;
+    }
+
+    private VelocityTracker(@VelocityTrackerStrategy int strategy) {
+        // If user has not selected a specific strategy
+        if (strategy == VELOCITY_TRACKER_STRATEGY_DEFAULT) {
+            // Check if user specified strategy by overriding system property.
+            String strategyProperty =
+                                    SystemProperties.get("persist.input.velocitytracker.strategy");
+            if (strategyProperty == null || strategyProperty.isEmpty()) {
+                mStrategy = strategy;
+            } else {
+                mStrategy = toStrategyId(strategyProperty);
+            }
+        } else {
+            // User specified strategy
+            mStrategy = strategy;
+        }
+        mPtr = nativeInitialize(mStrategy);
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mPtr != 0) {
+                nativeDispose(mPtr);
+                mPtr = 0;
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Reset the velocity tracker back to its initial state.
+     */
+    public void clear() {
+        nativeClear(mPtr);
+    }
+
+    /**
+     * Add a user's movement to the tracker.  You should call this for the
+     * initial {@link MotionEvent#ACTION_DOWN}, the following
+     * {@link MotionEvent#ACTION_MOVE} events that you receive, and the
+     * final {@link MotionEvent#ACTION_UP}.  You can, however, call this
+     * for whichever events you desire.
+     *
+     * @param event The MotionEvent you received and would like to track.
+     */
+    public void addMovement(MotionEvent event) {
+        if (event == null) {
+            throw new IllegalArgumentException("event must not be null");
+        }
+        nativeAddMovement(mPtr, event);
+    }
+
+    /**
+     * Equivalent to invoking {@link #computeCurrentVelocity(int, float)} with a maximum
+     * velocity of Float.MAX_VALUE.
+     *
+     * @see #computeCurrentVelocity(int, float)
+     */
+    public void computeCurrentVelocity(int units) {
+        nativeComputeCurrentVelocity(mPtr, units, Float.MAX_VALUE);
+    }
+
+    /**
+     * Compute the current velocity based on the points that have been
+     * collected.  Only call this when you actually want to retrieve velocity
+     * information, as it is relatively expensive.  You can then retrieve
+     * the velocity with {@link #getXVelocity()} and
+     * {@link #getYVelocity()}.
+     *
+     * @param units The units you would like the velocity in.  A value of 1
+     * provides pixels per millisecond, 1000 provides pixels per second, etc.
+     * @param maxVelocity The maximum velocity that can be computed by this method.
+     * This value must be declared in the same unit as the units parameter. This value
+     * must be positive.
+     */
+    public void computeCurrentVelocity(int units, float maxVelocity) {
+        nativeComputeCurrentVelocity(mPtr, units, maxVelocity);
+    }
+
+    /**
+     * Retrieve the last computed X velocity.  You must first call
+     * {@link #computeCurrentVelocity(int)} before calling this function.
+     *
+     * @return The previously computed X velocity.
+     */
+    public float getXVelocity() {
+        return nativeGetXVelocity(mPtr, ACTIVE_POINTER_ID);
+    }
+
+    /**
+     * Retrieve the last computed Y velocity.  You must first call
+     * {@link #computeCurrentVelocity(int)} before calling this function.
+     *
+     * @return The previously computed Y velocity.
+     */
+    public float getYVelocity() {
+        return nativeGetYVelocity(mPtr, ACTIVE_POINTER_ID);
+    }
+
+    /**
+     * Retrieve the last computed X velocity.  You must first call
+     * {@link #computeCurrentVelocity(int)} before calling this function.
+     *
+     * @param id Which pointer's velocity to return.
+     * @return The previously computed X velocity.
+     */
+    public float getXVelocity(int id) {
+        return nativeGetXVelocity(mPtr, id);
+    }
+
+    /**
+     * Retrieve the last computed Y velocity.  You must first call
+     * {@link #computeCurrentVelocity(int)} before calling this function.
+     *
+     * @param id Which pointer's velocity to return.
+     * @return The previously computed Y velocity.
+     */
+    public float getYVelocity(int id) {
+        return nativeGetYVelocity(mPtr, id);
+    }
+
+    /**
+     * Get an estimator for the movements of a pointer using past movements of the
+     * pointer to predict future movements.
+     *
+     * It is not necessary to call {@link #computeCurrentVelocity(int)} before calling
+     * this method.
+     *
+     * @param id Which pointer's velocity to return.
+     * @param outEstimator The estimator to populate.
+     * @return True if an estimator was obtained, false if there is no information
+     * available about the pointer.
+     *
+     * @hide For internal use only.  Not a final API.
+     */
+    public boolean getEstimator(int id, Estimator outEstimator) {
+        if (outEstimator == null) {
+            throw new IllegalArgumentException("outEstimator must not be null");
+        }
+        return nativeGetEstimator(mPtr, id, outEstimator);
+    }
+
+    /**
+     * An estimator for the movements of a pointer based on a polynomial model.
+     *
+     * The last recorded position of the pointer is at time zero seconds.
+     * Past estimated positions are at negative times and future estimated positions
+     * are at positive times.
+     *
+     * First coefficient is position (in pixels), second is velocity (in pixels per second),
+     * third is acceleration (in pixels per second squared).
+     *
+     * @hide For internal use only.  Not a final API.
+     */
+    public static final class Estimator {
+        // Must match VelocityTracker::Estimator::MAX_DEGREE
+        private static final int MAX_DEGREE = 4;
+
+        /**
+         * Polynomial coefficients describing motion in X.
+         */
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        public final float[] xCoeff = new float[MAX_DEGREE + 1];
+
+        /**
+         * Polynomial coefficients describing motion in Y.
+         */
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        public final float[] yCoeff = new float[MAX_DEGREE + 1];
+
+        /**
+         * Polynomial degree, or zero if only position information is available.
+         */
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        public int degree;
+
+        /**
+         * Confidence (coefficient of determination), between 0 (no fit) and 1 (perfect fit).
+         */
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        public float confidence;
+
+        /**
+         * Gets an estimate of the X position of the pointer at the specified time point.
+         * @param time The time point in seconds, 0 is the last recorded time.
+         * @return The estimated X coordinate.
+         */
+        public float estimateX(float time) {
+            return estimate(time, xCoeff);
+        }
+
+        /**
+         * Gets an estimate of the Y position of the pointer at the specified time point.
+         * @param time The time point in seconds, 0 is the last recorded time.
+         * @return The estimated Y coordinate.
+         */
+        public float estimateY(float time) {
+            return estimate(time, yCoeff);
+        }
+
+        /**
+         * Gets the X coefficient with the specified index.
+         * @param index The index of the coefficient to return.
+         * @return The X coefficient, or 0 if the index is greater than the degree.
+         */
+        public float getXCoeff(int index) {
+            return index <= degree ? xCoeff[index] : 0;
+        }
+
+        /**
+         * Gets the Y coefficient with the specified index.
+         * @param index The index of the coefficient to return.
+         * @return The Y coefficient, or 0 if the index is greater than the degree.
+         */
+        public float getYCoeff(int index) {
+            return index <= degree ? yCoeff[index] : 0;
+        }
+
+        private float estimate(float time, float[] c) {
+            float a = 0;
+            float scale = 1;
+            for (int i = 0; i <= degree; i++) {
+                a += c[i] * scale;
+                scale *= time;
+            }
+            return a;
+        }
+    }
+}
diff --git a/android/view/VerifiedInputEvent.java b/android/view/VerifiedInputEvent.java
new file mode 100644
index 0000000..cc6fbe7
--- /dev/null
+++ b/android/view/VerifiedInputEvent.java
@@ -0,0 +1,185 @@
+/*
+ * 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.view;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+
+
+/**
+ * Base class for verified events.
+ * Verified events contain the subset of an InputEvent that the system can verify.
+ * Data contained inside VerifiedInputEvent's should be considered trusted and contain only
+ * the original event data that first came from the system.
+ *
+ * @see android.hardware.input.InputManager#verifyInputEvent(InputEvent)
+ */
+@SuppressLint("ParcelNotFinal")
+public abstract class VerifiedInputEvent implements Parcelable {
+    private static final String TAG = "VerifiedInputEvent";
+
+    /** @hide */
+    protected static final int VERIFIED_KEY = 1;
+    /** @hide */
+    protected static final int VERIFIED_MOTION = 2;
+
+    /** @hide */
+    @Retention(SOURCE)
+    @IntDef(prefix = "VERIFIED", value = {VERIFIED_KEY, VERIFIED_MOTION})
+    public @interface VerifiedInputEventType {};
+
+    @VerifiedInputEventType
+    private int mType;
+
+    private int mDeviceId;
+    private long mEventTimeNanos;
+    private int mSource;
+    private int mDisplayId;
+
+    /** @hide */
+    protected VerifiedInputEvent(int type, int deviceId, long eventTimeNanos, int source,
+            int displayId) {
+        mType = type;
+        mDeviceId = deviceId;
+        mEventTimeNanos = eventTimeNanos;
+        mSource = source;
+        mDisplayId = displayId;
+    }
+    /** @hide */
+    protected VerifiedInputEvent(@NonNull Parcel in, int expectedType) {
+        mType = in.readInt();
+        if (mType != expectedType) {
+            throw new IllegalArgumentException("Unexpected input event type token in parcel.");
+        }
+        mDeviceId = in.readInt();
+        mEventTimeNanos = in.readLong();
+        mSource = in.readInt();
+        mDisplayId = in.readInt();
+    }
+
+    /**
+     * Get the id of the device that generated this event.
+     *
+     * @see InputEvent#getDeviceId()
+     */
+    public int getDeviceId() {
+        return mDeviceId;
+    }
+
+    /**
+     * Get the time this event occurred, in the {@link android.os.SystemClock#uptimeMillis()}
+     * time base.
+     *
+     * @see InputEvent#getEventTime()
+     */
+    @SuppressLint("MethodNameUnits")
+    public long getEventTimeNanos() {
+        return mEventTimeNanos;
+    }
+
+    /**
+     * Get the source of the event.
+     *
+     * @see InputEvent#getSource()
+     */
+    public int getSource() {
+        return mSource;
+    }
+
+    /**
+     * Get the display id that is associated with this event.
+     *
+     * @see Display#getDisplayId()
+     */
+    public int getDisplayId() {
+        return mDisplayId;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mType);
+        dest.writeInt(mDeviceId);
+        dest.writeLong(mEventTimeNanos);
+        dest.writeInt(mSource);
+        dest.writeInt(mDisplayId);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    private static int peekInt(@NonNull Parcel parcel) {
+        final int initialDataPosition = parcel.dataPosition();
+        int data = parcel.readInt();
+        parcel.setDataPosition(initialDataPosition);
+        return data;
+    }
+
+    public static final @NonNull Parcelable.Creator<VerifiedInputEvent> CREATOR =
+            new Parcelable.Creator<VerifiedInputEvent>() {
+        @Override
+        public VerifiedInputEvent[] newArray(int size) {
+            return new VerifiedInputEvent[size];
+        }
+
+        @Override
+        public VerifiedInputEvent createFromParcel(@NonNull Parcel in) {
+            final int type = peekInt(in);
+            if (type == VERIFIED_KEY) {
+                return VerifiedKeyEvent.CREATOR.createFromParcel(in);
+            } else if (type == VERIFIED_MOTION) {
+                return VerifiedMotionEvent.CREATOR.createFromParcel(in);
+            }
+            throw new IllegalArgumentException("Unexpected input event type in parcel.");
+        }
+    };
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        VerifiedInputEvent that = (VerifiedInputEvent) o;
+        return mType == that.mType
+                && getDeviceId() == that.getDeviceId()
+                && getEventTimeNanos() == that.getEventTimeNanos()
+                && getSource() == that.getSource()
+                && getDisplayId() == that.getDisplayId();
+    }
+
+    @Override
+    public int hashCode() {
+        int _hash = 1;
+        _hash = 31 * _hash + mType;
+        _hash = 31 * _hash + getDeviceId();
+        _hash = 31 * _hash + Long.hashCode(getEventTimeNanos());
+        _hash = 31 * _hash + getSource();
+        _hash = 31 * _hash + getDisplayId();
+        return _hash;
+    }
+}
diff --git a/android/view/VerifiedKeyEvent.java b/android/view/VerifiedKeyEvent.java
new file mode 100644
index 0000000..fc357cc
--- /dev/null
+++ b/android/view/VerifiedKeyEvent.java
@@ -0,0 +1,403 @@
+/*
+ * 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.view;
+
+import static android.view.KeyEvent.FLAG_CANCELED;
+import static android.view.KeyEvent.FLAG_IS_ACCESSIBILITY_EVENT;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.lang.annotation.Retention;
+
+/**
+ * KeyEvent that has been verified by the system.
+ * The data contained in this class is always a subset of a {@link KeyEvent}. Use this class to
+ * check which data has been confirmed by the system to be authentic.
+ *
+ * Most applications do not need to use this class.
+ *
+ * {@see android.hardware.input.InputManager#verifyInputEvent}
+ */
+@DataClass(genHiddenConstructor = true, genEqualsHashCode = true)
+public final class VerifiedKeyEvent extends VerifiedInputEvent implements Parcelable {
+    private static final String TAG = "VerifiedKeyEvent";
+
+    /** @hide */
+    @Retention(SOURCE)
+    @IntDef({KeyEvent.ACTION_DOWN, KeyEvent.ACTION_UP})
+    public @interface KeyEventAction {};
+
+    /**
+     * The action of this key event.  May be either {@link KeyEvent#ACTION_DOWN} or
+     * {@link KeyEvent#ACTION_UP}.
+     *
+     * @see KeyEvent#getAction()
+     */
+    @KeyEventAction
+    private int mAction;
+
+    /**
+     * Retrieve the time of the most recent key down event, in the
+     * {@link android.os.SystemClock#uptimeMillis} time base, but in nanoseconds. If this
+     * is a down event, this will be the same as {@link VerifiedInputEvent#getEventTimeNanos()}.
+     *
+     * @see KeyEvent#getDownTime()
+     */
+    @SuppressLint({"MethodNameUnits"})
+    private long mDownTimeNanos;
+
+    /**
+     * Returns the flags for this key event.
+     *
+     * @see KeyEvent#getFlags()
+     * @see KeyEvent#FLAG_CANCELED
+     * @see KeyEvent#FLAG_IS_ACCESSIBILITY_EVENT
+     *
+     * @hide
+     */
+    private int mFlags;
+
+    /**
+     * Retrieve the key code of the key event.
+     *
+     * @see KeyEvent#getKeyCode()
+     */
+    private int mKeyCode;
+
+    /**
+     * Retrieve the hardware key id of this key event. These values are not reliable
+     * and vary from device to device.
+     *
+     * @see KeyEvent#getScanCode()
+     */
+    private int mScanCode;
+
+    /**
+     * <p>Returns the state of the meta keys.</p>
+     *
+     * @return an integer in which each bit set to 1 represents a pressed meta key
+     * @see KeyEvent#getMetaState()
+     */
+    private int mMetaState;
+
+    /**
+     * Retrieve the repeat count of the event.  For key down events, this is the number of times
+     * the key has repeated with the first down starting at 0 and counting up from there.
+     * For key up events, this is always equal to zero. For multiple key events,
+     * this is the number of down/up pairs that have occurred.
+     */
+    private int mRepeatCount;
+
+    /**
+     * Get a specific flag of this key event, if possible. Return null if the flag value could
+     * not be checked.
+     *
+     * @param flag the flag of interest
+     * @return Boolean(true) if the key event has the requested flag
+     *         Boolean(false) if the key event does not have the requested flag
+     *         null if the flag value could not be checked
+     *
+     * @see KeyEvent#getFlags()
+     * @see KeyEvent#FLAG_CANCELED
+     */
+    public @Nullable Boolean getFlag(int flag) {
+        switch(flag) {
+            // InputDispatcher only verifies a subset of the KeyEvent flags.
+            // These values must be kept in sync with Input.cpp
+            case FLAG_CANCELED:
+            case FLAG_IS_ACCESSIBILITY_EVENT:
+                return (mFlags & flag) != 0;
+        }
+        return null;
+    }
+
+    // The codegen tool doesn't fully support subclasses, since it works on a per-file basis.
+    // To modify this file:
+    // 1. run codegen on this file
+    // 2. edit the constructor signature
+    // 3. add the "super" call for constructor that receives a Parcel
+    // 4. add the "super" call to the writeToParcel method
+    // 5. Update "equals" and "hashcode" methods to include VerifiedInputEvent fields
+
+
+
+    // Code below generated by codegen v1.0.20.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/VerifiedKeyEvent.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new VerifiedKeyEvent.
+     *
+     * @param action
+     *   The action of this key event.  May be either {@link KeyEvent#ACTION_DOWN} or
+     *   {@link KeyEvent#ACTION_UP}.
+     * @param downTimeNanos
+     *   Retrieve the time of the most recent key down event, in the
+     *   {@link android.os.SystemClock#uptimeMillis} time base, but in nanoseconds. If this
+     *   is a down event, this will be the same as {@link VerifiedInputEvent#getEventTimeNanos()}.
+     * @param flags
+     *   Returns the flags for this key event.
+     * @param keyCode
+     *   Retrieve the key code of the key event.
+     * @param scanCode
+     *   Retrieve the hardware key id of this key event. These values are not reliable
+     *   and vary from device to device.
+     * @param metaState
+     *   <p>Returns the state of the meta keys.</p>
+     * @param repeatCount
+     *   Retrieve the repeat count of the event.  For key down events, this is the number of times
+     *   the key has repeated with the first down starting at 0 and counting up from there.
+     *   For key up events, this is always equal to zero. For multiple key events,
+     *   this is the number of down/up pairs that have occurred.
+     * @hide
+     */
+    public VerifiedKeyEvent(
+            int deviceId,
+            long eventTimeNanos,
+            int source,
+            int displayId,
+            @KeyEventAction int action,
+            @SuppressLint({ "MethodNameUnits" }) long downTimeNanos,
+            int flags,
+            int keyCode,
+            int scanCode,
+            int metaState,
+            int repeatCount) {
+        super(VERIFIED_KEY, deviceId, eventTimeNanos, source, displayId);
+        this.mAction = action;
+        com.android.internal.util.AnnotationValidations.validate(
+                KeyEventAction.class, null, mAction);
+        this.mDownTimeNanos = downTimeNanos;
+        this.mFlags = flags;
+        this.mKeyCode = keyCode;
+        this.mScanCode = scanCode;
+        this.mMetaState = metaState;
+        this.mRepeatCount = repeatCount;
+    }
+
+    /**
+     * The action of this key event.  May be either {@link KeyEvent#ACTION_DOWN} or
+     * {@link KeyEvent#ACTION_UP}.
+     *
+     * @see KeyEvent#getAction()
+     */
+    @DataClass.Generated.Member
+    public @KeyEventAction int getAction() {
+        return mAction;
+    }
+
+    /**
+     * Retrieve the time of the most recent key down event, in the
+     * {@link android.os.SystemClock#uptimeMillis} time base, but in nanoseconds. If this
+     * is a down event, this will be the same as {@link VerifiedInputEvent#getEventTimeNanos()}.
+     *
+     * @see KeyEvent#getDownTime()
+     */
+    @DataClass.Generated.Member
+    public @SuppressLint({ "MethodNameUnits" }) long getDownTimeNanos() {
+        return mDownTimeNanos;
+    }
+
+    /**
+     * Returns the flags for this key event.
+     *
+     * @see KeyEvent#getFlags()
+     * @see KeyEvent#FLAG_CANCELED
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public int getFlags() {
+        return mFlags;
+    }
+
+    /**
+     * Retrieve the key code of the key event.
+     *
+     * @see KeyEvent#getKeyCode()
+     */
+    @DataClass.Generated.Member
+    public int getKeyCode() {
+        return mKeyCode;
+    }
+
+    /**
+     * Retrieve the hardware key id of this key event. These values are not reliable
+     * and vary from device to device.
+     *
+     * @see KeyEvent#getScanCode()
+     */
+    @DataClass.Generated.Member
+    public int getScanCode() {
+        return mScanCode;
+    }
+
+    /**
+     * <p>Returns the state of the meta keys.</p>
+     *
+     * @return an integer in which each bit set to 1 represents a pressed meta key
+     * @see KeyEvent#getMetaState()
+     */
+    @DataClass.Generated.Member
+    public int getMetaState() {
+        return mMetaState;
+    }
+
+    /**
+     * Retrieve the repeat count of the event.  For key down events, this is the number of times
+     * the key has repeated with the first down starting at 0 and counting up from there.
+     * For key up events, this is always equal to zero. For multiple key events,
+     * this is the number of down/up pairs that have occurred.
+     */
+    @DataClass.Generated.Member
+    public int getRepeatCount() {
+        return mRepeatCount;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(VerifiedKeyEvent other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        VerifiedKeyEvent that = (VerifiedKeyEvent) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && super.equals(that)
+                && mAction == that.mAction
+                && mDownTimeNanos == that.mDownTimeNanos
+                && mFlags == that.mFlags
+                && mKeyCode == that.mKeyCode
+                && mScanCode == that.mScanCode
+                && mMetaState == that.mMetaState
+                && mRepeatCount == that.mRepeatCount;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + super.hashCode();
+        _hash = 31 * _hash + mAction;
+        _hash = 31 * _hash + Long.hashCode(mDownTimeNanos);
+        _hash = 31 * _hash + mFlags;
+        _hash = 31 * _hash + mKeyCode;
+        _hash = 31 * _hash + mScanCode;
+        _hash = 31 * _hash + mMetaState;
+        _hash = 31 * _hash + mRepeatCount;
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@android.annotation.NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+        super.writeToParcel(dest, flags);
+
+        dest.writeInt(mAction);
+        dest.writeLong(mDownTimeNanos);
+        dest.writeInt(mFlags);
+        dest.writeInt(mKeyCode);
+        dest.writeInt(mScanCode);
+        dest.writeInt(mMetaState);
+        dest.writeInt(mRepeatCount);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ VerifiedKeyEvent(@android.annotation.NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+        super(in, VERIFIED_KEY);
+
+        int action = in.readInt();
+        long downTimeNanos = in.readLong();
+        int flags = in.readInt();
+        int keyCode = in.readInt();
+        int scanCode = in.readInt();
+        int metaState = in.readInt();
+        int repeatCount = in.readInt();
+
+        this.mAction = action;
+        com.android.internal.util.AnnotationValidations.validate(
+                KeyEventAction.class, null, mAction);
+        this.mDownTimeNanos = downTimeNanos;
+        this.mFlags = flags;
+        this.mKeyCode = keyCode;
+        this.mScanCode = scanCode;
+        this.mMetaState = metaState;
+        this.mRepeatCount = repeatCount;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @android.annotation.NonNull Parcelable.Creator<VerifiedKeyEvent> CREATOR
+            = new Parcelable.Creator<VerifiedKeyEvent>() {
+        @Override
+        public VerifiedKeyEvent[] newArray(int size) {
+            return new VerifiedKeyEvent[size];
+        }
+
+        @Override
+        public VerifiedKeyEvent createFromParcel(@android.annotation.NonNull Parcel in) {
+            return new VerifiedKeyEvent(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1604509197793L,
+            codegenVersion = "1.0.20",
+            sourceFile = "frameworks/base/core/java/android/view/VerifiedKeyEvent.java",
+            inputSignatures = "private static final  java.lang.String TAG\nprivate @android.view.VerifiedKeyEvent.KeyEventAction int mAction\nprivate @android.annotation.SuppressLint long mDownTimeNanos\nprivate  int mFlags\nprivate  int mKeyCode\nprivate  int mScanCode\nprivate  int mMetaState\nprivate  int mRepeatCount\npublic @android.annotation.Nullable java.lang.Boolean getFlag(int)\nclass VerifiedKeyEvent extends android.view.VerifiedInputEvent implements [android.os.Parcelable]\[email protected](genHiddenConstructor=true, genEqualsHashCode=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/android/view/VerifiedMotionEvent.java b/android/view/VerifiedMotionEvent.java
new file mode 100644
index 0000000..948575c
--- /dev/null
+++ b/android/view/VerifiedMotionEvent.java
@@ -0,0 +1,387 @@
+/*
+ * 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.view;
+
+import static android.view.MotionEvent.FLAG_IS_ACCESSIBILITY_EVENT;
+import static android.view.MotionEvent.FLAG_WINDOW_IS_OBSCURED;
+import static android.view.MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.lang.annotation.Retention;
+
+/**
+ * MotionEvent that has been verified by the system.
+ * The data contained in this class is always a subset of a {@link MotionEvent}. Use this class to
+ * check which data has been confirmed by the system to be authentic.
+ *
+ * Most applications do not need to use this class.
+ *
+ * {@see android.hardware.input.InputManager#verifyInputEvent}
+ */
+@DataClass(genHiddenConstructor = true, genEqualsHashCode = true)
+public final class VerifiedMotionEvent extends VerifiedInputEvent implements Parcelable {
+    private static final String TAG = "VerifiedMotionEvent";
+
+    /**
+     * The raw X coordinate of the primary pointer.
+     * @see MotionEvent#getRawX()
+     */
+    private float mRawX;
+
+    /**
+     * The raw Y coordinate of the primary pointer.
+     * @see MotionEvent#getRawY()
+     */
+    private float mRawY;
+
+    /** @hide */
+    @Retention(SOURCE)
+    @IntDef({MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN, MotionEvent.ACTION_CANCEL,
+            MotionEvent.ACTION_POINTER_UP, MotionEvent.ACTION_UP})
+    public @interface MotionEventAction {};
+
+    /**
+     * The masked action being performed, without pointer index information.
+     *
+     * @see MotionEvent#getActionMasked()
+     */
+    @MotionEventAction
+    private int mActionMasked;
+
+    /**
+     * The time that the gesture started, in nanoseconds.
+     * Uses the same time base as {@link android.os.SystemClock#uptimeMillis()}
+     *
+     * @see MotionEvent#getDownTime()
+     */
+    @SuppressLint({"MethodNameUnits"})
+    private long mDownTimeNanos;
+
+    /**
+     * Returns the flags for this motion event.
+     *
+     * @see MotionEvent#getFlags()
+     * @see MotionEvent#FLAG_IS_ACCESSIBILITY_EVENT
+     * @see MotionEvent#FLAG_WINDOW_IS_OBSCURED
+     * @see MotionEvent#FLAG_WINDOW_IS_PARTIALLY_OBSCURED
+     * @hide
+     */
+    private int mFlags;
+
+    /**
+     * The state of any meta / modifier keys that were in effect when the event was generated.
+     *
+     * @see MotionEvent#getMetaState()
+     */
+    private int mMetaState;
+
+    /**
+     *  The state of all buttons that are pressed such as a mouse or stylus button.
+     *
+     * @see MotionEvent#getButtonState()
+     */
+    private int mButtonState;
+
+    /**
+     * Get a specific flag of this motion event, if possible. Return null if the flag value could
+     * not be checked.
+     *
+     * @param flag the flag of interest
+     * @return Boolean(true) if the motion event has the requested flag
+     *         Boolean(false) if the motion event does not have the requested flag
+     *         null if the flag value could not be checked
+     *
+     * @see MotionEvent#FLAG_WINDOW_IS_OBSCURED
+     * @see MotionEvent#FLAG_WINDOW_IS_PARTIALLY_OBSCURED
+     */
+    public @Nullable Boolean getFlag(int flag) {
+        switch(flag) {
+            // InputDispatcher only verifies a subset of the MotionEvent flags.
+            // These values must be kept in sync with Input.cpp
+            case FLAG_IS_ACCESSIBILITY_EVENT:
+            case FLAG_WINDOW_IS_OBSCURED:
+            case FLAG_WINDOW_IS_PARTIALLY_OBSCURED:
+                return (mFlags & flag) != 0;
+        }
+        return null;
+    }
+
+    // The codegen tool doesn't fully support subclasses, since it works on a per-file basis.
+    // To modify this file:
+    // 1. run codegen on this file
+    // 2. edit the constructor signature
+    // 3. add the "super" call for constructor that receives a Parcel
+    // 4. add the "super" call to the writeToParcel method
+    // 5. Update "equals" and "hashcode" methods to include VerifiedInputEvent fields
+
+
+
+    // Code below generated by codegen v1.0.20.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/VerifiedMotionEvent.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new VerifiedMotionEvent.
+     *
+     * @param rawX
+     *   The raw X coordinate of the primary pointer.
+     * @param rawY
+     *   The raw Y coordinate of the primary pointer.
+     * @param actionMasked
+     *   The masked action being performed, without pointer index information.
+     * @param downTimeNanos
+     *   The time that the gesture started, in nanoseconds.
+     *   Uses the same time base as {@link android.os.SystemClock#uptimeMillis()}
+     * @param flags
+     *   Returns the flags for this motion event.
+     * @param metaState
+     *   The state of any meta / modifier keys that were in effect when the event was generated.
+     * @param buttonState
+     *    The state of all buttons that are pressed such as a mouse or stylus button.
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public VerifiedMotionEvent(
+            int deviceId, long eventTimeNanos, int source, int displayId,
+            float rawX,
+            float rawY,
+            @MotionEventAction int actionMasked,
+            @SuppressLint({ "MethodNameUnits" }) long downTimeNanos,
+            int flags,
+            int metaState,
+            int buttonState) {
+        super(VERIFIED_MOTION, deviceId, eventTimeNanos, source, displayId);
+        this.mRawX = rawX;
+        this.mRawY = rawY;
+        this.mActionMasked = actionMasked;
+        com.android.internal.util.AnnotationValidations.validate(
+                MotionEventAction.class, null, mActionMasked);
+        this.mDownTimeNanos = downTimeNanos;
+        this.mFlags = flags;
+        this.mMetaState = metaState;
+        this.mButtonState = buttonState;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * The raw X coordinate of the primary pointer.
+     *
+     * @see MotionEvent#getRawX()
+     */
+    @DataClass.Generated.Member
+    public float getRawX() {
+        return mRawX;
+    }
+
+    /**
+     * The raw Y coordinate of the primary pointer.
+     *
+     * @see MotionEvent#getRawY()
+     */
+    @DataClass.Generated.Member
+    public float getRawY() {
+        return mRawY;
+    }
+
+    /**
+     * The masked action being performed, without pointer index information.
+     *
+     * @see MotionEvent#getActionMasked()
+     */
+    @DataClass.Generated.Member
+    public @MotionEventAction int getActionMasked() {
+        return mActionMasked;
+    }
+
+    /**
+     * The time that the gesture started, in nanoseconds.
+     * Uses the same time base as {@link android.os.SystemClock#uptimeMillis()}
+     *
+     * @see MotionEvent#getDownTime()
+     */
+    @DataClass.Generated.Member
+    public @SuppressLint({ "MethodNameUnits" }) long getDownTimeNanos() {
+        return mDownTimeNanos;
+    }
+
+    /**
+     * Returns the flags for this motion event.
+     *
+     * @see MotionEvent#getFlags()
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public int getFlags() {
+        return mFlags;
+    }
+
+    /**
+     * The state of any meta / modifier keys that were in effect when the event was generated.
+     *
+     * @see MotionEvent#getMetaState()
+     */
+    @DataClass.Generated.Member
+    public int getMetaState() {
+        return mMetaState;
+    }
+
+    /**
+     *  The state of all buttons that are pressed such as a mouse or stylus button.
+     *
+     * @see MotionEvent#getButtonState()
+     */
+    @DataClass.Generated.Member
+    public int getButtonState() {
+        return mButtonState;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(VerifiedMotionEvent other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        VerifiedMotionEvent that = (VerifiedMotionEvent) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && super.equals(that)
+                && mRawX == that.mRawX
+                && mRawY == that.mRawY
+                && mActionMasked == that.mActionMasked
+                && mDownTimeNanos == that.mDownTimeNanos
+                && mFlags == that.mFlags
+                && mMetaState == that.mMetaState
+                && mButtonState == that.mButtonState;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + super.hashCode();
+        _hash = 31 * _hash + Float.hashCode(mRawX);
+        _hash = 31 * _hash + Float.hashCode(mRawY);
+        _hash = 31 * _hash + mActionMasked;
+        _hash = 31 * _hash + Long.hashCode(mDownTimeNanos);
+        _hash = 31 * _hash + mFlags;
+        _hash = 31 * _hash + mMetaState;
+        _hash = 31 * _hash + mButtonState;
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@android.annotation.NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+        super.writeToParcel(dest, flags);
+
+        dest.writeFloat(mRawX);
+        dest.writeFloat(mRawY);
+        dest.writeInt(mActionMasked);
+        dest.writeLong(mDownTimeNanos);
+        dest.writeInt(mFlags);
+        dest.writeInt(mMetaState);
+        dest.writeInt(mButtonState);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ VerifiedMotionEvent(@android.annotation.NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+        super(in, VERIFIED_MOTION);
+
+        float rawX = in.readFloat();
+        float rawY = in.readFloat();
+        int actionMasked = in.readInt();
+        long downTimeNanos = in.readLong();
+        int flags = in.readInt();
+        int metaState = in.readInt();
+        int buttonState = in.readInt();
+
+        this.mRawX = rawX;
+        this.mRawY = rawY;
+        this.mActionMasked = actionMasked;
+        com.android.internal.util.AnnotationValidations.validate(
+                MotionEventAction.class, null, mActionMasked);
+        this.mDownTimeNanos = downTimeNanos;
+        this.mFlags = flags;
+        this.mMetaState = metaState;
+        this.mButtonState = buttonState;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @android.annotation.NonNull Parcelable.Creator<VerifiedMotionEvent> CREATOR
+            = new Parcelable.Creator<VerifiedMotionEvent>() {
+        @Override
+        public VerifiedMotionEvent[] newArray(int size) {
+            return new VerifiedMotionEvent[size];
+        }
+
+        @Override
+        public VerifiedMotionEvent createFromParcel(@android.annotation.NonNull Parcel in) {
+            return new VerifiedMotionEvent(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1604509199368L,
+            codegenVersion = "1.0.20",
+            sourceFile = "frameworks/base/core/java/android/view/VerifiedMotionEvent.java",
+            inputSignatures = "private static final  java.lang.String TAG\nprivate  float mRawX\nprivate  float mRawY\nprivate @android.view.VerifiedMotionEvent.MotionEventAction int mActionMasked\nprivate @android.annotation.SuppressLint long mDownTimeNanos\nprivate  int mFlags\nprivate  int mMetaState\nprivate  int mButtonState\npublic @android.annotation.Nullable java.lang.Boolean getFlag(int)\nclass VerifiedMotionEvent extends android.view.VerifiedInputEvent implements [android.os.Parcelable]\[email protected](genHiddenConstructor=true, genEqualsHashCode=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/android/view/View.java b/android/view/View.java
new file mode 100644
index 0000000..f4223fb
--- /dev/null
+++ b/android/view/View.java
@@ -0,0 +1,31110 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.content.res.Resources.ID_NULL;
+import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP;
+import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
+import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS;
+import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW;
+import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN;
+import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_UNKNOWN;
+import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH;
+import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH_ERROR_CODE;
+
+import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS;
+import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS;
+import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP;
+import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION;
+
+import static java.lang.Math.max;
+
+import android.animation.AnimatorInflater;
+import android.animation.StateListAnimator;
+import android.annotation.AttrRes;
+import android.annotation.CallSuper;
+import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
+import android.annotation.FloatRange;
+import android.annotation.IdRes;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.LayoutRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.Size;
+import android.annotation.StyleRes;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
+import android.annotation.UiContext;
+import android.annotation.UiThread;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.AutofillOptions;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BlendMode;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Insets;
+import android.graphics.Interpolator;
+import android.graphics.LinearGradient;
+import android.graphics.Matrix;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.RecordingCanvas;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.graphics.RenderEffect;
+import android.graphics.RenderNode;
+import android.graphics.Shader;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.hardware.display.DisplayManagerGlobal;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.sysprop.DisplayProperties;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.FloatProperty;
+import android.util.LayoutDirection;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.LongSparseLongArray;
+import android.util.Pair;
+import android.util.Pools.SynchronizedPool;
+import android.util.Property;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.util.StateSet;
+import android.util.SuperNotCalledException;
+import android.util.TypedValue;
+import android.view.AccessibilityIterators.CharacterTextSegmentIterator;
+import android.view.AccessibilityIterators.ParagraphTextSegmentIterator;
+import android.view.AccessibilityIterators.TextSegmentIterator;
+import android.view.AccessibilityIterators.WordTextSegmentIterator;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.InputDevice.InputSourceClass;
+import android.view.Window.OnContentApplyWindowInsetsListener;
+import android.view.WindowInsets.Type;
+import android.view.WindowInsetsAnimation.Bounds;
+import android.view.WindowManager.LayoutParams;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityEventSource;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeIdManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import android.view.accessibility.AccessibilityNodeProvider;
+import android.view.accessibility.AccessibilityWindowInfo;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Transformation;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillValue;
+import android.view.contentcapture.ContentCaptureContext;
+import android.view.contentcapture.ContentCaptureManager;
+import android.view.contentcapture.ContentCaptureSession;
+import android.view.displayhash.DisplayHash;
+import android.view.displayhash.DisplayHashManager;
+import android.view.displayhash.DisplayHashResultCallback;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inspector.InspectableProperty;
+import android.view.inspector.InspectableProperty.EnumEntry;
+import android.view.inspector.InspectableProperty.FlagEntry;
+import android.view.translation.TranslationCapability;
+import android.view.translation.TranslationSpec.DataFormat;
+import android.view.translation.ViewTranslationCallback;
+import android.view.translation.ViewTranslationRequest;
+import android.view.translation.ViewTranslationResponse;
+import android.widget.Checkable;
+import android.widget.FrameLayout;
+import android.widget.ScrollBarDrawable;
+
+import com.android.internal.R;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.Preconditions;
+import com.android.internal.view.ScrollCaptureInternal;
+import com.android.internal.view.TooltipPopup;
+import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.widget.ScrollBarUtils;
+
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+/**
+ * <p>
+ * This class represents the basic building block for user interface components. A View
+ * occupies a rectangular area on the screen and is responsible for drawing and
+ * event handling. View is the base class for <em>widgets</em>, which are
+ * used to create interactive UI components (buttons, text fields, etc.). The
+ * {@link android.view.ViewGroup} subclass is the base class for <em>layouts</em>, which
+ * are invisible containers that hold other Views (or other ViewGroups) and define
+ * their layout properties.
+ * </p>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For information about using this class to develop your application's user interface,
+ * read the <a href="{@docRoot}guide/topics/ui/index.html">User Interface</a> developer guide.
+ * </div>
+ *
+ * <a name="Using"></a>
+ * <h3>Using Views</h3>
+ * <p>
+ * All of the views in a window are arranged in a single tree. You can add views
+ * either from code or by specifying a tree of views in one or more XML layout
+ * files. There are many specialized subclasses of views that act as controls or
+ * are capable of displaying text, images, or other content.
+ * </p>
+ * <p>
+ * Once you have created a tree of views, there are typically a few types of
+ * common operations you may wish to perform:
+ * <ul>
+ * <li><strong>Set properties:</strong> for example setting the text of a
+ * {@link android.widget.TextView}. The available properties and the methods
+ * that set them will vary among the different subclasses of views. Note that
+ * properties that are known at build time can be set in the XML layout
+ * files.</li>
+ * <li><strong>Set focus:</strong> The framework will handle moving focus in
+ * response to user input. To force focus to a specific view, call
+ * {@link #requestFocus}.</li>
+ * <li><strong>Set up listeners:</strong> Views allow clients to set listeners
+ * that will be notified when something interesting happens to the view. For
+ * example, all views will let you set a listener to be notified when the view
+ * gains or loses focus. You can register such a listener using
+ * {@link #setOnFocusChangeListener(android.view.View.OnFocusChangeListener)}.
+ * Other view subclasses offer more specialized listeners. For example, a Button
+ * exposes a listener to notify clients when the button is clicked.</li>
+ * <li><strong>Set visibility:</strong> You can hide or show views using
+ * {@link #setVisibility(int)}.</li>
+ * </ul>
+ * </p>
+ * <p><em>
+ * Note: The Android framework is responsible for measuring, laying out and
+ * drawing views. You should not call methods that perform these actions on
+ * views yourself unless you are actually implementing a
+ * {@link android.view.ViewGroup}.
+ * </em></p>
+ *
+ * <a name="Lifecycle"></a>
+ * <h3>Implementing a Custom View</h3>
+ *
+ * <p>
+ * To implement a custom view, you will usually begin by providing overrides for
+ * some of the standard methods that the framework calls on all views. You do
+ * not need to override all of these methods. In fact, you can start by just
+ * overriding {@link #onDraw(android.graphics.Canvas)}.
+ * <table border="2" width="85%" align="center" cellpadding="5">
+ *     <thead>
+ *         <tr><th>Category</th> <th>Methods</th> <th>Description</th></tr>
+ *     </thead>
+ *
+ *     <tbody>
+ *     <tr>
+ *         <td rowspan="2">Creation</td>
+ *         <td>Constructors</td>
+ *         <td>There is a form of the constructor that are called when the view
+ *         is created from code and a form that is called when the view is
+ *         inflated from a layout file. The second form should parse and apply
+ *         any attributes defined in the layout file.
+ *         </td>
+ *     </tr>
+ *     <tr>
+ *         <td><code>{@link #onFinishInflate()}</code></td>
+ *         <td>Called after a view and all of its children has been inflated
+ *         from XML.</td>
+ *     </tr>
+ *
+ *     <tr>
+ *         <td rowspan="3">Layout</td>
+ *         <td><code>{@link #onMeasure(int, int)}</code></td>
+ *         <td>Called to determine the size requirements for this view and all
+ *         of its children.
+ *         </td>
+ *     </tr>
+ *     <tr>
+ *         <td><code>{@link #onLayout(boolean, int, int, int, int)}</code></td>
+ *         <td>Called when this view should assign a size and position to all
+ *         of its children.
+ *         </td>
+ *     </tr>
+ *     <tr>
+ *         <td><code>{@link #onSizeChanged(int, int, int, int)}</code></td>
+ *         <td>Called when the size of this view has changed.
+ *         </td>
+ *     </tr>
+ *
+ *     <tr>
+ *         <td>Drawing</td>
+ *         <td><code>{@link #onDraw(android.graphics.Canvas)}</code></td>
+ *         <td>Called when the view should render its content.
+ *         </td>
+ *     </tr>
+ *
+ *     <tr>
+ *         <td rowspan="4">Event processing</td>
+ *         <td><code>{@link #onKeyDown(int, KeyEvent)}</code></td>
+ *         <td>Called when a new hardware key event occurs.
+ *         </td>
+ *     </tr>
+ *     <tr>
+ *         <td><code>{@link #onKeyUp(int, KeyEvent)}</code></td>
+ *         <td>Called when a hardware key up event occurs.
+ *         </td>
+ *     </tr>
+ *     <tr>
+ *         <td><code>{@link #onTrackballEvent(MotionEvent)}</code></td>
+ *         <td>Called when a trackball motion event occurs.
+ *         </td>
+ *     </tr>
+ *     <tr>
+ *         <td><code>{@link #onTouchEvent(MotionEvent)}</code></td>
+ *         <td>Called when a touch screen motion event occurs.
+ *         </td>
+ *     </tr>
+ *
+ *     <tr>
+ *         <td rowspan="2">Focus</td>
+ *         <td><code>{@link #onFocusChanged(boolean, int, android.graphics.Rect)}</code></td>
+ *         <td>Called when the view gains or loses focus.
+ *         </td>
+ *     </tr>
+ *
+ *     <tr>
+ *         <td><code>{@link #onWindowFocusChanged(boolean)}</code></td>
+ *         <td>Called when the window containing the view gains or loses focus.
+ *         </td>
+ *     </tr>
+ *
+ *     <tr>
+ *         <td rowspan="3">Attaching</td>
+ *         <td><code>{@link #onAttachedToWindow()}</code></td>
+ *         <td>Called when the view is attached to a window.
+ *         </td>
+ *     </tr>
+ *
+ *     <tr>
+ *         <td><code>{@link #onDetachedFromWindow}</code></td>
+ *         <td>Called when the view is detached from its window.
+ *         </td>
+ *     </tr>
+ *
+ *     <tr>
+ *         <td><code>{@link #onWindowVisibilityChanged(int)}</code></td>
+ *         <td>Called when the visibility of the window containing the view
+ *         has changed.
+ *         </td>
+ *     </tr>
+ *     </tbody>
+ *
+ * </table>
+ * </p>
+ *
+ * <a name="IDs"></a>
+ * <h3>IDs</h3>
+ * Views may have an integer id associated with them. These ids are typically
+ * assigned in the layout XML files, and are used to find specific views within
+ * the view tree. A common pattern is to:
+ * <ul>
+ * <li>Define a Button in the layout file and assign it a unique ID.
+ * <pre>
+ * &lt;Button
+ *     android:id="@+id/my_button"
+ *     android:layout_width="wrap_content"
+ *     android:layout_height="wrap_content"
+ *     android:text="@string/my_button_text"/&gt;
+ * </pre></li>
+ * <li>From the onCreate method of an Activity, find the Button
+ * <pre class="prettyprint">
+ *      Button myButton = findViewById(R.id.my_button);
+ * </pre></li>
+ * </ul>
+ * <p>
+ * View IDs need not be unique throughout the tree, but it is good practice to
+ * ensure that they are at least unique within the part of the tree you are
+ * searching.
+ * </p>
+ *
+ * <a name="Position"></a>
+ * <h3>Position</h3>
+ * <p>
+ * The geometry of a view is that of a rectangle. A view has a location,
+ * expressed as a pair of <em>left</em> and <em>top</em> coordinates, and
+ * two dimensions, expressed as a width and a height. The unit for location
+ * and dimensions is the pixel.
+ * </p>
+ *
+ * <p>
+ * It is possible to retrieve the location of a view by invoking the methods
+ * {@link #getLeft()} and {@link #getTop()}. The former returns the left, or X,
+ * coordinate of the rectangle representing the view. The latter returns the
+ * top, or Y, coordinate of the rectangle representing the view. These methods
+ * both return the location of the view relative to its parent. For instance,
+ * when getLeft() returns 20, that means the view is located 20 pixels to the
+ * right of the left edge of its direct parent.
+ * </p>
+ *
+ * <p>
+ * In addition, several convenience methods are offered to avoid unnecessary
+ * computations, namely {@link #getRight()} and {@link #getBottom()}.
+ * These methods return the coordinates of the right and bottom edges of the
+ * rectangle representing the view. For instance, calling {@link #getRight()}
+ * is similar to the following computation: <code>getLeft() + getWidth()</code>
+ * (see <a href="#SizePaddingMargins">Size</a> for more information about the width.)
+ * </p>
+ *
+ * <a name="SizePaddingMargins"></a>
+ * <h3>Size, padding and margins</h3>
+ * <p>
+ * The size of a view is expressed with a width and a height. A view actually
+ * possess two pairs of width and height values.
+ * </p>
+ *
+ * <p>
+ * The first pair is known as <em>measured width</em> and
+ * <em>measured height</em>. These dimensions define how big a view wants to be
+ * within its parent (see <a href="#Layout">Layout</a> for more details.) The
+ * measured dimensions can be obtained by calling {@link #getMeasuredWidth()}
+ * and {@link #getMeasuredHeight()}.
+ * </p>
+ *
+ * <p>
+ * The second pair is simply known as <em>width</em> and <em>height</em>, or
+ * sometimes <em>drawing width</em> and <em>drawing height</em>. These
+ * dimensions define the actual size of the view on screen, at drawing time and
+ * after layout. These values may, but do not have to, be different from the
+ * measured width and height. The width and height can be obtained by calling
+ * {@link #getWidth()} and {@link #getHeight()}.
+ * </p>
+ *
+ * <p>
+ * To measure its dimensions, a view takes into account its padding. The padding
+ * is expressed in pixels for the left, top, right and bottom parts of the view.
+ * Padding can be used to offset the content of the view by a specific amount of
+ * pixels. For instance, a left padding of 2 will push the view's content by
+ * 2 pixels to the right of the left edge. Padding can be set using the
+ * {@link #setPadding(int, int, int, int)} or {@link #setPaddingRelative(int, int, int, int)}
+ * method and queried by calling {@link #getPaddingLeft()}, {@link #getPaddingTop()},
+ * {@link #getPaddingRight()}, {@link #getPaddingBottom()}, {@link #getPaddingStart()},
+ * {@link #getPaddingEnd()}.
+ * </p>
+ *
+ * <p>
+ * Even though a view can define a padding, it does not provide any support for
+ * margins. However, view groups provide such a support. Refer to
+ * {@link android.view.ViewGroup} and
+ * {@link android.view.ViewGroup.MarginLayoutParams} for further information.
+ * </p>
+ *
+ * <a name="Layout"></a>
+ * <h3>Layout</h3>
+ * <p>
+ * Layout is a two pass process: a measure pass and a layout pass. The measuring
+ * pass is implemented in {@link #measure(int, int)} and is a top-down traversal
+ * of the view tree. Each view pushes dimension specifications down the tree
+ * during the recursion. At the end of the measure pass, every view has stored
+ * its measurements. The second pass happens in
+ * {@link #layout(int,int,int,int)} and is also top-down. During
+ * this pass each parent is responsible for positioning all of its children
+ * using the sizes computed in the measure pass.
+ * </p>
+ *
+ * <p>
+ * When a view's measure() method returns, its {@link #getMeasuredWidth()} and
+ * {@link #getMeasuredHeight()} values must be set, along with those for all of
+ * that view's descendants. A view's measured width and measured height values
+ * must respect the constraints imposed by the view's parents. This guarantees
+ * that at the end of the measure pass, all parents accept all of their
+ * children's measurements. A parent view may call measure() more than once on
+ * its children. For example, the parent may measure each child once with
+ * unspecified dimensions to find out how big they want to be, then call
+ * measure() on them again with actual numbers if the sum of all the children's
+ * unconstrained sizes is too big or too small.
+ * </p>
+ *
+ * <p>
+ * The measure pass uses two classes to communicate dimensions. The
+ * {@link MeasureSpec} class is used by views to tell their parents how they
+ * want to be measured and positioned. The base LayoutParams class just
+ * describes how big the view wants to be for both width and height. For each
+ * dimension, it can specify one of:
+ * <ul>
+ * <li> an exact number
+ * <li>MATCH_PARENT, which means the view wants to be as big as its parent
+ * (minus padding)
+ * <li> WRAP_CONTENT, which means that the view wants to be just big enough to
+ * enclose its content (plus padding).
+ * </ul>
+ * There are subclasses of LayoutParams for different subclasses of ViewGroup.
+ * For example, AbsoluteLayout has its own subclass of LayoutParams which adds
+ * an X and Y value.
+ * </p>
+ *
+ * <p>
+ * MeasureSpecs are used to push requirements down the tree from parent to
+ * child. A MeasureSpec can be in one of three modes:
+ * <ul>
+ * <li>UNSPECIFIED: This is used by a parent to determine the desired dimension
+ * of a child view. For example, a LinearLayout may call measure() on its child
+ * with the height set to UNSPECIFIED and a width of EXACTLY 240 to find out how
+ * tall the child view wants to be given a width of 240 pixels.
+ * <li>EXACTLY: This is used by the parent to impose an exact size on the
+ * child. The child must use this size, and guarantee that all of its
+ * descendants will fit within this size.
+ * <li>AT_MOST: This is used by the parent to impose a maximum size on the
+ * child. The child must guarantee that it and all of its descendants will fit
+ * within this size.
+ * </ul>
+ * </p>
+ *
+ * <p>
+ * To initiate a layout, call {@link #requestLayout}. This method is typically
+ * called by a view on itself when it believes that it can no longer fit within
+ * its current bounds.
+ * </p>
+ *
+ * <a name="Drawing"></a>
+ * <h3>Drawing</h3>
+ * <p>
+ * Drawing is handled by walking the tree and recording the drawing commands of
+ * any View that needs to update. After this, the drawing commands of the
+ * entire tree are issued to screen, clipped to the newly damaged area.
+ * </p>
+ *
+ * <p>
+ * The tree is largely recorded and drawn in order, with parents drawn before
+ * (i.e., behind) their children, with siblings drawn in the order they appear
+ * in the tree. If you set a background drawable for a View, then the View will
+ * draw it before calling back to its <code>onDraw()</code> method. The child
+ * drawing order can be overridden with
+ * {@link ViewGroup#setChildrenDrawingOrderEnabled(boolean) custom child drawing order}
+ * in a ViewGroup, and with {@link #setZ(float)} custom Z values} set on Views.
+ * </p>
+ *
+ * <p>
+ * To force a view to draw, call {@link #invalidate()}.
+ * </p>
+ *
+ * <a name="EventHandlingThreading"></a>
+ * <h3>Event Handling and Threading</h3>
+ * <p>
+ * The basic cycle of a view is as follows:
+ * <ol>
+ * <li>An event comes in and is dispatched to the appropriate view. The view
+ * handles the event and notifies any listeners.</li>
+ * <li>If in the course of processing the event, the view's bounds may need
+ * to be changed, the view will call {@link #requestLayout()}.</li>
+ * <li>Similarly, if in the course of processing the event the view's appearance
+ * may need to be changed, the view will call {@link #invalidate()}.</li>
+ * <li>If either {@link #requestLayout()} or {@link #invalidate()} were called,
+ * the framework will take care of measuring, laying out, and drawing the tree
+ * as appropriate.</li>
+ * </ol>
+ * </p>
+ *
+ * <p><em>Note: The entire view tree is single threaded. You must always be on
+ * the UI thread when calling any method on any view.</em>
+ * If you are doing work on other threads and want to update the state of a view
+ * from that thread, you should use a {@link Handler}.
+ * </p>
+ *
+ * <a name="FocusHandling"></a>
+ * <h3>Focus Handling</h3>
+ * <p>
+ * The framework will handle routine focus movement in response to user input.
+ * This includes changing the focus as views are removed or hidden, or as new
+ * views become available. Views indicate their willingness to take focus
+ * through the {@link #isFocusable} method. To change whether a view can take
+ * focus, call {@link #setFocusable(boolean)}.  When in touch mode (see notes below)
+ * views indicate whether they still would like focus via {@link #isFocusableInTouchMode}
+ * and can change this via {@link #setFocusableInTouchMode(boolean)}.
+ * </p>
+ * <p>
+ * Focus movement is based on an algorithm which finds the nearest neighbor in a
+ * given direction. In rare cases, the default algorithm may not match the
+ * intended behavior of the developer. In these situations, you can provide
+ * explicit overrides by using these XML attributes in the layout file:
+ * <pre>
+ * nextFocusDown
+ * nextFocusLeft
+ * nextFocusRight
+ * nextFocusUp
+ * </pre>
+ * </p>
+ *
+ *
+ * <p>
+ * To get a particular view to take focus, call {@link #requestFocus()}.
+ * </p>
+ *
+ * <a name="TouchMode"></a>
+ * <h3>Touch Mode</h3>
+ * <p>
+ * When a user is navigating a user interface via directional keys such as a D-pad, it is
+ * necessary to give focus to actionable items such as buttons so the user can see
+ * what will take input.  If the device has touch capabilities, however, and the user
+ * begins interacting with the interface by touching it, it is no longer necessary to
+ * always highlight, or give focus to, a particular view.  This motivates a mode
+ * for interaction named 'touch mode'.
+ * </p>
+ * <p>
+ * For a touch capable device, once the user touches the screen, the device
+ * will enter touch mode.  From this point onward, only views for which
+ * {@link #isFocusableInTouchMode} is true will be focusable, such as text editing widgets.
+ * Other views that are touchable, like buttons, will not take focus when touched; they will
+ * only fire the on click listeners.
+ * </p>
+ * <p>
+ * Any time a user hits a directional key, such as a D-pad direction, the view device will
+ * exit touch mode, and find a view to take focus, so that the user may resume interacting
+ * with the user interface without touching the screen again.
+ * </p>
+ * <p>
+ * The touch mode state is maintained across {@link android.app.Activity}s.  Call
+ * {@link #isInTouchMode} to see whether the device is currently in touch mode.
+ * </p>
+ *
+ * <a name="Scrolling"></a>
+ * <h3>Scrolling</h3>
+ * <p>
+ * The framework provides basic support for views that wish to internally
+ * scroll their content. This includes keeping track of the X and Y scroll
+ * offset as well as mechanisms for drawing scrollbars. See
+ * {@link #scrollBy(int, int)}, {@link #scrollTo(int, int)}, and
+ * {@link #awakenScrollBars()} for more details.
+ * </p>
+ *
+ * <a name="Tags"></a>
+ * <h3>Tags</h3>
+ * <p>
+ * Unlike IDs, tags are not used to identify views. Tags are essentially an
+ * extra piece of information that can be associated with a view. They are most
+ * often used as a convenience to store data related to views in the views
+ * themselves rather than by putting them in a separate structure.
+ * </p>
+ * <p>
+ * Tags may be specified with character sequence values in layout XML as either
+ * a single tag using the {@link android.R.styleable#View_tag android:tag}
+ * attribute or multiple tags using the {@code <tag>} child element:
+ * <pre>
+ *     &lt;View ...
+ *           android:tag="@string/mytag_value" /&gt;
+ *     &lt;View ...&gt;
+ *         &lt;tag android:id="@+id/mytag"
+ *              android:value="@string/mytag_value" /&gt;
+ *     &lt;/View>
+ * </pre>
+ * </p>
+ * <p>
+ * Tags may also be specified with arbitrary objects from code using
+ * {@link #setTag(Object)} or {@link #setTag(int, Object)}.
+ * </p>
+ *
+ * <a name="Themes"></a>
+ * <h3>Themes</h3>
+ * <p>
+ * By default, Views are created using the theme of the Context object supplied
+ * to their constructor; however, a different theme may be specified by using
+ * the {@link android.R.styleable#View_theme android:theme} attribute in layout
+ * XML or by passing a {@link ContextThemeWrapper} to the constructor from
+ * code.
+ * </p>
+ * <p>
+ * When the {@link android.R.styleable#View_theme android:theme} attribute is
+ * used in XML, the specified theme is applied on top of the inflation
+ * context's theme (see {@link LayoutInflater}) and used for the view itself as
+ * well as any child elements.
+ * </p>
+ * <p>
+ * In the following example, both views will be created using the Material dark
+ * color scheme; however, because an overlay theme is used which only defines a
+ * subset of attributes, the value of
+ * {@link android.R.styleable#Theme_colorAccent android:colorAccent} defined on
+ * the inflation context's theme (e.g. the Activity theme) will be preserved.
+ * <pre>
+ *     &lt;LinearLayout
+ *             ...
+ *             android:theme="@android:theme/ThemeOverlay.Material.Dark"&gt;
+ *         &lt;View ...&gt;
+ *     &lt;/LinearLayout&gt;
+ * </pre>
+ * </p>
+ *
+ * <a name="Properties"></a>
+ * <h3>Properties</h3>
+ * <p>
+ * The View class exposes an {@link #ALPHA} property, as well as several transform-related
+ * properties, such as {@link #TRANSLATION_X} and {@link #TRANSLATION_Y}. These properties are
+ * available both in the {@link Property} form as well as in similarly-named setter/getter
+ * methods (such as {@link #setAlpha(float)} for {@link #ALPHA}). These properties can
+ * be used to set persistent state associated with these rendering-related properties on the view.
+ * The properties and methods can also be used in conjunction with
+ * {@link android.animation.Animator Animator}-based animations, described more in the
+ * <a href="#Animation">Animation</a> section.
+ * </p>
+ *
+ * <a name="Animation"></a>
+ * <h3>Animation</h3>
+ * <p>
+ * Starting with Android 3.0, the preferred way of animating views is to use the
+ * {@link android.animation} package APIs. These {@link android.animation.Animator Animator}-based
+ * classes change actual properties of the View object, such as {@link #setAlpha(float) alpha} and
+ * {@link #setTranslationX(float) translationX}. This behavior is contrasted to that of the pre-3.0
+ * {@link android.view.animation.Animation Animation}-based classes, which instead animate only
+ * how the view is drawn on the display. In particular, the {@link ViewPropertyAnimator} class
+ * makes animating these View properties particularly easy and efficient.
+ * </p>
+ * <p>
+ * Alternatively, you can use the pre-3.0 animation classes to animate how Views are rendered.
+ * You can attach an {@link Animation} object to a view using
+ * {@link #setAnimation(Animation)} or
+ * {@link #startAnimation(Animation)}. The animation can alter the scale,
+ * rotation, translation and alpha of a view over time. If the animation is
+ * attached to a view that has children, the animation will affect the entire
+ * subtree rooted by that node. When an animation is started, the framework will
+ * take care of redrawing the appropriate views until the animation completes.
+ * </p>
+ *
+ * <a name="Security"></a>
+ * <h3>Security</h3>
+ * <p>
+ * Sometimes it is essential that an application be able to verify that an action
+ * is being performed with the full knowledge and consent of the user, such as
+ * granting a permission request, making a purchase or clicking on an advertisement.
+ * Unfortunately, a malicious application could try to spoof the user into
+ * performing these actions, unaware, by concealing the intended purpose of the view.
+ * As a remedy, the framework offers a touch filtering mechanism that can be used to
+ * improve the security of views that provide access to sensitive functionality.
+ * </p><p>
+ * To enable touch filtering, call {@link #setFilterTouchesWhenObscured(boolean)} or set the
+ * android:filterTouchesWhenObscured layout attribute to true.  When enabled, the framework
+ * will discard touches that are received whenever the view's window is obscured by
+ * another visible window.  As a result, the view will not receive touches whenever a
+ * toast, dialog or other window appears above the view's window.
+ * </p><p>
+ * For more fine-grained control over security, consider overriding the
+ * {@link #onFilterTouchEventForSecurity(MotionEvent)} method to implement your own
+ * security policy. See also {@link MotionEvent#FLAG_WINDOW_IS_OBSCURED}.
+ * </p>
+ *
+ * @attr ref android.R.styleable#View_accessibilityHeading
+ * @attr ref android.R.styleable#View_allowClickWhenDisabled
+ * @attr ref android.R.styleable#View_alpha
+ * @attr ref android.R.styleable#View_background
+ * @attr ref android.R.styleable#View_clickable
+ * @attr ref android.R.styleable#View_clipToOutline
+ * @attr ref android.R.styleable#View_contentDescription
+ * @attr ref android.R.styleable#View_drawingCacheQuality
+ * @attr ref android.R.styleable#View_duplicateParentState
+ * @attr ref android.R.styleable#View_id
+ * @attr ref android.R.styleable#View_requiresFadingEdge
+ * @attr ref android.R.styleable#View_fadeScrollbars
+ * @attr ref android.R.styleable#View_fadingEdgeLength
+ * @attr ref android.R.styleable#View_filterTouchesWhenObscured
+ * @attr ref android.R.styleable#View_fitsSystemWindows
+ * @attr ref android.R.styleable#View_isScrollContainer
+ * @attr ref android.R.styleable#View_focusable
+ * @attr ref android.R.styleable#View_focusableInTouchMode
+ * @attr ref android.R.styleable#View_focusedByDefault
+ * @attr ref android.R.styleable#View_hapticFeedbackEnabled
+ * @attr ref android.R.styleable#View_keepScreenOn
+ * @attr ref android.R.styleable#View_keyboardNavigationCluster
+ * @attr ref android.R.styleable#View_layerType
+ * @attr ref android.R.styleable#View_layoutDirection
+ * @attr ref android.R.styleable#View_longClickable
+ * @attr ref android.R.styleable#View_minHeight
+ * @attr ref android.R.styleable#View_minWidth
+ * @attr ref android.R.styleable#View_nextClusterForward
+ * @attr ref android.R.styleable#View_nextFocusDown
+ * @attr ref android.R.styleable#View_nextFocusLeft
+ * @attr ref android.R.styleable#View_nextFocusRight
+ * @attr ref android.R.styleable#View_nextFocusUp
+ * @attr ref android.R.styleable#View_onClick
+ * @attr ref android.R.styleable#View_outlineSpotShadowColor
+ * @attr ref android.R.styleable#View_outlineAmbientShadowColor
+ * @attr ref android.R.styleable#View_padding
+ * @attr ref android.R.styleable#View_paddingHorizontal
+ * @attr ref android.R.styleable#View_paddingVertical
+ * @attr ref android.R.styleable#View_paddingBottom
+ * @attr ref android.R.styleable#View_paddingLeft
+ * @attr ref android.R.styleable#View_paddingRight
+ * @attr ref android.R.styleable#View_paddingTop
+ * @attr ref android.R.styleable#View_paddingStart
+ * @attr ref android.R.styleable#View_paddingEnd
+ * @attr ref android.R.styleable#View_saveEnabled
+ * @attr ref android.R.styleable#View_rotation
+ * @attr ref android.R.styleable#View_rotationX
+ * @attr ref android.R.styleable#View_rotationY
+ * @attr ref android.R.styleable#View_scaleX
+ * @attr ref android.R.styleable#View_scaleY
+ * @attr ref android.R.styleable#View_scrollX
+ * @attr ref android.R.styleable#View_scrollY
+ * @attr ref android.R.styleable#View_scrollbarSize
+ * @attr ref android.R.styleable#View_scrollbarStyle
+ * @attr ref android.R.styleable#View_scrollbars
+ * @attr ref android.R.styleable#View_scrollbarDefaultDelayBeforeFade
+ * @attr ref android.R.styleable#View_scrollbarFadeDuration
+ * @attr ref android.R.styleable#View_scrollbarTrackHorizontal
+ * @attr ref android.R.styleable#View_scrollbarThumbHorizontal
+ * @attr ref android.R.styleable#View_scrollbarThumbVertical
+ * @attr ref android.R.styleable#View_scrollbarTrackVertical
+ * @attr ref android.R.styleable#View_scrollbarAlwaysDrawHorizontalTrack
+ * @attr ref android.R.styleable#View_scrollbarAlwaysDrawVerticalTrack
+ * @attr ref android.R.styleable#View_stateListAnimator
+ * @attr ref android.R.styleable#View_transitionName
+ * @attr ref android.R.styleable#View_soundEffectsEnabled
+ * @attr ref android.R.styleable#View_tag
+ * @attr ref android.R.styleable#View_textAlignment
+ * @attr ref android.R.styleable#View_textDirection
+ * @attr ref android.R.styleable#View_transformPivotX
+ * @attr ref android.R.styleable#View_transformPivotY
+ * @attr ref android.R.styleable#View_translationX
+ * @attr ref android.R.styleable#View_translationY
+ * @attr ref android.R.styleable#View_translationZ
+ * @attr ref android.R.styleable#View_visibility
+ * @attr ref android.R.styleable#View_theme
+ *
+ * @see android.view.ViewGroup
+ */
+@UiThread
+public class View implements Drawable.Callback, KeyEvent.Callback,
+        AccessibilityEventSource {
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private static final boolean DBG = false;
+
+    /** @hide */
+    public static boolean DEBUG_DRAW = false;
+
+    /**
+     * The logging tag used by this class with android.util.Log.
+     */
+    protected static final String VIEW_LOG_TAG = "View";
+
+    /**
+     * The logging tag used by this class when logging verbose, autofill-related messages.
+     */
+    // NOTE: We cannot use android.view.autofill.Helper.sVerbose because that variable is not
+    // set if a session is not started.
+    private static final String AUTOFILL_LOG_TAG = "View.Autofill";
+
+    /**
+     * The logging tag used by this class when logging content capture-related messages.
+     */
+    private static final String CONTENT_CAPTURE_LOG_TAG = "View.ContentCapture";
+
+    private static final boolean DEBUG_CONTENT_CAPTURE = false;
+
+    /**
+     * When set to true, this view will save its attribute data.
+     *
+     * @hide
+     */
+    public static boolean sDebugViewAttributes = false;
+
+    /**
+     * When set to this application package view will save its attribute data.
+     *
+     * @hide
+     */
+    public static String sDebugViewAttributesApplicationPackage;
+
+    /**
+     * Used to mark a View that has no ID.
+     */
+    public static final int NO_ID = -1;
+
+    /**
+     * Last ID that is given to Views that are no part of activities.
+     *
+     * {@hide}
+     */
+    public static final int LAST_APP_AUTOFILL_ID = Integer.MAX_VALUE / 2;
+
+    /**
+     * Attribute to find the autofilled highlight
+     *
+     * @see #getAutofilledDrawable()
+     */
+    private static final int[] AUTOFILL_HIGHLIGHT_ATTR =
+            new int[]{android.R.attr.autofilledHighlight};
+
+    /**
+     * Signals that compatibility booleans have been initialized according to
+     * target SDK versions.
+     */
+    private static boolean sCompatibilityDone = false;
+
+    /**
+     * Use the old (broken) way of building MeasureSpecs.
+     */
+    private static boolean sUseBrokenMakeMeasureSpec = false;
+
+    /**
+     * Always return a size of 0 for MeasureSpec values with a mode of UNSPECIFIED
+     */
+    static boolean sUseZeroUnspecifiedMeasureSpec = false;
+
+    /**
+     * Ignore any optimizations using the measure cache.
+     */
+    private static boolean sIgnoreMeasureCache = false;
+
+    /**
+     * Ignore an optimization that skips unnecessary EXACTLY layout passes.
+     */
+    private static boolean sAlwaysRemeasureExactly = false;
+
+    /**
+     * Allow setForeground/setBackground to be called (and ignored) on a textureview,
+     * without throwing
+     */
+    static boolean sTextureViewIgnoresDrawableSetters = false;
+
+    /**
+     * Prior to N, some ViewGroups would not convert LayoutParams properly even though both extend
+     * MarginLayoutParams. For instance, converting LinearLayout.LayoutParams to
+     * RelativeLayout.LayoutParams would lose margin information. This is fixed on N but target API
+     * check is implemented for backwards compatibility.
+     *
+     * {@hide}
+     */
+    protected static boolean sPreserveMarginParamsInLayoutParamConversion;
+
+    /**
+     * Prior to N, when drag enters into child of a view that has already received an
+     * ACTION_DRAG_ENTERED event, the parent doesn't get a ACTION_DRAG_EXITED event.
+     * ACTION_DRAG_LOCATION and ACTION_DROP were delivered to the parent of a view that returned
+     * false from its event handler for these events.
+     * Starting from N, the parent will get ACTION_DRAG_EXITED event before the child gets its
+     * ACTION_DRAG_ENTERED. ACTION_DRAG_LOCATION and ACTION_DROP are never propagated to the parent.
+     * sCascadedDragDrop is true for pre-N apps for backwards compatibility implementation.
+     */
+    static boolean sCascadedDragDrop;
+
+    /**
+     * Prior to O, auto-focusable didn't exist and widgets such as ListView use hasFocusable
+     * to determine things like whether or not to permit item click events. We can't break
+     * apps that do this just because more things (clickable things) are now auto-focusable
+     * and they would get different results, so give old behavior to old apps.
+     */
+    static boolean sHasFocusableExcludeAutoFocusable;
+
+    /**
+     * Prior to O, auto-focusable didn't exist and views marked as clickable weren't implicitly
+     * made focusable by default. As a result, apps could (incorrectly) change the clickable
+     * setting of views off the UI thread. Now that clickable can effect the focusable state,
+     * changing the clickable attribute off the UI thread will cause an exception (since changing
+     * the focusable state checks). In order to prevent apps from crashing, we will handle this
+     * specific case and just not notify parents on new focusables resulting from marking views
+     * clickable from outside the UI thread.
+     */
+    private static boolean sAutoFocusableOffUIThreadWontNotifyParents;
+
+    /**
+     * Prior to P things like setScaleX() allowed passing float values that were bogus such as
+     * Float.NaN. If the app is targetting P or later then passing these values will result in an
+     * exception being thrown. If the app is targetting an earlier SDK version, then we will
+     * silently clamp these values to avoid crashes elsewhere when the rendering code hits
+     * these bogus values.
+     */
+    private static boolean sThrowOnInvalidFloatProperties;
+
+    /**
+     * Prior to P, {@code #startDragAndDrop} accepts a builder which produces an empty drag shadow.
+     * Currently zero size SurfaceControl cannot be created thus we create a 1x1 surface instead.
+     */
+    private static boolean sAcceptZeroSizeDragShadow;
+
+    /**
+     * Prior to R, {@link #dispatchApplyWindowInsets} had an issue:
+     * <p>The modified insets changed by {@link #onApplyWindowInsets} were passed to the
+     * entire view hierarchy in prefix order, including siblings as well as siblings of parents
+     * further down the hierarchy. This violates the basic concepts of the view hierarchy, and
+     * thus, the hierarchical dispatching mechanism was hard to use for apps.
+     * <p>
+     * In order to make window inset dispatching work properly, we dispatch window insets
+     * in the view hierarchy in a proper hierarchical manner if this flag is set to {@code false}.
+     */
+    static boolean sBrokenInsetsDispatch;
+
+    /**
+     * Prior to Q, calling
+     * {@link com.android.internal.policy.DecorView#setBackgroundDrawable(Drawable)}
+     * did not call update the window format so the opacity of the background was not correctly
+     * applied to the window. Some applications rely on this misbehavior to work properly.
+     * <p>
+     * From Q, {@link com.android.internal.policy.DecorView#setBackgroundDrawable(Drawable)} is
+     * the same as {@link com.android.internal.policy.DecorView#setWindowBackground(Drawable)}
+     * which updates the window format.
+     * @hide
+     */
+    protected static boolean sBrokenWindowBackground;
+
+    /**
+     * Prior to R, we were always forcing a layout of the entire hierarchy when insets changed from
+     * the server. This is inefficient and not all apps use it. Instead, we want to rely on apps
+     * calling {@link #requestLayout} when they need to relayout based on an insets change.
+     */
+    static boolean sForceLayoutWhenInsetsChanged;
+
+    /** @hide */
+    @IntDef({NOT_FOCUSABLE, FOCUSABLE, FOCUSABLE_AUTO})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Focusable {}
+
+    /**
+     * This view does not want keystrokes.
+     * <p>
+     * Use with {@link #setFocusable(int)} and <a href="#attr_android:focusable">{@code
+     * android:focusable}.
+     */
+    public static final int NOT_FOCUSABLE = 0x00000000;
+
+    /**
+     * This view wants keystrokes.
+     * <p>
+     * Use with {@link #setFocusable(int)} and <a href="#attr_android:focusable">{@code
+     * android:focusable}.
+     */
+    public static final int FOCUSABLE = 0x00000001;
+
+    /**
+     * This view determines focusability automatically. This is the default.
+     * <p>
+     * Use with {@link #setFocusable(int)} and <a href="#attr_android:focusable">{@code
+     * android:focusable}.
+     */
+    public static final int FOCUSABLE_AUTO = 0x00000010;
+
+    /**
+     * Mask for use with setFlags indicating bits used for focus.
+     */
+    private static final int FOCUSABLE_MASK = 0x00000011;
+
+    /**
+     * This view will adjust its padding to fit sytem windows (e.g. status bar)
+     */
+    private static final int FITS_SYSTEM_WINDOWS = 0x00000002;
+
+    /** @hide */
+    @IntDef({VISIBLE, INVISIBLE, GONE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Visibility {}
+
+    /**
+     * This view is visible.
+     * Use with {@link #setVisibility} and <a href="#attr_android:visibility">{@code
+     * android:visibility}.
+     */
+    public static final int VISIBLE = 0x00000000;
+
+    /**
+     * This view is invisible, but it still takes up space for layout purposes.
+     * Use with {@link #setVisibility} and <a href="#attr_android:visibility">{@code
+     * android:visibility}.
+     */
+    public static final int INVISIBLE = 0x00000004;
+
+    /**
+     * This view is invisible, and it doesn't take any space for layout
+     * purposes. Use with {@link #setVisibility} and <a href="#attr_android:visibility">{@code
+     * android:visibility}.
+     */
+    public static final int GONE = 0x00000008;
+
+    /**
+     * Mask for use with setFlags indicating bits used for visibility.
+     * {@hide}
+     */
+    static final int VISIBILITY_MASK = 0x0000000C;
+
+    private static final int[] VISIBILITY_FLAGS = {VISIBLE, INVISIBLE, GONE};
+
+    /**
+     * Hint indicating that this view can be autofilled with an email address.
+     *
+     * <p>Can be used with either {@link #setAutofillHints(String[])} or
+     * <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
+     * value should be <code>{@value #AUTOFILL_HINT_EMAIL_ADDRESS}</code>).
+     *
+     * <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
+     */
+    public static final String AUTOFILL_HINT_EMAIL_ADDRESS = "emailAddress";
+
+    /**
+     * Hint indicating that this view can be autofilled with a user's real name.
+     *
+     * <p>Can be used with either {@link #setAutofillHints(String[])} or
+     * <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
+     * value should be <code>{@value #AUTOFILL_HINT_NAME}</code>).
+     *
+     * <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
+     */
+    public static final String AUTOFILL_HINT_NAME = "name";
+
+    /**
+     * Hint indicating that this view can be autofilled with a username.
+     *
+     * <p>Can be used with either {@link #setAutofillHints(String[])} or
+     * <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
+     * value should be <code>{@value #AUTOFILL_HINT_USERNAME}</code>).
+     *
+     * <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
+     */
+    public static final String AUTOFILL_HINT_USERNAME = "username";
+
+    /**
+     * Hint indicating that this view can be autofilled with a password.
+     *
+     * <p>Can be used with either {@link #setAutofillHints(String[])} or
+     * <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
+     * value should be <code>{@value #AUTOFILL_HINT_PASSWORD}</code>).
+     *
+     * <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
+     */
+    public static final String AUTOFILL_HINT_PASSWORD = "password";
+
+    /**
+     * Hint indicating that this view can be autofilled with a phone number.
+     *
+     * <p>Can be used with either {@link #setAutofillHints(String[])} or
+     * <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
+     * value should be <code>{@value #AUTOFILL_HINT_PHONE}</code>).
+     *
+     * <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
+     */
+    public static final String AUTOFILL_HINT_PHONE = "phone";
+
+    /**
+     * Hint indicating that this view can be autofilled with a postal address.
+     *
+     * <p>Can be used with either {@link #setAutofillHints(String[])} or
+     * <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
+     * value should be <code>{@value #AUTOFILL_HINT_POSTAL_ADDRESS}</code>).
+     *
+     * <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
+     */
+    public static final String AUTOFILL_HINT_POSTAL_ADDRESS = "postalAddress";
+
+    /**
+     * Hint indicating that this view can be autofilled with a postal code.
+     *
+     * <p>Can be used with either {@link #setAutofillHints(String[])} or
+     * <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
+     * value should be <code>{@value #AUTOFILL_HINT_POSTAL_CODE}</code>).
+     *
+     * <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
+     */
+    public static final String AUTOFILL_HINT_POSTAL_CODE = "postalCode";
+
+    /**
+     * Hint indicating that this view can be autofilled with a credit card number.
+     *
+     * <p>Can be used with either {@link #setAutofillHints(String[])} or
+     * <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
+     * value should be <code>{@value #AUTOFILL_HINT_CREDIT_CARD_NUMBER}</code>).
+     *
+     * <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
+     */
+    public static final String AUTOFILL_HINT_CREDIT_CARD_NUMBER = "creditCardNumber";
+
+    /**
+     * Hint indicating that this view can be autofilled with a credit card security code.
+     *
+     * <p>Can be used with either {@link #setAutofillHints(String[])} or
+     * <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
+     * value should be <code>{@value #AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE}</code>).
+     *
+     * <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
+     */
+    public static final String AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = "creditCardSecurityCode";
+
+    /**
+     * Hint indicating that this view can be autofilled with a credit card expiration date.
+     *
+     * <p>It should be used when the credit card expiration date is represented by just one view;
+     * if it is represented by more than one (for example, one view for the month and another view
+     * for the year), then each of these views should use the hint specific for the unit
+     * ({@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY},
+     * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH},
+     * or {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR}).
+     *
+     * <p>Can be used with either {@link #setAutofillHints(String[])} or
+     * <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
+     * value should be <code>{@value #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE}</code>).
+     *
+     * <p>When annotating a view with this hint, it's recommended to use a date autofill value to
+     * avoid ambiguity when the autofill service provides a value for it. To understand why a
+     * value can be ambiguous, consider "April of 2020", which could be represented as either of
+     * the following options:
+     *
+     * <ul>
+     *   <li>{@code "04/2020"}
+     *   <li>{@code "4/2020"}
+     *   <li>{@code "2020/04"}
+     *   <li>{@code "2020/4"}
+     *   <li>{@code "April/2020"}
+     *   <li>{@code "Apr/2020"}
+     * </ul>
+     *
+     * <p>You define a date autofill value for the view by overriding the following methods:
+     *
+     * <ol>
+     *   <li>{@link #getAutofillType()} to return {@link #AUTOFILL_TYPE_DATE}.
+     *   <li>{@link #getAutofillValue()} to return a
+     *       {@link AutofillValue#forDate(long) date autofillvalue}.
+     *   <li>{@link #autofill(AutofillValue)} to expect a data autofillvalue.
+     * </ol>
+     *
+     * <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
+     */
+    public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE =
+            "creditCardExpirationDate";
+
+    /**
+     * Hint indicating that this view can be autofilled with a credit card expiration month.
+     *
+     * <p>Can be used with either {@link #setAutofillHints(String[])} or
+     * <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
+     * value should be <code>{@value #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH}</code>).
+     *
+     * <p>When annotating a view with this hint, it's recommended to use a text autofill value
+     * whose value is the numerical representation of the month, starting on {@code 1} to avoid
+     * ambiguity when the autofill service provides a value for it. To understand why a
+     * value can be ambiguous, consider "January", which could be represented as either of
+     *
+     * <ul>
+     *   <li>{@code "1"}: recommended way.
+     *   <li>{@code "0"}: if following the {@link Calendar#MONTH} convention.
+     *   <li>{@code "January"}: full name, in English.
+     *   <li>{@code "jan"}: abbreviated name, in English.
+     *   <li>{@code "Janeiro"}: full name, in another language.
+     * </ul>
+     *
+     * <p>Another recommended approach is to use a date autofill value - see
+     * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE} for more details.
+     *
+     * <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
+     */
+    public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH =
+            "creditCardExpirationMonth";
+
+    /**
+     * Hint indicating that this view can be autofilled with a credit card expiration year.
+     *
+     * <p>Can be used with either {@link #setAutofillHints(String[])} or
+     * <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
+     * value should be <code>{@value #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR}</code>).
+     *
+     * <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
+     */
+    public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR =
+            "creditCardExpirationYear";
+
+    /**
+     * Hint indicating that this view can be autofilled with a credit card expiration day.
+     *
+     * <p>Can be used with either {@link #setAutofillHints(String[])} or
+     * <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
+     * value should be <code>{@value #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY}</code>).
+     *
+     * <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
+     */
+    public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY = "creditCardExpirationDay";
+
+    /**
+     * Hints for the autofill services that describes the content of the view.
+     */
+    private @Nullable String[] mAutofillHints;
+
+    /**
+     * Autofill id, lazily created on calls to {@link #getAutofillId()}.
+     */
+    private AutofillId mAutofillId;
+
+    /** @hide */
+    @IntDef(prefix = { "AUTOFILL_TYPE_" }, value = {
+            AUTOFILL_TYPE_NONE,
+            AUTOFILL_TYPE_TEXT,
+            AUTOFILL_TYPE_TOGGLE,
+            AUTOFILL_TYPE_LIST,
+            AUTOFILL_TYPE_DATE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AutofillType {}
+
+    /**
+     * Autofill type for views that cannot be autofilled.
+     *
+     * <p>Typically used when the view is read-only; for example, a text label.
+     *
+     * @see #getAutofillType()
+     */
+    public static final int AUTOFILL_TYPE_NONE = 0;
+
+    /**
+     * Autofill type for a text field, which is filled by a {@link CharSequence}.
+     *
+     * <p>{@link AutofillValue} instances for autofilling a {@link View} can be obtained through
+     * {@link AutofillValue#forText(CharSequence)}, and the value passed to autofill a
+     * {@link View} can be fetched through {@link AutofillValue#getTextValue()}.
+     *
+     * @see #getAutofillType()
+     */
+    public static final int AUTOFILL_TYPE_TEXT = 1;
+
+    /**
+     * Autofill type for a togglable field, which is filled by a {@code boolean}.
+     *
+     * <p>{@link AutofillValue} instances for autofilling a {@link View} can be obtained through
+     * {@link AutofillValue#forToggle(boolean)}, and the value passed to autofill a
+     * {@link View} can be fetched through {@link AutofillValue#getToggleValue()}.
+     *
+     * @see #getAutofillType()
+     */
+    public static final int AUTOFILL_TYPE_TOGGLE = 2;
+
+    /**
+     * Autofill type for a selection list field, which is filled by an {@code int}
+     * representing the element index inside the list (starting at {@code 0}).
+     *
+     * <p>{@link AutofillValue} instances for autofilling a {@link View} can be obtained through
+     * {@link AutofillValue#forList(int)}, and the value passed to autofill a
+     * {@link View} can be fetched through {@link AutofillValue#getListValue()}.
+     *
+     * <p>The available options in the selection list are typically provided by
+     * {@link android.app.assist.AssistStructure.ViewNode#getAutofillOptions()}.
+     *
+     * @see #getAutofillType()
+     */
+    public static final int AUTOFILL_TYPE_LIST = 3;
+
+    /**
+     * Autofill type for a field that contains a date, which is represented by a long representing
+     * the number of milliseconds since the standard base time known as "the epoch", namely
+     * January 1, 1970, 00:00:00 GMT (see {@link java.util.Date#getTime()}.
+     *
+     * <p>{@link AutofillValue} instances for autofilling a {@link View} can be obtained through
+     * {@link AutofillValue#forDate(long)}, and the values passed to
+     * autofill a {@link View} can be fetched through {@link AutofillValue#getDateValue()}.
+     *
+     * @see #getAutofillType()
+     */
+    public static final int AUTOFILL_TYPE_DATE = 4;
+
+
+    /** @hide */
+    @IntDef(prefix = { "IMPORTANT_FOR_AUTOFILL_" }, value = {
+            IMPORTANT_FOR_AUTOFILL_AUTO,
+            IMPORTANT_FOR_AUTOFILL_YES,
+            IMPORTANT_FOR_AUTOFILL_NO,
+            IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS,
+            IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AutofillImportance {}
+
+    /**
+     * Automatically determine whether a view is important for autofill.
+     *
+     * @see #isImportantForAutofill()
+     * @see #setImportantForAutofill(int)
+     */
+    public static final int IMPORTANT_FOR_AUTOFILL_AUTO = 0x0;
+
+    /**
+     * The view is important for autofill, and its children (if any) will be traversed.
+     *
+     * @see #isImportantForAutofill()
+     * @see #setImportantForAutofill(int)
+     */
+    public static final int IMPORTANT_FOR_AUTOFILL_YES = 0x1;
+
+    /**
+     * The view is not important for autofill, but its children (if any) will be traversed.
+     *
+     * @see #isImportantForAutofill()
+     * @see #setImportantForAutofill(int)
+     */
+    public static final int IMPORTANT_FOR_AUTOFILL_NO = 0x2;
+
+    /**
+     * The view is important for autofill, but its children (if any) will not be traversed.
+     *
+     * @see #isImportantForAutofill()
+     * @see #setImportantForAutofill(int)
+     */
+    public static final int IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS = 0x4;
+
+    /**
+     * The view is not important for autofill, and its children (if any) will not be traversed.
+     *
+     * @see #isImportantForAutofill()
+     * @see #setImportantForAutofill(int)
+     */
+    public static final int IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS = 0x8;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "AUTOFILL_FLAG_" }, value = {
+            AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AutofillFlags {}
+
+    /**
+     * Flag requesting you to add views that are marked as not important for autofill
+     * (see {@link #setImportantForAutofill(int)}) to a {@link ViewStructure}.
+     */
+    public static final int AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x1;
+
+    /** @hide */
+    @IntDef(prefix = { "IMPORTANT_FOR_CONTENT_CAPTURE_" }, value = {
+            IMPORTANT_FOR_CONTENT_CAPTURE_AUTO,
+            IMPORTANT_FOR_CONTENT_CAPTURE_YES,
+            IMPORTANT_FOR_CONTENT_CAPTURE_NO,
+            IMPORTANT_FOR_CONTENT_CAPTURE_YES_EXCLUDE_DESCENDANTS,
+            IMPORTANT_FOR_CONTENT_CAPTURE_NO_EXCLUDE_DESCENDANTS
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ContentCaptureImportance {}
+
+    /**
+     * Automatically determine whether a view is important for content capture.
+     *
+     * @see #isImportantForContentCapture()
+     * @see #setImportantForContentCapture(int)
+     */
+    public static final int IMPORTANT_FOR_CONTENT_CAPTURE_AUTO = 0x0;
+
+    /**
+     * The view is important for content capture, and its children (if any) will be traversed.
+     *
+     * @see #isImportantForContentCapture()
+     * @see #setImportantForContentCapture(int)
+     */
+    public static final int IMPORTANT_FOR_CONTENT_CAPTURE_YES = 0x1;
+
+    /**
+     * The view is not important for content capture, but its children (if any) will be traversed.
+     *
+     * @see #isImportantForContentCapture()
+     * @see #setImportantForContentCapture(int)
+     */
+    public static final int IMPORTANT_FOR_CONTENT_CAPTURE_NO = 0x2;
+
+    /**
+     * The view is important for content capture, but its children (if any) will not be traversed.
+     *
+     * @see #isImportantForContentCapture()
+     * @see #setImportantForContentCapture(int)
+     */
+    public static final int IMPORTANT_FOR_CONTENT_CAPTURE_YES_EXCLUDE_DESCENDANTS = 0x4;
+
+    /**
+     * The view is not important for content capture, and its children (if any) will not be
+     * traversed.
+     *
+     * @see #isImportantForContentCapture()
+     * @see #setImportantForContentCapture(int)
+     */
+    public static final int IMPORTANT_FOR_CONTENT_CAPTURE_NO_EXCLUDE_DESCENDANTS = 0x8;
+
+    /** {@hide} */
+    @IntDef(flag = true, prefix = {"SCROLL_CAPTURE_HINT_"},
+            value = {
+                    SCROLL_CAPTURE_HINT_AUTO,
+                    SCROLL_CAPTURE_HINT_EXCLUDE,
+                    SCROLL_CAPTURE_HINT_INCLUDE,
+                    SCROLL_CAPTURE_HINT_EXCLUDE_DESCENDANTS
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ScrollCaptureHint {}
+
+    /**
+     * The content of this view will be considered for scroll capture if scrolling is possible.
+     *
+     * @see #getScrollCaptureHint()
+     * @see #setScrollCaptureHint(int)
+     */
+    public static final int SCROLL_CAPTURE_HINT_AUTO = 0;
+
+    /**
+     * Explicitly exclude this view as a potential scroll capture target. The system will not
+     * consider it. Mutually exclusive with {@link #SCROLL_CAPTURE_HINT_INCLUDE}, which this flag
+     * takes precedence over.
+     *
+     * @see #getScrollCaptureHint()
+     * @see #setScrollCaptureHint(int)
+     */
+    public static final int SCROLL_CAPTURE_HINT_EXCLUDE = 0x1;
+
+    /**
+     * Explicitly include this view as a potential scroll capture target. When locating a scroll
+     * capture target, this view will be prioritized before others without this flag. Mutually
+     * exclusive with {@link #SCROLL_CAPTURE_HINT_EXCLUDE}, which takes precedence.
+     *
+     * @see #getScrollCaptureHint()
+     * @see #setScrollCaptureHint(int)
+     */
+    public static final int SCROLL_CAPTURE_HINT_INCLUDE = 0x2;
+
+    /**
+     * Explicitly exclude all children of this view as potential scroll capture targets. This view
+     * is unaffected. Note: Excluded children are not considered, regardless of {@link
+     * #SCROLL_CAPTURE_HINT_INCLUDE}.
+     *
+     * @see #getScrollCaptureHint()
+     * @see #setScrollCaptureHint(int)
+     */
+    public static final int SCROLL_CAPTURE_HINT_EXCLUDE_DESCENDANTS = 0x4;
+
+    /**
+     * This view is enabled. Interpretation varies by subclass.
+     * Use with ENABLED_MASK when calling setFlags.
+     * {@hide}
+     */
+    static final int ENABLED = 0x00000000;
+
+    /**
+     * This view is disabled. Interpretation varies by subclass.
+     * Use with ENABLED_MASK when calling setFlags.
+     * {@hide}
+     */
+    static final int DISABLED = 0x00000020;
+
+   /**
+    * Mask for use with setFlags indicating bits used for indicating whether
+    * this view is enabled
+    * {@hide}
+    */
+    static final int ENABLED_MASK = 0x00000020;
+
+    /**
+     * This view won't draw. {@link #onDraw(android.graphics.Canvas)} won't be
+     * called and further optimizations will be performed. It is okay to have
+     * this flag set and a background. Use with DRAW_MASK when calling setFlags.
+     * {@hide}
+     */
+    static final int WILL_NOT_DRAW = 0x00000080;
+
+    /**
+     * Mask for use with setFlags indicating bits used for indicating whether
+     * this view is will draw
+     * {@hide}
+     */
+    static final int DRAW_MASK = 0x00000080;
+
+    /**
+     * <p>This view doesn't show scrollbars.</p>
+     * {@hide}
+     */
+    static final int SCROLLBARS_NONE = 0x00000000;
+
+    /**
+     * <p>This view shows horizontal scrollbars.</p>
+     * {@hide}
+     */
+    static final int SCROLLBARS_HORIZONTAL = 0x00000100;
+
+    /**
+     * <p>This view shows vertical scrollbars.</p>
+     * {@hide}
+     */
+    static final int SCROLLBARS_VERTICAL = 0x00000200;
+
+    /**
+     * <p>Mask for use with setFlags indicating bits used for indicating which
+     * scrollbars are enabled.</p>
+     * {@hide}
+     */
+    static final int SCROLLBARS_MASK = 0x00000300;
+
+    /**
+     * Indicates that the view should filter touches when its window is obscured.
+     * Refer to the class comments for more information about this security feature.
+     * {@hide}
+     */
+    static final int FILTER_TOUCHES_WHEN_OBSCURED = 0x00000400;
+
+    /**
+     * Set for framework elements that use FITS_SYSTEM_WINDOWS, to indicate
+     * that they are optional and should be skipped if the window has
+     * requested system UI flags that ignore those insets for layout.
+     * <p>
+     * This is only used for support library as of Android R. The framework now uses
+     * {@link #PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS} such that it can skip the legacy
+     * insets path that loses insets information.
+     */
+    static final int OPTIONAL_FITS_SYSTEM_WINDOWS = 0x00000800;
+
+    /**
+     * <p>This view doesn't show fading edges.</p>
+     * {@hide}
+     */
+    static final int FADING_EDGE_NONE = 0x00000000;
+
+    /**
+     * <p>This view shows horizontal fading edges.</p>
+     * {@hide}
+     */
+    static final int FADING_EDGE_HORIZONTAL = 0x00001000;
+
+    /**
+     * <p>This view shows vertical fading edges.</p>
+     * {@hide}
+     */
+    static final int FADING_EDGE_VERTICAL = 0x00002000;
+
+    /**
+     * <p>Mask for use with setFlags indicating bits used for indicating which
+     * fading edges are enabled.</p>
+     * {@hide}
+     */
+    static final int FADING_EDGE_MASK = 0x00003000;
+
+    /**
+     * <p>Indicates this view can be clicked. When clickable, a View reacts
+     * to clicks by notifying the OnClickListener.<p>
+     * {@hide}
+     */
+    static final int CLICKABLE = 0x00004000;
+
+    /**
+     * <p>Indicates this view is caching its drawing into a bitmap.</p>
+     * {@hide}
+     */
+    static final int DRAWING_CACHE_ENABLED = 0x00008000;
+
+    /**
+     * <p>Indicates that no icicle should be saved for this view.<p>
+     * {@hide}
+     */
+    static final int SAVE_DISABLED = 0x000010000;
+
+    /**
+     * <p>Mask for use with setFlags indicating bits used for the saveEnabled
+     * property.</p>
+     * {@hide}
+     */
+    static final int SAVE_DISABLED_MASK = 0x000010000;
+
+    /**
+     * <p>Indicates that no drawing cache should ever be created for this view.<p>
+     * {@hide}
+     */
+    static final int WILL_NOT_CACHE_DRAWING = 0x000020000;
+
+    /**
+     * <p>Indicates this view can take / keep focus when int touch mode.</p>
+     * {@hide}
+     */
+    static final int FOCUSABLE_IN_TOUCH_MODE = 0x00040000;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "DRAWING_CACHE_QUALITY_" }, value = {
+            DRAWING_CACHE_QUALITY_LOW,
+            DRAWING_CACHE_QUALITY_HIGH,
+            DRAWING_CACHE_QUALITY_AUTO
+    })
+    public @interface DrawingCacheQuality {}
+
+    /**
+     * <p>Enables low quality mode for the drawing cache.</p>
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
+     */
+    @Deprecated
+    public static final int DRAWING_CACHE_QUALITY_LOW = 0x00080000;
+
+    /**
+     * <p>Enables high quality mode for the drawing cache.</p>
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
+     */
+    @Deprecated
+    public static final int DRAWING_CACHE_QUALITY_HIGH = 0x00100000;
+
+    /**
+     * <p>Enables automatic quality mode for the drawing cache.</p>
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
+     */
+    @Deprecated
+    public static final int DRAWING_CACHE_QUALITY_AUTO = 0x00000000;
+
+    private static final int[] DRAWING_CACHE_QUALITY_FLAGS = {
+            DRAWING_CACHE_QUALITY_AUTO, DRAWING_CACHE_QUALITY_LOW, DRAWING_CACHE_QUALITY_HIGH
+    };
+
+    /**
+     * <p>Mask for use with setFlags indicating bits used for the cache
+     * quality property.</p>
+     * {@hide}
+     */
+    static final int DRAWING_CACHE_QUALITY_MASK = 0x00180000;
+
+    /**
+     * <p>
+     * Indicates this view can be long clicked. When long clickable, a View
+     * reacts to long clicks by notifying the OnLongClickListener or showing a
+     * context menu.
+     * </p>
+     * {@hide}
+     */
+    static final int LONG_CLICKABLE = 0x00200000;
+
+    /**
+     * <p>Indicates that this view gets its drawable states from its direct parent
+     * and ignores its original internal states.</p>
+     *
+     * @hide
+     */
+    static final int DUPLICATE_PARENT_STATE = 0x00400000;
+
+    /**
+     * <p>
+     * Indicates this view can be context clicked. When context clickable, a View reacts to a
+     * context click (e.g. a primary stylus button press or right mouse click) by notifying the
+     * OnContextClickListener.
+     * </p>
+     * {@hide}
+     */
+    static final int CONTEXT_CLICKABLE = 0x00800000;
+
+    /** @hide */
+    @IntDef(prefix = { "SCROLLBARS_" }, value = {
+            SCROLLBARS_INSIDE_OVERLAY,
+            SCROLLBARS_INSIDE_INSET,
+            SCROLLBARS_OUTSIDE_OVERLAY,
+            SCROLLBARS_OUTSIDE_INSET
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ScrollBarStyle {}
+
+    /**
+     * The scrollbar style to display the scrollbars inside the content area,
+     * without increasing the padding. The scrollbars will be overlaid with
+     * translucency on the view's content.
+     */
+    public static final int SCROLLBARS_INSIDE_OVERLAY = 0;
+
+    /**
+     * The scrollbar style to display the scrollbars inside the padded area,
+     * increasing the padding of the view. The scrollbars will not overlap the
+     * content area of the view.
+     */
+    public static final int SCROLLBARS_INSIDE_INSET = 0x01000000;
+
+    /**
+     * The scrollbar style to display the scrollbars at the edge of the view,
+     * without increasing the padding. The scrollbars will be overlaid with
+     * translucency.
+     */
+    public static final int SCROLLBARS_OUTSIDE_OVERLAY = 0x02000000;
+
+    /**
+     * The scrollbar style to display the scrollbars at the edge of the view,
+     * increasing the padding of the view. The scrollbars will only overlap the
+     * background, if any.
+     */
+    public static final int SCROLLBARS_OUTSIDE_INSET = 0x03000000;
+
+    /**
+     * Mask to check if the scrollbar style is overlay or inset.
+     * {@hide}
+     */
+    static final int SCROLLBARS_INSET_MASK = 0x01000000;
+
+    /**
+     * Mask to check if the scrollbar style is inside or outside.
+     * {@hide}
+     */
+    static final int SCROLLBARS_OUTSIDE_MASK = 0x02000000;
+
+    /**
+     * Mask for scrollbar style.
+     * {@hide}
+     */
+    static final int SCROLLBARS_STYLE_MASK = 0x03000000;
+
+    /**
+     * View flag indicating that the screen should remain on while the
+     * window containing this view is visible to the user.  This effectively
+     * takes care of automatically setting the WindowManager's
+     * {@link WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON}.
+     */
+    public static final int KEEP_SCREEN_ON = 0x04000000;
+
+    /**
+     * View flag indicating whether this view should have sound effects enabled
+     * for events such as clicking and touching.
+     */
+    public static final int SOUND_EFFECTS_ENABLED = 0x08000000;
+
+    /**
+     * View flag indicating whether this view should have haptic feedback
+     * enabled for events such as long presses.
+     */
+    public static final int HAPTIC_FEEDBACK_ENABLED = 0x10000000;
+
+    /**
+     * <p>Indicates that the view hierarchy should stop saving state when
+     * it reaches this view.  If state saving is initiated immediately at
+     * the view, it will be allowed.
+     * {@hide}
+     */
+    static final int PARENT_SAVE_DISABLED = 0x20000000;
+
+    /**
+     * <p>Mask for use with setFlags indicating bits used for PARENT_SAVE_DISABLED.</p>
+     * {@hide}
+     */
+    static final int PARENT_SAVE_DISABLED_MASK = 0x20000000;
+
+    private static Paint sDebugPaint;
+
+    /**
+     * <p>Indicates this view can display a tooltip on hover or long press.</p>
+     * {@hide}
+     */
+    static final int TOOLTIP = 0x40000000;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "FOCUSABLES_" }, value = {
+            FOCUSABLES_ALL,
+            FOCUSABLES_TOUCH_MODE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FocusableMode {}
+
+    /**
+     * View flag indicating whether {@link #addFocusables(ArrayList, int, int)}
+     * should add all focusable Views regardless if they are focusable in touch mode.
+     */
+    public static final int FOCUSABLES_ALL = 0x00000000;
+
+    /**
+     * View flag indicating whether {@link #addFocusables(ArrayList, int, int)}
+     * should add only Views focusable in touch mode.
+     */
+    public static final int FOCUSABLES_TOUCH_MODE = 0x00000001;
+
+    /** @hide */
+    @IntDef(prefix = { "FOCUS_" }, value = {
+            FOCUS_BACKWARD,
+            FOCUS_FORWARD,
+            FOCUS_LEFT,
+            FOCUS_UP,
+            FOCUS_RIGHT,
+            FOCUS_DOWN
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FocusDirection {}
+
+    /** @hide */
+    @IntDef(prefix = { "FOCUS_" }, value = {
+            FOCUS_LEFT,
+            FOCUS_UP,
+            FOCUS_RIGHT,
+            FOCUS_DOWN
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FocusRealDirection {} // Like @FocusDirection, but without forward/backward
+
+    /**
+     * Use with {@link #focusSearch(int)}. Move focus to the previous selectable
+     * item.
+     */
+    public static final int FOCUS_BACKWARD = 0x00000001;
+
+    /**
+     * Use with {@link #focusSearch(int)}. Move focus to the next selectable
+     * item.
+     */
+    public static final int FOCUS_FORWARD = 0x00000002;
+
+    /**
+     * Use with {@link #focusSearch(int)}. Move focus to the left.
+     */
+    public static final int FOCUS_LEFT = 0x00000011;
+
+    /**
+     * Use with {@link #focusSearch(int)}. Move focus up.
+     */
+    public static final int FOCUS_UP = 0x00000021;
+
+    /**
+     * Use with {@link #focusSearch(int)}. Move focus to the right.
+     */
+    public static final int FOCUS_RIGHT = 0x00000042;
+
+    /**
+     * Use with {@link #focusSearch(int)}. Move focus down.
+     */
+    public static final int FOCUS_DOWN = 0x00000082;
+
+    /**
+     * Bits of {@link #getMeasuredWidthAndState()} and
+     * {@link #getMeasuredWidthAndState()} that provide the actual measured size.
+     */
+    public static final int MEASURED_SIZE_MASK = 0x00ffffff;
+
+    /**
+     * Bits of {@link #getMeasuredWidthAndState()} and
+     * {@link #getMeasuredWidthAndState()} that provide the additional state bits.
+     */
+    public static final int MEASURED_STATE_MASK = 0xff000000;
+
+    /**
+     * Bit shift of {@link #MEASURED_STATE_MASK} to get to the height bits
+     * for functions that combine both width and height into a single int,
+     * such as {@link #getMeasuredState()} and the childState argument of
+     * {@link #resolveSizeAndState(int, int, int)}.
+     */
+    public static final int MEASURED_HEIGHT_STATE_SHIFT = 16;
+
+    /**
+     * Bit of {@link #getMeasuredWidthAndState()} and
+     * {@link #getMeasuredWidthAndState()} that indicates the measured size
+     * is smaller that the space the view would like to have.
+     */
+    public static final int MEASURED_STATE_TOO_SMALL = 0x01000000;
+
+    /**
+     * Base View state sets
+     */
+    // Singles
+    /**
+     * Indicates the view has no states set. States are used with
+     * {@link android.graphics.drawable.Drawable} to change the drawing of the
+     * view depending on its state.
+     *
+     * @see android.graphics.drawable.Drawable
+     * @see #getDrawableState()
+     */
+    protected static final int[] EMPTY_STATE_SET;
+    /**
+     * Indicates the view is enabled. States are used with
+     * {@link android.graphics.drawable.Drawable} to change the drawing of the
+     * view depending on its state.
+     *
+     * @see android.graphics.drawable.Drawable
+     * @see #getDrawableState()
+     */
+    protected static final int[] ENABLED_STATE_SET;
+    /**
+     * Indicates the view is focused. States are used with
+     * {@link android.graphics.drawable.Drawable} to change the drawing of the
+     * view depending on its state.
+     *
+     * @see android.graphics.drawable.Drawable
+     * @see #getDrawableState()
+     */
+    protected static final int[] FOCUSED_STATE_SET;
+    /**
+     * Indicates the view is selected. States are used with
+     * {@link android.graphics.drawable.Drawable} to change the drawing of the
+     * view depending on its state.
+     *
+     * @see android.graphics.drawable.Drawable
+     * @see #getDrawableState()
+     */
+    protected static final int[] SELECTED_STATE_SET;
+    /**
+     * Indicates the view is pressed. States are used with
+     * {@link android.graphics.drawable.Drawable} to change the drawing of the
+     * view depending on its state.
+     *
+     * @see android.graphics.drawable.Drawable
+     * @see #getDrawableState()
+     */
+    protected static final int[] PRESSED_STATE_SET;
+    /**
+     * Indicates the view's window has focus. States are used with
+     * {@link android.graphics.drawable.Drawable} to change the drawing of the
+     * view depending on its state.
+     *
+     * @see android.graphics.drawable.Drawable
+     * @see #getDrawableState()
+     */
+    protected static final int[] WINDOW_FOCUSED_STATE_SET;
+    // Doubles
+    /**
+     * Indicates the view is enabled and has the focus.
+     *
+     * @see #ENABLED_STATE_SET
+     * @see #FOCUSED_STATE_SET
+     */
+    protected static final int[] ENABLED_FOCUSED_STATE_SET;
+    /**
+     * Indicates the view is enabled and selected.
+     *
+     * @see #ENABLED_STATE_SET
+     * @see #SELECTED_STATE_SET
+     */
+    protected static final int[] ENABLED_SELECTED_STATE_SET;
+    /**
+     * Indicates the view is enabled and that its window has focus.
+     *
+     * @see #ENABLED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] ENABLED_WINDOW_FOCUSED_STATE_SET;
+    /**
+     * Indicates the view is focused and selected.
+     *
+     * @see #FOCUSED_STATE_SET
+     * @see #SELECTED_STATE_SET
+     */
+    protected static final int[] FOCUSED_SELECTED_STATE_SET;
+    /**
+     * Indicates the view has the focus and that its window has the focus.
+     *
+     * @see #FOCUSED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] FOCUSED_WINDOW_FOCUSED_STATE_SET;
+    /**
+     * Indicates the view is selected and that its window has the focus.
+     *
+     * @see #SELECTED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] SELECTED_WINDOW_FOCUSED_STATE_SET;
+    // Triples
+    /**
+     * Indicates the view is enabled, focused and selected.
+     *
+     * @see #ENABLED_STATE_SET
+     * @see #FOCUSED_STATE_SET
+     * @see #SELECTED_STATE_SET
+     */
+    protected static final int[] ENABLED_FOCUSED_SELECTED_STATE_SET;
+    /**
+     * Indicates the view is enabled, focused and its window has the focus.
+     *
+     * @see #ENABLED_STATE_SET
+     * @see #FOCUSED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET;
+    /**
+     * Indicates the view is enabled, selected and its window has the focus.
+     *
+     * @see #ENABLED_STATE_SET
+     * @see #SELECTED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET;
+    /**
+     * Indicates the view is focused, selected and its window has the focus.
+     *
+     * @see #FOCUSED_STATE_SET
+     * @see #SELECTED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
+    /**
+     * Indicates the view is enabled, focused, selected and its window
+     * has the focus.
+     *
+     * @see #ENABLED_STATE_SET
+     * @see #FOCUSED_STATE_SET
+     * @see #SELECTED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
+    /**
+     * Indicates the view is pressed and its window has the focus.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] PRESSED_WINDOW_FOCUSED_STATE_SET;
+    /**
+     * Indicates the view is pressed and selected.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #SELECTED_STATE_SET
+     */
+    protected static final int[] PRESSED_SELECTED_STATE_SET;
+    /**
+     * Indicates the view is pressed, selected and its window has the focus.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #SELECTED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] PRESSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
+    /**
+     * Indicates the view is pressed and focused.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #FOCUSED_STATE_SET
+     */
+    protected static final int[] PRESSED_FOCUSED_STATE_SET;
+    /**
+     * Indicates the view is pressed, focused and its window has the focus.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #FOCUSED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] PRESSED_FOCUSED_WINDOW_FOCUSED_STATE_SET;
+    /**
+     * Indicates the view is pressed, focused and selected.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #SELECTED_STATE_SET
+     * @see #FOCUSED_STATE_SET
+     */
+    protected static final int[] PRESSED_FOCUSED_SELECTED_STATE_SET;
+    /**
+     * Indicates the view is pressed, focused, selected and its window has the focus.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #FOCUSED_STATE_SET
+     * @see #SELECTED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] PRESSED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
+    /**
+     * Indicates the view is pressed and enabled.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #ENABLED_STATE_SET
+     */
+    protected static final int[] PRESSED_ENABLED_STATE_SET;
+    /**
+     * Indicates the view is pressed, enabled and its window has the focus.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #ENABLED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] PRESSED_ENABLED_WINDOW_FOCUSED_STATE_SET;
+    /**
+     * Indicates the view is pressed, enabled and selected.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #ENABLED_STATE_SET
+     * @see #SELECTED_STATE_SET
+     */
+    protected static final int[] PRESSED_ENABLED_SELECTED_STATE_SET;
+    /**
+     * Indicates the view is pressed, enabled, selected and its window has the
+     * focus.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #ENABLED_STATE_SET
+     * @see #SELECTED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] PRESSED_ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET;
+    /**
+     * Indicates the view is pressed, enabled and focused.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #ENABLED_STATE_SET
+     * @see #FOCUSED_STATE_SET
+     */
+    protected static final int[] PRESSED_ENABLED_FOCUSED_STATE_SET;
+    /**
+     * Indicates the view is pressed, enabled, focused and its window has the
+     * focus.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #ENABLED_STATE_SET
+     * @see #FOCUSED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] PRESSED_ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET;
+    /**
+     * Indicates the view is pressed, enabled, focused and selected.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #ENABLED_STATE_SET
+     * @see #SELECTED_STATE_SET
+     * @see #FOCUSED_STATE_SET
+     */
+    protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET;
+    /**
+     * Indicates the view is pressed, enabled, focused, selected and its window
+     * has the focus.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #ENABLED_STATE_SET
+     * @see #SELECTED_STATE_SET
+     * @see #FOCUSED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
+
+    static {
+        EMPTY_STATE_SET = StateSet.get(0);
+
+        WINDOW_FOCUSED_STATE_SET = StateSet.get(StateSet.VIEW_STATE_WINDOW_FOCUSED);
+
+        SELECTED_STATE_SET = StateSet.get(StateSet.VIEW_STATE_SELECTED);
+        SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+                StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED);
+
+        FOCUSED_STATE_SET = StateSet.get(StateSet.VIEW_STATE_FOCUSED);
+        FOCUSED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+                StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_FOCUSED);
+        FOCUSED_SELECTED_STATE_SET = StateSet.get(
+                StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_FOCUSED);
+        FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+                StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED
+                        | StateSet.VIEW_STATE_FOCUSED);
+
+        ENABLED_STATE_SET = StateSet.get(StateSet.VIEW_STATE_ENABLED);
+        ENABLED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+                StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_ENABLED);
+        ENABLED_SELECTED_STATE_SET = StateSet.get(
+                StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_ENABLED);
+        ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+                StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED
+                        | StateSet.VIEW_STATE_ENABLED);
+        ENABLED_FOCUSED_STATE_SET = StateSet.get(
+                StateSet.VIEW_STATE_FOCUSED | StateSet.VIEW_STATE_ENABLED);
+        ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+                StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_FOCUSED
+                        | StateSet.VIEW_STATE_ENABLED);
+        ENABLED_FOCUSED_SELECTED_STATE_SET = StateSet.get(
+                StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_FOCUSED
+                        | StateSet.VIEW_STATE_ENABLED);
+        ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+                StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED
+                        | StateSet.VIEW_STATE_FOCUSED| StateSet.VIEW_STATE_ENABLED);
+
+        PRESSED_STATE_SET = StateSet.get(StateSet.VIEW_STATE_PRESSED);
+        PRESSED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+                StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_PRESSED);
+        PRESSED_SELECTED_STATE_SET = StateSet.get(
+                StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_PRESSED);
+        PRESSED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+                StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED
+                        | StateSet.VIEW_STATE_PRESSED);
+        PRESSED_FOCUSED_STATE_SET = StateSet.get(
+                StateSet.VIEW_STATE_FOCUSED | StateSet.VIEW_STATE_PRESSED);
+        PRESSED_FOCUSED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+                StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_FOCUSED
+                        | StateSet.VIEW_STATE_PRESSED);
+        PRESSED_FOCUSED_SELECTED_STATE_SET = StateSet.get(
+                StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_FOCUSED
+                        | StateSet.VIEW_STATE_PRESSED);
+        PRESSED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+                StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED
+                        | StateSet.VIEW_STATE_FOCUSED | StateSet.VIEW_STATE_PRESSED);
+        PRESSED_ENABLED_STATE_SET = StateSet.get(
+                StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_PRESSED);
+        PRESSED_ENABLED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+                StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_ENABLED
+                        | StateSet.VIEW_STATE_PRESSED);
+        PRESSED_ENABLED_SELECTED_STATE_SET = StateSet.get(
+                StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_ENABLED
+                        | StateSet.VIEW_STATE_PRESSED);
+        PRESSED_ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+                StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED
+                        | StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_PRESSED);
+        PRESSED_ENABLED_FOCUSED_STATE_SET = StateSet.get(
+                StateSet.VIEW_STATE_FOCUSED | StateSet.VIEW_STATE_ENABLED
+                        | StateSet.VIEW_STATE_PRESSED);
+        PRESSED_ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+                StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_FOCUSED
+                        | StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_PRESSED);
+        PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET = StateSet.get(
+                StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_FOCUSED
+                        | StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_PRESSED);
+        PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+                StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED
+                        | StateSet.VIEW_STATE_FOCUSED| StateSet.VIEW_STATE_ENABLED
+                        | StateSet.VIEW_STATE_PRESSED);
+    }
+
+    /**
+     * Accessibility event types that are dispatched for text population.
+     */
+    private static final int POPULATING_ACCESSIBILITY_EVENT_TYPES =
+            AccessibilityEvent.TYPE_VIEW_CLICKED
+            | AccessibilityEvent.TYPE_VIEW_LONG_CLICKED
+            | AccessibilityEvent.TYPE_VIEW_SELECTED
+            | AccessibilityEvent.TYPE_VIEW_FOCUSED
+            | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
+            | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER
+            | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT
+            | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED
+            | AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED
+            | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED
+            | AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY;
+
+    static final int DEBUG_CORNERS_COLOR = Color.rgb(63, 127, 255);
+
+    static final int DEBUG_CORNERS_SIZE_DIP = 8;
+
+    /**
+     * Temporary Rect currently for use in setBackground().  This will probably
+     * be extended in the future to hold our own class with more than just
+     * a Rect. :)
+     */
+    static final ThreadLocal<Rect> sThreadLocal = ThreadLocal.withInitial(Rect::new);
+
+    /**
+     * Map used to store views' tags.
+     */
+    @UnsupportedAppUsage
+    private SparseArray<Object> mKeyedTags;
+
+    /**
+     * The next available accessibility id.
+     */
+    private static int sNextAccessibilityViewId;
+
+    /**
+     * The animation currently associated with this view.
+     * @hide
+     */
+    protected Animation mCurrentAnimation = null;
+
+    /**
+     * Width as measured during measure pass.
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty(category = "measurement")
+    @UnsupportedAppUsage
+    int mMeasuredWidth;
+
+    /**
+     * Height as measured during measure pass.
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty(category = "measurement")
+    @UnsupportedAppUsage
+    int mMeasuredHeight;
+
+    /**
+     * Flag to indicate that this view was marked INVALIDATED, or had its display list
+     * invalidated, prior to the current drawing iteration. If true, the view must re-draw
+     * its display list. This flag, used only when hw accelerated, allows us to clear the
+     * flag while retaining this information until it's needed (at getDisplayList() time and
+     * in drawChild(), when we decide to draw a view's children's display lists into our own).
+     *
+     * {@hide}
+     */
+    @UnsupportedAppUsage
+    boolean mRecreateDisplayList = false;
+
+    /**
+     * The view's identifier.
+     * {@hide}
+     *
+     * @see #setId(int)
+     * @see #getId()
+     */
+    @IdRes
+    @ViewDebug.ExportedProperty(resolveId = true)
+    int mID = NO_ID;
+
+    /** The ID of this view for autofill purposes.
+     * <ul>
+     *     <li>== {@link #NO_ID}: ID has not been assigned yet
+     *     <li>&le; {@link #LAST_APP_AUTOFILL_ID}: View is not part of a activity. The ID is
+     *                                                  unique in the process. This might change
+     *                                                  over activity lifecycle events.
+     *     <li>&gt; {@link #LAST_APP_AUTOFILL_ID}: View is part of a activity. The ID is
+     *                                                  unique in the activity. This stays the same
+     *                                                  over activity lifecycle events.
+     */
+    private int mAutofillViewId = NO_ID;
+
+    // ID for accessibility purposes. This ID must be unique for every window
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private int mAccessibilityViewId = NO_ID;
+
+    private int mAccessibilityCursorPosition = ACCESSIBILITY_CURSOR_POSITION_UNDEFINED;
+
+    /**
+     * The view's tag.
+     * {@hide}
+     *
+     * @see #setTag(Object)
+     * @see #getTag()
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    protected Object mTag = null;
+
+    /*
+     * Masks for mPrivateFlags, as generated by dumpFlags():
+     *
+     * |-------|-------|-------|-------|
+     *                                 1 PFLAG_WANTS_FOCUS
+     *                                1  PFLAG_FOCUSED
+     *                               1   PFLAG_SELECTED
+     *                              1    PFLAG_IS_ROOT_NAMESPACE
+     *                             1     PFLAG_HAS_BOUNDS
+     *                            1      PFLAG_DRAWN
+     *                           1       PFLAG_DRAW_ANIMATION
+     *                          1        PFLAG_SKIP_DRAW
+     *                        1          PFLAG_REQUEST_TRANSPARENT_REGIONS
+     *                       1           PFLAG_DRAWABLE_STATE_DIRTY
+     *                      1            PFLAG_MEASURED_DIMENSION_SET
+     *                     1             PFLAG_FORCE_LAYOUT
+     *                    1              PFLAG_LAYOUT_REQUIRED
+     *                   1               PFLAG_PRESSED
+     *                  1                PFLAG_DRAWING_CACHE_VALID
+     *                 1                 PFLAG_ANIMATION_STARTED
+     *                1                  PFLAG_SAVE_STATE_CALLED
+     *               1                   PFLAG_ALPHA_SET
+     *              1                    PFLAG_SCROLL_CONTAINER
+     *             1                     PFLAG_SCROLL_CONTAINER_ADDED
+     *            1                      PFLAG_DIRTY
+     *            1                      PFLAG_DIRTY_MASK
+     *          1                        PFLAG_OPAQUE_BACKGROUND
+     *         1                         PFLAG_OPAQUE_SCROLLBARS
+     *         11                        PFLAG_OPAQUE_MASK
+     *        1                          PFLAG_PREPRESSED
+     *       1                           PFLAG_CANCEL_NEXT_UP_EVENT
+     *      1                            PFLAG_AWAKEN_SCROLL_BARS_ON_ATTACH
+     *     1                             PFLAG_HOVERED
+     *    1                              PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK
+     *   1                               PFLAG_ACTIVATED
+     *  1                                PFLAG_INVALIDATED
+     * |-------|-------|-------|-------|
+     */
+    /** {@hide} */
+    static final int PFLAG_WANTS_FOCUS                 = 0x00000001;
+    /** {@hide} */
+    static final int PFLAG_FOCUSED                     = 0x00000002;
+    /** {@hide} */
+    static final int PFLAG_SELECTED                    = 0x00000004;
+    /** {@hide} */
+    static final int PFLAG_IS_ROOT_NAMESPACE           = 0x00000008;
+    /** {@hide} */
+    static final int PFLAG_HAS_BOUNDS                  = 0x00000010;
+    /** {@hide} */
+    static final int PFLAG_DRAWN                       = 0x00000020;
+    /**
+     * When this flag is set, this view is running an animation on behalf of its
+     * children and should therefore not cancel invalidate requests, even if they
+     * lie outside of this view's bounds.
+     *
+     * {@hide}
+     */
+    static final int PFLAG_DRAW_ANIMATION              = 0x00000040;
+    /** {@hide} */
+    static final int PFLAG_SKIP_DRAW                   = 0x00000080;
+    /** {@hide} */
+    static final int PFLAG_REQUEST_TRANSPARENT_REGIONS = 0x00000200;
+    /** {@hide} */
+    static final int PFLAG_DRAWABLE_STATE_DIRTY        = 0x00000400;
+    /** {@hide} */
+    static final int PFLAG_MEASURED_DIMENSION_SET      = 0x00000800;
+    /** {@hide} */
+    static final int PFLAG_FORCE_LAYOUT                = 0x00001000;
+    /** {@hide} */
+    static final int PFLAG_LAYOUT_REQUIRED             = 0x00002000;
+
+    private static final int PFLAG_PRESSED             = 0x00004000;
+
+    /** {@hide} */
+    static final int PFLAG_DRAWING_CACHE_VALID         = 0x00008000;
+    /**
+     * Flag used to indicate that this view should be drawn once more (and only once
+     * more) after its animation has completed.
+     * {@hide}
+     */
+    static final int PFLAG_ANIMATION_STARTED           = 0x00010000;
+
+    private static final int PFLAG_SAVE_STATE_CALLED   = 0x00020000;
+
+    /**
+     * Indicates that the View returned true when onSetAlpha() was called and that
+     * the alpha must be restored.
+     * {@hide}
+     */
+    static final int PFLAG_ALPHA_SET                   = 0x00040000;
+
+    /**
+     * Set by {@link #setScrollContainer(boolean)}.
+     */
+    static final int PFLAG_SCROLL_CONTAINER            = 0x00080000;
+
+    /**
+     * Set by {@link #setScrollContainer(boolean)}.
+     */
+    static final int PFLAG_SCROLL_CONTAINER_ADDED      = 0x00100000;
+
+    /**
+     * View flag indicating whether this view was invalidated (fully or partially.)
+     *
+     * @hide
+     */
+    static final int PFLAG_DIRTY                       = 0x00200000;
+
+    /**
+     * Mask for {@link #PFLAG_DIRTY}.
+     *
+     * @hide
+     */
+    static final int PFLAG_DIRTY_MASK                  = 0x00200000;
+
+    /**
+     * Indicates whether the background is opaque.
+     *
+     * @hide
+     */
+    static final int PFLAG_OPAQUE_BACKGROUND           = 0x00800000;
+
+    /**
+     * Indicates whether the scrollbars are opaque.
+     *
+     * @hide
+     */
+    static final int PFLAG_OPAQUE_SCROLLBARS           = 0x01000000;
+
+    /**
+     * Indicates whether the view is opaque.
+     *
+     * @hide
+     */
+    static final int PFLAG_OPAQUE_MASK                 = 0x01800000;
+
+    /**
+     * Indicates a prepressed state;
+     * the short time between ACTION_DOWN and recognizing
+     * a 'real' press. Prepressed is used to recognize quick taps
+     * even when they are shorter than ViewConfiguration.getTapTimeout().
+     *
+     * @hide
+     */
+    private static final int PFLAG_PREPRESSED          = 0x02000000;
+
+    /**
+     * Indicates whether the view is temporarily detached.
+     *
+     * @hide
+     */
+    static final int PFLAG_CANCEL_NEXT_UP_EVENT        = 0x04000000;
+
+    /**
+     * Indicates that we should awaken scroll bars once attached
+     *
+     * PLEASE NOTE: This flag is now unused as we now send onVisibilityChanged
+     * during window attachment and it is no longer needed. Feel free to repurpose it.
+     *
+     * @hide
+     */
+    private static final int PFLAG_AWAKEN_SCROLL_BARS_ON_ATTACH = 0x08000000;
+
+    /**
+     * Indicates that the view has received HOVER_ENTER.  Cleared on HOVER_EXIT.
+     * @hide
+     */
+    private static final int PFLAG_HOVERED             = 0x10000000;
+
+    /**
+     * Flag set by {@link AutofillManager} if it needs to be notified when this view is clicked.
+     */
+    private static final int PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK = 0x20000000;
+
+    /** {@hide} */
+    static final int PFLAG_ACTIVATED                   = 0x40000000;
+
+    /**
+     * Indicates that this view was specifically invalidated, not just dirtied because some
+     * child view was invalidated. The flag is used to determine when we need to recreate
+     * a view's display list (as opposed to just returning a reference to its existing
+     * display list).
+     *
+     * @hide
+     */
+    static final int PFLAG_INVALIDATED                 = 0x80000000;
+
+    /* End of masks for mPrivateFlags */
+
+    /*
+     * Masks for mPrivateFlags2, as generated by dumpFlags():
+     *
+     * |-------|-------|-------|-------|
+     *                                 1 PFLAG2_DRAG_CAN_ACCEPT
+     *                                1  PFLAG2_DRAG_HOVERED
+     *                              11   PFLAG2_LAYOUT_DIRECTION_MASK
+     *                             1     PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL
+     *                            1      PFLAG2_LAYOUT_DIRECTION_RESOLVED
+     *                            11     PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK
+     *                           1       PFLAG2_TEXT_DIRECTION_FLAGS[1]
+     *                          1        PFLAG2_TEXT_DIRECTION_FLAGS[2]
+     *                          11       PFLAG2_TEXT_DIRECTION_FLAGS[3]
+     *                         1         PFLAG2_TEXT_DIRECTION_FLAGS[4]
+     *                         1 1       PFLAG2_TEXT_DIRECTION_FLAGS[5]
+     *                         11        PFLAG2_TEXT_DIRECTION_FLAGS[6]
+     *                         111       PFLAG2_TEXT_DIRECTION_FLAGS[7]
+     *                         111       PFLAG2_TEXT_DIRECTION_MASK
+     *                        1          PFLAG2_TEXT_DIRECTION_RESOLVED
+     *                       1           PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT
+     *                     111           PFLAG2_TEXT_DIRECTION_RESOLVED_MASK
+     *                    1              PFLAG2_TEXT_ALIGNMENT_FLAGS[1]
+     *                   1               PFLAG2_TEXT_ALIGNMENT_FLAGS[2]
+     *                   11              PFLAG2_TEXT_ALIGNMENT_FLAGS[3]
+     *                  1                PFLAG2_TEXT_ALIGNMENT_FLAGS[4]
+     *                  1 1              PFLAG2_TEXT_ALIGNMENT_FLAGS[5]
+     *                  11               PFLAG2_TEXT_ALIGNMENT_FLAGS[6]
+     *                  111              PFLAG2_TEXT_ALIGNMENT_MASK
+     *                 1                 PFLAG2_TEXT_ALIGNMENT_RESOLVED
+     *                1                  PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT
+     *              111                  PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK
+     *           111                     PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK
+     *         11                        PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK
+     *       1                           PFLAG2_ACCESSIBILITY_FOCUSED
+     *      1                            PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED
+     *     1                             PFLAG2_VIEW_QUICK_REJECTED
+     *    1                              PFLAG2_PADDING_RESOLVED
+     *   1                               PFLAG2_DRAWABLE_RESOLVED
+     *  1                                PFLAG2_HAS_TRANSIENT_STATE
+     * |-------|-------|-------|-------|
+     */
+
+    /**
+     * Indicates that this view has reported that it can accept the current drag's content.
+     * Cleared when the drag operation concludes.
+     * @hide
+     */
+    static final int PFLAG2_DRAG_CAN_ACCEPT            = 0x00000001;
+
+    /**
+     * Indicates that this view is currently directly under the drag location in a
+     * drag-and-drop operation involving content that it can accept.  Cleared when
+     * the drag exits the view, or when the drag operation concludes.
+     * @hide
+     */
+    static final int PFLAG2_DRAG_HOVERED               = 0x00000002;
+
+    /** @hide */
+    @IntDef(prefix = { "LAYOUT_DIRECTION_" }, value = {
+            LAYOUT_DIRECTION_LTR,
+            LAYOUT_DIRECTION_RTL,
+            LAYOUT_DIRECTION_INHERIT,
+            LAYOUT_DIRECTION_LOCALE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    // Not called LayoutDirection to avoid conflict with android.util.LayoutDirection
+    public @interface LayoutDir {}
+
+    /** @hide */
+    @IntDef(prefix = { "LAYOUT_DIRECTION_" }, value = {
+            LAYOUT_DIRECTION_LTR,
+            LAYOUT_DIRECTION_RTL
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ResolvedLayoutDir {}
+
+    /**
+     * A flag to indicate that the layout direction of this view has not been defined yet.
+     * @hide
+     */
+    public static final int LAYOUT_DIRECTION_UNDEFINED = LayoutDirection.UNDEFINED;
+
+    /**
+     * Horizontal layout direction of this view is from Left to Right.
+     * Use with {@link #setLayoutDirection}.
+     */
+    public static final int LAYOUT_DIRECTION_LTR = LayoutDirection.LTR;
+
+    /**
+     * Horizontal layout direction of this view is from Right to Left.
+     * Use with {@link #setLayoutDirection}.
+     */
+    public static final int LAYOUT_DIRECTION_RTL = LayoutDirection.RTL;
+
+    /**
+     * Horizontal layout direction of this view is inherited from its parent.
+     * Use with {@link #setLayoutDirection}.
+     */
+    public static final int LAYOUT_DIRECTION_INHERIT = LayoutDirection.INHERIT;
+
+    /**
+     * Horizontal layout direction of this view is from deduced from the default language
+     * script for the locale. Use with {@link #setLayoutDirection}.
+     */
+    public static final int LAYOUT_DIRECTION_LOCALE = LayoutDirection.LOCALE;
+
+    /**
+     * Bit shift to get the horizontal layout direction. (bits after DRAG_HOVERED)
+     * @hide
+     */
+    static final int PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT = 2;
+
+    /**
+     * Mask for use with private flags indicating bits used for horizontal layout direction.
+     * @hide
+     */
+    static final int PFLAG2_LAYOUT_DIRECTION_MASK = 0x00000003 << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT;
+
+    /**
+     * Indicates whether the view horizontal layout direction has been resolved and drawn to the
+     * right-to-left direction.
+     * @hide
+     */
+    static final int PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL = 4 << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT;
+
+    /**
+     * Indicates whether the view horizontal layout direction has been resolved.
+     * @hide
+     */
+    static final int PFLAG2_LAYOUT_DIRECTION_RESOLVED = 8 << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT;
+
+    /**
+     * Mask for use with private flags indicating bits used for resolved horizontal layout direction.
+     * @hide
+     */
+    static final int PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK = 0x0000000C
+            << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT;
+
+    /*
+     * Array of horizontal layout direction flags for mapping attribute "layoutDirection" to correct
+     * flag value.
+     * @hide
+     */
+    private static final int[] LAYOUT_DIRECTION_FLAGS = {
+            LAYOUT_DIRECTION_LTR,
+            LAYOUT_DIRECTION_RTL,
+            LAYOUT_DIRECTION_INHERIT,
+            LAYOUT_DIRECTION_LOCALE
+    };
+
+    /**
+     * Default horizontal layout direction.
+     */
+    private static final int LAYOUT_DIRECTION_DEFAULT = LAYOUT_DIRECTION_INHERIT;
+
+    /**
+     * Default horizontal layout direction.
+     * @hide
+     */
+    static final int LAYOUT_DIRECTION_RESOLVED_DEFAULT = LAYOUT_DIRECTION_LTR;
+
+    /**
+     * Text direction is inherited through {@link ViewGroup}
+     */
+    public static final int TEXT_DIRECTION_INHERIT = 0;
+
+    /**
+     * Text direction is using "first strong algorithm". The first strong directional character
+     * determines the paragraph direction. If there is no strong directional character, the
+     * paragraph direction is the view's resolved layout direction.
+     */
+    public static final int TEXT_DIRECTION_FIRST_STRONG = 1;
+
+    /**
+     * Text direction is using "any-RTL" algorithm. The paragraph direction is RTL if it contains
+     * any strong RTL character, otherwise it is LTR if it contains any strong LTR characters.
+     * If there are neither, the paragraph direction is the view's resolved layout direction.
+     */
+    public static final int TEXT_DIRECTION_ANY_RTL = 2;
+
+    /**
+     * Text direction is forced to LTR.
+     */
+    public static final int TEXT_DIRECTION_LTR = 3;
+
+    /**
+     * Text direction is forced to RTL.
+     */
+    public static final int TEXT_DIRECTION_RTL = 4;
+
+    /**
+     * Text direction is coming from the system Locale.
+     */
+    public static final int TEXT_DIRECTION_LOCALE = 5;
+
+    /**
+     * Text direction is using "first strong algorithm". The first strong directional character
+     * determines the paragraph direction. If there is no strong directional character, the
+     * paragraph direction is LTR.
+     */
+    public static final int TEXT_DIRECTION_FIRST_STRONG_LTR = 6;
+
+    /**
+     * Text direction is using "first strong algorithm". The first strong directional character
+     * determines the paragraph direction. If there is no strong directional character, the
+     * paragraph direction is RTL.
+     */
+    public static final int TEXT_DIRECTION_FIRST_STRONG_RTL = 7;
+
+    /**
+     * Default text direction is inherited
+     */
+    private static final int TEXT_DIRECTION_DEFAULT = TEXT_DIRECTION_INHERIT;
+
+    /**
+     * Default resolved text direction
+     * @hide
+     */
+    static final int TEXT_DIRECTION_RESOLVED_DEFAULT = TEXT_DIRECTION_FIRST_STRONG;
+
+    /**
+     * Bit shift to get the horizontal layout direction. (bits after LAYOUT_DIRECTION_RESOLVED)
+     * @hide
+     */
+    static final int PFLAG2_TEXT_DIRECTION_MASK_SHIFT = 6;
+
+    /**
+     * Mask for use with private flags indicating bits used for text direction.
+     * @hide
+     */
+    static final int PFLAG2_TEXT_DIRECTION_MASK = 0x00000007
+            << PFLAG2_TEXT_DIRECTION_MASK_SHIFT;
+
+    /**
+     * Array of text direction flags for mapping attribute "textDirection" to correct
+     * flag value.
+     * @hide
+     */
+    private static final int[] PFLAG2_TEXT_DIRECTION_FLAGS = {
+            TEXT_DIRECTION_INHERIT << PFLAG2_TEXT_DIRECTION_MASK_SHIFT,
+            TEXT_DIRECTION_FIRST_STRONG << PFLAG2_TEXT_DIRECTION_MASK_SHIFT,
+            TEXT_DIRECTION_ANY_RTL << PFLAG2_TEXT_DIRECTION_MASK_SHIFT,
+            TEXT_DIRECTION_LTR << PFLAG2_TEXT_DIRECTION_MASK_SHIFT,
+            TEXT_DIRECTION_RTL << PFLAG2_TEXT_DIRECTION_MASK_SHIFT,
+            TEXT_DIRECTION_LOCALE << PFLAG2_TEXT_DIRECTION_MASK_SHIFT,
+            TEXT_DIRECTION_FIRST_STRONG_LTR << PFLAG2_TEXT_DIRECTION_MASK_SHIFT,
+            TEXT_DIRECTION_FIRST_STRONG_RTL << PFLAG2_TEXT_DIRECTION_MASK_SHIFT
+    };
+
+    /**
+     * Indicates whether the view text direction has been resolved.
+     * @hide
+     */
+    static final int PFLAG2_TEXT_DIRECTION_RESOLVED = 0x00000008
+            << PFLAG2_TEXT_DIRECTION_MASK_SHIFT;
+
+    /**
+     * Bit shift to get the horizontal layout direction. (bits after DRAG_HOVERED)
+     * @hide
+     */
+    static final int PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT = 10;
+
+    /**
+     * Mask for use with private flags indicating bits used for resolved text direction.
+     * @hide
+     */
+    static final int PFLAG2_TEXT_DIRECTION_RESOLVED_MASK = 0x00000007
+            << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT;
+
+    /**
+     * Indicates whether the view text direction has been resolved to the "first strong" heuristic.
+     * @hide
+     */
+    static final int PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT =
+            TEXT_DIRECTION_RESOLVED_DEFAULT << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT;
+
+    /** @hide */
+    @IntDef(prefix = { "TEXT_ALIGNMENT_" }, value = {
+            TEXT_ALIGNMENT_INHERIT,
+            TEXT_ALIGNMENT_GRAVITY,
+            TEXT_ALIGNMENT_CENTER,
+            TEXT_ALIGNMENT_TEXT_START,
+            TEXT_ALIGNMENT_TEXT_END,
+            TEXT_ALIGNMENT_VIEW_START,
+            TEXT_ALIGNMENT_VIEW_END
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TextAlignment {}
+
+    /**
+     * Default text alignment. The text alignment of this View is inherited from its parent.
+     * Use with {@link #setTextAlignment(int)}
+     */
+    public static final int TEXT_ALIGNMENT_INHERIT = 0;
+
+    /**
+     * Default for the root view. The gravity determines the text alignment, ALIGN_NORMAL,
+     * ALIGN_CENTER, or ALIGN_OPPOSITE, which are relative to each paragraph's text direction.
+     *
+     * Use with {@link #setTextAlignment(int)}
+     */
+    public static final int TEXT_ALIGNMENT_GRAVITY = 1;
+
+    /**
+     * Align to the start of the paragraph, e.g. ALIGN_NORMAL.
+     *
+     * Use with {@link #setTextAlignment(int)}
+     */
+    public static final int TEXT_ALIGNMENT_TEXT_START = 2;
+
+    /**
+     * Align to the end of the paragraph, e.g. ALIGN_OPPOSITE.
+     *
+     * Use with {@link #setTextAlignment(int)}
+     */
+    public static final int TEXT_ALIGNMENT_TEXT_END = 3;
+
+    /**
+     * Center the paragraph, e.g. ALIGN_CENTER.
+     *
+     * Use with {@link #setTextAlignment(int)}
+     */
+    public static final int TEXT_ALIGNMENT_CENTER = 4;
+
+    /**
+     * Align to the start of the view, which is ALIGN_LEFT if the view's resolved
+     * layoutDirection is LTR, and ALIGN_RIGHT otherwise.
+     *
+     * Use with {@link #setTextAlignment(int)}
+     */
+    public static final int TEXT_ALIGNMENT_VIEW_START = 5;
+
+    /**
+     * Align to the end of the view, which is ALIGN_RIGHT if the view's resolved
+     * layoutDirection is LTR, and ALIGN_LEFT otherwise.
+     *
+     * Use with {@link #setTextAlignment(int)}
+     */
+    public static final int TEXT_ALIGNMENT_VIEW_END = 6;
+
+    /**
+     * Default text alignment is inherited
+     */
+    private static final int TEXT_ALIGNMENT_DEFAULT = TEXT_ALIGNMENT_GRAVITY;
+
+    /**
+     * Default resolved text alignment
+     * @hide
+     */
+    static final int TEXT_ALIGNMENT_RESOLVED_DEFAULT = TEXT_ALIGNMENT_GRAVITY;
+
+    /**
+      * Bit shift to get the horizontal layout direction. (bits after DRAG_HOVERED)
+      * @hide
+      */
+    static final int PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT = 13;
+
+    /**
+      * Mask for use with private flags indicating bits used for text alignment.
+      * @hide
+      */
+    static final int PFLAG2_TEXT_ALIGNMENT_MASK = 0x00000007 << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT;
+
+    /**
+     * Array of text direction flags for mapping attribute "textAlignment" to correct
+     * flag value.
+     * @hide
+     */
+    private static final int[] PFLAG2_TEXT_ALIGNMENT_FLAGS = {
+            TEXT_ALIGNMENT_INHERIT << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT,
+            TEXT_ALIGNMENT_GRAVITY << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT,
+            TEXT_ALIGNMENT_TEXT_START << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT,
+            TEXT_ALIGNMENT_TEXT_END << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT,
+            TEXT_ALIGNMENT_CENTER << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT,
+            TEXT_ALIGNMENT_VIEW_START << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT,
+            TEXT_ALIGNMENT_VIEW_END << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT
+    };
+
+    /**
+     * Indicates whether the view text alignment has been resolved.
+     * @hide
+     */
+    static final int PFLAG2_TEXT_ALIGNMENT_RESOLVED = 0x00000008 << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT;
+
+    /**
+     * Bit shift to get the resolved text alignment.
+     * @hide
+     */
+    static final int PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT = 17;
+
+    /**
+     * Mask for use with private flags indicating bits used for text alignment.
+     * @hide
+     */
+    static final int PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK = 0x00000007
+            << PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT;
+
+    /**
+     * Indicates whether if the view text alignment has been resolved to gravity
+     */
+    private static final int PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT =
+            TEXT_ALIGNMENT_RESOLVED_DEFAULT << PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT;
+
+    // Accessiblity constants for mPrivateFlags2
+
+    /**
+     * Shift for the bits in {@link #mPrivateFlags2} related to the
+     * "importantForAccessibility" attribute.
+     */
+    static final int PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT = 20;
+
+    /**
+     * Automatically determine whether a view is important for accessibility.
+     */
+    public static final int IMPORTANT_FOR_ACCESSIBILITY_AUTO = 0x00000000;
+
+    /**
+     * The view is important for accessibility.
+     */
+    public static final int IMPORTANT_FOR_ACCESSIBILITY_YES = 0x00000001;
+
+    /**
+     * The view is not important for accessibility.
+     */
+    public static final int IMPORTANT_FOR_ACCESSIBILITY_NO = 0x00000002;
+
+    /**
+     * The view is not important for accessibility, nor are any of its
+     * descendant views.
+     */
+    public static final int IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS = 0x00000004;
+
+    /**
+     * The default whether the view is important for accessibility.
+     */
+    static final int IMPORTANT_FOR_ACCESSIBILITY_DEFAULT = IMPORTANT_FOR_ACCESSIBILITY_AUTO;
+
+    /**
+     * Mask for obtaining the bits which specify how to determine
+     * whether a view is important for accessibility.
+     */
+    static final int PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK = (IMPORTANT_FOR_ACCESSIBILITY_AUTO
+        | IMPORTANT_FOR_ACCESSIBILITY_YES | IMPORTANT_FOR_ACCESSIBILITY_NO
+        | IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS)
+        << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT;
+
+    /**
+     * Shift for the bits in {@link #mPrivateFlags2} related to the
+     * "accessibilityLiveRegion" attribute.
+     */
+    static final int PFLAG2_ACCESSIBILITY_LIVE_REGION_SHIFT = 23;
+
+    /**
+     * Live region mode specifying that accessibility services should not
+     * automatically announce changes to this view. This is the default live
+     * region mode for most views.
+     * <p>
+     * Use with {@link #setAccessibilityLiveRegion(int)}.
+     */
+    public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0x00000000;
+
+    /**
+     * Live region mode specifying that accessibility services should announce
+     * changes to this view.
+     * <p>
+     * Use with {@link #setAccessibilityLiveRegion(int)}.
+     */
+    public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 0x00000001;
+
+    /**
+     * Live region mode specifying that accessibility services should interrupt
+     * ongoing speech to immediately announce changes to this view.
+     * <p>
+     * Use with {@link #setAccessibilityLiveRegion(int)}.
+     */
+    public static final int ACCESSIBILITY_LIVE_REGION_ASSERTIVE = 0x00000002;
+
+    /**
+     * The default whether the view is important for accessibility.
+     */
+    static final int ACCESSIBILITY_LIVE_REGION_DEFAULT = ACCESSIBILITY_LIVE_REGION_NONE;
+
+    /**
+     * Mask for obtaining the bits which specify a view's accessibility live
+     * region mode.
+     */
+    static final int PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK = (ACCESSIBILITY_LIVE_REGION_NONE
+            | ACCESSIBILITY_LIVE_REGION_POLITE | ACCESSIBILITY_LIVE_REGION_ASSERTIVE)
+            << PFLAG2_ACCESSIBILITY_LIVE_REGION_SHIFT;
+
+    /**
+     * Flag indicating whether a view has accessibility focus.
+     */
+    static final int PFLAG2_ACCESSIBILITY_FOCUSED = 0x04000000;
+
+    /**
+     * Flag whether the accessibility state of the subtree rooted at this view changed.
+     */
+    static final int PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED = 0x08000000;
+
+    /**
+     * Flag indicating whether a view failed the quickReject() check in draw(). This condition
+     * is used to check whether later changes to the view's transform should invalidate the
+     * view to force the quickReject test to run again.
+     */
+    static final int PFLAG2_VIEW_QUICK_REJECTED = 0x10000000;
+
+    /**
+     * Flag indicating that start/end padding has been resolved into left/right padding
+     * for use in measurement, layout, drawing, etc. This is set by {@link #resolvePadding()}
+     * and checked by {@link #measure(int, int)} to determine if padding needs to be resolved
+     * during measurement. In some special cases this is required such as when an adapter-based
+     * view measures prospective children without attaching them to a window.
+     */
+    static final int PFLAG2_PADDING_RESOLVED = 0x20000000;
+
+    /**
+     * Flag indicating that the start/end drawables has been resolved into left/right ones.
+     */
+    static final int PFLAG2_DRAWABLE_RESOLVED = 0x40000000;
+
+    /**
+     * Indicates that the view is tracking some sort of transient state
+     * that the app should not need to be aware of, but that the framework
+     * should take special care to preserve.
+     */
+    static final int PFLAG2_HAS_TRANSIENT_STATE = 0x80000000;
+
+    /**
+     * Group of bits indicating that RTL properties resolution is done.
+     */
+    static final int ALL_RTL_PROPERTIES_RESOLVED = PFLAG2_LAYOUT_DIRECTION_RESOLVED |
+            PFLAG2_TEXT_DIRECTION_RESOLVED |
+            PFLAG2_TEXT_ALIGNMENT_RESOLVED |
+            PFLAG2_PADDING_RESOLVED |
+            PFLAG2_DRAWABLE_RESOLVED;
+
+    // There are a couple of flags left in mPrivateFlags2
+
+    /* End of masks for mPrivateFlags2 */
+
+    /*
+     * Masks for mPrivateFlags3, as generated by dumpFlags():
+     *
+     * |-------|-------|-------|-------|
+     *                                 1 PFLAG3_VIEW_IS_ANIMATING_TRANSFORM
+     *                                1  PFLAG3_VIEW_IS_ANIMATING_ALPHA
+     *                               1   PFLAG3_IS_LAID_OUT
+     *                              1    PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT
+     *                             1     PFLAG3_CALLED_SUPER
+     *                            1      PFLAG3_APPLYING_INSETS
+     *                           1       PFLAG3_FITTING_SYSTEM_WINDOWS
+     *                          1        PFLAG3_NESTED_SCROLLING_ENABLED
+     *                         1         PFLAG3_SCROLL_INDICATOR_TOP
+     *                        1          PFLAG3_SCROLL_INDICATOR_BOTTOM
+     *                       1           PFLAG3_SCROLL_INDICATOR_LEFT
+     *                      1            PFLAG3_SCROLL_INDICATOR_RIGHT
+     *                     1             PFLAG3_SCROLL_INDICATOR_START
+     *                    1              PFLAG3_SCROLL_INDICATOR_END
+     *                   1               PFLAG3_ASSIST_BLOCKED
+     *                  1                PFLAG3_CLUSTER
+     *                 1                 PFLAG3_IS_AUTOFILLED
+     *                1                  PFLAG3_FINGER_DOWN
+     *               1                   PFLAG3_FOCUSED_BY_DEFAULT
+     *           1111                    PFLAG3_IMPORTANT_FOR_AUTOFILL
+     *          1                        PFLAG3_OVERLAPPING_RENDERING_FORCED_VALUE
+     *         1                         PFLAG3_HAS_OVERLAPPING_RENDERING_FORCED
+     *        1                          PFLAG3_TEMPORARY_DETACH
+     *       1                           PFLAG3_NO_REVEAL_ON_FOCUS
+     *      1                            PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT
+     *     1                             PFLAG3_SCREEN_READER_FOCUSABLE
+     *    1                              PFLAG3_AGGREGATED_VISIBLE
+     *   1                               PFLAG3_AUTOFILLID_EXPLICITLY_SET
+     *  1                                PFLAG3_ACCESSIBILITY_HEADING
+     * |-------|-------|-------|-------|
+     */
+
+    /**
+     * Flag indicating that view has a transform animation set on it. This is used to track whether
+     * an animation is cleared between successive frames, in order to tell the associated
+     * DisplayList to clear its animation matrix.
+     */
+    static final int PFLAG3_VIEW_IS_ANIMATING_TRANSFORM = 0x1;
+
+    /**
+     * Flag indicating that view has an alpha animation set on it. This is used to track whether an
+     * animation is cleared between successive frames, in order to tell the associated
+     * DisplayList to restore its alpha value.
+     */
+    static final int PFLAG3_VIEW_IS_ANIMATING_ALPHA = 0x2;
+
+    /**
+     * Flag indicating that the view has been through at least one layout since it
+     * was last attached to a window.
+     */
+    static final int PFLAG3_IS_LAID_OUT = 0x4;
+
+    /**
+     * Flag indicating that a call to measure() was skipped and should be done
+     * instead when layout() is invoked.
+     */
+    static final int PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT = 0x8;
+
+    /**
+     * Flag indicating that an overridden method correctly called down to
+     * the superclass implementation as required by the API spec.
+     */
+    static final int PFLAG3_CALLED_SUPER = 0x10;
+
+    /**
+     * Flag indicating that we're in the process of applying window insets.
+     */
+    static final int PFLAG3_APPLYING_INSETS = 0x20;
+
+    /**
+     * Flag indicating that we're in the process of fitting system windows using the old method.
+     */
+    static final int PFLAG3_FITTING_SYSTEM_WINDOWS = 0x40;
+
+    /**
+     * Flag indicating that nested scrolling is enabled for this view.
+     * The view will optionally cooperate with views up its parent chain to allow for
+     * integrated nested scrolling along the same axis.
+     */
+    static final int PFLAG3_NESTED_SCROLLING_ENABLED = 0x80;
+
+    /**
+     * Flag indicating that the bottom scroll indicator should be displayed
+     * when this view can scroll up.
+     */
+    static final int PFLAG3_SCROLL_INDICATOR_TOP = 0x0100;
+
+    /**
+     * Flag indicating that the bottom scroll indicator should be displayed
+     * when this view can scroll down.
+     */
+    static final int PFLAG3_SCROLL_INDICATOR_BOTTOM = 0x0200;
+
+    /**
+     * Flag indicating that the left scroll indicator should be displayed
+     * when this view can scroll left.
+     */
+    static final int PFLAG3_SCROLL_INDICATOR_LEFT = 0x0400;
+
+    /**
+     * Flag indicating that the right scroll indicator should be displayed
+     * when this view can scroll right.
+     */
+    static final int PFLAG3_SCROLL_INDICATOR_RIGHT = 0x0800;
+
+    /**
+     * Flag indicating that the start scroll indicator should be displayed
+     * when this view can scroll in the start direction.
+     */
+    static final int PFLAG3_SCROLL_INDICATOR_START = 0x1000;
+
+    /**
+     * Flag indicating that the end scroll indicator should be displayed
+     * when this view can scroll in the end direction.
+     */
+    static final int PFLAG3_SCROLL_INDICATOR_END = 0x2000;
+
+    static final int DRAG_MASK = PFLAG2_DRAG_CAN_ACCEPT | PFLAG2_DRAG_HOVERED;
+
+    static final int SCROLL_INDICATORS_NONE = 0x0000;
+
+    /**
+     * Mask for use with setFlags indicating bits used for indicating which
+     * scroll indicators are enabled.
+     */
+    static final int SCROLL_INDICATORS_PFLAG3_MASK = PFLAG3_SCROLL_INDICATOR_TOP
+            | PFLAG3_SCROLL_INDICATOR_BOTTOM | PFLAG3_SCROLL_INDICATOR_LEFT
+            | PFLAG3_SCROLL_INDICATOR_RIGHT | PFLAG3_SCROLL_INDICATOR_START
+            | PFLAG3_SCROLL_INDICATOR_END;
+
+    /**
+     * Left-shift required to translate between public scroll indicator flags
+     * and internal PFLAGS3 flags. When used as a right-shift, translates
+     * PFLAGS3 flags to public flags.
+     */
+    static final int SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT = 8;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "SCROLL_INDICATOR_" }, value = {
+            SCROLL_INDICATOR_TOP,
+            SCROLL_INDICATOR_BOTTOM,
+            SCROLL_INDICATOR_LEFT,
+            SCROLL_INDICATOR_RIGHT,
+            SCROLL_INDICATOR_START,
+            SCROLL_INDICATOR_END,
+    })
+    public @interface ScrollIndicators {}
+
+    /**
+     * Scroll indicator direction for the top edge of the view.
+     *
+     * @see #setScrollIndicators(int)
+     * @see #setScrollIndicators(int, int)
+     * @see #getScrollIndicators()
+     */
+    public static final int SCROLL_INDICATOR_TOP =
+            PFLAG3_SCROLL_INDICATOR_TOP >> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT;
+
+    /**
+     * Scroll indicator direction for the bottom edge of the view.
+     *
+     * @see #setScrollIndicators(int)
+     * @see #setScrollIndicators(int, int)
+     * @see #getScrollIndicators()
+     */
+    public static final int SCROLL_INDICATOR_BOTTOM =
+            PFLAG3_SCROLL_INDICATOR_BOTTOM >> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT;
+
+    /**
+     * Scroll indicator direction for the left edge of the view.
+     *
+     * @see #setScrollIndicators(int)
+     * @see #setScrollIndicators(int, int)
+     * @see #getScrollIndicators()
+     */
+    public static final int SCROLL_INDICATOR_LEFT =
+            PFLAG3_SCROLL_INDICATOR_LEFT >> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT;
+
+    /**
+     * Scroll indicator direction for the right edge of the view.
+     *
+     * @see #setScrollIndicators(int)
+     * @see #setScrollIndicators(int, int)
+     * @see #getScrollIndicators()
+     */
+    public static final int SCROLL_INDICATOR_RIGHT =
+            PFLAG3_SCROLL_INDICATOR_RIGHT >> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT;
+
+    /**
+     * Scroll indicator direction for the starting edge of the view.
+     * <p>
+     * Resolved according to the view's layout direction, see
+     * {@link #getLayoutDirection()} for more information.
+     *
+     * @see #setScrollIndicators(int)
+     * @see #setScrollIndicators(int, int)
+     * @see #getScrollIndicators()
+     */
+    public static final int SCROLL_INDICATOR_START =
+            PFLAG3_SCROLL_INDICATOR_START >> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT;
+
+    /**
+     * Scroll indicator direction for the ending edge of the view.
+     * <p>
+     * Resolved according to the view's layout direction, see
+     * {@link #getLayoutDirection()} for more information.
+     *
+     * @see #setScrollIndicators(int)
+     * @see #setScrollIndicators(int, int)
+     * @see #getScrollIndicators()
+     */
+    public static final int SCROLL_INDICATOR_END =
+            PFLAG3_SCROLL_INDICATOR_END >> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT;
+
+    /**
+     * <p>Indicates that we are allowing {@link ViewStructure} to traverse
+     * into this view.<p>
+     */
+    static final int PFLAG3_ASSIST_BLOCKED = 0x4000;
+
+    /**
+     * Flag indicating that the view is a root of a keyboard navigation cluster.
+     *
+     * @see #isKeyboardNavigationCluster()
+     * @see #setKeyboardNavigationCluster(boolean)
+     */
+    private static final int PFLAG3_CLUSTER = 0x8000;
+
+    /**
+     * Flag indicating that the view is autofilled
+     *
+     * @see #isAutofilled()
+     * @see #setAutofilled(boolean, boolean)
+     */
+    private static final int PFLAG3_IS_AUTOFILLED = 0x10000;
+
+    /**
+     * Indicates that the user is currently touching the screen.
+     * Currently used for the tooltip positioning only.
+     */
+    private static final int PFLAG3_FINGER_DOWN = 0x20000;
+
+    /**
+     * Flag indicating that this view is the default-focus view.
+     *
+     * @see #isFocusedByDefault()
+     * @see #setFocusedByDefault(boolean)
+     */
+    private static final int PFLAG3_FOCUSED_BY_DEFAULT = 0x40000;
+
+    /**
+     * Shift for the bits in {@link #mPrivateFlags3} related to the
+     * "importantForAutofill" attribute.
+     */
+    static final int PFLAG3_IMPORTANT_FOR_AUTOFILL_SHIFT = 19;
+
+    /**
+     * Mask for obtaining the bits which specify how to determine
+     * whether a view is important for autofill.
+     */
+    static final int PFLAG3_IMPORTANT_FOR_AUTOFILL_MASK = (IMPORTANT_FOR_AUTOFILL_AUTO
+            | IMPORTANT_FOR_AUTOFILL_YES | IMPORTANT_FOR_AUTOFILL_NO
+            | IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS
+            | IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS)
+            << PFLAG3_IMPORTANT_FOR_AUTOFILL_SHIFT;
+
+    /**
+     * Whether this view has rendered elements that overlap (see {@link
+     * #hasOverlappingRendering()}, {@link #forceHasOverlappingRendering(boolean)}, and
+     * {@link #getHasOverlappingRendering()} ). The value in this bit is only valid when
+     * PFLAG3_HAS_OVERLAPPING_RENDERING_FORCED has been set. Otherwise, the value is
+     * determined by whatever {@link #hasOverlappingRendering()} returns.
+     */
+    private static final int PFLAG3_OVERLAPPING_RENDERING_FORCED_VALUE = 0x800000;
+
+    /**
+     * Whether {@link #forceHasOverlappingRendering(boolean)} has been called. When true, value
+     * in PFLAG3_OVERLAPPING_RENDERING_FORCED_VALUE is valid.
+     */
+    private static final int PFLAG3_HAS_OVERLAPPING_RENDERING_FORCED = 0x1000000;
+
+    /**
+     * Flag indicating that the view is temporarily detached from the parent view.
+     *
+     * @see #onStartTemporaryDetach()
+     * @see #onFinishTemporaryDetach()
+     */
+    static final int PFLAG3_TEMPORARY_DETACH = 0x2000000;
+
+    /**
+     * Flag indicating that the view does not wish to be revealed within its parent
+     * hierarchy when it gains focus. Expressed in the negative since the historical
+     * default behavior is to reveal on focus; this flag suppresses that behavior.
+     *
+     * @see #setRevealOnFocusHint(boolean)
+     * @see #getRevealOnFocusHint()
+     */
+    private static final int PFLAG3_NO_REVEAL_ON_FOCUS = 0x4000000;
+
+    /**
+     * Flag indicating that when layout is completed we should notify
+     * that the view was entered for autofill purposes. To minimize
+     * showing autofill for views not visible to the user we evaluate
+     * user visibility which cannot be done until the view is laid out.
+     */
+    static final int PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT = 0x8000000;
+
+    /**
+     * Works like focusable for screen readers, but without the side effects on input focus.
+     * @see #setScreenReaderFocusable(boolean)
+     */
+    private static final int PFLAG3_SCREEN_READER_FOCUSABLE = 0x10000000;
+
+    /**
+     * The last aggregated visibility. Used to detect when it truly changes.
+     */
+    private static final int PFLAG3_AGGREGATED_VISIBLE = 0x20000000;
+
+    /**
+     * Used to indicate that {@link #mAutofillId} was explicitly set through
+     * {@link #setAutofillId(AutofillId)}.
+     */
+    private static final int PFLAG3_AUTOFILLID_EXPLICITLY_SET = 0x40000000;
+
+    /**
+     * Indicates if the View is a heading for accessibility purposes
+     */
+    private static final int PFLAG3_ACCESSIBILITY_HEADING = 0x80000000;
+
+    /* End of masks for mPrivateFlags3 */
+
+    /*
+     * Masks for mPrivateFlags4, as generated by dumpFlags():
+     *
+     * |-------|-------|-------|-------|
+     *                             1111 PFLAG4_IMPORTANT_FOR_CONTENT_CAPTURE_MASK
+     *                            1     PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED
+     *                           1      PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED
+     *                          1       PFLAG4_CONTENT_CAPTURE_IMPORTANCE_IS_CACHED
+     *                         1        PFLAG4_CONTENT_CAPTURE_IMPORTANCE_CACHED_VALUE
+     *                         11       PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK
+     *                        1         PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS
+     *                       1          PFLAG4_AUTOFILL_HIDE_HIGHLIGHT
+     *                     11           PFLAG4_SCROLL_CAPTURE_HINT_MASK
+     *                    1             PFLAG4_ALLOW_CLICK_WHEN_DISABLED
+     *                   1              PFLAG4_DETACHED
+     *                  1               PFLAG4_HAS_TRANSLATION_TRANSIENT_STATE
+     * |-------|-------|-------|-------|
+     */
+
+    /**
+     * Mask for obtaining the bits which specify how to determine
+     * whether a view is important for autofill.
+     *
+     * <p>NOTE: the important for content capture values were the first flags added and are set in
+     * the rightmost position, so we don't need to shift them
+     */
+    private static final int PFLAG4_IMPORTANT_FOR_CONTENT_CAPTURE_MASK =
+            IMPORTANT_FOR_CONTENT_CAPTURE_AUTO | IMPORTANT_FOR_CONTENT_CAPTURE_YES
+            | IMPORTANT_FOR_CONTENT_CAPTURE_NO
+            | IMPORTANT_FOR_CONTENT_CAPTURE_YES_EXCLUDE_DESCENDANTS
+            | IMPORTANT_FOR_CONTENT_CAPTURE_NO_EXCLUDE_DESCENDANTS;
+
+    /*
+     * Variables used to control when the IntelligenceManager.notifyNodeAdded()/removed() methods
+     * should be called.
+     *
+     * The idea is to call notifyAppeared() after the view is layout and visible, then call
+     * notifyDisappeared() when it's gone (without known when it was removed from the parent).
+     */
+    private static final int PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED = 0x10;
+    private static final int PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED = 0x20;
+
+    /*
+     * Flags used to cache the value returned by isImportantForContentCapture while the view
+     * hierarchy is being traversed.
+     */
+    private static final int PFLAG4_CONTENT_CAPTURE_IMPORTANCE_IS_CACHED = 0x40;
+    private static final int PFLAG4_CONTENT_CAPTURE_IMPORTANCE_CACHED_VALUE = 0x80;
+
+    private static final int PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK =
+            PFLAG4_CONTENT_CAPTURE_IMPORTANCE_IS_CACHED
+            | PFLAG4_CONTENT_CAPTURE_IMPORTANCE_CACHED_VALUE;
+
+    /**
+     * @see #OPTIONAL_FITS_SYSTEM_WINDOWS
+     */
+    static final int PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS = 0x000000100;
+
+    /**
+     * Flag indicating the field should not have yellow highlight when autofilled.
+     */
+    private static final int PFLAG4_AUTOFILL_HIDE_HIGHLIGHT = 0x200;
+
+    /**
+     * Shift for the bits in {@link #mPrivateFlags4} related to scroll capture.
+     */
+    static final int PFLAG4_SCROLL_CAPTURE_HINT_SHIFT = 10;
+
+    static final int PFLAG4_SCROLL_CAPTURE_HINT_MASK = (SCROLL_CAPTURE_HINT_INCLUDE
+            | SCROLL_CAPTURE_HINT_EXCLUDE | SCROLL_CAPTURE_HINT_EXCLUDE_DESCENDANTS)
+            << PFLAG4_SCROLL_CAPTURE_HINT_SHIFT;
+
+    /**
+     * Indicates if the view can receive click events when disabled.
+     */
+    private static final int PFLAG4_ALLOW_CLICK_WHEN_DISABLED = 0x000001000;
+
+    /**
+     * Indicates if the view is just detached.
+     */
+    private static final int PFLAG4_DETACHED = 0x000002000;
+
+    /**
+     * Indicates that the view has transient state because the system is translating it.
+     */
+    private static final int PFLAG4_HAS_TRANSLATION_TRANSIENT_STATE = 0x000004000;
+
+    /* End of masks for mPrivateFlags4 */
+
+    /** @hide */
+    protected static final int VIEW_STRUCTURE_FOR_ASSIST = 0;
+    /** @hide */
+    protected  static final int VIEW_STRUCTURE_FOR_AUTOFILL = 1;
+    /** @hide */
+    protected  static final int VIEW_STRUCTURE_FOR_CONTENT_CAPTURE = 2;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "VIEW_STRUCTURE_FOR" }, value = {
+            VIEW_STRUCTURE_FOR_ASSIST,
+            VIEW_STRUCTURE_FOR_AUTOFILL,
+            VIEW_STRUCTURE_FOR_CONTENT_CAPTURE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ViewStructureType {}
+
+    /**
+     * Always allow a user to over-scroll this view, provided it is a
+     * view that can scroll.
+     *
+     * @see #getOverScrollMode()
+     * @see #setOverScrollMode(int)
+     */
+    public static final int OVER_SCROLL_ALWAYS = 0;
+
+    /**
+     * Allow a user to over-scroll this view only if the content is large
+     * enough to meaningfully scroll, provided it is a view that can scroll.
+     *
+     * @see #getOverScrollMode()
+     * @see #setOverScrollMode(int)
+     */
+    public static final int OVER_SCROLL_IF_CONTENT_SCROLLS = 1;
+
+    /**
+     * Never allow a user to over-scroll this view.
+     *
+     * @see #getOverScrollMode()
+     * @see #setOverScrollMode(int)
+     */
+    public static final int OVER_SCROLL_NEVER = 2;
+
+    /**
+     * Special constant for {@link #setSystemUiVisibility(int)}: View has
+     * requested the system UI (status bar) to be visible (the default).
+     *
+     * @see #setSystemUiVisibility(int)
+     * @deprecated SystemUiVisibility flags are deprecated. Use {@link WindowInsetsController}
+     * instead.
+     */
+    @Deprecated
+    public static final int SYSTEM_UI_FLAG_VISIBLE = 0;
+
+    /**
+     * Flag for {@link #setSystemUiVisibility(int)}: View has requested the
+     * system UI to enter an unobtrusive "low profile" mode.
+     *
+     * <p>This is for use in games, book readers, video players, or any other
+     * "immersive" application where the usual system chrome is deemed too distracting.
+     *
+     * <p>In low profile mode, the status bar and/or navigation icons may dim.
+     *
+     * @see #setSystemUiVisibility(int)
+     * @deprecated Low profile mode is deprecated. Hide the system bars instead if the application
+     * needs to be in a unobtrusive mode. Use {@link WindowInsetsController#hide(int)} with
+     * {@link Type#systemBars()}.
+     */
+    @Deprecated
+    public static final int SYSTEM_UI_FLAG_LOW_PROFILE = 0x00000001;
+
+    /**
+     * Flag for {@link #setSystemUiVisibility(int)}: View has requested that the
+     * system navigation be temporarily hidden.
+     *
+     * <p>This is an even less obtrusive state than that called for by
+     * {@link #SYSTEM_UI_FLAG_LOW_PROFILE}; on devices that draw essential navigation controls
+     * (Home, Back, and the like) on screen, <code>SYSTEM_UI_FLAG_HIDE_NAVIGATION</code> will cause
+     * those to disappear. This is useful (in conjunction with the
+     * {@link android.view.WindowManager.LayoutParams#FLAG_FULLSCREEN FLAG_FULLSCREEN} and
+     * {@link android.view.WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN FLAG_LAYOUT_IN_SCREEN}
+     * window flags) for displaying content using every last pixel on the display.
+     *
+     * <p>There is a limitation: because navigation controls are so important, the least user
+     * interaction will cause them to reappear immediately.  When this happens, both
+     * this flag and {@link #SYSTEM_UI_FLAG_FULLSCREEN} will be cleared automatically,
+     * so that both elements reappear at the same time.
+     *
+     * @see #setSystemUiVisibility(int)
+     * @deprecated Use {@link WindowInsetsController#hide(int)} with {@link Type#navigationBars()}
+     * instead.
+     */
+    @Deprecated
+    public static final int SYSTEM_UI_FLAG_HIDE_NAVIGATION = 0x00000002;
+
+    /**
+     * Flag for {@link #setSystemUiVisibility(int)}: View has requested to go
+     * into the normal fullscreen mode so that its content can take over the screen
+     * while still allowing the user to interact with the application.
+     *
+     * <p>This has the same visual effect as
+     * {@link android.view.WindowManager.LayoutParams#FLAG_FULLSCREEN
+     * WindowManager.LayoutParams.FLAG_FULLSCREEN},
+     * meaning that non-critical screen decorations (such as the status bar) will be
+     * hidden while the user is in the View's window, focusing the experience on
+     * that content.  Unlike the window flag, if you are using ActionBar in
+     * overlay mode with {@link Window#FEATURE_ACTION_BAR_OVERLAY
+     * Window.FEATURE_ACTION_BAR_OVERLAY}, then enabling this flag will also
+     * hide the action bar.
+     *
+     * <p>This approach to going fullscreen is best used over the window flag when
+     * it is a transient state -- that is, the application does this at certain
+     * points in its user interaction where it wants to allow the user to focus
+     * on content, but not as a continuous state.  For situations where the application
+     * would like to simply stay full screen the entire time (such as a game that
+     * wants to take over the screen), the
+     * {@link android.view.WindowManager.LayoutParams#FLAG_FULLSCREEN window flag}
+     * is usually a better approach.  The state set here will be removed by the system
+     * in various situations (such as the user moving to another application) like
+     * the other system UI states.
+     *
+     * <p>When using this flag, the application should provide some easy facility
+     * for the user to go out of it.  A common example would be in an e-book
+     * reader, where tapping on the screen brings back whatever screen and UI
+     * decorations that had been hidden while the user was immersed in reading
+     * the book.
+     *
+     * @see #setSystemUiVisibility(int)
+     * @deprecated Use {@link WindowInsetsController#hide(int)} with {@link Type#statusBars()}
+     * instead.
+     */
+    @Deprecated
+    public static final int SYSTEM_UI_FLAG_FULLSCREEN = 0x00000004;
+
+    /**
+     * Flag for {@link #setSystemUiVisibility(int)}: When using other layout
+     * flags, we would like a stable view of the content insets given to
+     * {@link #fitSystemWindows(Rect)}.  This means that the insets seen there
+     * will always represent the worst case that the application can expect
+     * as a continuous state.  In the stock Android UI this is the space for
+     * the system bar, nav bar, and status bar, but not more transient elements
+     * such as an input method.
+     *
+     * The stable layout your UI sees is based on the system UI modes you can
+     * switch to.  That is, if you specify {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}
+     * then you will get a stable layout for changes of the
+     * {@link #SYSTEM_UI_FLAG_FULLSCREEN} mode; if you specify
+     * {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN} and
+     * {@link #SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION}, then you can transition
+     * to {@link #SYSTEM_UI_FLAG_FULLSCREEN} and {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}
+     * with a stable layout.  (Note that you should avoid using
+     * {@link #SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION} by itself.)
+     *
+     * If you have set the window flag {@link WindowManager.LayoutParams#FLAG_FULLSCREEN}
+     * to hide the status bar (instead of using {@link #SYSTEM_UI_FLAG_FULLSCREEN}),
+     * then a hidden status bar will be considered a "stable" state for purposes
+     * here.  This allows your UI to continually hide the status bar, while still
+     * using the system UI flags to hide the action bar while still retaining
+     * a stable layout.  Note that changing the window fullscreen flag will never
+     * provide a stable layout for a clean transition.
+     *
+     * <p>If you are using ActionBar in
+     * overlay mode with {@link Window#FEATURE_ACTION_BAR_OVERLAY
+     * Window.FEATURE_ACTION_BAR_OVERLAY}, this flag will also impact the
+     * insets it adds to those given to the application.
+     *
+     * @deprecated Use {@link WindowInsets#getInsetsIgnoringVisibility(int)} instead to retrieve
+     * insets that don't change when system bars change visibility state.
+     */
+    @Deprecated
+    public static final int SYSTEM_UI_FLAG_LAYOUT_STABLE = 0x00000100;
+
+    /**
+     * Flag for {@link #setSystemUiVisibility(int)}: View would like its window
+     * to be laid out as if it has requested
+     * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, even if it currently hasn't.  This
+     * allows it to avoid artifacts when switching in and out of that mode, at
+     * the expense that some of its user interface may be covered by screen
+     * decorations when they are shown.  You can perform layout of your inner
+     * UI elements to account for the navigation system UI through the
+     * {@link #fitSystemWindows(Rect)} method.
+     *
+     * @deprecated For floating windows, use {@link LayoutParams#setFitInsetsTypes(int)} with
+     * {@link Type#navigationBars()}. For non-floating windows that fill the screen, call
+     * {@link Window#setDecorFitsSystemWindows(boolean)} with {@code false}.
+     */
+    public static final int SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION = 0x00000200;
+
+    /**
+     * Flag for {@link #setSystemUiVisibility(int)}: View would like its window
+     * to be laid out as if it has requested
+     * {@link #SYSTEM_UI_FLAG_FULLSCREEN}, even if it currently hasn't.  This
+     * allows it to avoid artifacts when switching in and out of that mode, at
+     * the expense that some of its user interface may be covered by screen
+     * decorations when they are shown.  You can perform layout of your inner
+     * UI elements to account for non-fullscreen system UI through the
+     * {@link #fitSystemWindows(Rect)} method.
+     *
+     * <p>Note: on displays that have a {@link DisplayCutout}, the window may still be placed
+     *  differently than if {@link #SYSTEM_UI_FLAG_FULLSCREEN} was set, if the
+     *  window's {@link WindowManager.LayoutParams#layoutInDisplayCutoutMode
+     *  layoutInDisplayCutoutMode} is
+     *  {@link WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
+     *  LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT}. To avoid this, use either of the other modes.
+     *
+     * @see WindowManager.LayoutParams#layoutInDisplayCutoutMode
+     * @see WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
+     * @see WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
+     * @see WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
+     *
+     * @deprecated For floating windows, use {@link LayoutParams#setFitInsetsTypes(int)} with
+     * {@link Type#statusBars()} ()}. For non-floating windows that fill the screen, call
+     * {@link Window#setDecorFitsSystemWindows(boolean)} with {@code false}.
+     */
+    @Deprecated
+    public static final int SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = 0x00000400;
+
+    /**
+     * Flag for {@link #setSystemUiVisibility(int)}: View would like to remain interactive when
+     * hiding the navigation bar with {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}.  If this flag is
+     * not set, {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION} will be force cleared by the system on any
+     * user interaction.
+     * <p>Since this flag is a modifier for {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, it only
+     * has an effect when used in combination with that flag.</p>
+     *
+     * @deprecated Use {@link WindowInsetsController#BEHAVIOR_DEFAULT} instead.
+     */
+    @Deprecated
+    public static final int SYSTEM_UI_FLAG_IMMERSIVE = 0x00000800;
+
+    /**
+     * Flag for {@link #setSystemUiVisibility(int)}: View would like to remain interactive when
+     * hiding the status bar with {@link #SYSTEM_UI_FLAG_FULLSCREEN} and/or hiding the navigation
+     * bar with {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}.  Use this flag to create an immersive
+     * experience while also hiding the system bars.  If this flag is not set,
+     * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION} will be force cleared by the system on any user
+     * interaction, and {@link #SYSTEM_UI_FLAG_FULLSCREEN} will be force-cleared by the system
+     * if the user swipes from the top of the screen.
+     * <p>When system bars are hidden in immersive mode, they can be revealed temporarily with
+     * system gestures, such as swiping from the top of the screen.  These transient system bars
+     * will overlay app's content, may have some degree of transparency, and will automatically
+     * hide after a short timeout.
+     * </p><p>Since this flag is a modifier for {@link #SYSTEM_UI_FLAG_FULLSCREEN} and
+     * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, it only has an effect when used in combination
+     * with one or both of those flags.</p>
+     *
+     * @deprecated Use {@link WindowInsetsController#BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE} instead.
+     */
+    @Deprecated
+    public static final int SYSTEM_UI_FLAG_IMMERSIVE_STICKY = 0x00001000;
+
+    /**
+     * Flag for {@link #setSystemUiVisibility(int)}: Requests the status bar to draw in a mode that
+     * is compatible with light status bar backgrounds.
+     *
+     * <p>For this to take effect, the window must request
+     * {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
+     *         FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} but not
+     * {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_STATUS
+     *         FLAG_TRANSLUCENT_STATUS}.
+     *
+     * @see android.R.attr#windowLightStatusBar
+     * @deprecated Use {@link WindowInsetsController#APPEARANCE_LIGHT_STATUS_BARS} instead.
+     */
+    @Deprecated
+    public static final int SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = 0x00002000;
+
+    /**
+     * This flag was previously used for a private API. DO NOT reuse it for a public API as it might
+     * trigger undefined behavior on older platforms with apps compiled against a new SDK.
+     */
+    private static final int SYSTEM_UI_RESERVED_LEGACY1 = 0x00004000;
+
+    /**
+     * This flag was previously used for a private API. DO NOT reuse it for a public API as it might
+     * trigger undefined behavior on older platforms with apps compiled against a new SDK.
+     */
+    private static final int SYSTEM_UI_RESERVED_LEGACY2 = 0x00010000;
+
+    /**
+     * Flag for {@link #setSystemUiVisibility(int)}: Requests the navigation bar to draw in a mode
+     * that is compatible with light navigation bar backgrounds.
+     *
+     * <p>For this to take effect, the window must request
+     * {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
+     *         FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} but not
+     * {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_NAVIGATION
+     *         FLAG_TRANSLUCENT_NAVIGATION}.
+     *
+     * @see android.R.attr#windowLightNavigationBar
+     * @deprecated Use {@link WindowInsetsController#APPEARANCE_LIGHT_NAVIGATION_BARS} instead.
+     */
+    @Deprecated
+    public static final int SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR = 0x00000010;
+
+    /**
+     * @deprecated Use {@link #SYSTEM_UI_FLAG_LOW_PROFILE} instead.
+     */
+    @Deprecated
+    public static final int STATUS_BAR_HIDDEN = SYSTEM_UI_FLAG_LOW_PROFILE;
+
+    /**
+     * @deprecated Use {@link #SYSTEM_UI_FLAG_VISIBLE} instead.
+     */
+    @Deprecated
+    public static final int STATUS_BAR_VISIBLE = SYSTEM_UI_FLAG_VISIBLE;
+
+    /**
+     * @hide
+     *
+     * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+     * out of the public fields to keep the undefined bits out of the developer's way.
+     *
+     * Flag to make the status bar not expandable.  Unless you also
+     * set {@link #STATUS_BAR_DISABLE_NOTIFICATION_ICONS}, new notifications will continue to show.
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public static final int STATUS_BAR_DISABLE_EXPAND = 0x00010000;
+
+    /**
+     * @hide
+     *
+     * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+     * out of the public fields to keep the undefined bits out of the developer's way.
+     *
+     * Flag to hide notification icons and scrolling ticker text.
+     */
+    public static final int STATUS_BAR_DISABLE_NOTIFICATION_ICONS = 0x00020000;
+
+    /**
+     * @hide
+     *
+     * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+     * out of the public fields to keep the undefined bits out of the developer's way.
+     *
+     * Flag to disable incoming notification alerts.  This will not block
+     * icons, but it will block sound, vibrating and other visual or aural notifications.
+     */
+    public static final int STATUS_BAR_DISABLE_NOTIFICATION_ALERTS = 0x00040000;
+
+    /**
+     * @hide
+     *
+     * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+     * out of the public fields to keep the undefined bits out of the developer's way.
+     *
+     * Flag to hide only the scrolling ticker.  Note that
+     * {@link #STATUS_BAR_DISABLE_NOTIFICATION_ICONS} implies
+     * {@link #STATUS_BAR_DISABLE_NOTIFICATION_TICKER}.
+     */
+    public static final int STATUS_BAR_DISABLE_NOTIFICATION_TICKER = 0x00080000;
+
+    /**
+     * @hide
+     *
+     * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+     * out of the public fields to keep the undefined bits out of the developer's way.
+     *
+     * Flag to hide the center system info area.
+     */
+    public static final int STATUS_BAR_DISABLE_SYSTEM_INFO = 0x00100000;
+
+    /**
+     * @hide
+     *
+     * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+     * out of the public fields to keep the undefined bits out of the developer's way.
+     *
+     * Flag to hide only the home button.  Don't use this
+     * unless you're a special part of the system UI (i.e., setup wizard, keyguard).
+     */
+    @UnsupportedAppUsage
+    public static final int STATUS_BAR_DISABLE_HOME = 0x00200000;
+
+    /**
+     * @hide
+     *
+     * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+     * out of the public fields to keep the undefined bits out of the developer's way.
+     *
+     * Flag to hide only the back button. Don't use this
+     * unless you're a special part of the system UI (i.e., setup wizard, keyguard).
+     */
+    @UnsupportedAppUsage
+    public static final int STATUS_BAR_DISABLE_BACK = 0x00400000;
+
+    /**
+     * @hide
+     *
+     * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+     * out of the public fields to keep the undefined bits out of the developer's way.
+     *
+     * Flag to hide only the clock.  You might use this if your activity has
+     * its own clock making the status bar's clock redundant.
+     */
+    public static final int STATUS_BAR_DISABLE_CLOCK = 0x00800000;
+
+    /**
+     * @hide
+     *
+     * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+     * out of the public fields to keep the undefined bits out of the developer's way.
+     *
+     * Flag to hide only the recent apps button. Don't use this
+     * unless you're a special part of the system UI (i.e., setup wizard, keyguard).
+     */
+    @UnsupportedAppUsage
+    public static final int STATUS_BAR_DISABLE_RECENT = 0x01000000;
+
+    /**
+     * @hide
+     *
+     * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+     * out of the public fields to keep the undefined bits out of the developer's way.
+     *
+     * Flag to disable the global search gesture. Don't use this
+     * unless you're a special part of the system UI (i.e., setup wizard, keyguard).
+     */
+    public static final int STATUS_BAR_DISABLE_SEARCH = 0x02000000;
+
+    /**
+     * @hide
+     *
+     * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+     * out of the public fields to keep the undefined bits out of the developer's way.
+     *
+     * Flag to disable the ongoing call chip.
+     */
+    public static final int STATUS_BAR_DISABLE_ONGOING_CALL_CHIP = 0x04000000;
+
+    /**
+     * @hide
+     */
+    public static final int PUBLIC_STATUS_BAR_VISIBILITY_MASK = 0x00003FF7;
+
+    /**
+     * These are the system UI flags that can be cleared by events outside
+     * of an application.  Currently this is just the ability to tap on the
+     * screen while hiding the navigation bar to have it return.
+     * @hide
+     */
+    public static final int SYSTEM_UI_CLEARABLE_FLAGS =
+            SYSTEM_UI_FLAG_LOW_PROFILE | SYSTEM_UI_FLAG_HIDE_NAVIGATION
+            | SYSTEM_UI_FLAG_FULLSCREEN;
+
+    /**
+     * Flags that can impact the layout in relation to system UI.
+     *
+     * @deprecated System UI layout flags are deprecated.
+     */
+    @Deprecated
+    public static final int SYSTEM_UI_LAYOUT_FLAGS =
+            SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+            | SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "FIND_VIEWS_" }, value = {
+            FIND_VIEWS_WITH_TEXT,
+            FIND_VIEWS_WITH_CONTENT_DESCRIPTION
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FindViewFlags {}
+
+    /**
+     * Find views that render the specified text.
+     *
+     * @see #findViewsWithText(ArrayList, CharSequence, int)
+     */
+    public static final int FIND_VIEWS_WITH_TEXT = 0x00000001;
+
+    /**
+     * Find find views that contain the specified content description.
+     *
+     * @see #findViewsWithText(ArrayList, CharSequence, int)
+     */
+    public static final int FIND_VIEWS_WITH_CONTENT_DESCRIPTION = 0x00000002;
+
+    /**
+     * Find views that contain {@link AccessibilityNodeProvider}. Such
+     * a View is a root of virtual view hierarchy and may contain the searched
+     * text. If this flag is set Views with providers are automatically
+     * added and it is a responsibility of the client to call the APIs of
+     * the provider to determine whether the virtual tree rooted at this View
+     * contains the text, i.e. getting the list of {@link AccessibilityNodeInfo}s
+     * representing the virtual views with this text.
+     *
+     * @see #findViewsWithText(ArrayList, CharSequence, int)
+     *
+     * @hide
+     */
+    public static final int FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS = 0x00000004;
+
+    /**
+     * The undefined cursor position.
+     *
+     * @hide
+     */
+    public static final int ACCESSIBILITY_CURSOR_POSITION_UNDEFINED = -1;
+
+    /**
+     * Indicates that the screen has changed state and is now off.
+     *
+     * @see #onScreenStateChanged(int)
+     */
+    public static final int SCREEN_STATE_OFF = 0x0;
+
+    /**
+     * Indicates that the screen has changed state and is now on.
+     *
+     * @see #onScreenStateChanged(int)
+     */
+    public static final int SCREEN_STATE_ON = 0x1;
+
+    /**
+     * Indicates no axis of view scrolling.
+     */
+    public static final int SCROLL_AXIS_NONE = 0;
+
+    /**
+     * Indicates scrolling along the horizontal axis.
+     */
+    public static final int SCROLL_AXIS_HORIZONTAL = 1 << 0;
+
+    /**
+     * Indicates scrolling along the vertical axis.
+     */
+    public static final int SCROLL_AXIS_VERTICAL = 1 << 1;
+
+    /**
+     * Controls the over-scroll mode for this view.
+     * See {@link #overScrollBy(int, int, int, int, int, int, int, int, boolean)},
+     * {@link #OVER_SCROLL_ALWAYS}, {@link #OVER_SCROLL_IF_CONTENT_SCROLLS},
+     * and {@link #OVER_SCROLL_NEVER}.
+     */
+    private int mOverScrollMode;
+
+    /**
+     * The parent this view is attached to.
+     * {@hide}
+     *
+     * @see #getParent()
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    protected ViewParent mParent;
+
+    /**
+     * {@hide}
+     *
+     * Not available for general use. If you need help, hang up and then dial one of the following
+     * public APIs:
+     *
+     * @see #isAttachedToWindow() for current attach state
+     * @see #onAttachedToWindow() for subclasses performing work when becoming attached
+     * @see #onDetachedFromWindow() for subclasses performing work when becoming detached
+     * @see OnAttachStateChangeListener for other code performing work on attach/detach
+     * @see #getHandler() for posting messages to this view's UI thread/looper
+     * @see #getParent() for interacting with the parent chain
+     * @see #getWindowToken() for the current window token
+     * @see #getRootView() for the view at the root of the attached hierarchy
+     * @see #getDisplay() for the Display this view is presented on
+     * @see #getRootWindowInsets() for the current insets applied to the whole attached window
+     * @see #hasWindowFocus() for whether the attached window is currently focused
+     * @see #getWindowVisibility() for checking the visibility of the attached window
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    AttachInfo mAttachInfo;
+
+    /**
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty(flagMapping = {
+        @ViewDebug.FlagToString(mask = PFLAG_FORCE_LAYOUT, equals = PFLAG_FORCE_LAYOUT,
+                name = "FORCE_LAYOUT"),
+        @ViewDebug.FlagToString(mask = PFLAG_LAYOUT_REQUIRED, equals = PFLAG_LAYOUT_REQUIRED,
+                name = "LAYOUT_REQUIRED"),
+        @ViewDebug.FlagToString(mask = PFLAG_DRAWING_CACHE_VALID, equals = PFLAG_DRAWING_CACHE_VALID,
+            name = "DRAWING_CACHE_INVALID", outputIf = false),
+        @ViewDebug.FlagToString(mask = PFLAG_DRAWN, equals = PFLAG_DRAWN, name = "DRAWN", outputIf = true),
+        @ViewDebug.FlagToString(mask = PFLAG_DRAWN, equals = PFLAG_DRAWN, name = "NOT_DRAWN", outputIf = false),
+        @ViewDebug.FlagToString(mask = PFLAG_DIRTY_MASK, equals = PFLAG_DIRTY, name = "DIRTY")
+    }, formatToHexString = true)
+
+    /* @hide */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769414)
+    public int mPrivateFlags;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768943)
+    int mPrivateFlags2;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 129147060)
+    int mPrivateFlags3;
+
+    private int mPrivateFlags4;
+
+    /**
+     * This view's request for the visibility of the status bar.
+     * @hide
+     */
+    @ViewDebug.ExportedProperty(flagMapping = {
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LOW_PROFILE,
+                    equals = SYSTEM_UI_FLAG_LOW_PROFILE,
+                    name = "LOW_PROFILE"),
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_HIDE_NAVIGATION,
+                    equals = SYSTEM_UI_FLAG_HIDE_NAVIGATION,
+                    name = "HIDE_NAVIGATION"),
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_FULLSCREEN,
+                    equals = SYSTEM_UI_FLAG_FULLSCREEN,
+                    name = "FULLSCREEN"),
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LAYOUT_STABLE,
+                    equals = SYSTEM_UI_FLAG_LAYOUT_STABLE,
+                    name = "LAYOUT_STABLE"),
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION,
+                    equals = SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION,
+                    name = "LAYOUT_HIDE_NAVIGATION"),
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,
+                    equals = SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,
+                    name = "LAYOUT_FULLSCREEN"),
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_IMMERSIVE,
+                    equals = SYSTEM_UI_FLAG_IMMERSIVE,
+                    name = "IMMERSIVE"),
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_IMMERSIVE_STICKY,
+                    equals = SYSTEM_UI_FLAG_IMMERSIVE_STICKY,
+                    name = "IMMERSIVE_STICKY"),
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LIGHT_STATUS_BAR,
+                    equals = SYSTEM_UI_FLAG_LIGHT_STATUS_BAR,
+                    name = "LIGHT_STATUS_BAR"),
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
+                    equals = SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
+                    name = "LIGHT_NAVIGATION_BAR"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_EXPAND,
+                    equals = STATUS_BAR_DISABLE_EXPAND,
+                    name = "STATUS_BAR_DISABLE_EXPAND"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_NOTIFICATION_ICONS,
+                    equals = STATUS_BAR_DISABLE_NOTIFICATION_ICONS,
+                    name = "STATUS_BAR_DISABLE_NOTIFICATION_ICONS"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_NOTIFICATION_ALERTS,
+                    equals = STATUS_BAR_DISABLE_NOTIFICATION_ALERTS,
+                    name = "STATUS_BAR_DISABLE_NOTIFICATION_ALERTS"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_NOTIFICATION_TICKER,
+                    equals = STATUS_BAR_DISABLE_NOTIFICATION_TICKER,
+                    name = "STATUS_BAR_DISABLE_NOTIFICATION_TICKER"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_SYSTEM_INFO,
+                    equals = STATUS_BAR_DISABLE_SYSTEM_INFO,
+                    name = "STATUS_BAR_DISABLE_SYSTEM_INFO"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_HOME,
+                    equals = STATUS_BAR_DISABLE_HOME,
+                    name = "STATUS_BAR_DISABLE_HOME"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_BACK,
+                    equals = STATUS_BAR_DISABLE_BACK,
+                    name = "STATUS_BAR_DISABLE_BACK"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_CLOCK,
+                    equals = STATUS_BAR_DISABLE_CLOCK,
+                    name = "STATUS_BAR_DISABLE_CLOCK"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_RECENT,
+                    equals = STATUS_BAR_DISABLE_RECENT,
+                    name = "STATUS_BAR_DISABLE_RECENT"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_SEARCH,
+                    equals = STATUS_BAR_DISABLE_SEARCH,
+                    name = "STATUS_BAR_DISABLE_SEARCH"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_ONGOING_CALL_CHIP,
+                    equals = STATUS_BAR_DISABLE_ONGOING_CALL_CHIP,
+                    name = "STATUS_BAR_DISABLE_ONGOING_CALL_CHIP")
+    }, formatToHexString = true)
+    @SystemUiVisibility
+    int mSystemUiVisibility;
+
+    /**
+     * @hide
+     */
+    @IntDef(flag = true, prefix = "", value = {
+            SYSTEM_UI_FLAG_LOW_PROFILE,
+            SYSTEM_UI_FLAG_HIDE_NAVIGATION,
+            SYSTEM_UI_FLAG_FULLSCREEN,
+            SYSTEM_UI_FLAG_LAYOUT_STABLE,
+            SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION,
+            SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,
+            SYSTEM_UI_FLAG_IMMERSIVE,
+            SYSTEM_UI_FLAG_IMMERSIVE_STICKY,
+            SYSTEM_UI_FLAG_LIGHT_STATUS_BAR,
+            SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
+            STATUS_BAR_DISABLE_EXPAND,
+            STATUS_BAR_DISABLE_NOTIFICATION_ICONS,
+            STATUS_BAR_DISABLE_NOTIFICATION_ALERTS,
+            STATUS_BAR_DISABLE_NOTIFICATION_TICKER,
+            STATUS_BAR_DISABLE_SYSTEM_INFO,
+            STATUS_BAR_DISABLE_HOME,
+            STATUS_BAR_DISABLE_BACK,
+            STATUS_BAR_DISABLE_CLOCK,
+            STATUS_BAR_DISABLE_RECENT,
+            STATUS_BAR_DISABLE_SEARCH,
+            STATUS_BAR_DISABLE_ONGOING_CALL_CHIP,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SystemUiVisibility {}
+
+    /**
+     * Reference count for transient state.
+     * @see #setHasTransientState(boolean)
+     */
+    int mTransientStateCount = 0;
+
+    /**
+     * Count of how many windows this view has been attached to.
+     */
+    int mWindowAttachCount;
+
+    /**
+     * The layout parameters associated with this view and used by the parent
+     * {@link android.view.ViewGroup} to determine how this view should be
+     * laid out.
+     *
+     * The field should not be used directly. Instead {@link #getLayoutParams()} and {@link
+     * #setLayoutParams(ViewGroup.LayoutParams)} should be used. The setter guarantees internal
+     * state correctness of the class.
+     * {@hide}
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    protected ViewGroup.LayoutParams mLayoutParams;
+
+    /**
+     * The view flags hold various views states.
+     *
+     * Use {@link #setTransitionVisibility(int)} to change the visibility of this view without
+     * triggering updates.
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty(formatToHexString = true)
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    int mViewFlags;
+
+    static class TransformationInfo {
+        /**
+         * The transform matrix for the View. This transform is calculated internally
+         * based on the translation, rotation, and scale properties.
+         *
+         * Do *not* use this variable directly; instead call getMatrix(), which will
+         * load the value from the View's RenderNode.
+         */
+        private final Matrix mMatrix = new Matrix();
+
+        /**
+         * The inverse transform matrix for the View. This transform is calculated
+         * internally based on the translation, rotation, and scale properties.
+         *
+         * Do *not* use this variable directly; instead call getInverseMatrix(),
+         * which will load the value from the View's RenderNode.
+         */
+        private Matrix mInverseMatrix;
+
+        /**
+         * The opacity of the View. This is a value from 0 to 1, where 0 means
+         * completely transparent and 1 means completely opaque.
+         */
+        @ViewDebug.ExportedProperty
+        private float mAlpha = 1f;
+
+        /**
+         * The opacity of the view as manipulated by the Fade transition. This is a
+         * property only used by transitions, which is composited with the other alpha
+         * values to calculate the final visual alpha value.
+         */
+        float mTransitionAlpha = 1f;
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public TransformationInfo mTransformationInfo;
+
+    /**
+     * Current clip bounds. to which all drawing of this view are constrained.
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    Rect mClipBounds = null;
+
+    private boolean mLastIsOpaque;
+
+    /**
+     * The distance in pixels from the left edge of this view's parent
+     * to the left edge of this view.
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty(category = "layout")
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    protected int mLeft;
+    /**
+     * The distance in pixels from the left edge of this view's parent
+     * to the right edge of this view.
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty(category = "layout")
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    protected int mRight;
+    /**
+     * The distance in pixels from the top edge of this view's parent
+     * to the top edge of this view.
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty(category = "layout")
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    protected int mTop;
+    /**
+     * The distance in pixels from the top edge of this view's parent
+     * to the bottom edge of this view.
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty(category = "layout")
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    protected int mBottom;
+
+    /**
+     * The offset, in pixels, by which the content of this view is scrolled
+     * horizontally.
+     * Please use {@link View#getScrollX()} and {@link View#setScrollX(int)} instead of
+     * accessing these directly.
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty(category = "scrolling")
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    protected int mScrollX;
+    /**
+     * The offset, in pixels, by which the content of this view is scrolled
+     * vertically.
+     * Please use {@link View#getScrollY()} and {@link View#setScrollY(int)} instead of
+     * accessing these directly.
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty(category = "scrolling")
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    protected int mScrollY;
+
+    /**
+     * The final computed left padding in pixels that is used for drawing. This is the distance in
+     * pixels between the left edge of this view and the left edge of its content.
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty(category = "padding")
+    @UnsupportedAppUsage
+    protected int mPaddingLeft = 0;
+    /**
+     * The final computed right padding in pixels that is used for drawing. This is the distance in
+     * pixels between the right edge of this view and the right edge of its content.
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty(category = "padding")
+    @UnsupportedAppUsage
+    protected int mPaddingRight = 0;
+    /**
+     * The final computed top padding in pixels that is used for drawing. This is the distance in
+     * pixels between the top edge of this view and the top edge of its content.
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty(category = "padding")
+    @UnsupportedAppUsage
+    protected int mPaddingTop;
+    /**
+     * The final computed bottom padding in pixels that is used for drawing. This is the distance in
+     * pixels between the bottom edge of this view and the bottom edge of its content.
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty(category = "padding")
+    @UnsupportedAppUsage
+    protected int mPaddingBottom;
+
+    /**
+     * The layout insets in pixels, that is the distance in pixels between the
+     * visible edges of this view its bounds.
+     */
+    private Insets mLayoutInsets;
+
+    /**
+     * Briefly describes the state of the view and is primarily used for accessibility support.
+     */
+    private CharSequence mStateDescription;
+
+    /**
+     * Briefly describes the view and is primarily used for accessibility support.
+     */
+    private CharSequence mContentDescription;
+
+    /**
+     * If this view represents a distinct part of the window, it can have a title that labels the
+     * area.
+     */
+    private CharSequence mAccessibilityPaneTitle;
+
+    /**
+     * Specifies the id of a view for which this view serves as a label for
+     * accessibility purposes.
+     */
+    private int mLabelForId = View.NO_ID;
+
+    /**
+     * Predicate for matching labeled view id with its label for
+     * accessibility purposes.
+     */
+    private MatchLabelForPredicate mMatchLabelForPredicate;
+
+    /**
+     * Specifies a view before which this one is visited in accessibility traversal.
+     */
+    private int mAccessibilityTraversalBeforeId = NO_ID;
+
+    /**
+     * Specifies a view after which this one is visited in accessibility traversal.
+     */
+    private int mAccessibilityTraversalAfterId = NO_ID;
+
+    /**
+     * Predicate for matching a view by its id.
+     */
+    private MatchIdPredicate mMatchIdPredicate;
+
+    /**
+     * The right padding after RTL resolution, but before taking account of scroll bars.
+     *
+     * @hide
+     */
+    @ViewDebug.ExportedProperty(category = "padding")
+    protected int mUserPaddingRight;
+
+    /**
+     * The resolved bottom padding before taking account of scroll bars.
+     *
+     * @hide
+     */
+    @ViewDebug.ExportedProperty(category = "padding")
+    protected int mUserPaddingBottom;
+
+    /**
+     * The left padding after RTL resolution, but before taking account of scroll bars.
+     *
+     * @hide
+     */
+    @ViewDebug.ExportedProperty(category = "padding")
+    protected int mUserPaddingLeft;
+
+    /**
+     * Cache the paddingStart set by the user to append to the scrollbar's size.
+     *
+     */
+    @ViewDebug.ExportedProperty(category = "padding")
+    int mUserPaddingStart;
+
+    /**
+     * Cache the paddingEnd set by the user to append to the scrollbar's size.
+     *
+     */
+    @ViewDebug.ExportedProperty(category = "padding")
+    int mUserPaddingEnd;
+
+    /**
+     * The left padding as set by a setter method, a background's padding, or via XML property
+     * resolution. This value is the padding before LTR resolution or taking account of scrollbars.
+     *
+     * @hide
+     */
+    int mUserPaddingLeftInitial;
+
+    /**
+     * The right padding as set by a setter method, a background's padding, or via XML property
+     * resolution. This value is the padding before LTR resolution or taking account of scrollbars.
+     *
+     * @hide
+     */
+    int mUserPaddingRightInitial;
+
+    /**
+     * Default undefined padding
+     */
+    private static final int UNDEFINED_PADDING = Integer.MIN_VALUE;
+
+    /**
+     * Cache if a left padding has been defined explicitly via padding, horizontal padding,
+     * or leftPadding in XML, or by setPadding(...) or setRelativePadding(...)
+     */
+    private boolean mLeftPaddingDefined = false;
+
+    /**
+     * Cache if a right padding has been defined explicitly via padding, horizontal padding,
+     * or rightPadding in XML, or by setPadding(...) or setRelativePadding(...)
+     */
+    private boolean mRightPaddingDefined = false;
+
+    /**
+     * @hide
+     */
+    int mOldWidthMeasureSpec = Integer.MIN_VALUE;
+    /**
+     * @hide
+     */
+    int mOldHeightMeasureSpec = Integer.MIN_VALUE;
+
+    private LongSparseLongArray mMeasureCache;
+
+    @ViewDebug.ExportedProperty(deepExport = true, prefix = "bg_")
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private Drawable mBackground;
+    private TintInfo mBackgroundTint;
+
+    @ViewDebug.ExportedProperty(deepExport = true, prefix = "fg_")
+    private ForegroundInfo mForegroundInfo;
+
+    private Drawable mScrollIndicatorDrawable;
+
+    /**
+     * RenderNode used for backgrounds.
+     * <p>
+     * When non-null and valid, this is expected to contain an up-to-date copy
+     * of the background drawable. It is cleared on temporary detach, and reset
+     * on cleanup.
+     * @hide
+     */
+    RenderNode mBackgroundRenderNode;
+
+    @UnsupportedAppUsage
+    private int mBackgroundResource;
+    private boolean mBackgroundSizeChanged;
+
+    /** The default focus highlight.
+     * @see #mDefaultFocusHighlightEnabled
+     * @see Drawable#hasFocusStateSpecified()
+     */
+    private Drawable mDefaultFocusHighlight;
+    private Drawable mDefaultFocusHighlightCache;
+    private boolean mDefaultFocusHighlightSizeChanged;
+    /**
+     * True if the default focus highlight is needed on the target device.
+     */
+    private static boolean sUseDefaultFocusHighlight;
+
+    /**
+     * True if zero-sized views can be focused.
+     */
+    private static boolean sCanFocusZeroSized;
+
+    /**
+     * Always assign focus if a focusable View is available.
+     */
+    private static boolean sAlwaysAssignFocus;
+
+    private String mTransitionName;
+
+    static class TintInfo {
+        ColorStateList mTintList;
+        BlendMode mBlendMode;
+        boolean mHasTintMode;
+        boolean mHasTintList;
+    }
+
+    private static class ForegroundInfo {
+        private Drawable mDrawable;
+        private TintInfo mTintInfo;
+        private int mGravity = Gravity.FILL;
+        private boolean mInsidePadding = true;
+        private boolean mBoundsChanged = true;
+        private final Rect mSelfBounds = new Rect();
+        private final Rect mOverlayBounds = new Rect();
+    }
+
+    static class ListenerInfo {
+
+        @UnsupportedAppUsage
+        ListenerInfo() {
+        }
+
+        /**
+         * Listener used to dispatch focus change events.
+         * This field should be made private, so it is hidden from the SDK.
+         * {@hide}
+         */
+        @UnsupportedAppUsage
+        protected OnFocusChangeListener mOnFocusChangeListener;
+
+        /**
+         * Listeners for layout change events.
+         */
+        private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;
+
+        protected OnScrollChangeListener mOnScrollChangeListener;
+
+        /**
+         * Listeners for attach events.
+         */
+        private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;
+
+        /**
+         * Listener used to dispatch click events.
+         * This field should be made private, so it is hidden from the SDK.
+         * {@hide}
+         */
+        @UnsupportedAppUsage
+        public OnClickListener mOnClickListener;
+
+        /**
+         * Listener used to dispatch long click events.
+         * This field should be made private, so it is hidden from the SDK.
+         * {@hide}
+         */
+        @UnsupportedAppUsage
+        protected OnLongClickListener mOnLongClickListener;
+
+        /**
+         * Listener used to dispatch context click events. This field should be made private, so it
+         * is hidden from the SDK.
+         * {@hide}
+         */
+        protected OnContextClickListener mOnContextClickListener;
+
+        /**
+         * Listener used to build the context menu.
+         * This field should be made private, so it is hidden from the SDK.
+         * {@hide}
+         */
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        protected OnCreateContextMenuListener mOnCreateContextMenuListener;
+
+        @UnsupportedAppUsage
+        private OnKeyListener mOnKeyListener;
+
+        @UnsupportedAppUsage
+        private OnTouchListener mOnTouchListener;
+
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        private OnHoverListener mOnHoverListener;
+
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        private OnGenericMotionListener mOnGenericMotionListener;
+
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        private OnDragListener mOnDragListener;
+
+        private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;
+
+        OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;
+
+        OnCapturedPointerListener mOnCapturedPointerListener;
+
+        private ArrayList<OnUnhandledKeyEventListener> mUnhandledKeyListeners;
+
+        WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback;
+
+        /**
+         * This lives here since it's only valid for interactive views. This list is null until the
+         * first use.
+         */
+        private List<Rect> mSystemGestureExclusionRects = null;
+
+        /**
+         * Used to track {@link #mSystemGestureExclusionRects}
+         */
+        public RenderNode.PositionUpdateListener mPositionUpdateListener;
+
+        /**
+         * Allows the application to implement custom scroll capture support.
+         */
+        ScrollCaptureCallback mScrollCaptureCallback;
+
+        @Nullable
+        private OnReceiveContentListener mOnReceiveContentListener;
+    }
+
+    @UnsupportedAppUsage
+    ListenerInfo mListenerInfo;
+
+    private static class TooltipInfo {
+        /**
+         * Text to be displayed in a tooltip popup.
+         */
+        @Nullable
+        CharSequence mTooltipText;
+
+        /**
+         * View-relative position of the tooltip anchor point.
+         */
+        int mAnchorX;
+        int mAnchorY;
+
+        /**
+         * The tooltip popup.
+         */
+        @Nullable
+        TooltipPopup mTooltipPopup;
+
+        /**
+         * Set to true if the tooltip was shown as a result of a long click.
+         */
+        boolean mTooltipFromLongClick;
+
+        /**
+         * Keep these Runnables so that they can be used to reschedule.
+         */
+        Runnable mShowTooltipRunnable;
+        Runnable mHideTooltipRunnable;
+
+        /**
+         * Hover move is ignored if it is within this distance in pixels from the previous one.
+         */
+        int mHoverSlop;
+
+        /**
+         * Update the anchor position if it significantly (that is by at least mHoverSlop)
+         * different from the previously stored position. Ignoring insignificant changes
+         * filters out the jitter which is typical for such input sources as stylus.
+         *
+         * @return True if the position has been updated.
+         */
+        private boolean updateAnchorPos(MotionEvent event) {
+            final int newAnchorX = (int) event.getX();
+            final int newAnchorY = (int) event.getY();
+            if (Math.abs(newAnchorX - mAnchorX) <= mHoverSlop
+                    && Math.abs(newAnchorY - mAnchorY) <= mHoverSlop) {
+                return false;
+            }
+            mAnchorX = newAnchorX;
+            mAnchorY = newAnchorY;
+            return true;
+        }
+
+        /**
+         *  Clear the anchor position to ensure that the next change is considered significant.
+         */
+        private void clearAnchorPos() {
+            mAnchorX = Integer.MAX_VALUE;
+            mAnchorY = Integer.MAX_VALUE;
+        }
+    }
+
+    TooltipInfo mTooltipInfo;
+
+    // Temporary values used to hold (x,y) coordinates when delegating from the
+    // two-arg performLongClick() method to the legacy no-arg version.
+    private float mLongClickX = Float.NaN;
+    private float mLongClickY = Float.NaN;
+
+    /**
+     * The application environment this view lives in.
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty(deepExport = true)
+    @UnsupportedAppUsage
+    @UiContext
+    protected Context mContext;
+
+    @UnsupportedAppUsage
+    private final Resources mResources;
+
+    @UnsupportedAppUsage
+    private ScrollabilityCache mScrollCache;
+
+    private int[] mDrawableState = null;
+
+    ViewOutlineProvider mOutlineProvider = ViewOutlineProvider.BACKGROUND;
+
+    /**
+     * Animator that automatically runs based on state changes.
+     */
+    private StateListAnimator mStateListAnimator;
+
+    /**
+     * When this view has focus and the next focus is {@link #FOCUS_LEFT},
+     * the user may specify which view to go to next.
+     */
+    private int mNextFocusLeftId = View.NO_ID;
+
+    /**
+     * When this view has focus and the next focus is {@link #FOCUS_RIGHT},
+     * the user may specify which view to go to next.
+     */
+    private int mNextFocusRightId = View.NO_ID;
+
+    /**
+     * When this view has focus and the next focus is {@link #FOCUS_UP},
+     * the user may specify which view to go to next.
+     */
+    private int mNextFocusUpId = View.NO_ID;
+
+    /**
+     * When this view has focus and the next focus is {@link #FOCUS_DOWN},
+     * the user may specify which view to go to next.
+     */
+    private int mNextFocusDownId = View.NO_ID;
+
+    /**
+     * When this view has focus and the next focus is {@link #FOCUS_FORWARD},
+     * the user may specify which view to go to next.
+     */
+    int mNextFocusForwardId = View.NO_ID;
+
+    /**
+     * User-specified next keyboard navigation cluster in the {@link #FOCUS_FORWARD} direction.
+     *
+     * @see #findUserSetNextKeyboardNavigationCluster(View, int)
+     */
+    int mNextClusterForwardId = View.NO_ID;
+
+    /**
+     * Whether this View should use a default focus highlight when it gets focused but doesn't
+     * have {@link android.R.attr#state_focused} defined in its background.
+     */
+    boolean mDefaultFocusHighlightEnabled = true;
+
+    private CheckForLongPress mPendingCheckForLongPress;
+    @UnsupportedAppUsage
+    private CheckForTap mPendingCheckForTap = null;
+    private PerformClick mPerformClick;
+    private SendViewScrolledAccessibilityEvent mSendViewScrolledAccessibilityEvent;
+    private SendAccessibilityEventThrottle mSendStateChangedAccessibilityEvent;
+    private UnsetPressedState mUnsetPressedState;
+
+    /**
+     * Whether the long press's action has been invoked.  The tap's action is invoked on the
+     * up event while a long press is invoked as soon as the long press duration is reached, so
+     * a long press could be performed before the tap is checked, in which case the tap's action
+     * should not be invoked.
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    private boolean mHasPerformedLongPress;
+
+    /**
+     * Whether a context click button is currently pressed down. This is true when the stylus is
+     * touching the screen and the primary button has been pressed, or if a mouse's right button is
+     * pressed. This is false once the button is released or if the stylus has been lifted.
+     */
+    private boolean mInContextButtonPress;
+
+    /**
+     * Whether the next up event should be ignored for the purposes of gesture recognition. This is
+     * true after a stylus button press has occured, when the next up event should not be recognized
+     * as a tap.
+     */
+    private boolean mIgnoreNextUpEvent;
+
+    /**
+     * The minimum height of the view. We'll try our best to have the height
+     * of this view to at least this amount.
+     */
+    @ViewDebug.ExportedProperty(category = "measurement")
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    private int mMinHeight;
+
+    /**
+     * The minimum width of the view. We'll try our best to have the width
+     * of this view to at least this amount.
+     */
+    @ViewDebug.ExportedProperty(category = "measurement")
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    private int mMinWidth;
+
+    /**
+     * The delegate to handle touch events that are physically in this view
+     * but should be handled by another view.
+     */
+    private TouchDelegate mTouchDelegate = null;
+
+    /**
+     * While touch exploration is in use, set to true when hovering across boundaries and
+     * inside the touch area of the delegate at receiving {@link MotionEvent#ACTION_HOVER_ENTER}
+     * or {@link MotionEvent#ACTION_HOVER_MOVE}. False when leaving boundaries or receiving a
+     * {@link MotionEvent#ACTION_HOVER_EXIT}.
+     * Note that children of view group are excluded in the touch area.
+     * @see #dispatchTouchExplorationHoverEvent
+     */
+    private boolean mHoveringTouchDelegate = false;
+
+    /**
+     * Solid color to use as a background when creating the drawing cache. Enables
+     * the cache to use 16 bit bitmaps instead of 32 bit.
+     */
+    private int mDrawingCacheBackgroundColor = 0;
+
+    /**
+     * Special tree observer used when mAttachInfo is null.
+     */
+    private ViewTreeObserver mFloatingTreeObserver;
+
+    /**
+     * Cache the touch slop from the context that created the view.
+     */
+    private int mTouchSlop;
+
+    /**
+     * Cache the ambiguous gesture multiplier from the context that created the view.
+     */
+    private float mAmbiguousGestureMultiplier;
+
+    /**
+     * Object that handles automatic animation of view properties.
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    private ViewPropertyAnimator mAnimator = null;
+
+    /**
+     * List of registered FrameMetricsObservers.
+     */
+    private ArrayList<FrameMetricsObserver> mFrameMetricsObservers;
+
+    /**
+     * Flag indicating that a drag can cross window boundaries.  When
+     * {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object, int)} is called
+     * with this flag set, all visible applications with targetSdkVersion >=
+     * {@link android.os.Build.VERSION_CODES#N API 24} will be able to participate
+     * in the drag operation and receive the dragged content.
+     *
+     * <p>If this is the only flag set, then the drag recipient will only have access to text data
+     * and intents contained in the {@link ClipData} object. Access to URIs contained in the
+     * {@link ClipData} is determined by other DRAG_FLAG_GLOBAL_* flags</p>
+     */
+    public static final int DRAG_FLAG_GLOBAL = 1 << 8;  // 256
+
+    /**
+     * When this flag is used with {@link #DRAG_FLAG_GLOBAL}, the drag recipient will be able to
+     * request read access to the content URI(s) contained in the {@link ClipData} object.
+     * @see android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION
+     */
+    public static final int DRAG_FLAG_GLOBAL_URI_READ = Intent.FLAG_GRANT_READ_URI_PERMISSION;
+
+    /**
+     * When this flag is used with {@link #DRAG_FLAG_GLOBAL}, the drag recipient will be able to
+     * request write access to the content URI(s) contained in the {@link ClipData} object.
+     * @see android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION
+     */
+    public static final int DRAG_FLAG_GLOBAL_URI_WRITE = Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+
+    /**
+     * When this flag is used with {@link #DRAG_FLAG_GLOBAL_URI_READ} and/or {@link
+     * #DRAG_FLAG_GLOBAL_URI_WRITE}, the URI permission grant can be persisted across device
+     * reboots until explicitly revoked with
+     * {@link android.content.Context#revokeUriPermission(Uri, int)} Context.revokeUriPermission}.
+     * @see android.content.Intent#FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+     */
+    public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION =
+            Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION;
+
+    /**
+     * When this flag is used with {@link #DRAG_FLAG_GLOBAL_URI_READ} and/or {@link
+     * #DRAG_FLAG_GLOBAL_URI_WRITE}, the URI permission grant applies to any URI that is a prefix
+     * match against the original granted URI.
+     * @see android.content.Intent#FLAG_GRANT_PREFIX_URI_PERMISSION
+     */
+    public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION =
+            Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
+
+    /**
+     * Flag indicating that the drag shadow will be opaque.  When
+     * {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object, int)} is called
+     * with this flag set, the drag shadow will be opaque, otherwise, it will be semitransparent.
+     */
+    public static final int DRAG_FLAG_OPAQUE = 1 << 9;
+
+    /**
+     * Vertical scroll factor cached by {@link #getVerticalScrollFactor}.
+     */
+    private float mVerticalScrollFactor;
+
+    /**
+     * Position of the vertical scroll bar.
+     */
+    @UnsupportedAppUsage
+    private int mVerticalScrollbarPosition;
+
+    /**
+     * Position the scroll bar at the default position as determined by the system.
+     */
+    public static final int SCROLLBAR_POSITION_DEFAULT = 0;
+
+    /**
+     * Position the scroll bar along the left edge.
+     */
+    public static final int SCROLLBAR_POSITION_LEFT = 1;
+
+    /**
+     * Position the scroll bar along the right edge.
+     */
+    public static final int SCROLLBAR_POSITION_RIGHT = 2;
+
+    /**
+     * Indicates that the view does not have a layer.
+     *
+     * @see #getLayerType()
+     * @see #setLayerType(int, android.graphics.Paint)
+     * @see #LAYER_TYPE_SOFTWARE
+     * @see #LAYER_TYPE_HARDWARE
+     */
+    public static final int LAYER_TYPE_NONE = 0;
+
+    /**
+     * <p>Indicates that the view has a software layer. A software layer is backed
+     * by a bitmap and causes the view to be rendered using Android's software
+     * rendering pipeline, even if hardware acceleration is enabled.</p>
+     *
+     * <p>Software layers have various usages:</p>
+     * <p>When the application is not using hardware acceleration, a software layer
+     * is useful to apply a specific color filter and/or blending mode and/or
+     * translucency to a view and all its children.</p>
+     * <p>When the application is using hardware acceleration, a software layer
+     * is useful to render drawing primitives not supported by the hardware
+     * accelerated pipeline. It can also be used to cache a complex view tree
+     * into a texture and reduce the complexity of drawing operations. For instance,
+     * when animating a complex view tree with a translation, a software layer can
+     * be used to render the view tree only once.</p>
+     * <p>Software layers should be avoided when the affected view tree updates
+     * often. Every update will require to re-render the software layer, which can
+     * potentially be slow (particularly when hardware acceleration is turned on
+     * since the layer will have to be uploaded into a hardware texture after every
+     * update.)</p>
+     *
+     * @see #getLayerType()
+     * @see #setLayerType(int, android.graphics.Paint)
+     * @see #LAYER_TYPE_NONE
+     * @see #LAYER_TYPE_HARDWARE
+     */
+    public static final int LAYER_TYPE_SOFTWARE = 1;
+
+    /**
+     * <p>Indicates that the view has a hardware layer. A hardware layer is backed
+     * by a hardware specific texture (generally Frame Buffer Objects or FBO on
+     * OpenGL hardware) and causes the view to be rendered using Android's hardware
+     * rendering pipeline, but only if hardware acceleration is turned on for the
+     * view hierarchy. When hardware acceleration is turned off, hardware layers
+     * behave exactly as {@link #LAYER_TYPE_SOFTWARE software layers}.</p>
+     *
+     * <p>A hardware layer is useful to apply a specific color filter and/or
+     * blending mode and/or translucency to a view and all its children.</p>
+     * <p>A hardware layer can be used to cache a complex view tree into a
+     * texture and reduce the complexity of drawing operations. For instance,
+     * when animating a complex view tree with a translation, a hardware layer can
+     * be used to render the view tree only once.</p>
+     * <p>A hardware layer can also be used to increase the rendering quality when
+     * rotation transformations are applied on a view. It can also be used to
+     * prevent potential clipping issues when applying 3D transforms on a view.</p>
+     *
+     * @see #getLayerType()
+     * @see #setLayerType(int, android.graphics.Paint)
+     * @see #LAYER_TYPE_NONE
+     * @see #LAYER_TYPE_SOFTWARE
+     */
+    public static final int LAYER_TYPE_HARDWARE = 2;
+
+    /** @hide */
+    @IntDef(prefix = { "LAYER_TYPE_" }, value = {
+            LAYER_TYPE_NONE,
+            LAYER_TYPE_SOFTWARE,
+            LAYER_TYPE_HARDWARE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface LayerType {}
+
+    @ViewDebug.ExportedProperty(category = "drawing", mapping = {
+            @ViewDebug.IntToString(from = LAYER_TYPE_NONE, to = "NONE"),
+            @ViewDebug.IntToString(from = LAYER_TYPE_SOFTWARE, to = "SOFTWARE"),
+            @ViewDebug.IntToString(from = LAYER_TYPE_HARDWARE, to = "HARDWARE")
+    })
+    int mLayerType = LAYER_TYPE_NONE;
+    Paint mLayerPaint;
+
+    /**
+     * Set to true when drawing cache is enabled and cannot be created.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public boolean mCachingFailed;
+    @UnsupportedAppUsage
+    private Bitmap mDrawingCache;
+    @UnsupportedAppUsage
+    private Bitmap mUnscaledDrawingCache;
+
+    /**
+     * RenderNode holding View properties, potentially holding a DisplayList of View content.
+     * <p>
+     * When non-null and valid, this is expected to contain an up-to-date copy
+     * of the View content. Its DisplayList content is cleared on temporary detach and reset on
+     * cleanup.
+     */
+    @UnsupportedAppUsage
+    final RenderNode mRenderNode;
+
+    /**
+     * Set to true when the view is sending hover accessibility events because it
+     * is the innermost hovered view.
+     */
+    private boolean mSendingHoverAccessibilityEvents;
+
+    /**
+     * Delegate for injecting accessibility functionality.
+     */
+    @UnsupportedAppUsage
+    AccessibilityDelegate mAccessibilityDelegate;
+
+    /**
+     * The view's overlay layer. Developers get a reference to the overlay via getOverlay()
+     * and add/remove objects to/from the overlay directly through the Overlay methods.
+     */
+    ViewOverlay mOverlay;
+
+    /**
+     * The currently active parent view for receiving delegated nested scrolling events.
+     * This is set by {@link #startNestedScroll(int)} during a touch interaction and cleared
+     * by {@link #stopNestedScroll()} at the same point where we clear
+     * requestDisallowInterceptTouchEvent.
+     */
+    private ViewParent mNestedScrollingParent;
+
+    /**
+     * Consistency verifier for debugging purposes.
+     * @hide
+     */
+    protected final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
+            InputEventConsistencyVerifier.isInstrumentationEnabled() ?
+                    new InputEventConsistencyVerifier(this, 0) : null;
+
+    private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
+
+    private int[] mTempNestedScrollConsumed;
+
+    /**
+     * An overlay is going to draw this View instead of being drawn as part of this
+     * View's parent. mGhostView is the View in the Overlay that must be invalidated
+     * when this view is invalidated.
+     */
+    GhostView mGhostView;
+
+    /**
+     * Holds pairs of adjacent attribute data: attribute name followed by its value.
+     * @hide
+     */
+    @ViewDebug.ExportedProperty(category = "attributes", hasAdjacentMapping = true)
+    public String[] mAttributes;
+
+    /**
+     * Maps a Resource id to its name.
+     */
+    private static SparseArray<String> mAttributeMap;
+
+    /**
+     * Queue of pending runnables. Used to postpone calls to post() until this
+     * view is attached and has a handler.
+     */
+    private HandlerActionQueue mRunQueue;
+
+    /**
+     * The pointer icon when the mouse hovers on this view. The default is null.
+     */
+    private PointerIcon mPointerIcon;
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    String mStartActivityRequestWho;
+
+    @Nullable
+    private RoundScrollbarRenderer mRoundScrollbarRenderer;
+
+    /** Used to delay visibility updates sent to the autofill manager */
+    private Handler mVisibilityChangeForAutofillHandler;
+
+    /**
+     * Used when app developers explicitly set the {@link ContentCaptureSession} associated with the
+     * view (through {@link #setContentCaptureSession(ContentCaptureSession)}.
+     */
+    @Nullable
+    private ContentCaptureSession mContentCaptureSession;
+
+    /**
+     * Whether {@link ContentCaptureSession} is cached, resets on {@link #invalidate()}.
+     */
+    private boolean mContentCaptureSessionCached;
+
+    @LayoutRes
+    private int mSourceLayoutId = ID_NULL;
+
+    @Nullable
+    private SparseIntArray mAttributeSourceResId;
+
+    @Nullable
+    private SparseArray<int[]> mAttributeResolutionStacks;
+
+    @StyleRes
+    private int mExplicitStyle;
+
+    /**
+     * Specifies which input source classes should provide unbuffered input events to this view
+     *
+     * @see View#requestUnbufferedDispatch(int)
+     */
+    @InputSourceClass
+    int mUnbufferedInputSource = InputDevice.SOURCE_CLASS_NONE;
+
+    @Nullable
+    private String[] mReceiveContentMimeTypes;
+
+    @Nullable
+    private ViewTranslationCallback mViewTranslationCallback;
+
+    @Nullable
+
+    private ViewTranslationResponse mViewTranslationResponse;
+
+    /**
+     * Simple constructor to use when creating a view from code.
+     *
+     * @param context The Context the view is running in, through which it can
+     *        access the current theme, resources, etc.
+     */
+    public View(Context context) {
+        mContext = context;
+        mResources = context != null ? context.getResources() : null;
+        mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED | FOCUSABLE_AUTO;
+        // Set some flags defaults
+        mPrivateFlags2 =
+                (LAYOUT_DIRECTION_DEFAULT << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) |
+                (TEXT_DIRECTION_DEFAULT << PFLAG2_TEXT_DIRECTION_MASK_SHIFT) |
+                (PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT) |
+                (TEXT_ALIGNMENT_DEFAULT << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT) |
+                (PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT) |
+                (IMPORTANT_FOR_ACCESSIBILITY_DEFAULT << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT);
+
+        final ViewConfiguration configuration = ViewConfiguration.get(context);
+        mTouchSlop = configuration.getScaledTouchSlop();
+        mAmbiguousGestureMultiplier = configuration.getScaledAmbiguousGestureMultiplier();
+
+        setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS);
+        mUserPaddingStart = UNDEFINED_PADDING;
+        mUserPaddingEnd = UNDEFINED_PADDING;
+        mRenderNode = RenderNode.create(getClass().getName(), new ViewAnimationHostBridge(this));
+
+        if (!sCompatibilityDone && context != null) {
+            final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
+
+            // Older apps may need this compatibility hack for measurement.
+            sUseBrokenMakeMeasureSpec = targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR1;
+
+            // Older apps expect onMeasure() to always be called on a layout pass, regardless
+            // of whether a layout was requested on that View.
+            sIgnoreMeasureCache = targetSdkVersion < Build.VERSION_CODES.KITKAT;
+
+            // In M and newer, our widgets can pass a "hint" value in the size
+            // for UNSPECIFIED MeasureSpecs. This lets child views of scrolling containers
+            // know what the expected parent size is going to be, so e.g. list items can size
+            // themselves at 1/3 the size of their container. It breaks older apps though,
+            // specifically apps that use some popular open source libraries.
+            sUseZeroUnspecifiedMeasureSpec = targetSdkVersion < Build.VERSION_CODES.M;
+
+            // Old versions of the platform would give different results from
+            // LinearLayout measurement passes using EXACTLY and non-EXACTLY
+            // modes, so we always need to run an additional EXACTLY pass.
+            sAlwaysRemeasureExactly = targetSdkVersion <= Build.VERSION_CODES.M;
+
+            // Prior to N, TextureView would silently ignore calls to setBackground/setForeground.
+            // On N+, we throw, but that breaks compatibility with apps that use these methods.
+            sTextureViewIgnoresDrawableSetters = targetSdkVersion <= Build.VERSION_CODES.M;
+
+            // Prior to N, we would drop margins in LayoutParam conversions. The fix triggers bugs
+            // in apps so we target check it to avoid breaking existing apps.
+            sPreserveMarginParamsInLayoutParamConversion =
+                    targetSdkVersion >= Build.VERSION_CODES.N;
+
+            sCascadedDragDrop = targetSdkVersion < Build.VERSION_CODES.N;
+
+            sHasFocusableExcludeAutoFocusable = targetSdkVersion < Build.VERSION_CODES.O;
+
+            sAutoFocusableOffUIThreadWontNotifyParents = targetSdkVersion < Build.VERSION_CODES.O;
+
+            sUseDefaultFocusHighlight = context.getResources().getBoolean(
+                    com.android.internal.R.bool.config_useDefaultFocusHighlight);
+
+            sThrowOnInvalidFloatProperties = targetSdkVersion >= Build.VERSION_CODES.P;
+
+            sCanFocusZeroSized = targetSdkVersion < Build.VERSION_CODES.P;
+
+            sAlwaysAssignFocus = targetSdkVersion < Build.VERSION_CODES.P;
+
+            sAcceptZeroSizeDragShadow = targetSdkVersion < Build.VERSION_CODES.P;
+
+            sBrokenInsetsDispatch = targetSdkVersion < Build.VERSION_CODES.R;
+
+            sBrokenWindowBackground = targetSdkVersion < Build.VERSION_CODES.Q;
+
+            GradientDrawable.sWrapNegativeAngleMeasurements =
+                    targetSdkVersion >= Build.VERSION_CODES.Q;
+
+            sForceLayoutWhenInsetsChanged = targetSdkVersion < Build.VERSION_CODES.R;
+
+            sCompatibilityDone = true;
+        }
+    }
+
+    /**
+     * Constructor that is called when inflating a view from XML. This is called
+     * when a view is being constructed from an XML file, supplying attributes
+     * that were specified in the XML file. This version uses a default style of
+     * 0, so the only attribute values applied are those in the Context's Theme
+     * and the given AttributeSet.
+     *
+     * <p>
+     * The method onFinishInflate() will be called after all children have been
+     * added.
+     *
+     * @param context The Context the view is running in, through which it can
+     *        access the current theme, resources, etc.
+     * @param attrs The attributes of the XML tag that is inflating the view.
+     * @see #View(Context, AttributeSet, int)
+     */
+    public View(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    /**
+     * Perform inflation from XML and apply a class-specific base style from a
+     * theme attribute. This constructor of View allows subclasses to use their
+     * own base style when they are inflating. For example, a Button class's
+     * constructor would call this version of the super class constructor and
+     * supply <code>R.attr.buttonStyle</code> for <var>defStyleAttr</var>; this
+     * allows the theme's button style to modify all of the base view attributes
+     * (in particular its background) as well as the Button class's attributes.
+     *
+     * @param context The Context the view is running in, through which it can
+     *        access the current theme, resources, etc.
+     * @param attrs The attributes of the XML tag that is inflating the view.
+     * @param defStyleAttr An attribute in the current theme that contains a
+     *        reference to a style resource that supplies default values for
+     *        the view. Can be 0 to not look for defaults.
+     * @see #View(Context, AttributeSet)
+     */
+    public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    /**
+     * Perform inflation from XML and apply a class-specific base style from a
+     * theme attribute or style resource. This constructor of View allows
+     * subclasses to use their own base style when they are inflating.
+     * <p>
+     * When determining the final value of a particular attribute, there are
+     * four inputs that come into play:
+     * <ol>
+     * <li>Any attribute values in the given AttributeSet.
+     * <li>The style resource specified in the AttributeSet (named "style").
+     * <li>The default style specified by <var>defStyleAttr</var>.
+     * <li>The default style specified by <var>defStyleRes</var>.
+     * <li>The base values in this theme.
+     * </ol>
+     * <p>
+     * Each of these inputs is considered in-order, with the first listed taking
+     * precedence over the following ones. In other words, if in the
+     * AttributeSet you have supplied <code>&lt;Button * textColor="#ff000000"&gt;</code>
+     * , then the button's text will <em>always</em> be black, regardless of
+     * what is specified in any of the styles.
+     *
+     * @param context The Context the view is running in, through which it can
+     *        access the current theme, resources, etc.
+     * @param attrs The attributes of the XML tag that is inflating the view.
+     * @param defStyleAttr An attribute in the current theme that contains a
+     *        reference to a style resource that supplies default values for
+     *        the view. Can be 0 to not look for defaults.
+     * @param defStyleRes A resource identifier of a style resource that
+     *        supplies default values for the view, used only if
+     *        defStyleAttr is 0 or can not be found in the theme. Can be 0
+     *        to not look for defaults.
+     * @see #View(Context, AttributeSet, int)
+     */
+    public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        this(context);
+
+        mSourceLayoutId = Resources.getAttributeSetSourceResId(attrs);
+
+        final TypedArray a = context.obtainStyledAttributes(
+                attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
+
+        retrieveExplicitStyle(context.getTheme(), attrs);
+        saveAttributeDataForStyleable(context, com.android.internal.R.styleable.View, attrs, a,
+                defStyleAttr, defStyleRes);
+
+        if (sDebugViewAttributes) {
+            saveAttributeData(attrs, a);
+        }
+
+        Drawable background = null;
+
+        int leftPadding = -1;
+        int topPadding = -1;
+        int rightPadding = -1;
+        int bottomPadding = -1;
+        int startPadding = UNDEFINED_PADDING;
+        int endPadding = UNDEFINED_PADDING;
+
+        int padding = -1;
+        int paddingHorizontal = -1;
+        int paddingVertical = -1;
+
+        int viewFlagValues = 0;
+        int viewFlagMasks = 0;
+
+        boolean setScrollContainer = false;
+
+        int x = 0;
+        int y = 0;
+
+        float tx = 0;
+        float ty = 0;
+        float tz = 0;
+        float elevation = 0;
+        float rotation = 0;
+        float rotationX = 0;
+        float rotationY = 0;
+        float sx = 1f;
+        float sy = 1f;
+        boolean transformSet = false;
+
+        int scrollbarStyle = SCROLLBARS_INSIDE_OVERLAY;
+        int overScrollMode = mOverScrollMode;
+        boolean initializeScrollbars = false;
+        boolean initializeScrollIndicators = false;
+
+        boolean startPaddingDefined = false;
+        boolean endPaddingDefined = false;
+        boolean leftPaddingDefined = false;
+        boolean rightPaddingDefined = false;
+
+        final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
+
+        // Set default values.
+        viewFlagValues |= FOCUSABLE_AUTO;
+        viewFlagMasks |= FOCUSABLE_AUTO;
+
+        final int N = a.getIndexCount();
+        for (int i = 0; i < N; i++) {
+            int attr = a.getIndex(i);
+            switch (attr) {
+                case com.android.internal.R.styleable.View_background:
+                    background = a.getDrawable(attr);
+                    break;
+                case com.android.internal.R.styleable.View_padding:
+                    padding = a.getDimensionPixelSize(attr, -1);
+                    mUserPaddingLeftInitial = padding;
+                    mUserPaddingRightInitial = padding;
+                    leftPaddingDefined = true;
+                    rightPaddingDefined = true;
+                    break;
+                case com.android.internal.R.styleable.View_paddingHorizontal:
+                    paddingHorizontal = a.getDimensionPixelSize(attr, -1);
+                    mUserPaddingLeftInitial = paddingHorizontal;
+                    mUserPaddingRightInitial = paddingHorizontal;
+                    leftPaddingDefined = true;
+                    rightPaddingDefined = true;
+                    break;
+                case com.android.internal.R.styleable.View_paddingVertical:
+                    paddingVertical = a.getDimensionPixelSize(attr, -1);
+                    break;
+                case com.android.internal.R.styleable.View_paddingLeft:
+                    leftPadding = a.getDimensionPixelSize(attr, -1);
+                    mUserPaddingLeftInitial = leftPadding;
+                    leftPaddingDefined = true;
+                    break;
+                case com.android.internal.R.styleable.View_paddingTop:
+                    topPadding = a.getDimensionPixelSize(attr, -1);
+                    break;
+                case com.android.internal.R.styleable.View_paddingRight:
+                    rightPadding = a.getDimensionPixelSize(attr, -1);
+                    mUserPaddingRightInitial = rightPadding;
+                    rightPaddingDefined = true;
+                    break;
+                case com.android.internal.R.styleable.View_paddingBottom:
+                    bottomPadding = a.getDimensionPixelSize(attr, -1);
+                    break;
+                case com.android.internal.R.styleable.View_paddingStart:
+                    startPadding = a.getDimensionPixelSize(attr, UNDEFINED_PADDING);
+                    startPaddingDefined = (startPadding != UNDEFINED_PADDING);
+                    break;
+                case com.android.internal.R.styleable.View_paddingEnd:
+                    endPadding = a.getDimensionPixelSize(attr, UNDEFINED_PADDING);
+                    endPaddingDefined = (endPadding != UNDEFINED_PADDING);
+                    break;
+                case com.android.internal.R.styleable.View_scrollX:
+                    x = a.getDimensionPixelOffset(attr, 0);
+                    break;
+                case com.android.internal.R.styleable.View_scrollY:
+                    y = a.getDimensionPixelOffset(attr, 0);
+                    break;
+                case com.android.internal.R.styleable.View_alpha:
+                    setAlpha(a.getFloat(attr, 1f));
+                    break;
+                case com.android.internal.R.styleable.View_transformPivotX:
+                    setPivotX(a.getDimension(attr, 0));
+                    break;
+                case com.android.internal.R.styleable.View_transformPivotY:
+                    setPivotY(a.getDimension(attr, 0));
+                    break;
+                case com.android.internal.R.styleable.View_translationX:
+                    tx = a.getDimension(attr, 0);
+                    transformSet = true;
+                    break;
+                case com.android.internal.R.styleable.View_translationY:
+                    ty = a.getDimension(attr, 0);
+                    transformSet = true;
+                    break;
+                case com.android.internal.R.styleable.View_translationZ:
+                    tz = a.getDimension(attr, 0);
+                    transformSet = true;
+                    break;
+                case com.android.internal.R.styleable.View_elevation:
+                    elevation = a.getDimension(attr, 0);
+                    transformSet = true;
+                    break;
+                case com.android.internal.R.styleable.View_rotation:
+                    rotation = a.getFloat(attr, 0);
+                    transformSet = true;
+                    break;
+                case com.android.internal.R.styleable.View_rotationX:
+                    rotationX = a.getFloat(attr, 0);
+                    transformSet = true;
+                    break;
+                case com.android.internal.R.styleable.View_rotationY:
+                    rotationY = a.getFloat(attr, 0);
+                    transformSet = true;
+                    break;
+                case com.android.internal.R.styleable.View_scaleX:
+                    sx = a.getFloat(attr, 1f);
+                    transformSet = true;
+                    break;
+                case com.android.internal.R.styleable.View_scaleY:
+                    sy = a.getFloat(attr, 1f);
+                    transformSet = true;
+                    break;
+                case com.android.internal.R.styleable.View_id:
+                    mID = a.getResourceId(attr, NO_ID);
+                    break;
+                case com.android.internal.R.styleable.View_tag:
+                    mTag = a.getText(attr);
+                    break;
+                case com.android.internal.R.styleable.View_fitsSystemWindows:
+                    if (a.getBoolean(attr, false)) {
+                        viewFlagValues |= FITS_SYSTEM_WINDOWS;
+                        viewFlagMasks |= FITS_SYSTEM_WINDOWS;
+                    }
+                    break;
+                case com.android.internal.R.styleable.View_focusable:
+                    viewFlagValues = (viewFlagValues & ~FOCUSABLE_MASK) | getFocusableAttribute(a);
+                    if ((viewFlagValues & FOCUSABLE_AUTO) == 0) {
+                        viewFlagMasks |= FOCUSABLE_MASK;
+                    }
+                    break;
+                case com.android.internal.R.styleable.View_focusableInTouchMode:
+                    if (a.getBoolean(attr, false)) {
+                        // unset auto focus since focusableInTouchMode implies explicit focusable
+                        viewFlagValues &= ~FOCUSABLE_AUTO;
+                        viewFlagValues |= FOCUSABLE_IN_TOUCH_MODE | FOCUSABLE;
+                        viewFlagMasks |= FOCUSABLE_IN_TOUCH_MODE | FOCUSABLE_MASK;
+                    }
+                    break;
+                case com.android.internal.R.styleable.View_clickable:
+                    if (a.getBoolean(attr, false)) {
+                        viewFlagValues |= CLICKABLE;
+                        viewFlagMasks |= CLICKABLE;
+                    }
+                    break;
+                case com.android.internal.R.styleable.View_allowClickWhenDisabled:
+                    setAllowClickWhenDisabled(a.getBoolean(attr, false));
+                    break;
+                case com.android.internal.R.styleable.View_longClickable:
+                    if (a.getBoolean(attr, false)) {
+                        viewFlagValues |= LONG_CLICKABLE;
+                        viewFlagMasks |= LONG_CLICKABLE;
+                    }
+                    break;
+                case com.android.internal.R.styleable.View_contextClickable:
+                    if (a.getBoolean(attr, false)) {
+                        viewFlagValues |= CONTEXT_CLICKABLE;
+                        viewFlagMasks |= CONTEXT_CLICKABLE;
+                    }
+                    break;
+                case com.android.internal.R.styleable.View_saveEnabled:
+                    if (!a.getBoolean(attr, true)) {
+                        viewFlagValues |= SAVE_DISABLED;
+                        viewFlagMasks |= SAVE_DISABLED_MASK;
+                    }
+                    break;
+                case com.android.internal.R.styleable.View_duplicateParentState:
+                    if (a.getBoolean(attr, false)) {
+                        viewFlagValues |= DUPLICATE_PARENT_STATE;
+                        viewFlagMasks |= DUPLICATE_PARENT_STATE;
+                    }
+                    break;
+                case com.android.internal.R.styleable.View_visibility:
+                    final int visibility = a.getInt(attr, 0);
+                    if (visibility != 0) {
+                        viewFlagValues |= VISIBILITY_FLAGS[visibility];
+                        viewFlagMasks |= VISIBILITY_MASK;
+                    }
+                    break;
+                case com.android.internal.R.styleable.View_layoutDirection:
+                    // Clear any layout direction flags (included resolved bits) already set
+                    mPrivateFlags2 &=
+                            ~(PFLAG2_LAYOUT_DIRECTION_MASK | PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK);
+                    // Set the layout direction flags depending on the value of the attribute
+                    final int layoutDirection = a.getInt(attr, -1);
+                    final int value = (layoutDirection != -1) ?
+                            LAYOUT_DIRECTION_FLAGS[layoutDirection] : LAYOUT_DIRECTION_DEFAULT;
+                    mPrivateFlags2 |= (value << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT);
+                    break;
+                case com.android.internal.R.styleable.View_drawingCacheQuality:
+                    final int cacheQuality = a.getInt(attr, 0);
+                    if (cacheQuality != 0) {
+                        viewFlagValues |= DRAWING_CACHE_QUALITY_FLAGS[cacheQuality];
+                        viewFlagMasks |= DRAWING_CACHE_QUALITY_MASK;
+                    }
+                    break;
+                case com.android.internal.R.styleable.View_contentDescription:
+                    setContentDescription(a.getString(attr));
+                    break;
+                case com.android.internal.R.styleable.View_accessibilityTraversalBefore:
+                    setAccessibilityTraversalBefore(a.getResourceId(attr, NO_ID));
+                    break;
+                case com.android.internal.R.styleable.View_accessibilityTraversalAfter:
+                    setAccessibilityTraversalAfter(a.getResourceId(attr, NO_ID));
+                    break;
+                case com.android.internal.R.styleable.View_labelFor:
+                    setLabelFor(a.getResourceId(attr, NO_ID));
+                    break;
+                case com.android.internal.R.styleable.View_soundEffectsEnabled:
+                    if (!a.getBoolean(attr, true)) {
+                        viewFlagValues &= ~SOUND_EFFECTS_ENABLED;
+                        viewFlagMasks |= SOUND_EFFECTS_ENABLED;
+                    }
+                    break;
+                case com.android.internal.R.styleable.View_hapticFeedbackEnabled:
+                    if (!a.getBoolean(attr, true)) {
+                        viewFlagValues &= ~HAPTIC_FEEDBACK_ENABLED;
+                        viewFlagMasks |= HAPTIC_FEEDBACK_ENABLED;
+                    }
+                    break;
+                case R.styleable.View_scrollbars:
+                    final int scrollbars = a.getInt(attr, SCROLLBARS_NONE);
+                    if (scrollbars != SCROLLBARS_NONE) {
+                        viewFlagValues |= scrollbars;
+                        viewFlagMasks |= SCROLLBARS_MASK;
+                        initializeScrollbars = true;
+                    }
+                    break;
+                //noinspection deprecation
+                case R.styleable.View_fadingEdge:
+                    if (targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+                        // Ignore the attribute starting with ICS
+                        break;
+                    }
+                    // With builds < ICS, fall through and apply fading edges
+                case R.styleable.View_requiresFadingEdge:
+                    final int fadingEdge = a.getInt(attr, FADING_EDGE_NONE);
+                    if (fadingEdge != FADING_EDGE_NONE) {
+                        viewFlagValues |= fadingEdge;
+                        viewFlagMasks |= FADING_EDGE_MASK;
+                        initializeFadingEdgeInternal(a);
+                    }
+                    break;
+                case R.styleable.View_scrollbarStyle:
+                    scrollbarStyle = a.getInt(attr, SCROLLBARS_INSIDE_OVERLAY);
+                    if (scrollbarStyle != SCROLLBARS_INSIDE_OVERLAY) {
+                        viewFlagValues |= scrollbarStyle & SCROLLBARS_STYLE_MASK;
+                        viewFlagMasks |= SCROLLBARS_STYLE_MASK;
+                    }
+                    break;
+                case R.styleable.View_isScrollContainer:
+                    setScrollContainer = true;
+                    if (a.getBoolean(attr, false)) {
+                        setScrollContainer(true);
+                    }
+                    break;
+                case com.android.internal.R.styleable.View_keepScreenOn:
+                    if (a.getBoolean(attr, false)) {
+                        viewFlagValues |= KEEP_SCREEN_ON;
+                        viewFlagMasks |= KEEP_SCREEN_ON;
+                    }
+                    break;
+                case R.styleable.View_filterTouchesWhenObscured:
+                    if (a.getBoolean(attr, false)) {
+                        viewFlagValues |= FILTER_TOUCHES_WHEN_OBSCURED;
+                        viewFlagMasks |= FILTER_TOUCHES_WHEN_OBSCURED;
+                    }
+                    break;
+                case R.styleable.View_nextFocusLeft:
+                    mNextFocusLeftId = a.getResourceId(attr, View.NO_ID);
+                    break;
+                case R.styleable.View_nextFocusRight:
+                    mNextFocusRightId = a.getResourceId(attr, View.NO_ID);
+                    break;
+                case R.styleable.View_nextFocusUp:
+                    mNextFocusUpId = a.getResourceId(attr, View.NO_ID);
+                    break;
+                case R.styleable.View_nextFocusDown:
+                    mNextFocusDownId = a.getResourceId(attr, View.NO_ID);
+                    break;
+                case R.styleable.View_nextFocusForward:
+                    mNextFocusForwardId = a.getResourceId(attr, View.NO_ID);
+                    break;
+                case R.styleable.View_nextClusterForward:
+                    mNextClusterForwardId = a.getResourceId(attr, View.NO_ID);
+                    break;
+                case R.styleable.View_minWidth:
+                    mMinWidth = a.getDimensionPixelSize(attr, 0);
+                    break;
+                case R.styleable.View_minHeight:
+                    mMinHeight = a.getDimensionPixelSize(attr, 0);
+                    break;
+                case R.styleable.View_onClick:
+                    if (context.isRestricted()) {
+                        throw new IllegalStateException("The android:onClick attribute cannot "
+                                + "be used within a restricted context");
+                    }
+
+                    final String handlerName = a.getString(attr);
+                    if (handlerName != null) {
+                        setOnClickListener(new DeclaredOnClickListener(this, handlerName));
+                    }
+                    break;
+                case R.styleable.View_overScrollMode:
+                    overScrollMode = a.getInt(attr, OVER_SCROLL_IF_CONTENT_SCROLLS);
+                    break;
+                case R.styleable.View_verticalScrollbarPosition:
+                    mVerticalScrollbarPosition = a.getInt(attr, SCROLLBAR_POSITION_DEFAULT);
+                    break;
+                case R.styleable.View_layerType:
+                    setLayerType(a.getInt(attr, LAYER_TYPE_NONE), null);
+                    break;
+                case R.styleable.View_textDirection:
+                    // Clear any text direction flag already set
+                    mPrivateFlags2 &= ~PFLAG2_TEXT_DIRECTION_MASK;
+                    // Set the text direction flags depending on the value of the attribute
+                    final int textDirection = a.getInt(attr, -1);
+                    if (textDirection != -1) {
+                        mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_FLAGS[textDirection];
+                    }
+                    break;
+                case R.styleable.View_textAlignment:
+                    // Clear any text alignment flag already set
+                    mPrivateFlags2 &= ~PFLAG2_TEXT_ALIGNMENT_MASK;
+                    // Set the text alignment flag depending on the value of the attribute
+                    final int textAlignment = a.getInt(attr, TEXT_ALIGNMENT_DEFAULT);
+                    mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_FLAGS[textAlignment];
+                    break;
+                case R.styleable.View_importantForAccessibility:
+                    setImportantForAccessibility(a.getInt(attr,
+                            IMPORTANT_FOR_ACCESSIBILITY_DEFAULT));
+                    break;
+                case R.styleable.View_accessibilityLiveRegion:
+                    setAccessibilityLiveRegion(a.getInt(attr, ACCESSIBILITY_LIVE_REGION_DEFAULT));
+                    break;
+                case R.styleable.View_transitionName:
+                    setTransitionName(a.getString(attr));
+                    break;
+                case R.styleable.View_nestedScrollingEnabled:
+                    setNestedScrollingEnabled(a.getBoolean(attr, false));
+                    break;
+                case R.styleable.View_stateListAnimator:
+                    setStateListAnimator(AnimatorInflater.loadStateListAnimator(context,
+                            a.getResourceId(attr, 0)));
+                    break;
+                case R.styleable.View_backgroundTint:
+                    // This will get applied later during setBackground().
+                    if (mBackgroundTint == null) {
+                        mBackgroundTint = new TintInfo();
+                    }
+                    mBackgroundTint.mTintList = a.getColorStateList(
+                            R.styleable.View_backgroundTint);
+                    mBackgroundTint.mHasTintList = true;
+                    break;
+                case R.styleable.View_backgroundTintMode:
+                    // This will get applied later during setBackground().
+                    if (mBackgroundTint == null) {
+                        mBackgroundTint = new TintInfo();
+                    }
+                    mBackgroundTint.mBlendMode = Drawable.parseBlendMode(a.getInt(
+                            R.styleable.View_backgroundTintMode, -1), null);
+                    mBackgroundTint.mHasTintMode = true;
+                    break;
+                case R.styleable.View_outlineProvider:
+                    setOutlineProviderFromAttribute(a.getInt(R.styleable.View_outlineProvider,
+                            PROVIDER_BACKGROUND));
+                    break;
+                case R.styleable.View_foreground:
+                    if (targetSdkVersion >= Build.VERSION_CODES.M || this instanceof FrameLayout) {
+                        setForeground(a.getDrawable(attr));
+                    }
+                    break;
+                case R.styleable.View_foregroundGravity:
+                    if (targetSdkVersion >= Build.VERSION_CODES.M || this instanceof FrameLayout) {
+                        setForegroundGravity(a.getInt(attr, Gravity.NO_GRAVITY));
+                    }
+                    break;
+                case R.styleable.View_foregroundTintMode:
+                    if (targetSdkVersion >= Build.VERSION_CODES.M || this instanceof FrameLayout) {
+                        setForegroundTintBlendMode(
+                                Drawable.parseBlendMode(a.getInt(attr, -1),
+                                        null));
+                    }
+                    break;
+                case R.styleable.View_foregroundTint:
+                    if (targetSdkVersion >= Build.VERSION_CODES.M || this instanceof FrameLayout) {
+                        setForegroundTintList(a.getColorStateList(attr));
+                    }
+                    break;
+                case R.styleable.View_foregroundInsidePadding:
+                    if (targetSdkVersion >= Build.VERSION_CODES.M || this instanceof FrameLayout) {
+                        if (mForegroundInfo == null) {
+                            mForegroundInfo = new ForegroundInfo();
+                        }
+                        mForegroundInfo.mInsidePadding = a.getBoolean(attr,
+                                mForegroundInfo.mInsidePadding);
+                    }
+                    break;
+                case R.styleable.View_scrollIndicators:
+                    final int scrollIndicators =
+                            (a.getInt(attr, 0) << SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT)
+                                    & SCROLL_INDICATORS_PFLAG3_MASK;
+                    if (scrollIndicators != 0) {
+                        mPrivateFlags3 |= scrollIndicators;
+                        initializeScrollIndicators = true;
+                    }
+                    break;
+                case R.styleable.View_pointerIcon:
+                    final int resourceId = a.getResourceId(attr, 0);
+                    if (resourceId != 0) {
+                        setPointerIcon(PointerIcon.load(
+                                context.getResources(), resourceId));
+                    } else {
+                        final int pointerType = a.getInt(attr, PointerIcon.TYPE_NOT_SPECIFIED);
+                        if (pointerType != PointerIcon.TYPE_NOT_SPECIFIED) {
+                            setPointerIcon(PointerIcon.getSystemIcon(context, pointerType));
+                        }
+                    }
+                    break;
+                case R.styleable.View_forceHasOverlappingRendering:
+                    if (a.peekValue(attr) != null) {
+                        forceHasOverlappingRendering(a.getBoolean(attr, true));
+                    }
+                    break;
+                case R.styleable.View_tooltipText:
+                    setTooltipText(a.getText(attr));
+                    break;
+                case R.styleable.View_keyboardNavigationCluster:
+                    if (a.peekValue(attr) != null) {
+                        setKeyboardNavigationCluster(a.getBoolean(attr, true));
+                    }
+                    break;
+                case R.styleable.View_focusedByDefault:
+                    if (a.peekValue(attr) != null) {
+                        setFocusedByDefault(a.getBoolean(attr, true));
+                    }
+                    break;
+                case R.styleable.View_autofillHints:
+                    if (a.peekValue(attr) != null) {
+                        CharSequence[] rawHints = null;
+                        String rawString = null;
+
+                        if (a.getType(attr) == TypedValue.TYPE_REFERENCE) {
+                            int resId = a.getResourceId(attr, 0);
+
+                            try {
+                                rawHints = a.getTextArray(attr);
+                            } catch (Resources.NotFoundException e) {
+                                rawString = getResources().getString(resId);
+                            }
+                        } else {
+                            rawString = a.getString(attr);
+                        }
+
+                        if (rawHints == null) {
+                            if (rawString == null) {
+                                throw new IllegalArgumentException(
+                                        "Could not resolve autofillHints");
+                            } else {
+                                rawHints = rawString.split(",");
+                            }
+                        }
+
+                        String[] hints = new String[rawHints.length];
+
+                        int numHints = rawHints.length;
+                        for (int rawHintNum = 0; rawHintNum < numHints; rawHintNum++) {
+                            hints[rawHintNum] = rawHints[rawHintNum].toString().trim();
+                        }
+                        setAutofillHints(hints);
+                    }
+                    break;
+                case R.styleable.View_importantForAutofill:
+                    if (a.peekValue(attr) != null) {
+                        setImportantForAutofill(a.getInt(attr, IMPORTANT_FOR_AUTOFILL_AUTO));
+                    }
+                    break;
+                case R.styleable.View_importantForContentCapture:
+                    if (a.peekValue(attr) != null) {
+                        setImportantForContentCapture(a.getInt(attr,
+                                IMPORTANT_FOR_CONTENT_CAPTURE_AUTO));
+                    }
+                case R.styleable.View_defaultFocusHighlightEnabled:
+                    if (a.peekValue(attr) != null) {
+                        setDefaultFocusHighlightEnabled(a.getBoolean(attr, true));
+                    }
+                    break;
+                case R.styleable.View_screenReaderFocusable:
+                    if (a.peekValue(attr) != null) {
+                        setScreenReaderFocusable(a.getBoolean(attr, false));
+                    }
+                    break;
+                case R.styleable.View_accessibilityPaneTitle:
+                    if (a.peekValue(attr) != null) {
+                        setAccessibilityPaneTitle(a.getString(attr));
+                    }
+                    break;
+                case R.styleable.View_outlineSpotShadowColor:
+                    setOutlineSpotShadowColor(a.getColor(attr, Color.BLACK));
+                    break;
+                case R.styleable.View_outlineAmbientShadowColor:
+                    setOutlineAmbientShadowColor(a.getColor(attr, Color.BLACK));
+                    break;
+                case com.android.internal.R.styleable.View_accessibilityHeading:
+                    setAccessibilityHeading(a.getBoolean(attr, false));
+                    break;
+                case R.styleable.View_forceDarkAllowed:
+                    mRenderNode.setForceDarkAllowed(a.getBoolean(attr, true));
+                    break;
+                case R.styleable.View_scrollCaptureHint:
+                    setScrollCaptureHint((a.getInt(attr, SCROLL_CAPTURE_HINT_AUTO)));
+                    break;
+                case R.styleable.View_clipToOutline:
+                    setClipToOutline(a.getBoolean(attr, false));
+                    break;
+            }
+        }
+
+        setOverScrollMode(overScrollMode);
+
+        // Cache start/end user padding as we cannot fully resolve padding here (we don't have yet
+        // the resolved layout direction). Those cached values will be used later during padding
+        // resolution.
+        mUserPaddingStart = startPadding;
+        mUserPaddingEnd = endPadding;
+
+        if (background != null) {
+            setBackground(background);
+        }
+
+        // setBackground above will record that padding is currently provided by the background.
+        // If we have padding specified via xml, record that here instead and use it.
+        mLeftPaddingDefined = leftPaddingDefined;
+        mRightPaddingDefined = rightPaddingDefined;
+
+        // Valid paddingHorizontal/paddingVertical beats leftPadding, rightPadding, topPadding,
+        // bottomPadding, and padding set by background.  Valid padding beats everything.
+        if (padding >= 0) {
+            leftPadding = padding;
+            topPadding = padding;
+            rightPadding = padding;
+            bottomPadding = padding;
+            mUserPaddingLeftInitial = padding;
+            mUserPaddingRightInitial = padding;
+        } else {
+            if (paddingHorizontal >= 0) {
+                leftPadding = paddingHorizontal;
+                rightPadding = paddingHorizontal;
+                mUserPaddingLeftInitial = paddingHorizontal;
+                mUserPaddingRightInitial = paddingHorizontal;
+            }
+            if (paddingVertical >= 0) {
+                topPadding = paddingVertical;
+                bottomPadding = paddingVertical;
+            }
+        }
+
+        if (isRtlCompatibilityMode()) {
+            // RTL compatibility mode: pre Jelly Bean MR1 case OR no RTL support case.
+            // left / right padding are used if defined (meaning here nothing to do). If they are not
+            // defined and start / end padding are defined (e.g. in Frameworks resources), then we use
+            // start / end and resolve them as left / right (layout direction is not taken into account).
+            // Padding from the background drawable is stored at this point in mUserPaddingLeftInitial
+            // and mUserPaddingRightInitial) so drawable padding will be used as ultimate default if
+            // defined.
+            if (!mLeftPaddingDefined && startPaddingDefined) {
+                leftPadding = startPadding;
+            }
+            mUserPaddingLeftInitial = (leftPadding >= 0) ? leftPadding : mUserPaddingLeftInitial;
+            if (!mRightPaddingDefined && endPaddingDefined) {
+                rightPadding = endPadding;
+            }
+            mUserPaddingRightInitial = (rightPadding >= 0) ? rightPadding : mUserPaddingRightInitial;
+        } else {
+            // Jelly Bean MR1 and after case: if start/end defined, they will override any left/right
+            // values defined. Otherwise, left /right values are used.
+            // Padding from the background drawable is stored at this point in mUserPaddingLeftInitial
+            // and mUserPaddingRightInitial) so drawable padding will be used as ultimate default if
+            // defined.
+            final boolean hasRelativePadding = startPaddingDefined || endPaddingDefined;
+
+            if (mLeftPaddingDefined && !hasRelativePadding) {
+                mUserPaddingLeftInitial = leftPadding;
+            }
+            if (mRightPaddingDefined && !hasRelativePadding) {
+                mUserPaddingRightInitial = rightPadding;
+            }
+        }
+
+        // mPaddingTop and mPaddingBottom may have been set by setBackground(Drawable) so must pass
+        // them on if topPadding or bottomPadding are not valid.
+        internalSetPadding(
+                mUserPaddingLeftInitial,
+                topPadding >= 0 ? topPadding : mPaddingTop,
+                mUserPaddingRightInitial,
+                bottomPadding >= 0 ? bottomPadding : mPaddingBottom);
+
+        if (viewFlagMasks != 0) {
+            setFlags(viewFlagValues, viewFlagMasks);
+        }
+
+        if (initializeScrollbars) {
+            initializeScrollbarsInternal(a);
+        }
+
+        if (initializeScrollIndicators) {
+            initializeScrollIndicatorsInternal();
+        }
+
+        a.recycle();
+
+        // Needs to be called after mViewFlags is set
+        if (scrollbarStyle != SCROLLBARS_INSIDE_OVERLAY) {
+            recomputePadding();
+        }
+
+        if (x != 0 || y != 0) {
+            scrollTo(x, y);
+        }
+
+        if (transformSet) {
+            setTranslationX(tx);
+            setTranslationY(ty);
+            setTranslationZ(tz);
+            setElevation(elevation);
+            setRotation(rotation);
+            setRotationX(rotationX);
+            setRotationY(rotationY);
+            setScaleX(sx);
+            setScaleY(sy);
+        }
+
+        if (!setScrollContainer && (viewFlagValues&SCROLLBARS_VERTICAL) != 0) {
+            setScrollContainer(true);
+        }
+
+        computeOpaqueFlags();
+    }
+
+    /**
+     * Returns the ordered list of resource ID that are considered when resolving attribute values
+     * for this {@link View}. The list will include layout resource ID if the View is inflated from
+     * XML. It will also include a set of explicit styles if specified in XML using
+     * {@code style="..."}. Finally, it will include the default styles resolved from the theme.
+     *
+     * <p>
+     * <b>Note:</b> this method will only return actual values if the view attribute debugging
+     * is enabled in Android developer options.
+     *
+     * @param attribute Attribute resource ID for which the resolution stack should be returned.
+     * @return ordered list of resource ID that are considered when resolving attribute values for
+     * this {@link View}.
+     */
+    @NonNull
+    public int[] getAttributeResolutionStack(@AttrRes int attribute) {
+        if (!sDebugViewAttributes
+                || mAttributeResolutionStacks == null
+                || mAttributeResolutionStacks.get(attribute) == null) {
+            return new int[0];
+        }
+        int[] attributeResolutionStack = mAttributeResolutionStacks.get(attribute);
+        int stackSize = attributeResolutionStack.length;
+        if (mSourceLayoutId != ID_NULL) {
+            stackSize++;
+        }
+
+        int currentIndex = 0;
+        int[] stack = new int[stackSize];
+
+        if (mSourceLayoutId != ID_NULL) {
+            stack[currentIndex] = mSourceLayoutId;
+            currentIndex++;
+        }
+        for (int i = 0; i < attributeResolutionStack.length; i++) {
+            stack[currentIndex] = attributeResolutionStack[i];
+            currentIndex++;
+        }
+        return stack;
+    }
+
+    /**
+     * Returns the mapping of attribute resource ID to source resource ID where the attribute value
+     * was set. Source resource ID can either be a layout resource ID, if the value was set in XML
+     * within the View tag, or a style resource ID, if the attribute was set in a style. The source
+     * resource value will be one of the resource IDs from {@link #getAttributeSourceResourceMap()}.
+     *
+     * <p>
+     * <b>Note:</b> this method will only return actual values if the view attribute debugging
+     * is enabled in Android developer options.
+     *
+     * @return mapping of attribute resource ID to source resource ID where the attribute value
+     * was set.
+     */
+    @NonNull
+    @SuppressWarnings("AndroidFrameworkEfficientCollections")
+    public Map<Integer, Integer> getAttributeSourceResourceMap() {
+        HashMap<Integer, Integer> map = new HashMap<>();
+        if (!sDebugViewAttributes || mAttributeSourceResId == null) {
+            return map;
+        }
+        for (int i = 0; i < mAttributeSourceResId.size(); i++) {
+            map.put(mAttributeSourceResId.keyAt(i), mAttributeSourceResId.valueAt(i));
+        }
+        return map;
+    }
+
+    /**
+     * Returns the resource ID for the style specified using {@code style="..."} in the
+     * {@link AttributeSet}'s backing XML element or {@link Resources#ID_NULL} otherwise if not
+     * specified or otherwise not applicable.
+     * <p>
+     * Each {@link View} can have an explicit style specified in the layout file.
+     * This style is used first during the {@link View} attribute resolution, then if an attribute
+     * is not defined there the resource system looks at default style and theme as fallbacks.
+     *
+     * <p>
+     * <b>Note:</b> this method will only return actual values if the view attribute debugging
+     * is enabled in Android developer options.
+     *
+     * @return The resource ID for the style specified using {@code style="..."} in the
+     *      {@link AttributeSet}'s backing XML element or {@link Resources#ID_NULL} otherwise
+     *      if not specified or otherwise not applicable.
+     */
+    @StyleRes
+    public int getExplicitStyle() {
+        if (!sDebugViewAttributes) {
+            return ID_NULL;
+        }
+        return mExplicitStyle;
+    }
+
+    /**
+     * An implementation of OnClickListener that attempts to lazily load a
+     * named click handling method from a parent or ancestor context.
+     */
+    private static class DeclaredOnClickListener implements OnClickListener {
+        private final View mHostView;
+        private final String mMethodName;
+
+        private Method mResolvedMethod;
+        private Context mResolvedContext;
+
+        public DeclaredOnClickListener(@NonNull View hostView, @NonNull String methodName) {
+            mHostView = hostView;
+            mMethodName = methodName;
+        }
+
+        @Override
+        public void onClick(@NonNull View v) {
+            if (mResolvedMethod == null) {
+                resolveMethod(mHostView.getContext(), mMethodName);
+            }
+
+            try {
+                mResolvedMethod.invoke(mResolvedContext, v);
+            } catch (IllegalAccessException e) {
+                throw new IllegalStateException(
+                        "Could not execute non-public method for android:onClick", e);
+            } catch (InvocationTargetException e) {
+                throw new IllegalStateException(
+                        "Could not execute method for android:onClick", e);
+            }
+        }
+
+        @NonNull
+        private void resolveMethod(@Nullable Context context, @NonNull String name) {
+            while (context != null) {
+                try {
+                    if (!context.isRestricted()) {
+                        final Method method = context.getClass().getMethod(mMethodName, View.class);
+                        if (method != null) {
+                            mResolvedMethod = method;
+                            mResolvedContext = context;
+                            return;
+                        }
+                    }
+                } catch (NoSuchMethodException e) {
+                    // Failed to find method, keep searching up the hierarchy.
+                }
+
+                if (context instanceof ContextWrapper) {
+                    context = ((ContextWrapper) context).getBaseContext();
+                } else {
+                    // Can't search up the hierarchy, null out and fail.
+                    context = null;
+                }
+            }
+
+            final int id = mHostView.getId();
+            final String idText = id == NO_ID ? "" : " with id '"
+                    + mHostView.getContext().getResources().getResourceEntryName(id) + "'";
+            throw new IllegalStateException("Could not find method " + mMethodName
+                    + "(View) in a parent or ancestor Context for android:onClick "
+                    + "attribute defined on view " + mHostView.getClass() + idText);
+        }
+    }
+
+    /**
+     * Non-public constructor for use in testing
+     */
+    @UnsupportedAppUsage
+    View() {
+        mResources = null;
+        mRenderNode = RenderNode.create(getClass().getName(), new ViewAnimationHostBridge(this));
+    }
+
+    /**
+     * Returns {@code true} when the View is attached and the system developer setting to show
+     * the layout bounds is enabled or {@code false} otherwise.
+     */
+    public final boolean isShowingLayoutBounds() {
+        return DEBUG_DRAW || mAttachInfo != null && mAttachInfo.mDebugLayout;
+    }
+
+    /**
+     * Used to test isShowingLayoutBounds(). This sets the local value used
+     * by that function. This method does nothing if the layout isn't attached.
+     *
+     * @hide
+     */
+    @TestApi
+    public final void setShowingLayoutBounds(boolean debugLayout) {
+        if (mAttachInfo != null) {
+            mAttachInfo.mDebugLayout = debugLayout;
+        }
+    }
+
+    private static SparseArray<String> getAttributeMap() {
+        if (mAttributeMap == null) {
+            mAttributeMap = new SparseArray<>();
+        }
+        return mAttributeMap;
+    }
+
+    private void retrieveExplicitStyle(@NonNull Resources.Theme theme,
+            @Nullable AttributeSet attrs) {
+        if (!sDebugViewAttributes) {
+            return;
+        }
+        mExplicitStyle = theme.getExplicitStyle(attrs);
+    }
+
+    /**
+     * Stores debugging information about attributes. This should be called in a constructor by
+     * every custom {@link View} that uses a custom styleable. If the custom view does not call it,
+     * then the custom attributes used by this view will not be visible in layout inspection tools.
+     *
+     *  @param context Context under which this view is created.
+     * @param styleable A reference to styleable array R.styleable.Foo
+     * @param attrs AttributeSet used to construct this view.
+     * @param t Resolved {@link TypedArray} returned by a call to
+     *        {@link Resources#obtainAttributes(AttributeSet, int[])}.
+     * @param defStyleAttr Default style attribute passed into the view constructor.
+     * @param defStyleRes Default style resource passed into the view constructor.
+     */
+    public final void saveAttributeDataForStyleable(@NonNull Context context,
+            @NonNull int[] styleable, @Nullable AttributeSet attrs, @NonNull TypedArray t,
+            int defStyleAttr, int defStyleRes) {
+        if (!sDebugViewAttributes) {
+            return;
+        }
+
+        int[] attributeResolutionStack = context.getTheme().getAttributeResolutionStack(
+                defStyleAttr, defStyleRes, mExplicitStyle);
+
+        if (mAttributeResolutionStacks == null) {
+            mAttributeResolutionStacks = new SparseArray<>();
+        }
+
+        if (mAttributeSourceResId == null) {
+            mAttributeSourceResId = new SparseIntArray();
+        }
+
+        final int indexCount = t.getIndexCount();
+        for (int j = 0; j < indexCount; ++j) {
+            final int index = t.getIndex(j);
+            mAttributeSourceResId.append(styleable[index], t.getSourceResourceId(index, 0));
+            mAttributeResolutionStacks.append(styleable[index], attributeResolutionStack);
+        }
+    }
+
+    private void saveAttributeData(@Nullable AttributeSet attrs, @NonNull TypedArray t) {
+        final int attrsCount = attrs == null ? 0 : attrs.getAttributeCount();
+        final int indexCount = t.getIndexCount();
+        final String[] attributes = new String[(attrsCount + indexCount) * 2];
+
+        int i = 0;
+
+        // Store raw XML attributes.
+        for (int j = 0; j < attrsCount; ++j) {
+            attributes[i] = attrs.getAttributeName(j);
+            attributes[i + 1] = attrs.getAttributeValue(j);
+            i += 2;
+        }
+
+        // Store resolved styleable attributes.
+        final Resources res = t.getResources();
+        final SparseArray<String> attributeMap = getAttributeMap();
+        for (int j = 0; j < indexCount; ++j) {
+            final int index = t.getIndex(j);
+            if (!t.hasValueOrEmpty(index)) {
+                // Value is undefined. Skip it.
+                continue;
+            }
+
+            final int resourceId = t.getResourceId(index, 0);
+            if (resourceId == 0) {
+                // Value is not a reference. Skip it.
+                continue;
+            }
+
+            String resourceName = attributeMap.get(resourceId);
+            if (resourceName == null) {
+                try {
+                    resourceName = res.getResourceName(resourceId);
+                } catch (Resources.NotFoundException e) {
+                    resourceName = "0x" + Integer.toHexString(resourceId);
+                }
+                attributeMap.put(resourceId, resourceName);
+            }
+
+            attributes[i] = resourceName;
+            attributes[i + 1] = t.getString(index);
+            i += 2;
+        }
+
+        // Trim to fit contents.
+        final String[] trimmed = new String[i];
+        System.arraycopy(attributes, 0, trimmed, 0, i);
+        mAttributes = trimmed;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder out = new StringBuilder(128);
+        out.append(getClass().getName());
+        out.append('{');
+        out.append(Integer.toHexString(System.identityHashCode(this)));
+        out.append(' ');
+        switch (mViewFlags&VISIBILITY_MASK) {
+            case VISIBLE: out.append('V'); break;
+            case INVISIBLE: out.append('I'); break;
+            case GONE: out.append('G'); break;
+            default: out.append('.'); break;
+        }
+        out.append((mViewFlags & FOCUSABLE) == FOCUSABLE ? 'F' : '.');
+        out.append((mViewFlags&ENABLED_MASK) == ENABLED ? 'E' : '.');
+        out.append((mViewFlags&DRAW_MASK) == WILL_NOT_DRAW ? '.' : 'D');
+        out.append((mViewFlags&SCROLLBARS_HORIZONTAL) != 0 ? 'H' : '.');
+        out.append((mViewFlags&SCROLLBARS_VERTICAL) != 0 ? 'V' : '.');
+        out.append((mViewFlags&CLICKABLE) != 0 ? 'C' : '.');
+        out.append((mViewFlags&LONG_CLICKABLE) != 0 ? 'L' : '.');
+        out.append((mViewFlags&CONTEXT_CLICKABLE) != 0 ? 'X' : '.');
+        out.append(' ');
+        out.append((mPrivateFlags&PFLAG_IS_ROOT_NAMESPACE) != 0 ? 'R' : '.');
+        out.append((mPrivateFlags&PFLAG_FOCUSED) != 0 ? 'F' : '.');
+        out.append((mPrivateFlags&PFLAG_SELECTED) != 0 ? 'S' : '.');
+        if ((mPrivateFlags&PFLAG_PREPRESSED) != 0) {
+            out.append('p');
+        } else {
+            out.append((mPrivateFlags&PFLAG_PRESSED) != 0 ? 'P' : '.');
+        }
+        out.append((mPrivateFlags&PFLAG_HOVERED) != 0 ? 'H' : '.');
+        out.append((mPrivateFlags&PFLAG_ACTIVATED) != 0 ? 'A' : '.');
+        out.append((mPrivateFlags&PFLAG_INVALIDATED) != 0 ? 'I' : '.');
+        out.append((mPrivateFlags&PFLAG_DIRTY_MASK) != 0 ? 'D' : '.');
+        out.append(' ');
+        out.append(mLeft);
+        out.append(',');
+        out.append(mTop);
+        out.append('-');
+        out.append(mRight);
+        out.append(',');
+        out.append(mBottom);
+        final int id = getId();
+        if (id != NO_ID) {
+            out.append(" #");
+            out.append(Integer.toHexString(id));
+            final Resources r = mResources;
+            if (id > 0 && Resources.resourceHasPackage(id) && r != null) {
+                try {
+                    String pkgname;
+                    switch (id&0xff000000) {
+                        case 0x7f000000:
+                            pkgname="app";
+                            break;
+                        case 0x01000000:
+                            pkgname="android";
+                            break;
+                        default:
+                            pkgname = r.getResourcePackageName(id);
+                            break;
+                    }
+                    String typename = r.getResourceTypeName(id);
+                    String entryname = r.getResourceEntryName(id);
+                    out.append(" ");
+                    out.append(pkgname);
+                    out.append(":");
+                    out.append(typename);
+                    out.append("/");
+                    out.append(entryname);
+                } catch (Resources.NotFoundException e) {
+                }
+            }
+        }
+        if (mAutofillId != null) {
+            out.append(" aid="); out.append(mAutofillId);
+        }
+        out.append("}");
+        return out.toString();
+    }
+
+    /**
+     * <p>
+     * Initializes the fading edges from a given set of styled attributes. This
+     * method should be called by subclasses that need fading edges and when an
+     * instance of these subclasses is created programmatically rather than
+     * being inflated from XML. This method is automatically called when the XML
+     * is inflated.
+     * </p>
+     *
+     * @param a the styled attributes set to initialize the fading edges from
+     *
+     * @removed
+     */
+    protected void initializeFadingEdge(TypedArray a) {
+        // This method probably shouldn't have been included in the SDK to begin with.
+        // It relies on 'a' having been initialized using an attribute filter array that is
+        // not publicly available to the SDK. The old method has been renamed
+        // to initializeFadingEdgeInternal and hidden for framework use only;
+        // this one initializes using defaults to make it safe to call for apps.
+
+        TypedArray arr = mContext.obtainStyledAttributes(com.android.internal.R.styleable.View);
+
+        initializeFadingEdgeInternal(arr);
+
+        arr.recycle();
+    }
+
+    /**
+     * <p>
+     * Initializes the fading edges from a given set of styled attributes. This
+     * method should be called by subclasses that need fading edges and when an
+     * instance of these subclasses is created programmatically rather than
+     * being inflated from XML. This method is automatically called when the XML
+     * is inflated.
+     * </p>
+     *
+     * @param a the styled attributes set to initialize the fading edges from
+     * @hide This is the real method; the public one is shimmed to be safe to call from apps.
+     */
+    protected void initializeFadingEdgeInternal(TypedArray a) {
+        initScrollCache();
+
+        mScrollCache.fadingEdgeLength = a.getDimensionPixelSize(
+                R.styleable.View_fadingEdgeLength,
+                ViewConfiguration.get(mContext).getScaledFadingEdgeLength());
+    }
+
+    /**
+     * Returns the size of the vertical faded edges used to indicate that more
+     * content in this view is visible.
+     *
+     * @return The size in pixels of the vertical faded edge or 0 if vertical
+     *         faded edges are not enabled for this view.
+     * @attr ref android.R.styleable#View_fadingEdgeLength
+     */
+    public int getVerticalFadingEdgeLength() {
+        if (isVerticalFadingEdgeEnabled()) {
+            ScrollabilityCache cache = mScrollCache;
+            if (cache != null) {
+                return cache.fadingEdgeLength;
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Set the size of the faded edge used to indicate that more content in this
+     * view is available.  Will not change whether the fading edge is enabled; use
+     * {@link #setVerticalFadingEdgeEnabled(boolean)} or
+     * {@link #setHorizontalFadingEdgeEnabled(boolean)} to enable the fading edge
+     * for the vertical or horizontal fading edges.
+     *
+     * @param length The size in pixels of the faded edge used to indicate that more
+     *        content in this view is visible.
+     */
+    public void setFadingEdgeLength(int length) {
+        initScrollCache();
+        mScrollCache.fadingEdgeLength = length;
+    }
+
+    /**
+     * Returns the size of the horizontal faded edges used to indicate that more
+     * content in this view is visible.
+     *
+     * @return The size in pixels of the horizontal faded edge or 0 if horizontal
+     *         faded edges are not enabled for this view.
+     * @attr ref android.R.styleable#View_fadingEdgeLength
+     */
+    public int getHorizontalFadingEdgeLength() {
+        if (isHorizontalFadingEdgeEnabled()) {
+            ScrollabilityCache cache = mScrollCache;
+            if (cache != null) {
+                return cache.fadingEdgeLength;
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Returns the width of the vertical scrollbar.
+     *
+     * @return The width in pixels of the vertical scrollbar or 0 if there
+     *         is no vertical scrollbar.
+     */
+    public int getVerticalScrollbarWidth() {
+        ScrollabilityCache cache = mScrollCache;
+        if (cache != null) {
+            ScrollBarDrawable scrollBar = cache.scrollBar;
+            if (scrollBar != null) {
+                int size = scrollBar.getSize(true);
+                if (size <= 0) {
+                    size = cache.scrollBarSize;
+                }
+                return size;
+            }
+            return 0;
+        }
+        return 0;
+    }
+
+    /**
+     * Returns the height of the horizontal scrollbar.
+     *
+     * @return The height in pixels of the horizontal scrollbar or 0 if
+     *         there is no horizontal scrollbar.
+     */
+    protected int getHorizontalScrollbarHeight() {
+        ScrollabilityCache cache = mScrollCache;
+        if (cache != null) {
+            ScrollBarDrawable scrollBar = cache.scrollBar;
+            if (scrollBar != null) {
+                int size = scrollBar.getSize(false);
+                if (size <= 0) {
+                    size = cache.scrollBarSize;
+                }
+                return size;
+            }
+            return 0;
+        }
+        return 0;
+    }
+
+    /**
+     * <p>
+     * Initializes the scrollbars from a given set of styled attributes. This
+     * method should be called by subclasses that need scrollbars and when an
+     * instance of these subclasses is created programmatically rather than
+     * being inflated from XML. This method is automatically called when the XML
+     * is inflated.
+     * </p>
+     *
+     * @param a the styled attributes set to initialize the scrollbars from
+     *
+     * @removed
+     */
+    protected void initializeScrollbars(TypedArray a) {
+        // It's not safe to use this method from apps. The parameter 'a' must have been obtained
+        // using the View filter array which is not available to the SDK. As such, internal
+        // framework usage now uses initializeScrollbarsInternal and we grab a default
+        // TypedArray with the right filter instead here.
+        TypedArray arr = mContext.obtainStyledAttributes(com.android.internal.R.styleable.View);
+
+        initializeScrollbarsInternal(arr);
+
+        // We ignored the method parameter. Recycle the one we actually did use.
+        arr.recycle();
+    }
+
+    private void initializeScrollBarDrawable() {
+        initScrollCache();
+
+        if (mScrollCache.scrollBar == null) {
+            mScrollCache.scrollBar = new ScrollBarDrawable();
+            mScrollCache.scrollBar.setState(getDrawableState());
+            mScrollCache.scrollBar.setCallback(this);
+        }
+    }
+
+    /**
+     * <p>
+     * Initializes the scrollbars from a given set of styled attributes. This
+     * method should be called by subclasses that need scrollbars and when an
+     * instance of these subclasses is created programmatically rather than
+     * being inflated from XML. This method is automatically called when the XML
+     * is inflated.
+     * </p>
+     *
+     * @param a the styled attributes set to initialize the scrollbars from
+     * @hide
+     */
+    @UnsupportedAppUsage
+    protected void initializeScrollbarsInternal(TypedArray a) {
+        initScrollCache();
+
+        final ScrollabilityCache scrollabilityCache = mScrollCache;
+
+        if (scrollabilityCache.scrollBar == null) {
+            scrollabilityCache.scrollBar = new ScrollBarDrawable();
+            scrollabilityCache.scrollBar.setState(getDrawableState());
+            scrollabilityCache.scrollBar.setCallback(this);
+        }
+
+        final boolean fadeScrollbars = a.getBoolean(R.styleable.View_fadeScrollbars, true);
+
+        if (!fadeScrollbars) {
+            scrollabilityCache.state = ScrollabilityCache.ON;
+        }
+        scrollabilityCache.fadeScrollBars = fadeScrollbars;
+
+
+        scrollabilityCache.scrollBarFadeDuration = a.getInt(
+                R.styleable.View_scrollbarFadeDuration, ViewConfiguration
+                        .getScrollBarFadeDuration());
+        scrollabilityCache.scrollBarDefaultDelayBeforeFade = a.getInt(
+                R.styleable.View_scrollbarDefaultDelayBeforeFade,
+                ViewConfiguration.getScrollDefaultDelay());
+
+
+        scrollabilityCache.scrollBarSize = a.getDimensionPixelSize(
+                com.android.internal.R.styleable.View_scrollbarSize,
+                ViewConfiguration.get(mContext).getScaledScrollBarSize());
+
+        Drawable track = a.getDrawable(R.styleable.View_scrollbarTrackHorizontal);
+        scrollabilityCache.scrollBar.setHorizontalTrackDrawable(track);
+
+        Drawable thumb = a.getDrawable(R.styleable.View_scrollbarThumbHorizontal);
+        if (thumb != null) {
+            scrollabilityCache.scrollBar.setHorizontalThumbDrawable(thumb);
+        }
+
+        boolean alwaysDraw = a.getBoolean(R.styleable.View_scrollbarAlwaysDrawHorizontalTrack,
+                false);
+        if (alwaysDraw) {
+            scrollabilityCache.scrollBar.setAlwaysDrawHorizontalTrack(true);
+        }
+
+        track = a.getDrawable(R.styleable.View_scrollbarTrackVertical);
+        scrollabilityCache.scrollBar.setVerticalTrackDrawable(track);
+
+        thumb = a.getDrawable(R.styleable.View_scrollbarThumbVertical);
+        if (thumb != null) {
+            scrollabilityCache.scrollBar.setVerticalThumbDrawable(thumb);
+        }
+
+        alwaysDraw = a.getBoolean(R.styleable.View_scrollbarAlwaysDrawVerticalTrack,
+                false);
+        if (alwaysDraw) {
+            scrollabilityCache.scrollBar.setAlwaysDrawVerticalTrack(true);
+        }
+
+        // Apply layout direction to the new Drawables if needed
+        final int layoutDirection = getLayoutDirection();
+        if (track != null) {
+            track.setLayoutDirection(layoutDirection);
+        }
+        if (thumb != null) {
+            thumb.setLayoutDirection(layoutDirection);
+        }
+
+        // Re-apply user/background padding so that scrollbar(s) get added
+        resolvePadding();
+    }
+
+    /**
+     * Defines the vertical scrollbar thumb drawable
+     * @attr ref android.R.styleable#View_scrollbarThumbVertical
+     *
+     * @see #awakenScrollBars(int)
+     * @see #isVerticalScrollBarEnabled()
+     * @see #setVerticalScrollBarEnabled(boolean)
+     */
+    public void setVerticalScrollbarThumbDrawable(@Nullable Drawable drawable) {
+        initializeScrollBarDrawable();
+        mScrollCache.scrollBar.setVerticalThumbDrawable(drawable);
+    }
+
+    /**
+     * Defines the vertical scrollbar track drawable
+     * @attr ref android.R.styleable#View_scrollbarTrackVertical
+     *
+     * @see #awakenScrollBars(int)
+     * @see #isVerticalScrollBarEnabled()
+     * @see #setVerticalScrollBarEnabled(boolean)
+     */
+    public void setVerticalScrollbarTrackDrawable(@Nullable Drawable drawable) {
+        initializeScrollBarDrawable();
+        mScrollCache.scrollBar.setVerticalTrackDrawable(drawable);
+    }
+
+    /**
+     * Defines the horizontal thumb drawable
+     * @attr ref android.R.styleable#View_scrollbarThumbHorizontal
+     *
+     * @see #awakenScrollBars(int)
+     * @see #isHorizontalScrollBarEnabled()
+     * @see #setHorizontalScrollBarEnabled(boolean)
+     */
+    public void setHorizontalScrollbarThumbDrawable(@Nullable Drawable drawable) {
+        initializeScrollBarDrawable();
+        mScrollCache.scrollBar.setHorizontalThumbDrawable(drawable);
+    }
+
+    /**
+     * Defines the horizontal track drawable
+     * @attr ref android.R.styleable#View_scrollbarTrackHorizontal
+     *
+     * @see #awakenScrollBars(int)
+     * @see #isHorizontalScrollBarEnabled()
+     * @see #setHorizontalScrollBarEnabled(boolean)
+     */
+    public void setHorizontalScrollbarTrackDrawable(@Nullable Drawable drawable) {
+        initializeScrollBarDrawable();
+        mScrollCache.scrollBar.setHorizontalTrackDrawable(drawable);
+    }
+
+    /**
+     * Returns the currently configured Drawable for the thumb of the vertical scroll bar if it
+     * exists, null otherwise.
+     *
+     * @see #awakenScrollBars(int)
+     * @see #isVerticalScrollBarEnabled()
+     * @see #setVerticalScrollBarEnabled(boolean)
+     */
+    public @Nullable Drawable getVerticalScrollbarThumbDrawable() {
+        return mScrollCache != null ? mScrollCache.scrollBar.getVerticalThumbDrawable() : null;
+    }
+
+    /**
+     * Returns the currently configured Drawable for the track of the vertical scroll bar if it
+     * exists, null otherwise.
+     *
+     * @see #awakenScrollBars(int)
+     * @see #isVerticalScrollBarEnabled()
+     * @see #setVerticalScrollBarEnabled(boolean)
+     */
+    public @Nullable Drawable getVerticalScrollbarTrackDrawable() {
+        return mScrollCache != null ? mScrollCache.scrollBar.getVerticalTrackDrawable() : null;
+    }
+
+    /**
+     * Returns the currently configured Drawable for the thumb of the horizontal scroll bar if it
+     * exists, null otherwise.
+     *
+     * @see #awakenScrollBars(int)
+     * @see #isHorizontalScrollBarEnabled()
+     * @see #setHorizontalScrollBarEnabled(boolean)
+     */
+    public @Nullable Drawable getHorizontalScrollbarThumbDrawable() {
+        return mScrollCache != null ? mScrollCache.scrollBar.getHorizontalThumbDrawable() : null;
+    }
+
+    /**
+     * Returns the currently configured Drawable for the track of the horizontal scroll bar if it
+     * exists, null otherwise.
+     *
+     * @see #awakenScrollBars(int)
+     * @see #isHorizontalScrollBarEnabled()
+     * @see #setHorizontalScrollBarEnabled(boolean)
+     */
+    public @Nullable Drawable getHorizontalScrollbarTrackDrawable() {
+        return mScrollCache != null ? mScrollCache.scrollBar.getHorizontalTrackDrawable() : null;
+    }
+
+    private void initializeScrollIndicatorsInternal() {
+        // Some day maybe we'll break this into top/left/start/etc. and let the
+        // client control it. Until then, you can have any scroll indicator you
+        // want as long as it's a 1dp foreground-colored rectangle.
+        if (mScrollIndicatorDrawable == null) {
+            mScrollIndicatorDrawable = mContext.getDrawable(R.drawable.scroll_indicator_material);
+        }
+    }
+
+    /**
+     * <p>
+     * Initalizes the scrollability cache if necessary.
+     * </p>
+     */
+    private void initScrollCache() {
+        if (mScrollCache == null) {
+            mScrollCache = new ScrollabilityCache(ViewConfiguration.get(mContext), this);
+        }
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private ScrollabilityCache getScrollCache() {
+        initScrollCache();
+        return mScrollCache;
+    }
+
+    /**
+     * Set the position of the vertical scroll bar. Should be one of
+     * {@link #SCROLLBAR_POSITION_DEFAULT}, {@link #SCROLLBAR_POSITION_LEFT} or
+     * {@link #SCROLLBAR_POSITION_RIGHT}.
+     *
+     * @param position Where the vertical scroll bar should be positioned.
+     */
+    public void setVerticalScrollbarPosition(int position) {
+        if (mVerticalScrollbarPosition != position) {
+            mVerticalScrollbarPosition = position;
+            computeOpaqueFlags();
+            resolvePadding();
+        }
+    }
+
+    /**
+     * @return The position where the vertical scroll bar will show, if applicable.
+     * @see #setVerticalScrollbarPosition(int)
+     */
+    public int getVerticalScrollbarPosition() {
+        return mVerticalScrollbarPosition;
+    }
+
+    boolean isOnScrollbar(float x, float y) {
+        if (mScrollCache == null) {
+            return false;
+        }
+        x += getScrollX();
+        y += getScrollY();
+        final boolean canScrollVertically =
+                computeVerticalScrollRange() > computeVerticalScrollExtent();
+        if (isVerticalScrollBarEnabled() && !isVerticalScrollBarHidden() && canScrollVertically) {
+            final Rect touchBounds = mScrollCache.mScrollBarTouchBounds;
+            getVerticalScrollBarBounds(null, touchBounds);
+            if (touchBounds.contains((int) x, (int) y)) {
+                return true;
+            }
+        }
+        final boolean canScrollHorizontally =
+                computeHorizontalScrollRange() > computeHorizontalScrollExtent();
+        if (isHorizontalScrollBarEnabled() && canScrollHorizontally) {
+            final Rect touchBounds = mScrollCache.mScrollBarTouchBounds;
+            getHorizontalScrollBarBounds(null, touchBounds);
+            if (touchBounds.contains((int) x, (int) y)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @UnsupportedAppUsage
+    boolean isOnScrollbarThumb(float x, float y) {
+        return isOnVerticalScrollbarThumb(x, y) || isOnHorizontalScrollbarThumb(x, y);
+    }
+
+    private boolean isOnVerticalScrollbarThumb(float x, float y) {
+        if (mScrollCache == null || !isVerticalScrollBarEnabled() || isVerticalScrollBarHidden()) {
+            return false;
+        }
+        final int range = computeVerticalScrollRange();
+        final int extent = computeVerticalScrollExtent();
+        if (range > extent) {
+            x += getScrollX();
+            y += getScrollY();
+            final Rect bounds = mScrollCache.mScrollBarBounds;
+            final Rect touchBounds = mScrollCache.mScrollBarTouchBounds;
+            getVerticalScrollBarBounds(bounds, touchBounds);
+            final int offset = computeVerticalScrollOffset();
+            final int thumbLength = ScrollBarUtils.getThumbLength(bounds.height(), bounds.width(),
+                    extent, range);
+            final int thumbOffset = ScrollBarUtils.getThumbOffset(bounds.height(), thumbLength,
+                    extent, range, offset);
+            final int thumbTop = bounds.top + thumbOffset;
+            final int adjust = Math.max(mScrollCache.scrollBarMinTouchTarget - thumbLength, 0) / 2;
+            if (x >= touchBounds.left && x <= touchBounds.right
+                    && y >= thumbTop - adjust && y <= thumbTop + thumbLength + adjust) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean isOnHorizontalScrollbarThumb(float x, float y) {
+        if (mScrollCache == null || !isHorizontalScrollBarEnabled()) {
+            return false;
+        }
+        final int range = computeHorizontalScrollRange();
+        final int extent = computeHorizontalScrollExtent();
+        if (range > extent) {
+            x += getScrollX();
+            y += getScrollY();
+            final Rect bounds = mScrollCache.mScrollBarBounds;
+            final Rect touchBounds = mScrollCache.mScrollBarTouchBounds;
+            getHorizontalScrollBarBounds(bounds, touchBounds);
+            final int offset = computeHorizontalScrollOffset();
+
+            final int thumbLength = ScrollBarUtils.getThumbLength(bounds.width(), bounds.height(),
+                    extent, range);
+            final int thumbOffset = ScrollBarUtils.getThumbOffset(bounds.width(), thumbLength,
+                    extent, range, offset);
+            final int thumbLeft = bounds.left + thumbOffset;
+            final int adjust = Math.max(mScrollCache.scrollBarMinTouchTarget - thumbLength, 0) / 2;
+            if (x >= thumbLeft - adjust && x <= thumbLeft + thumbLength + adjust
+                    && y >= touchBounds.top && y <= touchBounds.bottom) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @UnsupportedAppUsage
+    boolean isDraggingScrollBar() {
+        return mScrollCache != null
+                && mScrollCache.mScrollBarDraggingState != ScrollabilityCache.NOT_DRAGGING;
+    }
+
+    /**
+     * Sets the state of all scroll indicators.
+     * <p>
+     * See {@link #setScrollIndicators(int, int)} for usage information.
+     *
+     * @param indicators a bitmask of indicators that should be enabled, or
+     *                   {@code 0} to disable all indicators
+     * @see #setScrollIndicators(int, int)
+     * @see #getScrollIndicators()
+     * @attr ref android.R.styleable#View_scrollIndicators
+     */
+    @RemotableViewMethod
+    public void setScrollIndicators(@ScrollIndicators int indicators) {
+        setScrollIndicators(indicators,
+                SCROLL_INDICATORS_PFLAG3_MASK >>> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT);
+    }
+
+    /**
+     * Sets the state of the scroll indicators specified by the mask. To change
+     * all scroll indicators at once, see {@link #setScrollIndicators(int)}.
+     * <p>
+     * When a scroll indicator is enabled, it will be displayed if the view
+     * can scroll in the direction of the indicator.
+     * <p>
+     * Multiple indicator types may be enabled or disabled by passing the
+     * logical OR of the desired types. If multiple types are specified, they
+     * will all be set to the same enabled state.
+     * <p>
+     * For example, to enable the top scroll indicatorExample: {@code setScrollIndicators
+     *
+     * @param indicators the indicator direction, or the logical OR of multiple
+     *             indicator directions. One or more of:
+     *             <ul>
+     *               <li>{@link #SCROLL_INDICATOR_TOP}</li>
+     *               <li>{@link #SCROLL_INDICATOR_BOTTOM}</li>
+     *               <li>{@link #SCROLL_INDICATOR_LEFT}</li>
+     *               <li>{@link #SCROLL_INDICATOR_RIGHT}</li>
+     *               <li>{@link #SCROLL_INDICATOR_START}</li>
+     *               <li>{@link #SCROLL_INDICATOR_END}</li>
+     *             </ul>
+     * @see #setScrollIndicators(int)
+     * @see #getScrollIndicators()
+     * @attr ref android.R.styleable#View_scrollIndicators
+     */
+    public void setScrollIndicators(@ScrollIndicators int indicators, @ScrollIndicators int mask) {
+        // Shift and sanitize mask.
+        mask <<= SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT;
+        mask &= SCROLL_INDICATORS_PFLAG3_MASK;
+
+        // Shift and mask indicators.
+        indicators <<= SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT;
+        indicators &= mask;
+
+        // Merge with non-masked flags.
+        final int updatedFlags = indicators | (mPrivateFlags3 & ~mask);
+
+        if (mPrivateFlags3 != updatedFlags) {
+            mPrivateFlags3 = updatedFlags;
+
+            if (indicators != 0) {
+                initializeScrollIndicatorsInternal();
+            }
+            invalidate();
+        }
+    }
+
+    /**
+     * Returns a bitmask representing the enabled scroll indicators.
+     * <p>
+     * For example, if the top and left scroll indicators are enabled and all
+     * other indicators are disabled, the return value will be
+     * {@code View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_LEFT}.
+     * <p>
+     * To check whether the bottom scroll indicator is enabled, use the value
+     * of {@code (getScrollIndicators() & View.SCROLL_INDICATOR_BOTTOM) != 0}.
+     *
+     * @return a bitmask representing the enabled scroll indicators
+     */
+    @InspectableProperty(flagMapping = {
+            @FlagEntry(target = SCROLL_INDICATORS_NONE, mask = 0xffff_ffff, name = "none"),
+            @FlagEntry(target = SCROLL_INDICATOR_TOP, name = "top"),
+            @FlagEntry(target = SCROLL_INDICATOR_BOTTOM, name = "bottom"),
+            @FlagEntry(target = SCROLL_INDICATOR_LEFT, name = "left"),
+            @FlagEntry(target = SCROLL_INDICATOR_RIGHT, name = "right"),
+            @FlagEntry(target = SCROLL_INDICATOR_START, name = "start"),
+            @FlagEntry(target = SCROLL_INDICATOR_END, name = "end")
+    })
+    @ScrollIndicators
+    public int getScrollIndicators() {
+        return (mPrivateFlags3 & SCROLL_INDICATORS_PFLAG3_MASK)
+                >>> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT;
+    }
+
+    @UnsupportedAppUsage
+    ListenerInfo getListenerInfo() {
+        if (mListenerInfo != null) {
+            return mListenerInfo;
+        }
+        mListenerInfo = new ListenerInfo();
+        return mListenerInfo;
+    }
+
+    /**
+     * Register a callback to be invoked when the scroll X or Y positions of
+     * this view change.
+     * <p>
+     * <b>Note:</b> Some views handle scrolling independently from View and may
+     * have their own separate listeners for scroll-type events. For example,
+     * {@link android.widget.ListView ListView} allows clients to register an
+     * {@link android.widget.ListView#setOnScrollListener(android.widget.AbsListView.OnScrollListener) AbsListView.OnScrollListener}
+     * to listen for changes in list scroll position.
+     *
+     * @param l The listener to notify when the scroll X or Y position changes.
+     * @see android.view.View#getScrollX()
+     * @see android.view.View#getScrollY()
+     */
+    public void setOnScrollChangeListener(OnScrollChangeListener l) {
+        getListenerInfo().mOnScrollChangeListener = l;
+    }
+
+    /**
+     * Register a callback to be invoked when focus of this view changed.
+     *
+     * @param l The callback that will run.
+     */
+    public void setOnFocusChangeListener(OnFocusChangeListener l) {
+        getListenerInfo().mOnFocusChangeListener = l;
+    }
+
+    /**
+     * Add a listener that will be called when the bounds of the view change due to
+     * layout processing.
+     *
+     * @param listener The listener that will be called when layout bounds change.
+     */
+    public void addOnLayoutChangeListener(OnLayoutChangeListener listener) {
+        ListenerInfo li = getListenerInfo();
+        if (li.mOnLayoutChangeListeners == null) {
+            li.mOnLayoutChangeListeners = new ArrayList<OnLayoutChangeListener>();
+        }
+        if (!li.mOnLayoutChangeListeners.contains(listener)) {
+            li.mOnLayoutChangeListeners.add(listener);
+        }
+    }
+
+    /**
+     * Remove a listener for layout changes.
+     *
+     * @param listener The listener for layout bounds change.
+     */
+    public void removeOnLayoutChangeListener(OnLayoutChangeListener listener) {
+        ListenerInfo li = mListenerInfo;
+        if (li == null || li.mOnLayoutChangeListeners == null) {
+            return;
+        }
+        li.mOnLayoutChangeListeners.remove(listener);
+    }
+
+    /**
+     * Add a listener for attach state changes.
+     *
+     * This listener will be called whenever this view is attached or detached
+     * from a window. Remove the listener using
+     * {@link #removeOnAttachStateChangeListener(OnAttachStateChangeListener)}.
+     *
+     * @param listener Listener to attach
+     * @see #removeOnAttachStateChangeListener(OnAttachStateChangeListener)
+     */
+    public void addOnAttachStateChangeListener(OnAttachStateChangeListener listener) {
+        ListenerInfo li = getListenerInfo();
+        if (li.mOnAttachStateChangeListeners == null) {
+            li.mOnAttachStateChangeListeners
+                    = new CopyOnWriteArrayList<OnAttachStateChangeListener>();
+        }
+        li.mOnAttachStateChangeListeners.add(listener);
+    }
+
+    /**
+     * Remove a listener for attach state changes. The listener will receive no further
+     * notification of window attach/detach events.
+     *
+     * @param listener Listener to remove
+     * @see #addOnAttachStateChangeListener(OnAttachStateChangeListener)
+     */
+    public void removeOnAttachStateChangeListener(OnAttachStateChangeListener listener) {
+        ListenerInfo li = mListenerInfo;
+        if (li == null || li.mOnAttachStateChangeListeners == null) {
+            return;
+        }
+        li.mOnAttachStateChangeListeners.remove(listener);
+    }
+
+    /**
+     * Returns the focus-change callback registered for this view.
+     *
+     * @return The callback, or null if one is not registered.
+     */
+    public OnFocusChangeListener getOnFocusChangeListener() {
+        ListenerInfo li = mListenerInfo;
+        return li != null ? li.mOnFocusChangeListener : null;
+    }
+
+    /**
+     * Register a callback to be invoked when this view is clicked. If this view is not
+     * clickable, it becomes clickable.
+     *
+     * @param l The callback that will run
+     *
+     * @see #setClickable(boolean)
+     */
+    public void setOnClickListener(@Nullable OnClickListener l) {
+        if (!isClickable()) {
+            setClickable(true);
+        }
+        getListenerInfo().mOnClickListener = l;
+    }
+
+    /**
+     * Return whether this view has an attached OnClickListener.  Returns
+     * true if there is a listener, false if there is none.
+     */
+    public boolean hasOnClickListeners() {
+        ListenerInfo li = mListenerInfo;
+        return (li != null && li.mOnClickListener != null);
+    }
+
+    /**
+     * Register a callback to be invoked when this view is clicked and held. If this view is not
+     * long clickable, it becomes long clickable.
+     *
+     * @param l The callback that will run
+     *
+     * @see #setLongClickable(boolean)
+     */
+    public void setOnLongClickListener(@Nullable OnLongClickListener l) {
+        if (!isLongClickable()) {
+            setLongClickable(true);
+        }
+        getListenerInfo().mOnLongClickListener = l;
+    }
+
+    /**
+     * Return whether this view has an attached OnLongClickListener.  Returns
+     * true if there is a listener, false if there is none.
+     */
+    public boolean hasOnLongClickListeners() {
+        ListenerInfo li = mListenerInfo;
+        return (li != null && li.mOnLongClickListener != null);
+    }
+
+    /**
+     * @return the registered {@link OnLongClickListener} if there is one, {@code null} otherwise.
+     * @hide
+     */
+    @Nullable
+    public OnLongClickListener getOnLongClickListener() {
+        ListenerInfo li = mListenerInfo;
+        return (li != null) ? li.mOnLongClickListener : null;
+    }
+
+    /**
+     * Register a callback to be invoked when this view is context clicked. If the view is not
+     * context clickable, it becomes context clickable.
+     *
+     * @param l The callback that will run
+     * @see #setContextClickable(boolean)
+     */
+    public void setOnContextClickListener(@Nullable OnContextClickListener l) {
+        if (!isContextClickable()) {
+            setContextClickable(true);
+        }
+        getListenerInfo().mOnContextClickListener = l;
+    }
+
+    /**
+     * Register a callback to be invoked when the context menu for this view is
+     * being built. If this view is not long clickable, it becomes long clickable.
+     *
+     * @param l The callback that will run
+     *
+     */
+    public void setOnCreateContextMenuListener(OnCreateContextMenuListener l) {
+        if (!isLongClickable()) {
+            setLongClickable(true);
+        }
+        getListenerInfo().mOnCreateContextMenuListener = l;
+    }
+
+    /**
+     * Set an observer to collect stats for each frame rendered for this view.
+     *
+     * @hide
+     */
+    public void addFrameMetricsListener(Window window,
+            Window.OnFrameMetricsAvailableListener listener,
+            Handler handler) {
+        if (mAttachInfo != null) {
+            if (mAttachInfo.mThreadedRenderer != null) {
+                if (mFrameMetricsObservers == null) {
+                    mFrameMetricsObservers = new ArrayList<>();
+                }
+
+                FrameMetricsObserver fmo = new FrameMetricsObserver(window, handler, listener);
+                mFrameMetricsObservers.add(fmo);
+                mAttachInfo.mThreadedRenderer.addObserver(fmo.getRendererObserver());
+            } else {
+                Log.w(VIEW_LOG_TAG, "View not hardware-accelerated. Unable to observe frame stats");
+            }
+        } else {
+            if (mFrameMetricsObservers == null) {
+                mFrameMetricsObservers = new ArrayList<>();
+            }
+
+            FrameMetricsObserver fmo = new FrameMetricsObserver(window, handler, listener);
+            mFrameMetricsObservers.add(fmo);
+        }
+    }
+
+    /**
+     * Remove observer configured to collect frame stats for this view.
+     *
+     * @hide
+     */
+    public void removeFrameMetricsListener(
+            Window.OnFrameMetricsAvailableListener listener) {
+        ThreadedRenderer renderer = getThreadedRenderer();
+        FrameMetricsObserver fmo = findFrameMetricsObserver(listener);
+        if (fmo == null) {
+            throw new IllegalArgumentException(
+                    "attempt to remove OnFrameMetricsAvailableListener that was never added");
+        }
+
+        if (mFrameMetricsObservers != null) {
+            mFrameMetricsObservers.remove(fmo);
+            if (renderer != null) {
+                renderer.removeObserver(fmo.getRendererObserver());
+            }
+        }
+    }
+
+    private void registerPendingFrameMetricsObservers() {
+        if (mFrameMetricsObservers != null) {
+            ThreadedRenderer renderer = getThreadedRenderer();
+            if (renderer != null) {
+                for (FrameMetricsObserver fmo : mFrameMetricsObservers) {
+                    renderer.addObserver(fmo.getRendererObserver());
+                }
+            } else {
+                Log.w(VIEW_LOG_TAG, "View not hardware-accelerated. Unable to observe frame stats");
+            }
+        }
+    }
+
+    private FrameMetricsObserver findFrameMetricsObserver(
+            Window.OnFrameMetricsAvailableListener listener) {
+        if (mFrameMetricsObservers != null) {
+            for (int i = 0; i < mFrameMetricsObservers.size(); i++) {
+                FrameMetricsObserver observer = mFrameMetricsObservers.get(i);
+                if (observer.mListener == listener) {
+                    return observer;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /** @hide */
+    public void setNotifyAutofillManagerOnClick(boolean notify) {
+        if (notify) {
+            mPrivateFlags |= PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK;
+        } else {
+            mPrivateFlags &= ~PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK;
+        }
+    }
+
+    private void notifyAutofillManagerOnClick() {
+        if ((mPrivateFlags & PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK) != 0) {
+            try {
+                getAutofillManager().notifyViewClicked(this);
+            } finally {
+                // Set it to already called so it's not called twice when called by
+                // performClickInternal()
+                mPrivateFlags &= ~PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK;
+            }
+        }
+    }
+
+    /**
+     * Entry point for {@link #performClick()} - other methods on View should call it instead of
+     * {@code performClick()} directly to make sure the autofill manager is notified when
+     * necessary (as subclasses could extend {@code performClick()} without calling the parent's
+     * method).
+     */
+    private boolean performClickInternal() {
+        // Must notify autofill manager before performing the click actions to avoid scenarios where
+        // the app has a click listener that changes the state of views the autofill service might
+        // be interested on.
+        notifyAutofillManagerOnClick();
+
+        return performClick();
+    }
+
+    /**
+     * Call this view's OnClickListener, if it is defined.  Performs all normal
+     * actions associated with clicking: reporting accessibility event, playing
+     * a sound, etc.
+     *
+     * @return True there was an assigned OnClickListener that was called, false
+     *         otherwise is returned.
+     */
+    // NOTE: other methods on View should not call this method directly, but performClickInternal()
+    // instead, to guarantee that the autofill manager is notified when necessary (as subclasses
+    // could extend this method without calling super.performClick()).
+    public boolean performClick() {
+        // We still need to call this method to handle the cases where performClick() was called
+        // externally, instead of through performClickInternal()
+        notifyAutofillManagerOnClick();
+
+        final boolean result;
+        final ListenerInfo li = mListenerInfo;
+        if (li != null && li.mOnClickListener != null) {
+            playSoundEffect(SoundEffectConstants.CLICK);
+            li.mOnClickListener.onClick(this);
+            result = true;
+        } else {
+            result = false;
+        }
+
+        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
+
+        notifyEnterOrExitForAutoFillIfNeeded(true);
+
+        return result;
+    }
+
+    /**
+     * Directly call any attached OnClickListener.  Unlike {@link #performClick()},
+     * this only calls the listener, and does not do any associated clicking
+     * actions like reporting an accessibility event.
+     *
+     * @return True there was an assigned OnClickListener that was called, false
+     *         otherwise is returned.
+     */
+    public boolean callOnClick() {
+        ListenerInfo li = mListenerInfo;
+        if (li != null && li.mOnClickListener != null) {
+            li.mOnClickListener.onClick(this);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Calls this view's OnLongClickListener, if it is defined. Invokes the
+     * context menu if the OnLongClickListener did not consume the event.
+     *
+     * @return {@code true} if one of the above receivers consumed the event,
+     *         {@code false} otherwise
+     */
+    public boolean performLongClick() {
+        return performLongClickInternal(mLongClickX, mLongClickY);
+    }
+
+    /**
+     * Calls this view's OnLongClickListener, if it is defined. Invokes the
+     * context menu if the OnLongClickListener did not consume the event,
+     * anchoring it to an (x,y) coordinate.
+     *
+     * @param x x coordinate of the anchoring touch event, or {@link Float#NaN}
+     *          to disable anchoring
+     * @param y y coordinate of the anchoring touch event, or {@link Float#NaN}
+     *          to disable anchoring
+     * @return {@code true} if one of the above receivers consumed the event,
+     *         {@code false} otherwise
+     */
+    public boolean performLongClick(float x, float y) {
+        mLongClickX = x;
+        mLongClickY = y;
+        final boolean handled = performLongClick();
+        mLongClickX = Float.NaN;
+        mLongClickY = Float.NaN;
+        return handled;
+    }
+
+    /**
+     * Calls this view's OnLongClickListener, if it is defined. Invokes the
+     * context menu if the OnLongClickListener did not consume the event,
+     * optionally anchoring it to an (x,y) coordinate.
+     *
+     * @param x x coordinate of the anchoring touch event, or {@link Float#NaN}
+     *          to disable anchoring
+     * @param y y coordinate of the anchoring touch event, or {@link Float#NaN}
+     *          to disable anchoring
+     * @return {@code true} if one of the above receivers consumed the event,
+     *         {@code false} otherwise
+     */
+    private boolean performLongClickInternal(float x, float y) {
+        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
+
+        boolean handled = false;
+        final ListenerInfo li = mListenerInfo;
+        if (li != null && li.mOnLongClickListener != null) {
+            handled = li.mOnLongClickListener.onLongClick(View.this);
+        }
+        if (!handled) {
+            final boolean isAnchored = !Float.isNaN(x) && !Float.isNaN(y);
+            handled = isAnchored ? showContextMenu(x, y) : showContextMenu();
+        }
+        if ((mViewFlags & TOOLTIP) == TOOLTIP) {
+            if (!handled) {
+                handled = showLongClickTooltip((int) x, (int) y);
+            }
+        }
+        if (handled) {
+            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+        }
+        return handled;
+    }
+
+    /**
+     * Call this view's OnContextClickListener, if it is defined.
+     *
+     * @param x the x coordinate of the context click
+     * @param y the y coordinate of the context click
+     * @return True if there was an assigned OnContextClickListener that consumed the event, false
+     *         otherwise.
+     */
+    public boolean performContextClick(float x, float y) {
+        return performContextClick();
+    }
+
+    /**
+     * Call this view's OnContextClickListener, if it is defined.
+     *
+     * @return True if there was an assigned OnContextClickListener that consumed the event, false
+     *         otherwise.
+     */
+    public boolean performContextClick() {
+        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CONTEXT_CLICKED);
+
+        boolean handled = false;
+        ListenerInfo li = mListenerInfo;
+        if (li != null && li.mOnContextClickListener != null) {
+            handled = li.mOnContextClickListener.onContextClick(View.this);
+        }
+        if (handled) {
+            performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
+        }
+        return handled;
+    }
+
+    /**
+     * Performs button-related actions during a touch down event.
+     *
+     * @param event The event.
+     * @return True if the down was consumed.
+     *
+     * @hide
+     */
+    protected boolean performButtonActionOnTouchDown(MotionEvent event) {
+        if (event.isFromSource(InputDevice.SOURCE_MOUSE) &&
+            (event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {
+            showContextMenu(event.getX(), event.getY());
+            mPrivateFlags |= PFLAG_CANCEL_NEXT_UP_EVENT;
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Shows the context menu for this view.
+     *
+     * @return {@code true} if the context menu was shown, {@code false}
+     *         otherwise
+     * @see #showContextMenu(float, float)
+     */
+    public boolean showContextMenu() {
+        return getParent().showContextMenuForChild(this);
+    }
+
+    /**
+     * Shows the context menu for this view anchored to the specified
+     * view-relative coordinate.
+     *
+     * @param x the X coordinate in pixels relative to the view to which the
+     *          menu should be anchored, or {@link Float#NaN} to disable anchoring
+     * @param y the Y coordinate in pixels relative to the view to which the
+     *          menu should be anchored, or {@link Float#NaN} to disable anchoring
+     * @return {@code true} if the context menu was shown, {@code false}
+     *         otherwise
+     */
+    public boolean showContextMenu(float x, float y) {
+        return getParent().showContextMenuForChild(this, x, y);
+    }
+
+    /**
+     * Start an action mode with the default type {@link ActionMode#TYPE_PRIMARY}.
+     *
+     * @param callback Callback that will control the lifecycle of the action mode
+     * @return The new action mode if it is started, null otherwise
+     *
+     * @see ActionMode
+     * @see #startActionMode(android.view.ActionMode.Callback, int)
+     */
+    public ActionMode startActionMode(ActionMode.Callback callback) {
+        return startActionMode(callback, ActionMode.TYPE_PRIMARY);
+    }
+
+    /**
+     * Start an action mode with the given type.
+     *
+     * @param callback Callback that will control the lifecycle of the action mode
+     * @param type One of {@link ActionMode#TYPE_PRIMARY} or {@link ActionMode#TYPE_FLOATING}.
+     * @return The new action mode if it is started, null otherwise
+     *
+     * @see ActionMode
+     */
+    public ActionMode startActionMode(ActionMode.Callback callback, int type) {
+        ViewParent parent = getParent();
+        if (parent == null) return null;
+        try {
+            return parent.startActionModeForChild(this, callback, type);
+        } catch (AbstractMethodError ame) {
+            // Older implementations of custom views might not implement this.
+            return parent.startActionModeForChild(this, callback);
+        }
+    }
+
+    /**
+     * Call {@link Context#startActivityForResult(String, Intent, int, Bundle)} for the View's
+     * Context, creating a unique View identifier to retrieve the result.
+     *
+     * @param intent The Intent to be started.
+     * @param requestCode The request code to use.
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    public void startActivityForResult(Intent intent, int requestCode) {
+        mStartActivityRequestWho = "@android:view:" + System.identityHashCode(this);
+        getContext().startActivityForResult(mStartActivityRequestWho, intent, requestCode, null);
+    }
+
+    /**
+     * If this View corresponds to the calling who, dispatches the activity result.
+     * @param who The identifier for the targeted View to receive the result.
+     * @param requestCode The integer request code originally supplied to
+     *                    startActivityForResult(), allowing you to identify who this
+     *                    result came from.
+     * @param resultCode The integer result code returned by the child activity
+     *                   through its setResult().
+     * @param data An Intent, which can return result data to the caller
+     *               (various data can be attached to Intent "extras").
+     * @return {@code true} if the activity result was dispatched.
+     * @hide
+     */
+    public boolean dispatchActivityResult(
+            String who, int requestCode, int resultCode, Intent data) {
+        if (mStartActivityRequestWho != null && mStartActivityRequestWho.equals(who)) {
+            onActivityResult(requestCode, resultCode, data);
+            mStartActivityRequestWho = null;
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Receive the result from a previous call to {@link #startActivityForResult(Intent, int)}.
+     *
+     * @param requestCode The integer request code originally supplied to
+     *                    startActivityForResult(), allowing you to identify who this
+     *                    result came from.
+     * @param resultCode The integer result code returned by the child activity
+     *                   through its setResult().
+     * @param data An Intent, which can return result data to the caller
+     *               (various data can be attached to Intent "extras").
+     * @hide
+     */
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        // Do nothing.
+    }
+
+    /**
+     * Register a callback to be invoked when a hardware key is pressed in this view.
+     * Key presses in software input methods will generally not trigger the methods of
+     * this listener.
+     * @param l the key listener to attach to this view
+     */
+    public void setOnKeyListener(OnKeyListener l) {
+        getListenerInfo().mOnKeyListener = l;
+    }
+
+    /**
+     * Register a callback to be invoked when a touch event is sent to this view.
+     * @param l the touch listener to attach to this view
+     */
+    public void setOnTouchListener(OnTouchListener l) {
+        getListenerInfo().mOnTouchListener = l;
+    }
+
+    /**
+     * Register a callback to be invoked when a generic motion event is sent to this view.
+     * @param l the generic motion listener to attach to this view
+     */
+    public void setOnGenericMotionListener(OnGenericMotionListener l) {
+        getListenerInfo().mOnGenericMotionListener = l;
+    }
+
+    /**
+     * Register a callback to be invoked when a hover event is sent to this view.
+     * @param l the hover listener to attach to this view
+     */
+    public void setOnHoverListener(OnHoverListener l) {
+        getListenerInfo().mOnHoverListener = l;
+    }
+
+    /**
+     * Register a drag event listener callback object for this View. The parameter is
+     * an implementation of {@link android.view.View.OnDragListener}. To send a drag event to a
+     * View, the system calls the
+     * {@link android.view.View.OnDragListener#onDrag(View,DragEvent)} method.
+     * @param l An implementation of {@link android.view.View.OnDragListener}.
+     */
+    public void setOnDragListener(OnDragListener l) {
+        getListenerInfo().mOnDragListener = l;
+    }
+
+    /**
+     * Give this view focus. This will cause
+     * {@link #onFocusChanged(boolean, int, android.graphics.Rect)} to be called.
+     *
+     * Note: this does not check whether this {@link View} should get focus, it just
+     * gives it focus no matter what.  It should only be called internally by framework
+     * code that knows what it is doing, namely {@link #requestFocus(int, Rect)}.
+     *
+     * @param direction values are {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
+     *        {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT}. This is the direction which
+     *        focus moved when requestFocus() is called. It may not always
+     *        apply, in which case use the default View.FOCUS_DOWN.
+     * @param previouslyFocusedRect The rectangle of the view that had focus
+     *        prior in this View's coordinate system.
+     */
+    void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) {
+        if (DBG) {
+            System.out.println(this + " requestFocus()");
+        }
+
+        if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
+            mPrivateFlags |= PFLAG_FOCUSED;
+
+            View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;
+
+            if (mParent != null) {
+                mParent.requestChildFocus(this, this);
+                updateFocusedInCluster(oldFocus, direction);
+            }
+
+            if (mAttachInfo != null) {
+                mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
+            }
+
+            onFocusChanged(true, direction, previouslyFocusedRect);
+            refreshDrawableState();
+        }
+    }
+
+    /**
+     * Sets this view's preference for reveal behavior when it gains focus.
+     *
+     * <p>When set to true, this is a signal to ancestor views in the hierarchy that
+     * this view would prefer to be brought fully into view when it gains focus.
+     * For example, a text field that a user is meant to type into. Other views such
+     * as scrolling containers may prefer to opt-out of this behavior.</p>
+     *
+     * <p>The default value for views is true, though subclasses may change this
+     * based on their preferred behavior.</p>
+     *
+     * @param revealOnFocus true to request reveal on focus in ancestors, false otherwise
+     *
+     * @see #getRevealOnFocusHint()
+     */
+    public final void setRevealOnFocusHint(boolean revealOnFocus) {
+        if (revealOnFocus) {
+            mPrivateFlags3 &= ~PFLAG3_NO_REVEAL_ON_FOCUS;
+        } else {
+            mPrivateFlags3 |= PFLAG3_NO_REVEAL_ON_FOCUS;
+        }
+    }
+
+    /**
+     * Returns this view's preference for reveal behavior when it gains focus.
+     *
+     * <p>When this method returns true for a child view requesting focus, ancestor
+     * views responding to a focus change in {@link ViewParent#requestChildFocus(View, View)}
+     * should make a best effort to make the newly focused child fully visible to the user.
+     * When it returns false, ancestor views should preferably not disrupt scroll positioning or
+     * other properties affecting visibility to the user as part of the focus change.</p>
+     *
+     * @return true if this view would prefer to become fully visible when it gains focus,
+     *         false if it would prefer not to disrupt scroll positioning
+     *
+     * @see #setRevealOnFocusHint(boolean)
+     */
+    public final boolean getRevealOnFocusHint() {
+        return (mPrivateFlags3 & PFLAG3_NO_REVEAL_ON_FOCUS) == 0;
+    }
+
+    /**
+     * Populates <code>outRect</code> with the hotspot bounds. By default,
+     * the hotspot bounds are identical to the screen bounds.
+     *
+     * @param outRect rect to populate with hotspot bounds
+     * @hide Only for internal use by views and widgets.
+     */
+    public void getHotspotBounds(Rect outRect) {
+        final Drawable background = getBackground();
+        if (background != null) {
+            background.getHotspotBounds(outRect);
+        } else {
+            getBoundsOnScreen(outRect);
+        }
+    }
+
+    /**
+     * Request that a rectangle of this view be visible on the screen,
+     * scrolling if necessary just enough.
+     *
+     * <p>A View should call this if it maintains some notion of which part
+     * of its content is interesting.  For example, a text editing view
+     * should call this when its cursor moves.
+     * <p>The Rectangle passed into this method should be in the View's content coordinate space.
+     * It should not be affected by which part of the View is currently visible or its scroll
+     * position.
+     *
+     * @param rectangle The rectangle in the View's content coordinate space
+     * @return Whether any parent scrolled.
+     */
+    public boolean requestRectangleOnScreen(Rect rectangle) {
+        return requestRectangleOnScreen(rectangle, false);
+    }
+
+    /**
+     * Request that a rectangle of this view be visible on the screen,
+     * scrolling if necessary just enough.
+     *
+     * <p>A View should call this if it maintains some notion of which part
+     * of its content is interesting.  For example, a text editing view
+     * should call this when its cursor moves.
+     * <p>The Rectangle passed into this method should be in the View's content coordinate space.
+     * It should not be affected by which part of the View is currently visible or its scroll
+     * position.
+     * <p>When <code>immediate</code> is set to true, scrolling will not be
+     * animated.
+     *
+     * @param rectangle The rectangle in the View's content coordinate space
+     * @param immediate True to forbid animated scrolling, false otherwise
+     * @return Whether any parent scrolled.
+     */
+    public boolean requestRectangleOnScreen(Rect rectangle, boolean immediate) {
+        if (mParent == null) {
+            return false;
+        }
+
+        View child = this;
+
+        RectF position = (mAttachInfo != null) ? mAttachInfo.mTmpTransformRect : new RectF();
+        position.set(rectangle);
+
+        ViewParent parent = mParent;
+        boolean scrolled = false;
+        while (parent != null) {
+            rectangle.set((int) position.left, (int) position.top,
+                    (int) position.right, (int) position.bottom);
+
+            scrolled |= parent.requestChildRectangleOnScreen(child, rectangle, immediate);
+
+            if (!(parent instanceof View)) {
+                break;
+            }
+
+            // move it from child's content coordinate space to parent's content coordinate space
+            position.offset(child.mLeft - child.getScrollX(), child.mTop -child.getScrollY());
+
+            child = (View) parent;
+            parent = child.getParent();
+        }
+
+        return scrolled;
+    }
+
+    /**
+     * Called when this view wants to give up focus. If focus is cleared
+     * {@link #onFocusChanged(boolean, int, android.graphics.Rect)} is called.
+     * <p>
+     * <strong>Note:</strong> When not in touch-mode, the framework will try to give focus
+     * to the first focusable View from the top after focus is cleared. Hence, if this
+     * View is the first from the top that can take focus, then all callbacks
+     * related to clearing focus will be invoked after which the framework will
+     * give focus to this view.
+     * </p>
+     */
+    public void clearFocus() {
+        if (DBG) {
+            System.out.println(this + " clearFocus()");
+        }
+
+        final boolean refocus = sAlwaysAssignFocus || !isInTouchMode();
+        clearFocusInternal(null, true, refocus);
+    }
+
+    /**
+     * Clears focus from the view, optionally propagating the change up through
+     * the parent hierarchy and requesting that the root view place new focus.
+     *
+     * @param propagate whether to propagate the change up through the parent
+     *            hierarchy
+     * @param refocus when propagate is true, specifies whether to request the
+     *            root view place new focus
+     */
+    void clearFocusInternal(View focused, boolean propagate, boolean refocus) {
+        if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
+            mPrivateFlags &= ~PFLAG_FOCUSED;
+            clearParentsWantFocus();
+
+            if (propagate && mParent != null) {
+                mParent.clearChildFocus(this);
+            }
+
+            onFocusChanged(false, 0, null);
+            refreshDrawableState();
+
+            if (propagate && (!refocus || !rootViewRequestFocus())) {
+                notifyGlobalFocusCleared(this);
+            }
+        }
+    }
+
+    void notifyGlobalFocusCleared(View oldFocus) {
+        if (oldFocus != null && mAttachInfo != null) {
+            mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, null);
+        }
+    }
+
+    boolean rootViewRequestFocus() {
+        final View root = getRootView();
+        return root != null && root.requestFocus();
+    }
+
+    /**
+     * Called internally by the view system when a new view is getting focus.
+     * This is what clears the old focus.
+     * <p>
+     * <b>NOTE:</b> The parent view's focused child must be updated manually
+     * after calling this method. Otherwise, the view hierarchy may be left in
+     * an inconstent state.
+     */
+    void unFocus(View focused) {
+        if (DBG) {
+            System.out.println(this + " unFocus()");
+        }
+
+        clearFocusInternal(focused, false, false);
+    }
+
+    /**
+     * Returns true if this view has focus itself, or is the ancestor of the
+     * view that has focus.
+     *
+     * @return True if this view has or contains focus, false otherwise.
+     */
+    @ViewDebug.ExportedProperty(category = "focus")
+    public boolean hasFocus() {
+        return (mPrivateFlags & PFLAG_FOCUSED) != 0;
+    }
+
+    /**
+     * Returns true if this view is focusable or if it contains a reachable View
+     * for which {@link #hasFocusable()} returns {@code true}. A "reachable hasFocusable()"
+     * is a view whose parents do not block descendants focus.
+     * Only {@link #VISIBLE} views are considered focusable.
+     *
+     * <p>As of {@link Build.VERSION_CODES#O} views that are determined to be focusable
+     * through {@link #FOCUSABLE_AUTO} will also cause this method to return {@code true}.
+     * Apps that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion} of
+     * earlier than {@link Build.VERSION_CODES#O} will continue to see this method return
+     * {@code false} for views not explicitly marked as focusable.
+     * Use {@link #hasExplicitFocusable()} if you require the pre-{@link Build.VERSION_CODES#O}
+     * behavior.</p>
+     *
+     * @return {@code true} if the view is focusable or if the view contains a focusable
+     *         view, {@code false} otherwise
+     *
+     * @see ViewGroup#FOCUS_BLOCK_DESCENDANTS
+     * @see ViewGroup#getTouchscreenBlocksFocus()
+     * @see #hasExplicitFocusable()
+     */
+    public boolean hasFocusable() {
+        return hasFocusable(!sHasFocusableExcludeAutoFocusable, false);
+    }
+
+    /**
+     * Returns true if this view is focusable or if it contains a reachable View
+     * for which {@link #hasExplicitFocusable()} returns {@code true}.
+     * A "reachable hasExplicitFocusable()" is a view whose parents do not block descendants focus.
+     * Only {@link #VISIBLE} views for which {@link #getFocusable()} would return
+     * {@link #FOCUSABLE} are considered focusable.
+     *
+     * <p>This method preserves the pre-{@link Build.VERSION_CODES#O} behavior of
+     * {@link #hasFocusable()} in that only views explicitly set focusable will cause
+     * this method to return true. A view set to {@link #FOCUSABLE_AUTO} that resolves
+     * to focusable will not.</p>
+     *
+     * @return {@code true} if the view is focusable or if the view contains a focusable
+     *         view, {@code false} otherwise
+     *
+     * @see #hasFocusable()
+     */
+    public boolean hasExplicitFocusable() {
+        return hasFocusable(false, true);
+    }
+
+    boolean hasFocusable(boolean allowAutoFocus, boolean dispatchExplicit) {
+        if (!isFocusableInTouchMode()) {
+            for (ViewParent p = mParent; p instanceof ViewGroup; p = p.getParent()) {
+                final ViewGroup g = (ViewGroup) p;
+                if (g.shouldBlockFocusForTouchscreen()) {
+                    return false;
+                }
+            }
+        }
+
+        // Invisible, gone, or disabled views are never focusable.
+        if ((mViewFlags & VISIBILITY_MASK) != VISIBLE
+                || (mViewFlags & ENABLED_MASK) != ENABLED) {
+            return false;
+        }
+
+        // Only use effective focusable value when allowed.
+        if ((allowAutoFocus || getFocusable() != FOCUSABLE_AUTO) && isFocusable()) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Called by the view system when the focus state of this view changes.
+     * When the focus change event is caused by directional navigation, direction
+     * and previouslyFocusedRect provide insight into where the focus is coming from.
+     * When overriding, be sure to call up through to the super class so that
+     * the standard focus handling will occur.
+     *
+     * @param gainFocus True if the View has focus; false otherwise.
+     * @param direction The direction focus has moved when requestFocus()
+     *                  is called to give this view focus. Values are
+     *                  {@link #FOCUS_UP}, {@link #FOCUS_DOWN}, {@link #FOCUS_LEFT},
+     *                  {@link #FOCUS_RIGHT}, {@link #FOCUS_FORWARD}, or {@link #FOCUS_BACKWARD}.
+     *                  It may not always apply, in which case use the default.
+     * @param previouslyFocusedRect The rectangle, in this view's coordinate
+     *        system, of the previously focused view.  If applicable, this will be
+     *        passed in as finer grained information about where the focus is coming
+     *        from (in addition to direction).  Will be <code>null</code> otherwise.
+     */
+    @CallSuper
+    protected void onFocusChanged(boolean gainFocus, @FocusDirection int direction,
+            @Nullable Rect previouslyFocusedRect) {
+        if (gainFocus) {
+            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+        } else {
+            notifyViewAccessibilityStateChangedIfNeeded(
+                    AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+        }
+
+        // Here we check whether we still need the default focus highlight, and switch it on/off.
+        switchDefaultFocusHighlight();
+
+        if (!gainFocus) {
+            if (isPressed()) {
+                setPressed(false);
+            }
+            if (hasWindowFocus()) {
+                notifyFocusChangeToImeFocusController(false /* hasFocus */);
+            }
+            onFocusLost();
+        } else if (hasWindowFocus()) {
+            notifyFocusChangeToImeFocusController(true /* hasFocus */);
+        }
+
+        invalidate(true);
+        ListenerInfo li = mListenerInfo;
+        if (li != null && li.mOnFocusChangeListener != null) {
+            li.mOnFocusChangeListener.onFocusChange(this, gainFocus);
+        }
+
+        if (mAttachInfo != null) {
+            mAttachInfo.mKeyDispatchState.reset(this);
+        }
+
+        if (mParent != null) {
+            mParent.onDescendantUnbufferedRequested();
+        }
+
+        notifyEnterOrExitForAutoFillIfNeeded(gainFocus);
+    }
+
+    /**
+     * Notify {@link ImeFocusController} about the focus change of the {@link View}.
+     *
+     * @param hasFocus {@code true} when the {@link View} is being focused.
+     */
+    private void notifyFocusChangeToImeFocusController(boolean hasFocus) {
+        if (mAttachInfo == null) {
+            return;
+        }
+        mAttachInfo.mViewRootImpl.getImeFocusController().onViewFocusChanged(this, hasFocus);
+    }
+
+    /** @hide */
+    public void notifyEnterOrExitForAutoFillIfNeeded(boolean enter) {
+        if (canNotifyAutofillEnterExitEvent()) {
+            AutofillManager afm = getAutofillManager();
+            if (afm != null) {
+                if (enter && isFocused()) {
+                    // We have not been laid out yet, hence cannot evaluate
+                    // whether this view is visible to the user, we will do
+                    // the evaluation once layout is complete.
+                    if (!isLaidOut()) {
+                        mPrivateFlags3 |= PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
+                    } else if (isVisibleToUser()) {
+                        // TODO This is a potential problem that View gets focus before it's visible
+                        // to User. Ideally View should handle the event when isVisibleToUser()
+                        // becomes true where it should issue notifyViewEntered().
+                        afm.notifyViewEntered(this);
+                    }
+                } else if (!enter && !isFocused()) {
+                    afm.notifyViewExited(this);
+                }
+            }
+        }
+    }
+
+    /**
+     * Visually distinct portion of a window with window-like semantics are considered panes for
+     * accessibility purposes. One example is the content view of a fragment that is replaced.
+     * In order for accessibility services to understand a pane's window-like behavior, panes
+     * should have descriptive titles. Views with pane titles produce {@link AccessibilityEvent}s
+     * when they appear, disappear, or change title.
+     *
+     * @param accessibilityPaneTitle The pane's title. Setting to {@code null} indicates that this
+     *                               View is not a pane.
+     *
+     * {@see AccessibilityNodeInfo#setPaneTitle(CharSequence)}
+     *
+     * @attr ref android.R.styleable#View_accessibilityPaneTitle
+     */
+    public void setAccessibilityPaneTitle(@Nullable CharSequence accessibilityPaneTitle) {
+        if (!TextUtils.equals(accessibilityPaneTitle, mAccessibilityPaneTitle)) {
+            mAccessibilityPaneTitle = accessibilityPaneTitle;
+            notifyViewAccessibilityStateChangedIfNeeded(
+                    AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_TITLE);
+        }
+    }
+
+    /**
+     * Get the title of the pane for purposes of accessibility.
+     *
+     * @return The current pane title.
+     *
+     * {@see #setAccessibilityPaneTitle}.
+     *
+     * @attr ref android.R.styleable#View_accessibilityPaneTitle
+     */
+    @InspectableProperty
+    @Nullable
+    public CharSequence getAccessibilityPaneTitle() {
+        return mAccessibilityPaneTitle;
+    }
+
+    private boolean isAccessibilityPane() {
+        return mAccessibilityPaneTitle != null;
+    }
+
+    /**
+     * Sends an accessibility event of the given type. If accessibility is
+     * not enabled this method has no effect. The default implementation calls
+     * {@link #onInitializeAccessibilityEvent(AccessibilityEvent)} first
+     * to populate information about the event source (this View), then calls
+     * {@link #dispatchPopulateAccessibilityEvent(AccessibilityEvent)} to
+     * populate the text content of the event source including its descendants,
+     * and last calls
+     * {@link ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)}
+     * on its parent to request sending of the event to interested parties.
+     * <p>
+     * If an {@link AccessibilityDelegate} has been specified via calling
+     * {@link #setAccessibilityDelegate(AccessibilityDelegate)} its
+     * {@link AccessibilityDelegate#sendAccessibilityEvent(View, int)} is
+     * responsible for handling this call.
+     * </p>
+     *
+     * @param eventType The type of the event to send, as defined by several types from
+     * {@link android.view.accessibility.AccessibilityEvent}, such as
+     * {@link android.view.accessibility.AccessibilityEvent#TYPE_VIEW_CLICKED} or
+     * {@link android.view.accessibility.AccessibilityEvent#TYPE_VIEW_HOVER_ENTER}.
+     *
+     * @see #onInitializeAccessibilityEvent(AccessibilityEvent)
+     * @see #dispatchPopulateAccessibilityEvent(AccessibilityEvent)
+     * @see ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)
+     * @see AccessibilityDelegate
+     */
+    public void sendAccessibilityEvent(int eventType) {
+        if (mAccessibilityDelegate != null) {
+            mAccessibilityDelegate.sendAccessibilityEvent(this, eventType);
+        } else {
+            sendAccessibilityEventInternal(eventType);
+        }
+    }
+
+    /**
+     * Convenience method for sending a {@link AccessibilityEvent#TYPE_ANNOUNCEMENT}
+     * {@link AccessibilityEvent} to suggest that an accessibility service announce the
+     * specified text to its users.
+     * <p>
+     * Note: The event generated with this API carries no semantic meaning, and is appropriate only
+     * in exceptional situations. Apps can generally achieve correct behavior for accessibility by
+     * accurately supplying the semantics of their UI.
+     * They should not need to specify what exactly is announced to users.
+     *
+     * @param text The announcement text.
+     */
+    public void announceForAccessibility(CharSequence text) {
+        if (AccessibilityManager.getInstance(mContext).isEnabled() && mParent != null) {
+            AccessibilityEvent event = AccessibilityEvent.obtain(
+                    AccessibilityEvent.TYPE_ANNOUNCEMENT);
+            onInitializeAccessibilityEvent(event);
+            event.getText().add(text);
+            event.setContentDescription(null);
+            mParent.requestSendAccessibilityEvent(this, event);
+        }
+    }
+
+    /**
+     * @see #sendAccessibilityEvent(int)
+     *
+     * Note: Called from the default {@link AccessibilityDelegate}.
+     *
+     * @hide
+     */
+    public void sendAccessibilityEventInternal(int eventType) {
+        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+            sendAccessibilityEventUnchecked(AccessibilityEvent.obtain(eventType));
+        }
+    }
+
+    /**
+     * This method behaves exactly as {@link #sendAccessibilityEvent(int)} but
+     * takes as an argument an empty {@link AccessibilityEvent} and does not
+     * perform a check whether accessibility is enabled.
+     * <p>
+     * If an {@link AccessibilityDelegate} has been specified via calling
+     * {@link #setAccessibilityDelegate(AccessibilityDelegate)} its
+     * {@link AccessibilityDelegate#sendAccessibilityEventUnchecked(View, AccessibilityEvent)}
+     * is responsible for handling this call.
+     * </p>
+     *
+     * @param event The event to send.
+     *
+     * @see #sendAccessibilityEvent(int)
+     */
+    public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
+        if (mAccessibilityDelegate != null) {
+            mAccessibilityDelegate.sendAccessibilityEventUnchecked(this, event);
+        } else {
+            sendAccessibilityEventUncheckedInternal(event);
+        }
+    }
+
+    /**
+     * @see #sendAccessibilityEventUnchecked(AccessibilityEvent)
+     *
+     * Note: Called from the default {@link AccessibilityDelegate}.
+     *
+     * @hide
+     */
+    public void sendAccessibilityEventUncheckedInternal(AccessibilityEvent event) {
+        // Panes disappearing are relevant even if though the view is no longer visible.
+        boolean isWindowStateChanged =
+                (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+        boolean isWindowDisappearedEvent = isWindowStateChanged && ((event.getContentChangeTypes()
+                & AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED) != 0);
+        boolean detached = detached();
+        if (!isShown() && !isWindowDisappearedEvent && !detached) {
+            return;
+        }
+        onInitializeAccessibilityEvent(event);
+        // Only a subset of accessibility events populates text content.
+        if ((event.getEventType() & POPULATING_ACCESSIBILITY_EVENT_TYPES) != 0) {
+            dispatchPopulateAccessibilityEvent(event);
+        }
+        SendAccessibilityEventThrottle throttle = getThrottleForAccessibilityEvent(event);
+        if (throttle != null) {
+            throttle.post(event);
+        } else if (!isWindowDisappearedEvent && detached) {
+            // Views could be attached soon later. Accessibility events during this temporarily
+            // detached period should be sent too.
+            postDelayed(() -> {
+                if (AccessibilityManager.getInstance(mContext).isEnabled() && isShown()) {
+                    requestParentSendAccessibilityEvent(event);
+                }
+            }, ViewConfiguration.getSendRecurringAccessibilityEventsInterval());
+        } else {
+            requestParentSendAccessibilityEvent(event);
+        }
+    }
+
+    private void requestParentSendAccessibilityEvent(AccessibilityEvent event) {
+        ViewParent parent = getParent();
+        if (parent != null) {
+            getParent().requestSendAccessibilityEvent(this, event);
+        }
+    }
+
+    private SendAccessibilityEventThrottle getThrottleForAccessibilityEvent(
+            AccessibilityEvent event) {
+        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
+            if (mSendViewScrolledAccessibilityEvent == null) {
+                mSendViewScrolledAccessibilityEvent = new SendViewScrolledAccessibilityEvent();
+            }
+            return mSendViewScrolledAccessibilityEvent;
+        }
+        boolean isStateContentChanged = (event.getContentChangeTypes()
+                & AccessibilityEvent.CONTENT_CHANGE_TYPE_STATE_DESCRIPTION) != 0;
+        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
+                && isStateContentChanged) {
+            if (mSendStateChangedAccessibilityEvent == null) {
+                mSendStateChangedAccessibilityEvent = new SendAccessibilityEventThrottle();
+            }
+            return mSendStateChangedAccessibilityEvent;
+        }
+        return null;
+    }
+
+    private void clearAccessibilityThrottles() {
+        cancel(mSendViewScrolledAccessibilityEvent);
+        cancel(mSendStateChangedAccessibilityEvent);
+    }
+
+    /**
+     * Dispatches an {@link AccessibilityEvent} to the {@link View} first and then
+     * to its children for adding their text content to the event. Note that the
+     * event text is populated in a separate dispatch path since we add to the
+     * event not only the text of the source but also the text of all its descendants.
+     * A typical implementation will call
+     * {@link #onPopulateAccessibilityEvent(AccessibilityEvent)} on the this view
+     * and then call the {@link #dispatchPopulateAccessibilityEvent(AccessibilityEvent)}
+     * on each child. Override this method if custom population of the event text
+     * content is required.
+     * <p>
+     * If an {@link AccessibilityDelegate} has been specified via calling
+     * {@link #setAccessibilityDelegate(AccessibilityDelegate)} its
+     * {@link AccessibilityDelegate#dispatchPopulateAccessibilityEvent(View, AccessibilityEvent)}
+     * is responsible for handling this call.
+     * </p>
+     * <p>
+     * <em>Note:</em> Accessibility events of certain types are not dispatched for
+     * populating the event text via this method. For details refer to {@link AccessibilityEvent}.
+     * </p>
+     *
+     * @param event The event.
+     *
+     * @return True if the event population was completed.
+     */
+    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+        if (mAccessibilityDelegate != null) {
+            return mAccessibilityDelegate.dispatchPopulateAccessibilityEvent(this, event);
+        } else {
+            return dispatchPopulateAccessibilityEventInternal(event);
+        }
+    }
+
+    /**
+     * @see #dispatchPopulateAccessibilityEvent(AccessibilityEvent)
+     *
+     * Note: Called from the default {@link AccessibilityDelegate}.
+     *
+     * @hide
+     */
+    public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+        onPopulateAccessibilityEvent(event);
+        return false;
+    }
+
+    /**
+     * Called from {@link #dispatchPopulateAccessibilityEvent(AccessibilityEvent)}
+     * giving a chance to this View to populate the accessibility event with its
+     * text content. While this method is free to modify event
+     * attributes other than text content, doing so should normally be performed in
+     * {@link #onInitializeAccessibilityEvent(AccessibilityEvent)}.
+     * <p>
+     * Example: Adding formatted date string to an accessibility event in addition
+     *          to the text added by the super implementation:
+     * <pre> public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+     *     super.onPopulateAccessibilityEvent(event);
+     *     final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY;
+     *     String selectedDateUtterance = DateUtils.formatDateTime(mContext,
+     *         mCurrentDate.getTimeInMillis(), flags);
+     *     event.getText().add(selectedDateUtterance);
+     * }</pre>
+     * <p>
+     * If an {@link AccessibilityDelegate} has been specified via calling
+     * {@link #setAccessibilityDelegate(AccessibilityDelegate)} its
+     * {@link AccessibilityDelegate#onPopulateAccessibilityEvent(View, AccessibilityEvent)}
+     * is responsible for handling this call.
+     * </p>
+     * <p class="note"><strong>Note:</strong> Always call the super implementation before adding
+     * information to the event, in case the default implementation has basic information to add.
+     * </p>
+     *
+     * @param event The accessibility event which to populate.
+     *
+     * @see #sendAccessibilityEvent(int)
+     * @see #dispatchPopulateAccessibilityEvent(AccessibilityEvent)
+     */
+    @CallSuper
+    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+        if (mAccessibilityDelegate != null) {
+            mAccessibilityDelegate.onPopulateAccessibilityEvent(this, event);
+        } else {
+            onPopulateAccessibilityEventInternal(event);
+        }
+    }
+
+    /**
+     * @see #onPopulateAccessibilityEvent(AccessibilityEvent)
+     *
+     * Note: Called from the default {@link AccessibilityDelegate}.
+     *
+     * @hide
+     */
+    public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+        if ((event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED)
+                && isAccessibilityPane()) {
+            event.getText().add(getAccessibilityPaneTitle());
+        }
+    }
+
+    /**
+     * Initializes an {@link AccessibilityEvent} with information about
+     * this View which is the event source. In other words, the source of
+     * an accessibility event is the view whose state change triggered firing
+     * the event.
+     * <p>
+     * Example: Setting the password property of an event in addition
+     *          to properties set by the super implementation:
+     * <pre> public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+     *     super.onInitializeAccessibilityEvent(event);
+     *     event.setPassword(true);
+     * }</pre>
+     * <p>
+     * If an {@link AccessibilityDelegate} has been specified via calling
+     * {@link #setAccessibilityDelegate(AccessibilityDelegate)} its
+     * {@link AccessibilityDelegate#onInitializeAccessibilityEvent(View, AccessibilityEvent)}
+     * is responsible for handling this call.
+     * </p>
+     * <p class="note"><strong>Note:</strong> Always call the super implementation before adding
+     * information to the event, in case the default implementation has basic information to add.
+     * </p>
+     * @param event The event to initialize.
+     *
+     * @see #sendAccessibilityEvent(int)
+     * @see #dispatchPopulateAccessibilityEvent(AccessibilityEvent)
+     */
+    @CallSuper
+    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+        if (mAccessibilityDelegate != null) {
+            mAccessibilityDelegate.onInitializeAccessibilityEvent(this, event);
+        } else {
+            onInitializeAccessibilityEventInternal(event);
+        }
+    }
+
+    /**
+     * @see #onInitializeAccessibilityEvent(AccessibilityEvent)
+     *
+     * Note: Called from the default {@link AccessibilityDelegate}.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+        event.setSource(this);
+        event.setClassName(getAccessibilityClassName());
+        event.setPackageName(getContext().getPackageName());
+        event.setEnabled(isEnabled());
+        event.setContentDescription(mContentDescription);
+        event.setScrollX(getScrollX());
+        event.setScrollY(getScrollY());
+
+        switch (event.getEventType()) {
+            case AccessibilityEvent.TYPE_VIEW_FOCUSED: {
+                ArrayList<View> focusablesTempList = (mAttachInfo != null)
+                        ? mAttachInfo.mTempArrayList : new ArrayList<View>();
+                getRootView().addFocusables(focusablesTempList, View.FOCUS_FORWARD, FOCUSABLES_ALL);
+                event.setItemCount(focusablesTempList.size());
+                event.setCurrentItemIndex(focusablesTempList.indexOf(this));
+                if (mAttachInfo != null) {
+                    focusablesTempList.clear();
+                }
+            } break;
+            case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: {
+                CharSequence text = getIterableTextForAccessibility();
+                if (text != null && text.length() > 0) {
+                    event.setFromIndex(getAccessibilitySelectionStart());
+                    event.setToIndex(getAccessibilitySelectionEnd());
+                    event.setItemCount(text.length());
+                }
+            } break;
+        }
+    }
+
+    /**
+     * Returns an {@link AccessibilityNodeInfo} representing this view from the
+     * point of view of an {@link android.accessibilityservice.AccessibilityService}.
+     * This method is responsible for obtaining an accessibility node info from a
+     * pool of reusable instances and calling
+     * {@link #onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)} on this view to
+     * initialize the former.
+     * <p>
+     * Note: The client is responsible for recycling the obtained instance by calling
+     *       {@link AccessibilityNodeInfo#recycle()} to minimize object creation.
+     * </p>
+     *
+     * @return A populated {@link AccessibilityNodeInfo}.
+     *
+     * @see AccessibilityNodeInfo
+     */
+    public AccessibilityNodeInfo createAccessibilityNodeInfo() {
+        if (mAccessibilityDelegate != null) {
+            return mAccessibilityDelegate.createAccessibilityNodeInfo(this);
+        } else {
+            return createAccessibilityNodeInfoInternal();
+        }
+    }
+
+    /**
+     * @see #createAccessibilityNodeInfo()
+     *
+     * @hide
+     */
+    public AccessibilityNodeInfo createAccessibilityNodeInfoInternal() {
+        AccessibilityNodeProvider provider = getAccessibilityNodeProvider();
+        if (provider != null) {
+            return provider.createAccessibilityNodeInfo(AccessibilityNodeProvider.HOST_VIEW_ID);
+        } else {
+            AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(this);
+            onInitializeAccessibilityNodeInfo(info);
+            return info;
+        }
+    }
+
+    /**
+     * Initializes an {@link AccessibilityNodeInfo} with information about this view.
+     * The base implementation sets:
+     * <ul>
+     *   <li>{@link AccessibilityNodeInfo#setParent(View)},</li>
+     *   <li>{@link AccessibilityNodeInfo#setBoundsInParent(Rect)},</li>
+     *   <li>{@link AccessibilityNodeInfo#setBoundsInScreen(Rect)},</li>
+     *   <li>{@link AccessibilityNodeInfo#setPackageName(CharSequence)},</li>
+     *   <li>{@link AccessibilityNodeInfo#setClassName(CharSequence)},</li>
+     *   <li>{@link AccessibilityNodeInfo#setContentDescription(CharSequence)},</li>
+     *   <li>{@link AccessibilityNodeInfo#setEnabled(boolean)},</li>
+     *   <li>{@link AccessibilityNodeInfo#setClickable(boolean)},</li>
+     *   <li>{@link AccessibilityNodeInfo#setFocusable(boolean)},</li>
+     *   <li>{@link AccessibilityNodeInfo#setFocused(boolean)},</li>
+     *   <li>{@link AccessibilityNodeInfo#setLongClickable(boolean)},</li>
+     *   <li>{@link AccessibilityNodeInfo#setSelected(boolean)},</li>
+     *   <li>{@link AccessibilityNodeInfo#setContextClickable(boolean)}</li>
+     * </ul>
+     * <p>
+     * Subclasses should override this method, call the super implementation,
+     * and set additional attributes.
+     * </p>
+     * <p>
+     * If an {@link AccessibilityDelegate} has been specified via calling
+     * {@link #setAccessibilityDelegate(AccessibilityDelegate)} its
+     * {@link AccessibilityDelegate#onInitializeAccessibilityNodeInfo(View, AccessibilityNodeInfo)}
+     * is responsible for handling this call.
+     * </p>
+     *
+     * @param info The instance to initialize.
+     */
+    @CallSuper
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        if (mAccessibilityDelegate != null) {
+            mAccessibilityDelegate.onInitializeAccessibilityNodeInfo(this, info);
+        } else {
+            onInitializeAccessibilityNodeInfoInternal(info);
+        }
+    }
+
+    /**
+     * Gets the location of this view in screen coordinates.
+     *
+     * @param outRect The output location
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void getBoundsOnScreen(Rect outRect) {
+        getBoundsOnScreen(outRect, false);
+    }
+
+    /**
+     * Gets the location of this view in screen coordinates.
+     *
+     * @param outRect The output location
+     * @param clipToParent Whether to clip child bounds to the parent ones.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void getBoundsOnScreen(Rect outRect, boolean clipToParent) {
+        if (mAttachInfo == null) {
+            return;
+        }
+
+        RectF position = mAttachInfo.mTmpTransformRect;
+        position.set(0, 0, mRight - mLeft, mBottom - mTop);
+        mapRectFromViewToScreenCoords(position, clipToParent);
+        outRect.set(Math.round(position.left), Math.round(position.top),
+                Math.round(position.right), Math.round(position.bottom));
+    }
+
+    /**
+     * Map a rectangle from view-relative coordinates to screen-relative coordinates
+     *
+     * @param rect The rectangle to be mapped
+     * @param clipToParent Whether to clip child bounds to the parent ones.
+     * @hide
+     */
+    public void mapRectFromViewToScreenCoords(RectF rect, boolean clipToParent) {
+        if (!hasIdentityMatrix()) {
+            getMatrix().mapRect(rect);
+        }
+
+        rect.offset(mLeft, mTop);
+
+        ViewParent parent = mParent;
+        while (parent instanceof View) {
+            View parentView = (View) parent;
+
+            rect.offset(-parentView.mScrollX, -parentView.mScrollY);
+
+            if (clipToParent) {
+                rect.left = Math.max(rect.left, 0);
+                rect.top = Math.max(rect.top, 0);
+                rect.right = Math.min(rect.right, parentView.getWidth());
+                rect.bottom = Math.min(rect.bottom, parentView.getHeight());
+            }
+
+            if (!parentView.hasIdentityMatrix()) {
+                parentView.getMatrix().mapRect(rect);
+            }
+
+            rect.offset(parentView.mLeft, parentView.mTop);
+
+            parent = parentView.mParent;
+        }
+
+        if (parent instanceof ViewRootImpl) {
+            ViewRootImpl viewRootImpl = (ViewRootImpl) parent;
+            rect.offset(0, -viewRootImpl.mCurScrollY);
+        }
+
+        rect.offset(mAttachInfo.mWindowLeft, mAttachInfo.mWindowTop);
+    }
+
+    /**
+     * Return the class name of this object to be used for accessibility purposes.
+     * Subclasses should only override this if they are implementing something that
+     * should be seen as a completely new class of view when used by accessibility,
+     * unrelated to the class it is deriving from.  This is used to fill in
+     * {@link AccessibilityNodeInfo#setClassName AccessibilityNodeInfo.setClassName}.
+     */
+    public CharSequence getAccessibilityClassName() {
+        return View.class.getName();
+    }
+
+    /**
+     * Called when assist structure is being retrieved from a view as part of
+     * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData}.
+     * @param structure Fill in with structured view data.  The default implementation
+     * fills in all data that can be inferred from the view itself.
+     */
+    public void onProvideStructure(ViewStructure structure) {
+        onProvideStructure(structure, VIEW_STRUCTURE_FOR_ASSIST, /* flags= */ 0);
+    }
+
+    /**
+     * Populates a {@link ViewStructure} to fullfil an autofill request.
+     *
+     * <p>The structure should contain at least the following properties:
+     * <ul>
+     *   <li>Autofill id ({@link ViewStructure#setAutofillId(AutofillId, int)}).
+     *   <li>Autofill type ({@link ViewStructure#setAutofillType(int)}).
+     *   <li>Autofill value ({@link ViewStructure#setAutofillValue(AutofillValue)}).
+     *   <li>Whether the data is sensitive ({@link ViewStructure#setDataIsSensitive(boolean)}).
+     * </ul>
+     *
+     * <p>It's also recommended to set the following properties - the more properties the structure
+     * has, the higher the chances of an {@link android.service.autofill.AutofillService} properly
+     * using the structure:
+     *
+     * <ul>
+     *   <li>Autofill hints ({@link ViewStructure#setAutofillHints(String[])}).
+     *   <li>Autofill options ({@link ViewStructure#setAutofillOptions(CharSequence[])}) when the
+     *       view can only be filled with predefined values (typically used when the autofill type
+     *       is {@link #AUTOFILL_TYPE_LIST}).
+     *   <li>Resource id ({@link ViewStructure#setId(int, String, String, String)}).
+     *   <li>Class name ({@link ViewStructure#setClassName(String)}).
+     *   <li>Content description ({@link ViewStructure#setContentDescription(CharSequence)}).
+     *   <li>Visual properties such as visibility ({@link ViewStructure#setVisibility(int)}),
+     *       dimensions ({@link ViewStructure#setDimens(int, int, int, int, int, int)}), and
+     *       opacity ({@link ViewStructure#setOpaque(boolean)}).
+     *   <li>For views representing text fields, text properties such as the text itself
+     *       ({@link ViewStructure#setText(CharSequence)}), text hints
+     *       ({@link ViewStructure#setHint(CharSequence)}, input type
+     *       ({@link ViewStructure#setInputType(int)}),
+     *   <li>For views representing HTML nodes, its web domain
+     *       ({@link ViewStructure#setWebDomain(String)}) and HTML properties
+     *       (({@link ViewStructure#setHtmlInfo(android.view.ViewStructure.HtmlInfo)}).
+     * </ul>
+     *
+     * <p>The default implementation of this method already sets most of these properties based on
+     * related {@link View} methods (for example, the autofill id is set using
+     * {@link #getAutofillId()}, the autofill type set using {@link #getAutofillType()}, etc.),
+     * and views in the standard Android widgets library also override it to set their
+     * relevant properties (for example, {@link android.widget.TextView} already sets the text
+     * properties), so it's recommended to only override this method
+     * (and call {@code super.onProvideAutofillStructure()}) when:
+     *
+     * <ul>
+     *   <li>The view contents does not include PII (Personally Identifiable Information), so it
+     *       can call {@link ViewStructure#setDataIsSensitive(boolean)} passing {@code false}.
+     *   <li>The view can only be autofilled with predefined options, so it can call
+     *       {@link ViewStructure#setAutofillOptions(CharSequence[])}.
+     * </ul>
+     *
+     * <p><b>Note:</b> The {@code left} and {@code top} values set in
+     * {@link ViewStructure#setDimens(int, int, int, int, int, int)} must be relative to the next
+     * {@link ViewGroup#isImportantForAutofill()} predecessor view included in the structure.
+     *
+     * <p>Views support the Autofill Framework mainly by:
+     * <ul>
+     *   <li>Providing the metadata defining what the view means and how it can be autofilled.
+     *   <li>Notifying the Android System when the view value changed by calling
+     *       {@link AutofillManager#notifyValueChanged(View)}.
+     *   <li>Implementing the methods that autofill the view.
+     * </ul>
+     * <p>This method is responsible for the former; {@link #autofill(AutofillValue)} is responsible
+     * for the latter.
+     *
+     * @param structure fill in with structured view data for autofill purposes.
+     * @param flags optional flags.
+     *
+     * @see #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+     */
+    public void onProvideAutofillStructure(ViewStructure structure, @AutofillFlags int flags) {
+        onProvideStructure(structure, VIEW_STRUCTURE_FOR_AUTOFILL, flags);
+    }
+
+    /**
+     * Populates a {@link ViewStructure} for content capture.
+     *
+     * <p>This method is called after a view that is eligible for content capture
+     * (for example, if it {@link #isImportantForContentCapture()}, an intelligence service is
+     * enabled for the user, and the activity rendering the view is enabled for content capture)
+     * is laid out and is visible. The populated structure is then passed to the service through
+     * {@link ContentCaptureSession#notifyViewAppeared(ViewStructure)}.
+     *
+     * <p>The default implementation of this method sets the most relevant properties based on
+     * related {@link View} methods, and views in the standard Android widgets library also
+     * override it to set their relevant properties. Therefore, if overriding this method, it
+     * is recommended to call {@code super.onProvideContentCaptureStructure()}.
+     *
+     * <p><b>Note: </b>views that manage a virtual structure under this view must populate just
+     * the node representing this view and return right away, then asynchronously report (not
+     * necessarily in the UI thread) when the children nodes appear, disappear or have their text
+     * changed by calling
+     * {@link ContentCaptureSession#notifyViewAppeared(ViewStructure)},
+     * {@link ContentCaptureSession#notifyViewDisappeared(AutofillId)}, and
+     * {@link ContentCaptureSession#notifyViewTextChanged(AutofillId, CharSequence)}
+     * respectively. The structure for a child must be created using
+     * {@link ContentCaptureSession#newVirtualViewStructure(AutofillId, long)}, and the
+     * {@code autofillId} for a child can be obtained either through
+     * {@code childStructure.getAutofillId()} or
+     * {@link ContentCaptureSession#newAutofillId(AutofillId, long)}.
+     *
+     * <p>When the virtual view hierarchy represents a web page, you should also:
+     *
+     * <ul>
+     *   <li>Call {@link ContentCaptureManager#getContentCaptureConditions()} to infer content
+     *   capture events should be generate for that URL.
+     *   <li>Create a new {@link ContentCaptureSession} child for every HTML element that
+     *   renders a new URL (like an {@code IFRAME}) and use that session to notify events from
+     *   that subtree.
+     * </ul>
+     *
+     * <p><b>Note: </b>the following methods of the {@code structure} will be ignored:
+     * <ul>
+     *   <li>{@link ViewStructure#setChildCount(int)}
+     *   <li>{@link ViewStructure#addChildCount(int)}
+     *   <li>{@link ViewStructure#getChildCount()}
+     *   <li>{@link ViewStructure#newChild(int)}
+     *   <li>{@link ViewStructure#asyncNewChild(int)}
+     *   <li>{@link ViewStructure#asyncCommit()}
+     *   <li>{@link ViewStructure#setWebDomain(String)}
+     *   <li>{@link ViewStructure#newHtmlInfoBuilder(String)}
+     *   <li>{@link ViewStructure#setHtmlInfo(android.view.ViewStructure.HtmlInfo)}
+     *   <li>{@link ViewStructure#setDataIsSensitive(boolean)}
+     *   <li>{@link ViewStructure#setAlpha(float)}
+     *   <li>{@link ViewStructure#setElevation(float)}
+     *   <li>{@link ViewStructure#setTransformation(Matrix)}
+     *
+     * </ul>
+     */
+    public void onProvideContentCaptureStructure(@NonNull ViewStructure structure, int flags) {
+        onProvideStructure(structure, VIEW_STRUCTURE_FOR_CONTENT_CAPTURE, flags);
+    }
+
+    /** @hide */
+    protected void onProvideStructure(@NonNull ViewStructure structure,
+            @ViewStructureType int viewFor, int flags) {
+        final int id = mID;
+        if (id != NO_ID && !isViewIdGenerated(id)) {
+            String pkg, type, entry;
+            try {
+                final Resources res = getResources();
+                entry = res.getResourceEntryName(id);
+                type = res.getResourceTypeName(id);
+                pkg = res.getResourcePackageName(id);
+            } catch (Resources.NotFoundException e) {
+                entry = type = pkg = null;
+            }
+            structure.setId(id, pkg, type, entry);
+        } else {
+            structure.setId(id, null, null, null);
+        }
+
+        if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
+                || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
+            final @AutofillType int autofillType = getAutofillType();
+            // Don't need to fill autofill info if view does not support it.
+            // For example, only TextViews that are editable support autofill
+            if (autofillType != AUTOFILL_TYPE_NONE) {
+                structure.setAutofillType(autofillType);
+                structure.setAutofillHints(getAutofillHints());
+                structure.setAutofillValue(getAutofillValue());
+            }
+            structure.setImportantForAutofill(getImportantForAutofill());
+            structure.setReceiveContentMimeTypes(getReceiveContentMimeTypes());
+        }
+
+        int ignoredParentLeft = 0;
+        int ignoredParentTop = 0;
+        if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
+                && (flags & AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) == 0) {
+            View parentGroup = null;
+
+            ViewParent viewParent = getParent();
+            if (viewParent instanceof View) {
+                parentGroup = (View) viewParent;
+            }
+
+            while (parentGroup != null && !parentGroup.isImportantForAutofill()) {
+                ignoredParentLeft += parentGroup.mLeft;
+                ignoredParentTop += parentGroup.mTop;
+
+                viewParent = parentGroup.getParent();
+                if (viewParent instanceof View) {
+                    parentGroup = (View) viewParent;
+                } else {
+                    break;
+                }
+            }
+        }
+
+        structure.setDimens(ignoredParentLeft + mLeft, ignoredParentTop + mTop, mScrollX, mScrollY,
+                mRight - mLeft, mBottom - mTop);
+        if (viewFor == VIEW_STRUCTURE_FOR_ASSIST) {
+            if (!hasIdentityMatrix()) {
+                structure.setTransformation(getMatrix());
+            }
+            structure.setElevation(getZ());
+        }
+        structure.setVisibility(getVisibility());
+        structure.setEnabled(isEnabled());
+        if (isClickable()) {
+            structure.setClickable(true);
+        }
+        if (isFocusable()) {
+            structure.setFocusable(true);
+        }
+        if (isFocused()) {
+            structure.setFocused(true);
+        }
+        if (isAccessibilityFocused()) {
+            structure.setAccessibilityFocused(true);
+        }
+        if (isSelected()) {
+            structure.setSelected(true);
+        }
+        if (isActivated()) {
+            structure.setActivated(true);
+        }
+        if (isLongClickable()) {
+            structure.setLongClickable(true);
+        }
+        if (this instanceof Checkable) {
+            structure.setCheckable(true);
+            if (((Checkable)this).isChecked()) {
+                structure.setChecked(true);
+            }
+        }
+        if (isOpaque()) {
+            structure.setOpaque(true);
+        }
+        if (isContextClickable()) {
+            structure.setContextClickable(true);
+        }
+        structure.setClassName(getAccessibilityClassName().toString());
+        structure.setContentDescription(getContentDescription());
+    }
+
+    /**
+     * Called when assist structure is being retrieved from a view as part of
+     * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData} to
+     * generate additional virtual structure under this view.  The default implementation
+     * uses {@link #getAccessibilityNodeProvider()} to try to generate this from the
+     * view's virtual accessibility nodes, if any.  You can override this for a more
+     * optimal implementation providing this data.
+     */
+    public void onProvideVirtualStructure(ViewStructure structure) {
+        onProvideVirtualStructureCompat(structure, false);
+    }
+
+    /**
+     * Fallback implementation to populate a ViewStructure from accessibility state.
+     *
+     * @param structure The structure to populate.
+     * @param forAutofill Whether the structure is needed for autofill.
+     */
+    private void onProvideVirtualStructureCompat(ViewStructure structure, boolean forAutofill) {
+        final AccessibilityNodeProvider provider = getAccessibilityNodeProvider();
+        if (provider != null) {
+            if (forAutofill && Log.isLoggable(AUTOFILL_LOG_TAG, Log.VERBOSE)) {
+                Log.v(AUTOFILL_LOG_TAG, "onProvideVirtualStructureCompat() for " + this);
+            }
+            final AccessibilityNodeInfo info = createAccessibilityNodeInfo();
+            structure.setChildCount(1);
+            final ViewStructure root = structure.newChild(0);
+            populateVirtualStructure(root, provider, info, forAutofill);
+            info.recycle();
+        }
+    }
+
+    /**
+     * Populates a {@link ViewStructure} containing virtual children to fullfil an autofill
+     * request.
+     *
+     * <p>This method should be used when the view manages a virtual structure under this view. For
+     * example, a view that draws input fields using {@link #draw(Canvas)}.
+     *
+     * <p>When implementing this method, subclasses must follow the rules below:
+     *
+     * <ul>
+     *   <li>Add virtual children by calling the {@link ViewStructure#newChild(int)} or
+     *       {@link ViewStructure#asyncNewChild(int)} methods, where the {@code id} is an unique id
+     *       identifying the children in the virtual structure.
+     *   <li>The children hierarchy can have multiple levels if necessary, but ideally it should
+     *       exclude intermediate levels that are irrelevant for autofill; that would improve the
+     *       autofill performance.
+     *   <li>Also implement {@link #autofill(SparseArray)} to autofill the virtual
+     *       children.
+     *   <li>Set the autofill properties of the child structure as defined by
+     *       {@link #onProvideAutofillStructure(ViewStructure, int)}, using
+     *       {@link ViewStructure#setAutofillId(AutofillId, int)} to set its autofill id.
+     *   <li>Call {@link android.view.autofill.AutofillManager#notifyViewEntered(View, int, Rect)}
+     *       and/or {@link android.view.autofill.AutofillManager#notifyViewExited(View, int)}
+     *       when the focused virtual child changed.
+     *   <li>Override {@link #isVisibleToUserForAutofill(int)} to allow the platform to query
+     *       whether a given virtual view is visible to the user in order to support triggering
+     *       save when all views of interest go away.
+     *   <li>Call
+     *    {@link android.view.autofill.AutofillManager#notifyValueChanged(View, int, AutofillValue)}
+     *       when the value of a virtual child changed.
+     *   <li>Call {@link
+     *    android.view.autofill.AutofillManager#notifyViewVisibilityChanged(View, int, boolean)}
+     *       when the visibility of a virtual child changed.
+     *   <li>Call
+     *    {@link android.view.autofill.AutofillManager#notifyViewClicked(View, int)} when a virtual
+     *       child is clicked.
+     *   <li>Call {@link AutofillManager#commit()} when the autofill context of the view structure
+     *       changed and the current context should be committed (for example, when the user tapped
+     *       a {@code SUBMIT} button in an HTML page).
+     *   <li>Call {@link AutofillManager#cancel()} when the autofill context of the view structure
+     *       changed and the current context should be canceled (for example, when the user tapped
+     *       a {@code CANCEL} button in an HTML page).
+     *   <li>Provide ways for users to manually request autofill by calling
+     *       {@link AutofillManager#requestAutofill(View, int, Rect)}.
+     *   <li>The {@code left} and {@code top} values set in
+     *       {@link ViewStructure#setDimens(int, int, int, int, int, int)} must be relative to the
+     *       next {@link ViewGroup#isImportantForAutofill()} predecessor view included in the
+     *       structure.
+     * </ul>
+     *
+     * <p>Views with virtual children support the Autofill Framework mainly by:
+     * <ul>
+     *   <li>Providing the metadata defining what the virtual children mean and how they can be
+     *       autofilled.
+     *   <li>Implementing the methods that autofill the virtual children.
+     * </ul>
+     * <p>This method is responsible for the former; {@link #autofill(SparseArray)} is responsible
+     * for the latter.
+     *
+     * @param structure fill in with virtual children data for autofill purposes.
+     * @param flags optional flags.
+     *
+     * @see #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+     */
+    public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) {
+        if (mContext.isAutofillCompatibilityEnabled()) {
+            onProvideVirtualStructureCompat(structure, true);
+        }
+    }
+
+    /**
+     * Sets the listener to be {@link #performReceiveContent used} to handle insertion of
+     * content into this view.
+     *
+     * <p>Depending on the type of view, this listener may be invoked for different scenarios. For
+     * example, for an editable {@link android.widget.TextView}, this listener will be invoked for
+     * the following scenarios:
+     * <ol>
+     *     <li>Paste from the clipboard (e.g. "Paste" or "Paste as plain text" action in the
+     *     insertion/selection menu)
+     *     <li>Content insertion from the keyboard (from {@link InputConnection#commitContent})
+     *     <li>Drag and drop (drop events from {@link #onDragEvent})
+     *     <li>Autofill
+     *     <li>Selection replacement via {@link Intent#ACTION_PROCESS_TEXT}
+     * </ol>
+     *
+     * <p>When setting a listener, clients must also declare the accepted MIME types.
+     * The listener will still be invoked even if the MIME type of the content is not one of the
+     * declared MIME types (e.g. if the user pastes content whose type is not one of the declared
+     * MIME types).
+     * In that case, the listener may reject the content (defer to the default platform behavior)
+     * or execute some other fallback logic (e.g. show an appropriate message to the user).
+     * The declared MIME types serve as a hint to allow different features to optionally alter
+     * their behavior. For example, a soft keyboard may optionally choose to hide its UI for
+     * inserting GIFs for a particular input field if the MIME types set here for that field
+     * don't include "image/gif" or "image/*".
+     *
+     * <p>Note: MIME type matching in the Android framework is case-sensitive, unlike formal RFC
+     * MIME types. As a result, you should always write your MIME types with lowercase letters,
+     * or use {@link android.content.Intent#normalizeMimeType} to ensure that it is converted to
+     * lowercase.
+     *
+     * @param mimeTypes The MIME types accepted by the given listener. These may use patterns
+     *                  such as "image/*", but may not start with a wildcard. This argument must
+     *                  not be null or empty if a non-null listener is passed in.
+     * @param listener The listener to use. This can be null to reset to the default behavior.
+     */
+    public void setOnReceiveContentListener(
+            @SuppressLint("NullableCollection") @Nullable String[] mimeTypes,
+            @Nullable OnReceiveContentListener listener) {
+        if (listener != null) {
+            Preconditions.checkArgument(mimeTypes != null && mimeTypes.length > 0,
+                    "When the listener is set, MIME types must also be set");
+        }
+        if (mimeTypes != null) {
+            Preconditions.checkArgument(Arrays.stream(mimeTypes).noneMatch(t -> t.startsWith("*")),
+                    "A MIME type set here must not start with *: " + Arrays.toString(mimeTypes));
+        }
+        mReceiveContentMimeTypes = ArrayUtils.isEmpty(mimeTypes) ? null : mimeTypes;
+        getListenerInfo().mOnReceiveContentListener = listener;
+    }
+
+    /**
+     * Receives the given content. If no listener is set, invokes {@link #onReceiveContent}. If a
+     * listener is {@link #setOnReceiveContentListener set}, invokes the listener instead; if the
+     * listener returns a non-null result, invokes {@link #onReceiveContent} to handle it.
+     *
+     * @param payload The content to insert and related metadata.
+     *
+     * @return The portion of the passed-in content that was not accepted (may be all, some, or none
+     * of the passed-in content).
+     */
+    @Nullable
+    public ContentInfo performReceiveContent(@NonNull ContentInfo payload) {
+        final OnReceiveContentListener listener = (mListenerInfo == null) ? null
+                : getListenerInfo().mOnReceiveContentListener;
+        if (listener != null) {
+            final ContentInfo remaining = listener.onReceiveContent(this, payload);
+            return (remaining == null) ? null : onReceiveContent(remaining);
+        }
+        return onReceiveContent(payload);
+    }
+
+    /**
+     * Implements the default behavior for receiving content for this type of view. The default
+     * view implementation is a no-op (returns the passed-in content without acting on it).
+     *
+     * <p>Widgets should override this method to define their default behavior for receiving
+     * content. Apps should {@link #setOnReceiveContentListener set a listener} to provide
+     * app-specific handling for receiving content.
+     *
+     * <p>See {@link #setOnReceiveContentListener} and {@link #performReceiveContent} for more info.
+     *
+     * @param payload The content to insert and related metadata.
+     *
+     * @return The portion of the passed-in content that was not handled (may be all, some, or none
+     * of the passed-in content).
+     */
+    @Nullable
+    public ContentInfo onReceiveContent(@NonNull ContentInfo payload) {
+        return payload;
+    }
+
+    /**
+     * Returns the MIME types accepted by {@link #performReceiveContent} for this view, as
+     * configured via {@link #setOnReceiveContentListener}. By default returns null.
+     *
+     * <p>Different features (e.g. pasting from the clipboard, inserting stickers from the soft
+     * keyboard, etc) may optionally use this metadata to conditionally alter their behavior. For
+     * example, a soft keyboard may choose to hide its UI for inserting GIFs for a particular
+     * input field if the MIME types returned here for that field don't include "image/gif" or
+     * "image/*".
+     *
+     * <p>Note: Comparisons of MIME types should be performed using utilities such as
+     * {@link ClipDescription#compareMimeTypes} rather than simple string equality, in order to
+     * correctly handle patterns such as "text/*", "image/*", etc. Note that MIME type matching
+     * in the Android framework is case-sensitive, unlike formal RFC MIME types. As a result,
+     * you should always write your MIME types with lowercase letters, or use
+     * {@link android.content.Intent#normalizeMimeType} to ensure that it is converted to
+     * lowercase.
+     *
+     * @return The MIME types accepted by {@link #performReceiveContent} for this view (may
+     * include patterns such as "image/*").
+     */
+    @SuppressLint("NullableCollection")
+    @Nullable
+    public String[] getReceiveContentMimeTypes() {
+        return mReceiveContentMimeTypes;
+    }
+
+    /**
+     * Automatically fills the content of this view with the {@code value}.
+     *
+     * <p>Views support the Autofill Framework mainly by:
+     * <ul>
+     *   <li>Providing the metadata defining what the view means and how it can be autofilled.
+     *   <li>Implementing the methods that autofill the view.
+     * </ul>
+     * <p>{@link #onProvideAutofillStructure(ViewStructure, int)} is responsible for the former,
+     * this method is responsible for latter.
+     *
+     * <p>This method does nothing by default, but when overridden it typically:
+     * <ol>
+     *   <li>Checks if the provided value matches the expected type (which is defined by
+     *       {@link #getAutofillType()}).
+     *   <li>Checks if the view is editable - if it isn't, it should return right away.
+     *   <li>Call the proper getter method on {@link AutofillValue} to fetch the actual value.
+     *   <li>Pass the actual value to the equivalent setter in the view.
+     * </ol>
+     *
+     * <p>For example, a text-field view could implement the method this way:
+     *
+     * <pre class="prettyprint">
+     * &#64;Override
+     * public void autofill(AutofillValue value) {
+     *   if (!value.isText() || !this.isEditable()) {
+     *      return;
+     *   }
+     *   CharSequence text = value.getTextValue();
+     *   if (text != null) {
+     *     this.setText(text);
+     *   }
+     * }
+     * </pre>
+     *
+     * <p>If the value is updated asynchronously, the next call to
+     * {@link AutofillManager#notifyValueChanged(View)} must happen <b>after</b> the value was
+     * changed to the autofilled value. If not, the view will not be considered autofilled.
+     *
+     * <p><b>Note:</b> After this method is called, the value returned by
+     * {@link #getAutofillValue()} must be equal to the {@code value} passed to it, otherwise the
+     * view will not be highlighted as autofilled.
+     *
+     * @param value value to be autofilled.
+     */
+    public void autofill(@SuppressWarnings("unused") AutofillValue value) {
+    }
+
+    /**
+     * Automatically fills the content of the virtual children within this view.
+     *
+     * <p>Views with virtual children support the Autofill Framework mainly by:
+     * <ul>
+     *   <li>Providing the metadata defining what the virtual children mean and how they can be
+     *       autofilled.
+     *   <li>Implementing the methods that autofill the virtual children.
+     * </ul>
+     * <p>{@link #onProvideAutofillVirtualStructure(ViewStructure, int)} is responsible for the
+     * former, this method is responsible for the latter - see {@link #autofill(AutofillValue)} and
+     * {@link #onProvideAutofillVirtualStructure(ViewStructure, int)} for more info about autofill.
+     *
+     * <p>If a child value is updated asynchronously, the next call to
+     * {@link AutofillManager#notifyValueChanged(View, int, AutofillValue)} must happen
+     * <b>after</b> the value was changed to the autofilled value. If not, the child will not be
+     * considered autofilled.
+     *
+     * <p><b>Note:</b> To indicate that a virtual view was autofilled,
+     * <code>?android:attr/autofilledHighlight</code> should be drawn over it until the data
+     * changes.
+     *
+     * @param values map of values to be autofilled, keyed by virtual child id.
+     *
+     * @attr ref android.R.styleable#Theme_autofilledHighlight
+     */
+    public void autofill(@NonNull @SuppressWarnings("unused") SparseArray<AutofillValue> values) {
+        if (!mContext.isAutofillCompatibilityEnabled()) {
+            return;
+        }
+        final AccessibilityNodeProvider provider = getAccessibilityNodeProvider();
+        if (provider == null) {
+            return;
+        }
+        final int valueCount = values.size();
+        for (int i = 0; i < valueCount; i++) {
+            final AutofillValue value = values.valueAt(i);
+            if (value.isText()) {
+                final int virtualId = values.keyAt(i);
+                final CharSequence text = value.getTextValue();
+                final Bundle arguments = new Bundle();
+                arguments.putCharSequence(
+                        AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text);
+                provider.performAction(virtualId, AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
+            }
+        }
+    }
+
+    /**
+     * Gets the unique, logical identifier of this view in the activity, for autofill purposes.
+     *
+     * <p>The autofill id is created on demand, unless it is explicitly set by
+     * {@link #setAutofillId(AutofillId)}.
+     *
+     * <p>See {@link #setAutofillId(AutofillId)} for more info.
+     *
+     * @return The View's autofill id.
+     */
+    public final AutofillId getAutofillId() {
+        if (mAutofillId == null) {
+            // The autofill id needs to be unique, but its value doesn't matter,
+            // so it's better to reuse the accessibility id to save space.
+            mAutofillId = new AutofillId(getAutofillViewId());
+        }
+        return mAutofillId;
+    }
+
+    /**
+     * Sets the unique, logical identifier of this view in the activity, for autofill purposes.
+     *
+     * <p>The autofill id is created on demand, and this method should only be called when a view is
+     * reused after {@link #dispatchProvideAutofillStructure(ViewStructure, int)} is called, as
+     * that method creates a snapshot of the view that is passed along to the autofill service.
+     *
+     * <p>This method is typically used when view subtrees are recycled to represent different
+     * content* &mdash;in this case, the autofill id can be saved before the view content is swapped
+     * out, and restored later when it's swapped back in. For example:
+     *
+     * <pre>
+     * EditText reusableView = ...;
+     * ViewGroup parentView = ...;
+     * AutofillManager afm = ...;
+     *
+     * // Swap out the view and change its contents
+     * AutofillId oldId = reusableView.getAutofillId();
+     * CharSequence oldText = reusableView.getText();
+     * parentView.removeView(reusableView);
+     * AutofillId newId = afm.getNextAutofillId();
+     * reusableView.setText("New I am");
+     * reusableView.setAutofillId(newId);
+     * parentView.addView(reusableView);
+     *
+     * // Later, swap the old content back in
+     * parentView.removeView(reusableView);
+     * reusableView.setAutofillId(oldId);
+     * reusableView.setText(oldText);
+     * parentView.addView(reusableView);
+     * </pre>
+     *
+     * <p>NOTE: If this view is a descendant of an {@link android.widget.AdapterView}, the system
+     * may reset its autofill id when this view is recycled. If the autofill ids need to be stable,
+     * they should be set again in
+     * {@link android.widget.Adapter#getView(int, android.view.View, android.view.ViewGroup)}.
+     *
+     * @param id an autofill ID that is unique in the {@link android.app.Activity} hosting the view,
+     * or {@code null} to reset it. Usually it's an id previously allocated to another view (and
+     * obtained through {@link #getAutofillId()}), or a new value obtained through
+     * {@link AutofillManager#getNextAutofillId()}.
+     *
+     * @throws IllegalStateException if the view is already {@link #isAttachedToWindow() attached to
+     * a window}.
+     *
+     * @throws IllegalArgumentException if the id is an autofill id associated with a virtual view.
+     */
+    public void setAutofillId(@Nullable AutofillId id) {
+        // TODO(b/37566627): add unit / CTS test for all possible combinations below
+        if (Log.isLoggable(AUTOFILL_LOG_TAG, Log.VERBOSE)) {
+            Log.v(AUTOFILL_LOG_TAG, "setAutofill(): from " + mAutofillId + " to " + id);
+        }
+        if (isAttachedToWindow()) {
+            throw new IllegalStateException("Cannot set autofill id when view is attached");
+        }
+        if (id != null && !id.isNonVirtual()) {
+            throw new IllegalStateException("Cannot set autofill id assigned to virtual views");
+        }
+        if (id == null && (mPrivateFlags3 & PFLAG3_AUTOFILLID_EXPLICITLY_SET) == 0) {
+            // Ignore reset because it was never explicitly set before.
+            return;
+        }
+        mAutofillId = id;
+        if (id != null) {
+            mAutofillViewId = id.getViewId();
+            mPrivateFlags3 |= PFLAG3_AUTOFILLID_EXPLICITLY_SET;
+        } else {
+            mAutofillViewId = NO_ID;
+            mPrivateFlags3 &= ~PFLAG3_AUTOFILLID_EXPLICITLY_SET;
+        }
+    }
+
+    /**
+     * Forces a reset of the autofill ids of the subtree rooted at this view. Like calling
+     * {@link #setAutofillId(AutofillId) setAutofillId(null)} for each view, but works even if the
+     * views are attached to a window.
+     *
+     * <p>This is useful if the views are being recycled, since an autofill id should uniquely
+     * identify a particular piece of content.
+     *
+     * @hide
+     */
+    public void resetSubtreeAutofillIds() {
+        if (mAutofillViewId == NO_ID) {
+            return;
+        }
+        if (Log.isLoggable(CONTENT_CAPTURE_LOG_TAG, Log.VERBOSE)) {
+            Log.v(CONTENT_CAPTURE_LOG_TAG, "resetAutofillId() for " + mAutofillViewId);
+        } else if (Log.isLoggable(AUTOFILL_LOG_TAG, Log.VERBOSE)) {
+            Log.v(AUTOFILL_LOG_TAG, "resetAutofillId() for " + mAutofillViewId);
+        }
+        mAutofillId = null;
+        mAutofillViewId = NO_ID;
+        mPrivateFlags3 &= ~PFLAG3_AUTOFILLID_EXPLICITLY_SET;
+    }
+
+    /**
+     * Describes the autofill type of this view, so an
+     * {@link android.service.autofill.AutofillService} can create the proper {@link AutofillValue}
+     * when autofilling the view.
+     *
+     * <p>By default returns {@link #AUTOFILL_TYPE_NONE}, but views should override it to properly
+     * support the Autofill Framework.
+     *
+     * @return either {@link #AUTOFILL_TYPE_NONE}, {@link #AUTOFILL_TYPE_TEXT},
+     * {@link #AUTOFILL_TYPE_LIST}, {@link #AUTOFILL_TYPE_DATE}, or {@link #AUTOFILL_TYPE_TOGGLE}.
+     *
+     * @see #onProvideAutofillStructure(ViewStructure, int)
+     * @see #autofill(AutofillValue)
+     */
+    public @AutofillType int getAutofillType() {
+        return AUTOFILL_TYPE_NONE;
+    }
+
+    /**
+     * Gets the hints that help an {@link android.service.autofill.AutofillService} determine how
+     * to autofill the view with the user's data.
+     *
+     * <p>See {@link #setAutofillHints(String...)} for more info about these hints.
+     *
+     * @return The hints set via the attribute or {@link #setAutofillHints(String...)}, or
+     * {@code null} if no hints were set.
+     *
+     * @attr ref android.R.styleable#View_autofillHints
+     */
+    @ViewDebug.ExportedProperty()
+    @InspectableProperty
+    @Nullable public String[] getAutofillHints() {
+        return mAutofillHints;
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    public boolean isAutofilled() {
+        return (mPrivateFlags3 & PFLAG3_IS_AUTOFILLED) != 0;
+    }
+
+    /**
+     * @hide
+     */
+    public boolean hideAutofillHighlight() {
+        return (mPrivateFlags4 & PFLAG4_AUTOFILL_HIDE_HIGHLIGHT) != 0;
+    }
+
+    /**
+     * Gets the {@link View}'s current autofill value.
+     *
+     * <p>By default returns {@code null}, but subclasses should override it and return an
+     * appropriate value to properly support the Autofill Framework.
+     *
+     * @see #onProvideAutofillStructure(ViewStructure, int)
+     * @see #autofill(AutofillValue)
+     */
+    @Nullable
+    public AutofillValue getAutofillValue() {
+        return null;
+    }
+
+    /**
+     * Gets the mode for determining whether this view is important for autofill.
+     *
+     * <p>See {@link #setImportantForAutofill(int)} and {@link #isImportantForAutofill()} for more
+     * info about this mode.
+     *
+     * @return {@link #IMPORTANT_FOR_AUTOFILL_AUTO} by default, or value passed to
+     * {@link #setImportantForAutofill(int)}.
+     *
+     * @attr ref android.R.styleable#View_importantForAutofill
+     */
+    @ViewDebug.ExportedProperty(mapping = {
+            @ViewDebug.IntToString(from = IMPORTANT_FOR_AUTOFILL_AUTO, to = "auto"),
+            @ViewDebug.IntToString(from = IMPORTANT_FOR_AUTOFILL_YES, to = "yes"),
+            @ViewDebug.IntToString(from = IMPORTANT_FOR_AUTOFILL_NO, to = "no"),
+            @ViewDebug.IntToString(from = IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS,
+                to = "yesExcludeDescendants"),
+            @ViewDebug.IntToString(from = IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS,
+                to = "noExcludeDescendants")})
+    @InspectableProperty(enumMapping = {
+            @EnumEntry(value = IMPORTANT_FOR_AUTOFILL_AUTO, name = "auto"),
+            @EnumEntry(value = IMPORTANT_FOR_AUTOFILL_YES, name = "yes"),
+            @EnumEntry(value = IMPORTANT_FOR_AUTOFILL_NO, name = "no"),
+            @EnumEntry(value = IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS,
+                    name = "yesExcludeDescendants"),
+            @EnumEntry(value = IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS,
+                    name = "noExcludeDescendants"),
+    })
+    public @AutofillImportance int getImportantForAutofill() {
+        return (mPrivateFlags3
+                & PFLAG3_IMPORTANT_FOR_AUTOFILL_MASK) >> PFLAG3_IMPORTANT_FOR_AUTOFILL_SHIFT;
+    }
+
+    /**
+     * Sets the mode for determining whether this view is considered important for autofill.
+     *
+     * <p>The platform determines the importance for autofill automatically but you
+     * can use this method to customize the behavior. For example:
+     *
+     * <ol>
+     *   <li>When the view contents is irrelevant for autofill (for example, a text field used in a
+     *       "Captcha" challenge), it should be {@link #IMPORTANT_FOR_AUTOFILL_NO}.
+     *   <li>When both the view and its children are irrelevant for autofill (for example, the root
+     *       view of an activity containing a spreadhseet editor), it should be
+     *       {@link #IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS}.
+     *   <li>When the view content is relevant for autofill but its children aren't (for example,
+     *       a credit card expiration date represented by a custom view that overrides the proper
+     *       autofill methods and has 2 children representing the month and year), it should
+     *       be {@link #IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS}.
+     * </ol>
+     *
+     * <p><b>Note:</b> Setting the mode as {@link #IMPORTANT_FOR_AUTOFILL_NO} or
+     * {@link #IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS} does not guarantee the view (and its
+     * children) will be always be considered not important; for example, when the user explicitly
+     * makes an autofill request, all views are considered important. See
+     * {@link #isImportantForAutofill()} for more details about how the View's importance for
+     * autofill is used.
+     *
+     * @param mode {@link #IMPORTANT_FOR_AUTOFILL_AUTO}, {@link #IMPORTANT_FOR_AUTOFILL_YES},
+     * {@link #IMPORTANT_FOR_AUTOFILL_NO}, {@link #IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS},
+     * or {@link #IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS}.
+     *
+     * @attr ref android.R.styleable#View_importantForAutofill
+     */
+    public void setImportantForAutofill(@AutofillImportance int mode) {
+        mPrivateFlags3 &= ~PFLAG3_IMPORTANT_FOR_AUTOFILL_MASK;
+        mPrivateFlags3 |= (mode << PFLAG3_IMPORTANT_FOR_AUTOFILL_SHIFT)
+                & PFLAG3_IMPORTANT_FOR_AUTOFILL_MASK;
+    }
+
+    /**
+     * Hints the Android System whether the {@link android.app.assist.AssistStructure.ViewNode}
+     * associated with this view is considered important for autofill purposes.
+     *
+     * <p>Generally speaking, a view is important for autofill if:
+     * <ol>
+     * <li>The view can be autofilled by an {@link android.service.autofill.AutofillService}.
+     * <li>The view contents can help an {@link android.service.autofill.AutofillService}
+     *     determine how other views can be autofilled.
+     * <ol>
+     *
+     * <p>For example, view containers should typically return {@code false} for performance reasons
+     * (since the important info is provided by their children), but if its properties have relevant
+     * information (for example, a resource id called {@code credentials}, it should return
+     * {@code true}. On the other hand, views representing labels or editable fields should
+     * typically return {@code true}, but in some cases they could return {@code false}
+     * (for example, if they're part of a "Captcha" mechanism).
+     *
+     * <p>The value returned by this method depends on the value returned by
+     * {@link #getImportantForAutofill()}:
+     *
+     * <ol>
+     *   <li>if it returns {@link #IMPORTANT_FOR_AUTOFILL_YES} or
+     *       {@link #IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS}, then it returns {@code true}
+     *   <li>if it returns {@link #IMPORTANT_FOR_AUTOFILL_NO} or
+     *       {@link #IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS}, then it returns {@code false}
+     *   <li>if it returns {@link #IMPORTANT_FOR_AUTOFILL_AUTO}, then it uses some simple heuristics
+     *       that can return {@code true} in some cases (like a container with a resource id),
+     *       but {@code false} in most.
+     *   <li>otherwise, it returns {@code false}.
+     * </ol>
+     *
+     * <p>When a view is considered important for autofill:
+     * <ul>
+     *   <li>The view might automatically trigger an autofill request when focused on.
+     *   <li>The contents of the view are included in the {@link ViewStructure} used in an autofill
+     *       request.
+     * </ul>
+     *
+     * <p>On the other hand, when a view is considered not important for autofill:
+     * <ul>
+     *   <li>The view never automatically triggers autofill requests, but it can trigger a manual
+     *       request through {@link AutofillManager#requestAutofill(View)}.
+     *   <li>The contents of the view are not included in the {@link ViewStructure} used in an
+     *       autofill request, unless the request has the
+     *       {@link #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS} flag.
+     * </ul>
+     *
+     * @return whether the view is considered important for autofill.
+     *
+     * @see #setImportantForAutofill(int)
+     * @see #IMPORTANT_FOR_AUTOFILL_AUTO
+     * @see #IMPORTANT_FOR_AUTOFILL_YES
+     * @see #IMPORTANT_FOR_AUTOFILL_NO
+     * @see #IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS
+     * @see #IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS
+     * @see AutofillManager#requestAutofill(View)
+     */
+    public final boolean isImportantForAutofill() {
+        // Check parent mode to ensure we're not hidden.
+        ViewParent parent = mParent;
+        while (parent instanceof View) {
+            final int parentImportance = ((View) parent).getImportantForAutofill();
+            if (parentImportance == IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS
+                    || parentImportance == IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS) {
+                if (Log.isLoggable(AUTOFILL_LOG_TAG, Log.VERBOSE)) {
+                    Log.v(AUTOFILL_LOG_TAG, "View (" +  this + ") is not important for autofill "
+                            + "because parent " + parent + "'s importance is " + parentImportance);
+                }
+                return false;
+            }
+            parent = parent.getParent();
+        }
+
+        final int importance = getImportantForAutofill();
+
+        // First, check the explicit states.
+        if (importance == IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS
+                || importance == IMPORTANT_FOR_AUTOFILL_YES) {
+            return true;
+        }
+        if (importance == IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS
+                || importance == IMPORTANT_FOR_AUTOFILL_NO) {
+            if (Log.isLoggable(AUTOFILL_LOG_TAG, Log.VERBOSE)) {
+                Log.v(AUTOFILL_LOG_TAG, "View (" +  this + ") is not important for autofill "
+                        + "because its importance is " + importance);
+            }
+            return false;
+        }
+
+        // Then use some heuristics to handle AUTO.
+        if (importance != IMPORTANT_FOR_AUTOFILL_AUTO) {
+            Log.w(AUTOFILL_LOG_TAG, "invalid autofill importance (" + importance + " on view "
+                    + this);
+            return false;
+        }
+
+        // Always include views that have an explicit resource id.
+        final int id = mID;
+        if (id != NO_ID && !isViewIdGenerated(id)) {
+            final Resources res = getResources();
+            String entry = null;
+            String pkg = null;
+            try {
+                entry = res.getResourceEntryName(id);
+                pkg = res.getResourcePackageName(id);
+            } catch (Resources.NotFoundException e) {
+                // ignore
+            }
+            if (entry != null && pkg != null && pkg.equals(mContext.getPackageName())) {
+                return true;
+            }
+        }
+
+        // If the app developer explicitly set hints for it, it's important.
+        if (getAutofillHints() != null) {
+            return true;
+        }
+
+        // Otherwise, assume it's not important...
+        return false;
+    }
+
+    /**
+     * Gets the mode for determining whether this view is important for content capture.
+     *
+     * <p>See {@link #setImportantForContentCapture(int)} and
+     * {@link #isImportantForContentCapture()} for more info about this mode.
+     *
+     * @return {@link #IMPORTANT_FOR_CONTENT_CAPTURE_AUTO} by default, or value passed to
+     * {@link #setImportantForContentCapture(int)}.
+     *
+     * @attr ref android.R.styleable#View_importantForContentCapture
+     */
+    @ViewDebug.ExportedProperty(mapping = {
+            @ViewDebug.IntToString(from = IMPORTANT_FOR_CONTENT_CAPTURE_AUTO, to = "auto"),
+            @ViewDebug.IntToString(from = IMPORTANT_FOR_CONTENT_CAPTURE_YES, to = "yes"),
+            @ViewDebug.IntToString(from = IMPORTANT_FOR_CONTENT_CAPTURE_NO, to = "no"),
+            @ViewDebug.IntToString(from = IMPORTANT_FOR_CONTENT_CAPTURE_YES_EXCLUDE_DESCENDANTS,
+                to = "yesExcludeDescendants"),
+            @ViewDebug.IntToString(from = IMPORTANT_FOR_CONTENT_CAPTURE_NO_EXCLUDE_DESCENDANTS,
+                to = "noExcludeDescendants")})
+    @InspectableProperty(enumMapping = {
+            @EnumEntry(value = IMPORTANT_FOR_CONTENT_CAPTURE_AUTO, name = "auto"),
+            @EnumEntry(value = IMPORTANT_FOR_CONTENT_CAPTURE_YES, name = "yes"),
+            @EnumEntry(value = IMPORTANT_FOR_CONTENT_CAPTURE_NO, name = "no"),
+            @EnumEntry(value = IMPORTANT_FOR_CONTENT_CAPTURE_YES_EXCLUDE_DESCENDANTS,
+                    name = "yesExcludeDescendants"),
+            @EnumEntry(value = IMPORTANT_FOR_CONTENT_CAPTURE_NO_EXCLUDE_DESCENDANTS,
+                    name = "noExcludeDescendants"),
+    })
+    public @ContentCaptureImportance int getImportantForContentCapture() {
+        // NOTE: the important for content capture values were the first flags added and are set in
+        // the rightmost position, so we don't need to shift them
+        return mPrivateFlags4 & PFLAG4_IMPORTANT_FOR_CONTENT_CAPTURE_MASK;
+    }
+
+    /**
+     * Sets the mode for determining whether this view is considered important for content capture.
+     *
+     * <p>The platform determines the importance for autofill automatically but you
+     * can use this method to customize the behavior. Typically, a view that provides text should
+     * be marked as {@link #IMPORTANT_FOR_CONTENT_CAPTURE_YES}.
+     *
+     * @param mode {@link #IMPORTANT_FOR_CONTENT_CAPTURE_AUTO},
+     * {@link #IMPORTANT_FOR_CONTENT_CAPTURE_YES}, {@link #IMPORTANT_FOR_CONTENT_CAPTURE_NO},
+     * {@link #IMPORTANT_FOR_CONTENT_CAPTURE_YES_EXCLUDE_DESCENDANTS},
+     * or {@link #IMPORTANT_FOR_CONTENT_CAPTURE_NO_EXCLUDE_DESCENDANTS}.
+     *
+     * @attr ref android.R.styleable#View_importantForContentCapture
+     */
+    public void setImportantForContentCapture(@ContentCaptureImportance int mode) {
+        // Reset first
+        mPrivateFlags4 &= ~PFLAG4_IMPORTANT_FOR_CONTENT_CAPTURE_MASK;
+        // Then set again
+        // NOTE: the important for content capture values were the first flags added and are set in
+        // the rightmost position, so we don't need to shift them
+        mPrivateFlags4 |= (mode & PFLAG4_IMPORTANT_FOR_CONTENT_CAPTURE_MASK);
+    }
+
+    /**
+     * Hints the Android System whether this view is considered important for content capture, based
+     * on the value explicitly set by {@link #setImportantForContentCapture(int)} and heuristics
+     * when it's {@link #IMPORTANT_FOR_CONTENT_CAPTURE_AUTO}.
+     *
+     * <p>See {@link ContentCaptureManager} for more info about content capture.
+     *
+     * @return whether the view is considered important for content capture.
+     *
+     * @see #setImportantForContentCapture(int)
+     * @see #IMPORTANT_FOR_CONTENT_CAPTURE_AUTO
+     * @see #IMPORTANT_FOR_CONTENT_CAPTURE_YES
+     * @see #IMPORTANT_FOR_CONTENT_CAPTURE_NO
+     * @see #IMPORTANT_FOR_CONTENT_CAPTURE_YES_EXCLUDE_DESCENDANTS
+     * @see #IMPORTANT_FOR_CONTENT_CAPTURE_NO_EXCLUDE_DESCENDANTS
+     */
+    public final boolean isImportantForContentCapture() {
+        boolean isImportant;
+        if ((mPrivateFlags4 & PFLAG4_CONTENT_CAPTURE_IMPORTANCE_IS_CACHED) != 0) {
+            isImportant = (mPrivateFlags4 & PFLAG4_CONTENT_CAPTURE_IMPORTANCE_CACHED_VALUE) != 0;
+            return isImportant;
+        }
+
+        isImportant = calculateIsImportantForContentCapture();
+
+        mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_CACHED_VALUE;
+        if (isImportant) {
+            mPrivateFlags4 |= PFLAG4_CONTENT_CAPTURE_IMPORTANCE_CACHED_VALUE;
+        }
+        mPrivateFlags4 |= PFLAG4_CONTENT_CAPTURE_IMPORTANCE_IS_CACHED;
+        return isImportant;
+    }
+
+    /**
+     * Calculates whether the flag is important for content capture so it can be used by
+     * {@link #isImportantForContentCapture()} while the tree is traversed.
+     */
+    private boolean calculateIsImportantForContentCapture() {
+        // Check parent mode to ensure we're important
+        ViewParent parent = mParent;
+        while (parent instanceof View) {
+            final int parentImportance = ((View) parent).getImportantForContentCapture();
+            if (parentImportance == IMPORTANT_FOR_CONTENT_CAPTURE_NO_EXCLUDE_DESCENDANTS
+                    || parentImportance == IMPORTANT_FOR_CONTENT_CAPTURE_YES_EXCLUDE_DESCENDANTS) {
+                if (Log.isLoggable(CONTENT_CAPTURE_LOG_TAG, Log.VERBOSE)) {
+                    Log.v(CONTENT_CAPTURE_LOG_TAG, "View (" +  this + ") is not important for "
+                            + "content capture because parent " + parent + "'s importance is "
+                            + parentImportance);
+                }
+                return false;
+            }
+            parent = parent.getParent();
+        }
+
+        final int importance = getImportantForContentCapture();
+
+        // First, check the explicit states.
+        if (importance == IMPORTANT_FOR_CONTENT_CAPTURE_YES_EXCLUDE_DESCENDANTS
+                || importance == IMPORTANT_FOR_CONTENT_CAPTURE_YES) {
+            return true;
+        }
+        if (importance == IMPORTANT_FOR_CONTENT_CAPTURE_NO_EXCLUDE_DESCENDANTS
+                || importance == IMPORTANT_FOR_CONTENT_CAPTURE_NO) {
+            if (Log.isLoggable(CONTENT_CAPTURE_LOG_TAG, Log.VERBOSE)) {
+                Log.v(CONTENT_CAPTURE_LOG_TAG, "View (" +  this + ") is not important for content "
+                        + "capture because its importance is " + importance);
+            }
+            return false;
+        }
+
+        // Then use some heuristics to handle AUTO.
+        if (importance != IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) {
+            Log.w(CONTENT_CAPTURE_LOG_TAG, "invalid content capture importance (" + importance
+                    + " on view " + this);
+            return false;
+        }
+
+        // View group is important if at least one children also is
+        if (this instanceof ViewGroup) {
+            final ViewGroup group = (ViewGroup) this;
+            for (int i = 0; i < group.getChildCount(); i++) {
+                final View child = group.getChildAt(i);
+                if (child.isImportantForContentCapture()) {
+                    return true;
+                }
+            }
+        }
+
+        // If the app developer explicitly set hints or autofill hintsfor it, it's important.
+        if (getAutofillHints() != null) {
+            return true;
+        }
+
+        // Otherwise, assume it's not important...
+        return false;
+    }
+
+    /**
+     * Helper used to notify the {@link ContentCaptureManager} when the view is removed or
+     * added, based on whether it's laid out and visible, and without knowing if the parent removed
+     * it from the view hierarchy.
+     *
+     * <p>This method is called from many places (visibility changed, view laid out, view attached
+     * or detached to/from window, etc...) and hence must contain the logic to call the manager, as
+     * described below:
+     *
+     * <ol>
+     *   <li>It should only be called when content capture is enabled for the view.
+     *   <li>It must call viewAppeared() before viewDisappeared()
+     *   <li>viewAppearead() can only be called when the view is visible and laidout
+     *   <li>It should not call the same event twice.
+     * </ol>
+     */
+    private void notifyAppearedOrDisappearedForContentCaptureIfNeeded(boolean appeared) {
+        AttachInfo ai = mAttachInfo;
+        // Skip it while the view is being laid out for the first time
+        if (ai != null && !ai.mReadyForContentCaptureUpdates) return;
+
+        // First check if context has client, so it saves a service lookup when it doesn't
+        if (mContext.getContentCaptureOptions() == null) return;
+
+        if (appeared) {
+            // The appeared event stops sending to AiAi.
+            // 1. The view is hidden.
+            // 2. The same event was sent.
+            // 3. The view is not laid out, and it will be laid out in the future.
+            //    Some recycled views cached its layout and a relayout is unnecessary. In this case,
+            // system still needs to notify content capture the view appeared. When a view is
+            // recycled, it will set the flag PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED.
+            final boolean isRecycledWithoutRelayout = getNotifiedContentCaptureDisappeared()
+                    && getVisibility() == VISIBLE
+                    && !isLayoutRequested();
+            if (getVisibility() != VISIBLE || getNotifiedContentCaptureAppeared()
+                    || !(isLaidOut() || isRecycledWithoutRelayout)) {
+                if (DEBUG_CONTENT_CAPTURE) {
+                    Log.v(CONTENT_CAPTURE_LOG_TAG, "Ignoring 'appeared' on " + this + ": laid="
+                            + isLaidOut() + ", visibleToUser=" + isVisibleToUser()
+                            + ", visible=" + (getVisibility() == VISIBLE)
+                            + ": alreadyNotifiedAppeared=" + getNotifiedContentCaptureAppeared()
+                            + ", alreadyNotifiedDisappeared="
+                            + getNotifiedContentCaptureDisappeared());
+                }
+                return;
+            }
+        } else {
+            if (!getNotifiedContentCaptureAppeared() || getNotifiedContentCaptureDisappeared()) {
+                if (DEBUG_CONTENT_CAPTURE) {
+                    Log.v(CONTENT_CAPTURE_LOG_TAG, "Ignoring 'disappeared' on " + this + ": laid="
+                            + isLaidOut() + ", visibleToUser=" + isVisibleToUser()
+                            + ", visible=" + (getVisibility() == VISIBLE)
+                            + ": alreadyNotifiedAppeared=" + getNotifiedContentCaptureAppeared()
+                            + ", alreadyNotifiedDisappeared="
+                            + getNotifiedContentCaptureDisappeared());
+                }
+                return;
+            }
+        }
+
+        ContentCaptureSession session = getContentCaptureSession();
+        if (session == null) return;
+
+        // ... and finally at the view level
+        // NOTE: isImportantForContentCapture() is more expensive than cm.isContentCaptureEnabled()
+        if (!isImportantForContentCapture()) return;
+
+        if (appeared) {
+            setNotifiedContentCaptureAppeared();
+
+            if (ai != null) {
+                ai.delayNotifyContentCaptureEvent(session, this, appeared);
+            } else {
+                if (DEBUG_CONTENT_CAPTURE) {
+                    Log.w(CONTENT_CAPTURE_LOG_TAG, "no AttachInfo on appeared for " + this);
+                }
+            }
+        } else {
+            mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED;
+            mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED;
+
+            if (ai != null) {
+                ai.delayNotifyContentCaptureEvent(session, this, appeared);
+            } else {
+                if (DEBUG_CONTENT_CAPTURE) {
+                    Log.v(CONTENT_CAPTURE_LOG_TAG, "no AttachInfo on disappeared for " + this);
+                }
+            }
+        }
+    }
+
+    private void setNotifiedContentCaptureAppeared() {
+        mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED;
+        mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED;
+    }
+
+    /** @hide */
+    protected boolean getNotifiedContentCaptureAppeared() {
+        return (mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0;
+    }
+
+
+    private boolean getNotifiedContentCaptureDisappeared() {
+        return (mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0;
+    }
+
+    /**
+     * Sets the (optional) {@link ContentCaptureSession} associated with this view.
+     *
+     * <p>This method should be called when you need to associate a {@link ContentCaptureContext} to
+     * the content capture events associated with this view or its view hierarchy (if it's a
+     * {@link ViewGroup}).
+     *
+     * <p>For example, if your activity is associated with a web domain, first you would need to
+     * set the context for the main DOM:
+     *
+     * <pre>
+     *   ContentCaptureSession mainSession = rootView.getContentCaptureSession();
+     *   mainSession.setContentCaptureContext(ContentCaptureContext.forLocusId(Uri.parse(myUrl));
+     * </pre>
+     *
+     * <p>Then if the page had an {@code IFRAME}, you would create a new session for it:
+     *
+     * <pre>
+     *   ContentCaptureSession iframeSession = mainSession.createContentCaptureSession(
+     *       ContentCaptureContext.forLocusId(Uri.parse(iframeUrl)));
+     *   iframeView.setContentCaptureSession(iframeSession);
+     * </pre>
+     *
+     * @param contentCaptureSession a session created by
+     * {@link ContentCaptureSession#createContentCaptureSession(
+     *        android.view.contentcapture.ContentCaptureContext)}.
+     */
+    public void setContentCaptureSession(@Nullable ContentCaptureSession contentCaptureSession) {
+        mContentCaptureSession = contentCaptureSession;
+    }
+
+    /**
+     * Gets the session used to notify content capture events.
+     *
+     * @return session explicitly set by {@link #setContentCaptureSession(ContentCaptureSession)},
+     * inherited by ancestors, default session or {@code null} if content capture is disabled for
+     * this view.
+     */
+    @Nullable
+    public final ContentCaptureSession getContentCaptureSession() {
+        if (mContentCaptureSessionCached) {
+            return mContentCaptureSession;
+        }
+
+        mContentCaptureSession = getAndCacheContentCaptureSession();
+        mContentCaptureSessionCached = true;
+        return mContentCaptureSession;
+    }
+
+    @Nullable
+    private ContentCaptureSession getAndCacheContentCaptureSession() {
+        // First try the session explicitly set by setContentCaptureSession()
+        if (mContentCaptureSession != null) {
+            return mContentCaptureSession;
+        }
+
+        // Then the session explicitly set in an ancestor
+        ContentCaptureSession session = null;
+        if (mParent instanceof View) {
+            session = ((View) mParent).getContentCaptureSession();
+        }
+
+        // Finally, if no session was explicitly set, use the context's default session.
+        if (session == null) {
+            final ContentCaptureManager ccm = mContext
+                    .getSystemService(ContentCaptureManager.class);
+            return ccm == null ? null : ccm.getMainContentCaptureSession();
+        }
+        return session;
+    }
+
+    @Nullable
+    private AutofillManager getAutofillManager() {
+        return mContext.getSystemService(AutofillManager.class);
+    }
+
+    private boolean isAutofillable() {
+        if (getAutofillType() == AUTOFILL_TYPE_NONE) return false;
+
+        if (!isImportantForAutofill()) {
+            // View is not important for "regular" autofill, so we must check if Augmented Autofill
+            // is enabled for the activity
+            final AutofillOptions options = mContext.getAutofillOptions();
+            if (options == null || !options.isAugmentedAutofillEnabled(mContext)) {
+                return false;
+            }
+            final AutofillManager afm = getAutofillManager();
+            if (afm == null) return false;
+            afm.notifyViewEnteredForAugmentedAutofill(this);
+        }
+
+        return getAutofillViewId() > LAST_APP_AUTOFILL_ID;
+    }
+
+    /** @hide */
+    public boolean canNotifyAutofillEnterExitEvent() {
+        return isAutofillable() && isAttachedToWindow();
+    }
+
+    private void populateVirtualStructure(ViewStructure structure,
+            AccessibilityNodeProvider provider, AccessibilityNodeInfo info,
+            boolean forAutofill) {
+        structure.setId(AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId()),
+                null, null, info.getViewIdResourceName());
+        Rect rect = structure.getTempRect();
+        info.getBoundsInParent(rect);
+        structure.setDimens(rect.left, rect.top, 0, 0, rect.width(), rect.height());
+        structure.setVisibility(VISIBLE);
+        structure.setEnabled(info.isEnabled());
+        if (info.isClickable()) {
+            structure.setClickable(true);
+        }
+        if (info.isFocusable()) {
+            structure.setFocusable(true);
+        }
+        if (info.isFocused()) {
+            structure.setFocused(true);
+        }
+        if (info.isAccessibilityFocused()) {
+            structure.setAccessibilityFocused(true);
+        }
+        if (info.isSelected()) {
+            structure.setSelected(true);
+        }
+        if (info.isLongClickable()) {
+            structure.setLongClickable(true);
+        }
+        if (info.isCheckable()) {
+            structure.setCheckable(true);
+            if (info.isChecked()) {
+                structure.setChecked(true);
+            }
+        }
+        if (info.isContextClickable()) {
+            structure.setContextClickable(true);
+        }
+        if (forAutofill) {
+            structure.setAutofillId(new AutofillId(getAutofillId(),
+                    AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId())));
+        }
+        CharSequence cname = info.getClassName();
+        structure.setClassName(cname != null ? cname.toString() : null);
+        structure.setContentDescription(info.getContentDescription());
+        if (forAutofill) {
+            final int maxTextLength = info.getMaxTextLength();
+            if (maxTextLength != -1) {
+                structure.setMaxTextLength(maxTextLength);
+            }
+            structure.setHint(info.getHintText());
+        }
+        CharSequence text = info.getText();
+        boolean hasText = text != null || info.getError() != null;
+        if (hasText) {
+            structure.setText(text, info.getTextSelectionStart(), info.getTextSelectionEnd());
+        }
+        if (forAutofill) {
+            if (info.isEditable()) {
+                structure.setDataIsSensitive(true);
+                if (hasText) {
+                    structure.setAutofillType(AUTOFILL_TYPE_TEXT);
+                    structure.setAutofillValue(AutofillValue.forText(text));
+                }
+                int inputType = info.getInputType();
+                if (inputType == 0 && info.isPassword()) {
+                    inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD;
+                }
+                structure.setInputType(inputType);
+            } else {
+                structure.setDataIsSensitive(false);
+            }
+        }
+        final int NCHILDREN = info.getChildCount();
+        if (NCHILDREN > 0) {
+            structure.setChildCount(NCHILDREN);
+            for (int i=0; i<NCHILDREN; i++) {
+                if (AccessibilityNodeInfo.getVirtualDescendantId(info.getChildNodeIds().get(i))
+                        == AccessibilityNodeProvider.HOST_VIEW_ID) {
+                    Log.e(VIEW_LOG_TAG, "Virtual view pointing to its host. Ignoring");
+                    continue;
+                }
+                AccessibilityNodeInfo cinfo = provider.createAccessibilityNodeInfo(
+                        AccessibilityNodeInfo.getVirtualDescendantId(info.getChildId(i)));
+                if (cinfo != null) {
+                    ViewStructure child = structure.newChild(i);
+                    populateVirtualStructure(child, provider, cinfo, forAutofill);
+                    cinfo.recycle();
+                }
+            }
+        }
+    }
+
+    /**
+     * Dispatch creation of {@link ViewStructure} down the hierarchy.  The default
+     * implementation calls {@link #onProvideStructure} and
+     * {@link #onProvideVirtualStructure}.
+     */
+    public void dispatchProvideStructure(ViewStructure structure) {
+        dispatchProvideStructure(structure, VIEW_STRUCTURE_FOR_ASSIST, /* flags= */ 0);
+    }
+
+    /**
+     * Dispatches creation of a {@link ViewStructure}s for autofill purposes down the hierarchy,
+     * when an Assist structure is being created as part of an autofill request.
+     *
+     * <p>The default implementation does the following:
+     * <ul>
+     *   <li>Sets the {@link AutofillId} in the structure.
+     *   <li>Calls {@link #onProvideAutofillStructure(ViewStructure, int)}.
+     *   <li>Calls {@link #onProvideAutofillVirtualStructure(ViewStructure, int)}.
+     * </ul>
+     *
+     * <p>Typically, this method should only be overridden by subclasses that provide a view
+     * hierarchy (such as {@link ViewGroup}) - other classes should override
+     * {@link #onProvideAutofillStructure(ViewStructure, int)} or
+     * {@link #onProvideAutofillVirtualStructure(ViewStructure, int)} instead.
+     *
+     * <p>When overridden, it must:
+     *
+     * <ul>
+     *   <li>Either call
+     *       {@code super.dispatchProvideAutofillStructure(structure, flags)} or explicitly
+     *       set the {@link AutofillId} in the structure (for example, by calling
+     *       {@code structure.setAutofillId(getAutofillId())}).
+     *   <li>Decide how to handle the {@link #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS} flag - when
+     *       set, all views in the structure should be considered important for autofill,
+     *       regardless of what {@link #isImportantForAutofill()} returns. We encourage you to
+     *       respect this flag to provide a better user experience - this flag is typically used
+     *       when an user explicitly requested autofill. If the flag is not set,
+     *       then only views marked as important for autofill should be included in the
+     *       structure - skipping non-important views optimizes the overall autofill performance.
+     * </ul>
+     *
+     * @param structure fill in with structured view data for autofill purposes.
+     * @param flags optional flags.
+     *
+     * @see #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+     */
+    public void dispatchProvideAutofillStructure(@NonNull ViewStructure structure,
+            @AutofillFlags int flags) {
+        dispatchProvideStructure(structure, VIEW_STRUCTURE_FOR_AUTOFILL, flags);
+    }
+
+    private void dispatchProvideStructure(@NonNull ViewStructure structure,
+            @ViewStructureType int viewFor, @AutofillFlags int flags) {
+        if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
+            structure.setAutofillId(getAutofillId());
+            onProvideAutofillStructure(structure, flags);
+            onProvideAutofillVirtualStructure(structure, flags);
+        } else if (!isAssistBlocked()) {
+            onProvideStructure(structure);
+            onProvideVirtualStructure(structure);
+        } else {
+            structure.setClassName(getAccessibilityClassName().toString());
+            structure.setAssistBlocked(true);
+        }
+    }
+
+    /**
+     * Dispatches the initial content capture events for a view structure.
+     *
+     * @hide
+     */
+    public void dispatchInitialProvideContentCaptureStructure() {
+        AttachInfo ai = mAttachInfo;
+        if (ai == null) {
+            Log.w(CONTENT_CAPTURE_LOG_TAG,
+                    "dispatchProvideContentCaptureStructure(): no AttachInfo for " + this);
+            return;
+        }
+        ContentCaptureManager ccm = ai.mContentCaptureManager;
+        if (ccm == null) {
+            Log.w(CONTENT_CAPTURE_LOG_TAG, "dispatchProvideContentCaptureStructure(): "
+                    + "no ContentCaptureManager for " + this);
+            return;
+        }
+
+        // We must set it before checkign if the view itself is important, because it might
+        // initially not be (for example, if it's empty), although that might change later (for
+        // example, if important views are added)
+        ai.mReadyForContentCaptureUpdates = true;
+
+        if (!isImportantForContentCapture()) {
+            if (Log.isLoggable(CONTENT_CAPTURE_LOG_TAG, Log.DEBUG)) {
+                Log.d(CONTENT_CAPTURE_LOG_TAG,
+                        "dispatchProvideContentCaptureStructure(): decorView is not important");
+            }
+            return;
+        }
+
+        ai.mContentCaptureManager = ccm;
+
+        ContentCaptureSession session = getContentCaptureSession();
+        if (session == null) {
+            if (Log.isLoggable(CONTENT_CAPTURE_LOG_TAG, Log.DEBUG)) {
+                Log.d(CONTENT_CAPTURE_LOG_TAG,
+                        "dispatchProvideContentCaptureStructure(): no session for " + this);
+            }
+            return;
+        }
+
+        session.internalNotifyViewTreeEvent(/* started= */ true);
+        try {
+            dispatchProvideContentCaptureStructure();
+        } finally {
+            session.internalNotifyViewTreeEvent(/* started= */ false);
+        }
+    }
+
+    /** @hide */
+    void dispatchProvideContentCaptureStructure() {
+        ContentCaptureSession session = getContentCaptureSession();
+        if (session != null) {
+            ViewStructure structure = session.newViewStructure(this);
+            onProvideContentCaptureStructure(structure, /* flags= */ 0);
+            setNotifiedContentCaptureAppeared();
+            session.notifyViewAppeared(structure);
+        }
+    }
+
+    /**
+     * @see #onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
+     *
+     * Note: Called from the default {@link AccessibilityDelegate}.
+     *
+     * @hide
+     */
+    public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+        if (mAttachInfo == null) {
+            return;
+        }
+
+        Rect bounds = mAttachInfo.mTmpInvalRect;
+
+        getDrawingRect(bounds);
+        info.setBoundsInParent(bounds);
+
+        getBoundsOnScreen(bounds, true);
+        info.setBoundsInScreen(bounds);
+
+        ViewParent parent = getParentForAccessibility();
+        if (parent instanceof View) {
+            info.setParent((View) parent);
+        }
+
+        if (mID != View.NO_ID) {
+            View rootView = getRootView();
+            if (rootView == null) {
+                rootView = this;
+            }
+
+            View label = rootView.findLabelForView(this, mID);
+            if (label != null) {
+                info.setLabeledBy(label);
+            }
+
+            if ((mAttachInfo.mAccessibilityFetchFlags
+                    & AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS) != 0
+                    && Resources.resourceHasPackage(mID)) {
+                try {
+                    String viewId = getResources().getResourceName(mID);
+                    info.setViewIdResourceName(viewId);
+                } catch (Resources.NotFoundException nfe) {
+                    /* ignore */
+                }
+            }
+        }
+
+        if (mLabelForId != View.NO_ID) {
+            View rootView = getRootView();
+            if (rootView == null) {
+                rootView = this;
+            }
+            View labeled = rootView.findViewInsideOutShouldExist(this, mLabelForId);
+            if (labeled != null) {
+                info.setLabelFor(labeled);
+            }
+        }
+
+        if (mAccessibilityTraversalBeforeId != View.NO_ID) {
+            View rootView = getRootView();
+            if (rootView == null) {
+                rootView = this;
+            }
+            View next = rootView.findViewInsideOutShouldExist(this,
+                    mAccessibilityTraversalBeforeId);
+            if (next != null && next.includeForAccessibility()) {
+                info.setTraversalBefore(next);
+            }
+        }
+
+        if (mAccessibilityTraversalAfterId != View.NO_ID) {
+            View rootView = getRootView();
+            if (rootView == null) {
+                rootView = this;
+            }
+            View next = rootView.findViewInsideOutShouldExist(this,
+                    mAccessibilityTraversalAfterId);
+            if (next != null && next.includeForAccessibility()) {
+                info.setTraversalAfter(next);
+            }
+        }
+
+        info.setVisibleToUser(isVisibleToUser());
+
+        info.setImportantForAccessibility(isImportantForAccessibility());
+        info.setPackageName(mContext.getPackageName());
+        info.setClassName(getAccessibilityClassName());
+        info.setStateDescription(getStateDescription());
+        info.setContentDescription(getContentDescription());
+
+        info.setEnabled(isEnabled());
+        info.setClickable(isClickable());
+        info.setFocusable(isFocusable());
+        info.setScreenReaderFocusable(isScreenReaderFocusable());
+        info.setFocused(isFocused());
+        info.setAccessibilityFocused(isAccessibilityFocused());
+        info.setSelected(isSelected());
+        info.setLongClickable(isLongClickable());
+        info.setContextClickable(isContextClickable());
+        info.setLiveRegion(getAccessibilityLiveRegion());
+        if ((mTooltipInfo != null) && (mTooltipInfo.mTooltipText != null)) {
+            info.setTooltipText(mTooltipInfo.mTooltipText);
+            info.addAction((mTooltipInfo.mTooltipPopup == null)
+                    ? AccessibilityNodeInfo.AccessibilityAction.ACTION_SHOW_TOOLTIP
+                    : AccessibilityNodeInfo.AccessibilityAction.ACTION_HIDE_TOOLTIP);
+        }
+
+        // TODO: These make sense only if we are in an AdapterView but all
+        // views can be selected. Maybe from accessibility perspective
+        // we should report as selectable view in an AdapterView.
+        info.addAction(AccessibilityNodeInfo.ACTION_SELECT);
+        info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_SELECTION);
+
+        if (isFocusable()) {
+            if (isFocused()) {
+                info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_FOCUS);
+            } else {
+                info.addAction(AccessibilityNodeInfo.ACTION_FOCUS);
+            }
+        }
+
+        if (!isAccessibilityFocused()) {
+            info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+        } else {
+            info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
+        }
+
+        if (isClickable() && isEnabled()) {
+            info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
+        }
+
+        if (isLongClickable() && isEnabled()) {
+            info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
+        }
+
+        if (isContextClickable() && isEnabled()) {
+            info.addAction(AccessibilityAction.ACTION_CONTEXT_CLICK);
+        }
+
+        CharSequence text = getIterableTextForAccessibility();
+        if (text != null && text.length() > 0) {
+            info.setTextSelection(getAccessibilitySelectionStart(), getAccessibilitySelectionEnd());
+
+            info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
+            info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
+            info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
+            info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
+                    | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
+                    | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+        }
+
+        info.addAction(AccessibilityAction.ACTION_SHOW_ON_SCREEN);
+        populateAccessibilityNodeInfoDrawingOrderInParent(info);
+        info.setPaneTitle(mAccessibilityPaneTitle);
+        info.setHeading(isAccessibilityHeading());
+
+        if (mTouchDelegate != null) {
+            info.setTouchDelegateInfo(mTouchDelegate.getTouchDelegateInfo());
+        }
+    }
+
+    /**
+     * Adds extra data to an {@link AccessibilityNodeInfo} based on an explicit request for the
+     * additional data.
+     * <p>
+     * This method only needs overloading if the node is marked as having extra data available.
+     * </p>
+     *
+     * @param info The info to which to add the extra data. Never {@code null}.
+     * @param extraDataKey A key specifying the type of extra data to add to the info. The
+     *                     extra data should be added to the {@link Bundle} returned by
+     *                     the info's {@link AccessibilityNodeInfo#getExtras} method. Never
+     *                     {@code null}.
+     * @param arguments A {@link Bundle} holding any arguments relevant for this request. May be
+     *                  {@code null} if the service provided no arguments.
+     *
+     * @see AccessibilityNodeInfo#setAvailableExtraData(List)
+     */
+    public void addExtraDataToAccessibilityNodeInfo(
+            @NonNull AccessibilityNodeInfo info, @NonNull String extraDataKey,
+            @Nullable Bundle arguments) {
+    }
+
+    /**
+     * Determine the order in which this view will be drawn relative to its siblings for a11y
+     *
+     * @param info The info whose drawing order should be populated
+     */
+    private void populateAccessibilityNodeInfoDrawingOrderInParent(AccessibilityNodeInfo info) {
+        /*
+         * If the view's bounds haven't been set yet, layout has not completed. In that situation,
+         * drawing order may not be well-defined, and some Views with custom drawing order may
+         * not be initialized sufficiently to respond properly getChildDrawingOrder.
+         */
+        if ((mPrivateFlags & PFLAG_HAS_BOUNDS) == 0) {
+            info.setDrawingOrder(0);
+            return;
+        }
+        int drawingOrderInParent = 1;
+        // Iterate up the hierarchy if parents are not important for a11y
+        View viewAtDrawingLevel = this;
+        final ViewParent parent = getParentForAccessibility();
+        while (viewAtDrawingLevel != parent) {
+            final ViewParent currentParent = viewAtDrawingLevel.getParent();
+            if (!(currentParent instanceof ViewGroup)) {
+                // Should only happen for the Decor
+                drawingOrderInParent = 0;
+                break;
+            } else {
+                final ViewGroup parentGroup = (ViewGroup) currentParent;
+                final int childCount = parentGroup.getChildCount();
+                if (childCount > 1) {
+                    List<View> preorderedList = parentGroup.buildOrderedChildList();
+                    if (preorderedList != null) {
+                        final int childDrawIndex = preorderedList.indexOf(viewAtDrawingLevel);
+                        for (int i = 0; i < childDrawIndex; i++) {
+                            drawingOrderInParent += numViewsForAccessibility(preorderedList.get(i));
+                        }
+                        preorderedList.clear();
+                    } else {
+                        final int childIndex = parentGroup.indexOfChild(viewAtDrawingLevel);
+                        final boolean customOrder = parentGroup.isChildrenDrawingOrderEnabled();
+                        final int childDrawIndex = ((childIndex >= 0) && customOrder) ? parentGroup
+                                .getChildDrawingOrder(childCount, childIndex) : childIndex;
+                        final int numChildrenToIterate = customOrder ? childCount : childDrawIndex;
+                        if (childDrawIndex != 0) {
+                            for (int i = 0; i < numChildrenToIterate; i++) {
+                                final int otherDrawIndex = (customOrder ?
+                                        parentGroup.getChildDrawingOrder(childCount, i) : i);
+                                if (otherDrawIndex < childDrawIndex) {
+                                    drawingOrderInParent +=
+                                            numViewsForAccessibility(parentGroup.getChildAt(i));
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            viewAtDrawingLevel = (View) currentParent;
+        }
+        info.setDrawingOrder(drawingOrderInParent);
+    }
+
+    private static int numViewsForAccessibility(View view) {
+        if (view != null) {
+            if (view.includeForAccessibility()) {
+                return 1;
+            } else if (view instanceof ViewGroup) {
+                return ((ViewGroup) view).getNumChildrenForAccessibility();
+            }
+        }
+        return 0;
+    }
+
+    private View findLabelForView(View view, int labeledId) {
+        if (mMatchLabelForPredicate == null) {
+            mMatchLabelForPredicate = new MatchLabelForPredicate();
+        }
+        mMatchLabelForPredicate.mLabeledId = labeledId;
+        return findViewByPredicateInsideOut(view, mMatchLabelForPredicate);
+    }
+
+    /**
+     * Computes whether this virtual autofill view is visible to the user.
+     *
+     * <p><b>Note: </b>By default it returns {@code true}, but views providing a virtual hierarchy
+     * view must override it.
+     *
+     * @return Whether the view is visible on the screen.
+     */
+    public boolean isVisibleToUserForAutofill(int virtualId) {
+        if (mContext.isAutofillCompatibilityEnabled()) {
+            final AccessibilityNodeProvider provider = getAccessibilityNodeProvider();
+            if (provider != null) {
+                final AccessibilityNodeInfo node = provider.createAccessibilityNodeInfo(virtualId);
+                if (node != null) {
+                    return node.isVisibleToUser();
+                }
+                // if node is null, assume it's not visible anymore
+            } else {
+                Log.w(VIEW_LOG_TAG, "isVisibleToUserForAutofill(" + virtualId + "): no provider");
+            }
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Computes whether this view is visible to the user. Such a view is
+     * attached, visible, all its predecessors are visible, it is not clipped
+     * entirely by its predecessors, and has an alpha greater than zero.
+     *
+     * @return Whether the view is visible on the screen.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public boolean isVisibleToUser() {
+        return isVisibleToUser(null);
+    }
+
+    /**
+     * Computes whether the given portion of this view is visible to the user.
+     * Such a view is attached, visible, all its predecessors are visible,
+     * has an alpha greater than zero, and the specified portion is not
+     * clipped entirely by its predecessors.
+     *
+     * @param boundInView the portion of the view to test; coordinates should be relative; may be
+     *                    <code>null</code>, and the entire view will be tested in this case.
+     *                    When <code>true</code> is returned by the function, the actual visible
+     *                    region will be stored in this parameter; that is, if boundInView is fully
+     *                    contained within the view, no modification will be made, otherwise regions
+     *                    outside of the visible area of the view will be clipped.
+     *
+     * @return Whether the specified portion of the view is visible on the screen.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage(trackingBug = 171933273)
+    protected boolean isVisibleToUser(Rect boundInView) {
+        if (mAttachInfo != null) {
+            // Attached to invisible window means this view is not visible.
+            if (mAttachInfo.mWindowVisibility != View.VISIBLE) {
+                return false;
+            }
+            // An invisible predecessor or one with alpha zero means
+            // that this view is not visible to the user.
+            Object current = this;
+            while (current instanceof View) {
+                View view = (View) current;
+                // We have attach info so this view is attached and there is no
+                // need to check whether we reach to ViewRootImpl on the way up.
+                if (view.getAlpha() <= 0 || view.getTransitionAlpha() <= 0 ||
+                        view.getVisibility() != VISIBLE) {
+                    return false;
+                }
+                current = view.mParent;
+            }
+            // Check if the view is entirely covered by its predecessors.
+            Rect visibleRect = mAttachInfo.mTmpInvalRect;
+            Point offset = mAttachInfo.mPoint;
+            if (!getGlobalVisibleRect(visibleRect, offset)) {
+                return false;
+            }
+            // Check if the visible portion intersects the rectangle of interest.
+            if (boundInView != null) {
+                visibleRect.offset(-offset.x, -offset.y);
+                return boundInView.intersect(visibleRect);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns the delegate for implementing accessibility support via
+     * composition. For more details see {@link AccessibilityDelegate}.
+     *
+     * @return The delegate, or null if none set.
+     */
+    public AccessibilityDelegate getAccessibilityDelegate() {
+        return mAccessibilityDelegate;
+    }
+
+    /**
+     * Sets a delegate for implementing accessibility support via composition
+     * (as opposed to inheritance). For more details, see
+     * {@link AccessibilityDelegate}.
+     * <p>
+     * <strong>Note:</strong> On platform versions prior to
+     * {@link android.os.Build.VERSION_CODES#M API 23}, delegate methods on
+     * views in the {@code android.widget.*} package are called <i>before</i>
+     * host methods. This prevents certain properties such as class name from
+     * being modified by overriding
+     * {@link AccessibilityDelegate#onInitializeAccessibilityNodeInfo(View, AccessibilityNodeInfo)},
+     * as any changes will be overwritten by the host class.
+     * <p>
+     * Starting in {@link android.os.Build.VERSION_CODES#M API 23}, delegate
+     * methods are called <i>after</i> host methods, which all properties to be
+     * modified without being overwritten by the host class.
+     *
+     * @param delegate the object to which accessibility method calls should be
+     *                 delegated
+     * @see AccessibilityDelegate
+     */
+    public void setAccessibilityDelegate(@Nullable AccessibilityDelegate delegate) {
+        mAccessibilityDelegate = delegate;
+    }
+
+    /**
+     * Gets the provider for managing a virtual view hierarchy rooted at this View
+     * and reported to {@link android.accessibilityservice.AccessibilityService}s
+     * that explore the window content.
+     * <p>
+     * If this method returns an instance, this instance is responsible for managing
+     * {@link AccessibilityNodeInfo}s describing the virtual sub-tree rooted at this
+     * View including the one representing the View itself. Similarly the returned
+     * instance is responsible for performing accessibility actions on any virtual
+     * view or the root view itself.
+     * </p>
+     * <p>
+     * If an {@link AccessibilityDelegate} has been specified via calling
+     * {@link #setAccessibilityDelegate(AccessibilityDelegate)} its
+     * {@link AccessibilityDelegate#getAccessibilityNodeProvider(View)}
+     * is responsible for handling this call.
+     * </p>
+     *
+     * @return The provider.
+     *
+     * @see AccessibilityNodeProvider
+     */
+    public AccessibilityNodeProvider getAccessibilityNodeProvider() {
+        if (mAccessibilityDelegate != null) {
+            return mAccessibilityDelegate.getAccessibilityNodeProvider(this);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Gets the unique identifier of this view on the screen for accessibility purposes.
+     *
+     * @return The view accessibility id.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public int getAccessibilityViewId() {
+        if (mAccessibilityViewId == NO_ID) {
+            mAccessibilityViewId = sNextAccessibilityViewId++;
+        }
+        return mAccessibilityViewId;
+    }
+
+    /**
+     * Gets the unique identifier of this view on the screen for autofill purposes.
+     *
+     * @return The view autofill id.
+     *
+     * @hide
+     */
+    public int getAutofillViewId() {
+        if (mAutofillViewId == NO_ID) {
+            mAutofillViewId = mContext.getNextAutofillId();
+        }
+        return mAutofillViewId;
+    }
+
+    /**
+     * Gets the unique identifier of the window in which this View resides.
+     *
+     * @return The window accessibility id.
+     *
+     * @hide
+     */
+    public int getAccessibilityWindowId() {
+        return mAttachInfo != null ? mAttachInfo.mAccessibilityWindowId
+                : AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+    }
+
+    /**
+     * Returns the {@link View}'s state description.
+     * <p>
+     * <strong>Note:</strong> Do not override this method, as it will have no
+     * effect on the state description presented to accessibility services.
+     * You must call {@link #setStateDescription(CharSequence)} to modify the
+     * state description.
+     *
+     * @return the state description
+     * @see #setStateDescription(CharSequence)
+     */
+    @ViewDebug.ExportedProperty(category = "accessibility")
+    public final @Nullable CharSequence getStateDescription() {
+        return mStateDescription;
+    }
+
+    /**
+     * Returns the {@link View}'s content description.
+     * <p>
+     * <strong>Note:</strong> Do not override this method, as it will have no
+     * effect on the content description presented to accessibility services.
+     * You must call {@link #setContentDescription(CharSequence)} to modify the
+     * content description.
+     *
+     * @return the content description
+     * @see #setContentDescription(CharSequence)
+     * @attr ref android.R.styleable#View_contentDescription
+     */
+    @ViewDebug.ExportedProperty(category = "accessibility")
+    @InspectableProperty
+    public CharSequence getContentDescription() {
+        return mContentDescription;
+    }
+
+    /**
+     * Sets the {@link View}'s state description.
+     * <p>
+     * A state description briefly describes the states of the view and is primarily used
+     * for accessibility support to determine how the states of a view should be presented to
+     * the user. It is a supplement to the boolean states (for example, checked/unchecked) and
+     * it is used for customized state description (for example, "wifi, connected, three bars").
+     * State description changes frequently while content description should change less often.
+     * State description should be localized. For android widgets which have default state
+     * descriptions, app developers can call this method to override the state descriptions.
+     * Setting state description to null restores the default behavior.
+     *
+     * @param stateDescription The state description.
+     * @see #getStateDescription()
+     */
+    @RemotableViewMethod
+    public void setStateDescription(@Nullable CharSequence stateDescription) {
+        if (mStateDescription == null) {
+            if (stateDescription == null) {
+                return;
+            }
+        } else if (mStateDescription.equals(stateDescription)) {
+            return;
+        }
+        mStateDescription = stateDescription;
+        if (!TextUtils.isEmpty(stateDescription)
+                && getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+            setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+        }
+        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+            AccessibilityEvent event = AccessibilityEvent.obtain();
+            event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+            event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_STATE_DESCRIPTION);
+            sendAccessibilityEventUnchecked(event);
+        }
+    }
+
+    /**
+     * Sets the {@link View}'s content description.
+     * <p>
+     * A content description briefly describes the view and is primarily used
+     * for accessibility support to determine how a view should be presented to
+     * the user. In the case of a view with no textual representation, such as
+     * {@link android.widget.ImageButton}, a useful content description
+     * explains what the view does. For example, an image button with a phone
+     * icon that is used to place a call may use "Call" as its content
+     * description. An image of a floppy disk that is used to save a file may
+     * use "Save".
+     *
+     * @param contentDescription The content description.
+     * @see #getContentDescription()
+     * @attr ref android.R.styleable#View_contentDescription
+     */
+    @RemotableViewMethod
+    public void setContentDescription(CharSequence contentDescription) {
+        if (mContentDescription == null) {
+            if (contentDescription == null) {
+                return;
+            }
+        } else if (mContentDescription.equals(contentDescription)) {
+            return;
+        }
+        mContentDescription = contentDescription;
+        final boolean nonEmptyDesc = contentDescription != null && contentDescription.length() > 0;
+        if (nonEmptyDesc && getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+            setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+            notifySubtreeAccessibilityStateChangedIfNeeded();
+        } else {
+            notifyViewAccessibilityStateChangedIfNeeded(
+                    AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION);
+        }
+    }
+
+    /**
+     * Sets the id of a view before which this one is visited in accessibility traversal.
+     * A screen-reader must visit the content of this view before the content of the one
+     * it precedes. For example, if view B is set to be before view A, then a screen-reader
+     * will traverse the entire content of B before traversing the entire content of A,
+     * regardles of what traversal strategy it is using.
+     * <p>
+     * Views that do not have specified before/after relationships are traversed in order
+     * determined by the screen-reader.
+     * </p>
+     * <p>
+     * Setting that this view is before a view that is not important for accessibility
+     * or if this view is not important for accessibility will have no effect as the
+     * screen-reader is not aware of unimportant views.
+     * </p>
+     *
+     * @param beforeId The id of a view this one precedes in accessibility traversal.
+     *
+     * @attr ref android.R.styleable#View_accessibilityTraversalBefore
+     *
+     * @see #setImportantForAccessibility(int)
+     */
+    @RemotableViewMethod
+    public void setAccessibilityTraversalBefore(@IdRes int beforeId) {
+        if (mAccessibilityTraversalBeforeId == beforeId) {
+            return;
+        }
+        mAccessibilityTraversalBeforeId = beforeId;
+        notifyViewAccessibilityStateChangedIfNeeded(
+                AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+    }
+
+    /**
+     * Gets the id of a view before which this one is visited in accessibility traversal.
+     *
+     * @return The id of a view this one precedes in accessibility traversal if
+     *         specified, otherwise {@link #NO_ID}.
+     *
+     * @see #setAccessibilityTraversalBefore(int)
+     */
+    @IdRes
+    @InspectableProperty
+    public int getAccessibilityTraversalBefore() {
+        return mAccessibilityTraversalBeforeId;
+    }
+
+    /**
+     * Sets the id of a view after which this one is visited in accessibility traversal.
+     * A screen-reader must visit the content of the other view before the content of this
+     * one. For example, if view B is set to be after view A, then a screen-reader
+     * will traverse the entire content of A before traversing the entire content of B,
+     * regardles of what traversal strategy it is using.
+     * <p>
+     * Views that do not have specified before/after relationships are traversed in order
+     * determined by the screen-reader.
+     * </p>
+     * <p>
+     * Setting that this view is after a view that is not important for accessibility
+     * or if this view is not important for accessibility will have no effect as the
+     * screen-reader is not aware of unimportant views.
+     * </p>
+     *
+     * @param afterId The id of a view this one succedees in accessibility traversal.
+     *
+     * @attr ref android.R.styleable#View_accessibilityTraversalAfter
+     *
+     * @see #setImportantForAccessibility(int)
+     */
+    @RemotableViewMethod
+    public void setAccessibilityTraversalAfter(@IdRes int afterId) {
+        if (mAccessibilityTraversalAfterId == afterId) {
+            return;
+        }
+        mAccessibilityTraversalAfterId = afterId;
+        notifyViewAccessibilityStateChangedIfNeeded(
+                AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+    }
+
+    /**
+     * Gets the id of a view after which this one is visited in accessibility traversal.
+     *
+     * @return The id of a view this one succeedes in accessibility traversal if
+     *         specified, otherwise {@link #NO_ID}.
+     *
+     * @see #setAccessibilityTraversalAfter(int)
+     */
+    @IdRes
+    @InspectableProperty
+    public int getAccessibilityTraversalAfter() {
+        return mAccessibilityTraversalAfterId;
+    }
+
+    /**
+     * Gets the id of a view for which this view serves as a label for
+     * accessibility purposes.
+     *
+     * @return The labeled view id.
+     */
+    @IdRes
+    @ViewDebug.ExportedProperty(category = "accessibility")
+    @InspectableProperty
+    public int getLabelFor() {
+        return mLabelForId;
+    }
+
+    /**
+     * Sets the id of a view for which this view serves as a label for
+     * accessibility purposes.
+     *
+     * @param id The labeled view id.
+     */
+    @RemotableViewMethod
+    public void setLabelFor(@IdRes int id) {
+        if (mLabelForId == id) {
+            return;
+        }
+        mLabelForId = id;
+        if (mLabelForId != View.NO_ID
+                && mID == View.NO_ID) {
+            mID = generateViewId();
+        }
+        notifyViewAccessibilityStateChangedIfNeeded(
+                AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+    }
+
+    /**
+     * Invoked whenever this view loses focus, either by losing window focus or by losing
+     * focus within its window. This method can be used to clear any state tied to the
+     * focus. For instance, if a button is held pressed with the trackball and the window
+     * loses focus, this method can be used to cancel the press.
+     *
+     * Subclasses of View overriding this method should always call super.onFocusLost().
+     *
+     * @see #onFocusChanged(boolean, int, android.graphics.Rect)
+     * @see #onWindowFocusChanged(boolean)
+     *
+     * @hide pending API council approval
+     */
+    @CallSuper
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    protected void onFocusLost() {
+        resetPressedState();
+    }
+
+    private void resetPressedState() {
+        if ((mViewFlags & ENABLED_MASK) == DISABLED) {
+            return;
+        }
+
+        if (isPressed()) {
+            setPressed(false);
+
+            if (!mHasPerformedLongPress) {
+                removeLongPressCallback();
+            }
+        }
+    }
+
+    /**
+     * Returns true if this view has focus
+     *
+     * @return True if this view has focus, false otherwise.
+     */
+    @ViewDebug.ExportedProperty(category = "focus")
+    @InspectableProperty(hasAttributeId = false)
+    public boolean isFocused() {
+        return (mPrivateFlags & PFLAG_FOCUSED) != 0;
+    }
+
+    /**
+     * Find the view in the hierarchy rooted at this view that currently has
+     * focus.
+     *
+     * @return The view that currently has focus, or null if no focused view can
+     *         be found.
+     */
+    public View findFocus() {
+        return (mPrivateFlags & PFLAG_FOCUSED) != 0 ? this : null;
+    }
+
+    /**
+     * Indicates whether this view is one of the set of scrollable containers in
+     * its window.
+     *
+     * @return whether this view is one of the set of scrollable containers in
+     * its window
+     *
+     * @attr ref android.R.styleable#View_isScrollContainer
+     */
+    @InspectableProperty(name = "isScrollContainer")
+    public boolean isScrollContainer() {
+        return (mPrivateFlags & PFLAG_SCROLL_CONTAINER_ADDED) != 0;
+    }
+
+    /**
+     * Change whether this view is one of the set of scrollable containers in
+     * its window.  This will be used to determine whether the window can
+     * resize or must pan when a soft input area is open -- scrollable
+     * containers allow the window to use resize mode since the container
+     * will appropriately shrink.
+     *
+     * @attr ref android.R.styleable#View_isScrollContainer
+     */
+    public void setScrollContainer(boolean isScrollContainer) {
+        if (isScrollContainer) {
+            if (mAttachInfo != null && (mPrivateFlags&PFLAG_SCROLL_CONTAINER_ADDED) == 0) {
+                mAttachInfo.mScrollContainers.add(this);
+                mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
+            }
+            mPrivateFlags |= PFLAG_SCROLL_CONTAINER;
+        } else {
+            if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER_ADDED) != 0) {
+                mAttachInfo.mScrollContainers.remove(this);
+            }
+            mPrivateFlags &= ~(PFLAG_SCROLL_CONTAINER|PFLAG_SCROLL_CONTAINER_ADDED);
+        }
+    }
+
+    /**
+     * Returns the quality of the drawing cache.
+     *
+     * @return One of {@link #DRAWING_CACHE_QUALITY_AUTO},
+     *         {@link #DRAWING_CACHE_QUALITY_LOW}, or {@link #DRAWING_CACHE_QUALITY_HIGH}
+     *
+     * @see #setDrawingCacheQuality(int)
+     * @see #setDrawingCacheEnabled(boolean)
+     * @see #isDrawingCacheEnabled()
+     *
+     * @attr ref android.R.styleable#View_drawingCacheQuality
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
+     */
+    @Deprecated
+    @DrawingCacheQuality
+    @InspectableProperty(enumMapping = {
+            @EnumEntry(value = DRAWING_CACHE_QUALITY_LOW, name = "low"),
+            @EnumEntry(value = DRAWING_CACHE_QUALITY_HIGH, name = "high"),
+            @EnumEntry(value = DRAWING_CACHE_QUALITY_AUTO, name = "auto")
+    })
+    public int getDrawingCacheQuality() {
+        return mViewFlags & DRAWING_CACHE_QUALITY_MASK;
+    }
+
+    /**
+     * Set the drawing cache quality of this view. This value is used only when the
+     * drawing cache is enabled
+     *
+     * @param quality One of {@link #DRAWING_CACHE_QUALITY_AUTO},
+     *        {@link #DRAWING_CACHE_QUALITY_LOW}, or {@link #DRAWING_CACHE_QUALITY_HIGH}
+     *
+     * @see #getDrawingCacheQuality()
+     * @see #setDrawingCacheEnabled(boolean)
+     * @see #isDrawingCacheEnabled()
+     *
+     * @attr ref android.R.styleable#View_drawingCacheQuality
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
+     */
+    @Deprecated
+    public void setDrawingCacheQuality(@DrawingCacheQuality int quality) {
+        setFlags(quality, DRAWING_CACHE_QUALITY_MASK);
+    }
+
+    /**
+     * Returns whether the screen should remain on, corresponding to the current
+     * value of {@link #KEEP_SCREEN_ON}.
+     *
+     * @return Returns true if {@link #KEEP_SCREEN_ON} is set.
+     *
+     * @see #setKeepScreenOn(boolean)
+     *
+     * @attr ref android.R.styleable#View_keepScreenOn
+     */
+    @InspectableProperty
+    public boolean getKeepScreenOn() {
+        return (mViewFlags & KEEP_SCREEN_ON) != 0;
+    }
+
+    /**
+     * Controls whether the screen should remain on, modifying the
+     * value of {@link #KEEP_SCREEN_ON}.
+     *
+     * @param keepScreenOn Supply true to set {@link #KEEP_SCREEN_ON}.
+     *
+     * @see #getKeepScreenOn()
+     *
+     * @attr ref android.R.styleable#View_keepScreenOn
+     */
+    public void setKeepScreenOn(boolean keepScreenOn) {
+        setFlags(keepScreenOn ? KEEP_SCREEN_ON : 0, KEEP_SCREEN_ON);
+    }
+
+    /**
+     * Gets the id of the view to use when the next focus is {@link #FOCUS_LEFT}.
+     * @return The next focus ID, or {@link #NO_ID} if the framework should decide automatically.
+     *
+     * @attr ref android.R.styleable#View_nextFocusLeft
+     */
+    @IdRes
+    @InspectableProperty(name = "nextFocusLeft")
+    public int getNextFocusLeftId() {
+        return mNextFocusLeftId;
+    }
+
+    /**
+     * Sets the id of the view to use when the next focus is {@link #FOCUS_LEFT}.
+     * @param nextFocusLeftId The next focus ID, or {@link #NO_ID} if the framework should
+     * decide automatically.
+     *
+     * @attr ref android.R.styleable#View_nextFocusLeft
+     */
+    public void setNextFocusLeftId(@IdRes int nextFocusLeftId) {
+        mNextFocusLeftId = nextFocusLeftId;
+    }
+
+    /**
+     * Gets the id of the view to use when the next focus is {@link #FOCUS_RIGHT}.
+     * @return The next focus ID, or {@link #NO_ID} if the framework should decide automatically.
+     *
+     * @attr ref android.R.styleable#View_nextFocusRight
+     */
+    @IdRes
+    @InspectableProperty(name = "nextFocusRight")
+    public int getNextFocusRightId() {
+        return mNextFocusRightId;
+    }
+
+    /**
+     * Sets the id of the view to use when the next focus is {@link #FOCUS_RIGHT}.
+     * @param nextFocusRightId The next focus ID, or {@link #NO_ID} if the framework should
+     * decide automatically.
+     *
+     * @attr ref android.R.styleable#View_nextFocusRight
+     */
+    public void setNextFocusRightId(@IdRes int nextFocusRightId) {
+        mNextFocusRightId = nextFocusRightId;
+    }
+
+    /**
+     * Gets the id of the view to use when the next focus is {@link #FOCUS_UP}.
+     * @return The next focus ID, or {@link #NO_ID} if the framework should decide automatically.
+     *
+     * @attr ref android.R.styleable#View_nextFocusUp
+     */
+    @IdRes
+    @InspectableProperty(name = "nextFocusUp")
+    public int getNextFocusUpId() {
+        return mNextFocusUpId;
+    }
+
+    /**
+     * Sets the id of the view to use when the next focus is {@link #FOCUS_UP}.
+     * @param nextFocusUpId The next focus ID, or {@link #NO_ID} if the framework should
+     * decide automatically.
+     *
+     * @attr ref android.R.styleable#View_nextFocusUp
+     */
+    public void setNextFocusUpId(@IdRes int nextFocusUpId) {
+        mNextFocusUpId = nextFocusUpId;
+    }
+
+    /**
+     * Gets the id of the view to use when the next focus is {@link #FOCUS_DOWN}.
+     * @return The next focus ID, or {@link #NO_ID} if the framework should decide automatically.
+     *
+     * @attr ref android.R.styleable#View_nextFocusDown
+     */
+    @IdRes
+    @InspectableProperty(name = "nextFocusDown")
+    public int getNextFocusDownId() {
+        return mNextFocusDownId;
+    }
+
+    /**
+     * Sets the id of the view to use when the next focus is {@link #FOCUS_DOWN}.
+     * @param nextFocusDownId The next focus ID, or {@link #NO_ID} if the framework should
+     * decide automatically.
+     *
+     * @attr ref android.R.styleable#View_nextFocusDown
+     */
+    public void setNextFocusDownId(@IdRes int nextFocusDownId) {
+        mNextFocusDownId = nextFocusDownId;
+    }
+
+    /**
+     * Gets the id of the view to use when the next focus is {@link #FOCUS_FORWARD}.
+     * @return The next focus ID, or {@link #NO_ID} if the framework should decide automatically.
+     *
+     * @attr ref android.R.styleable#View_nextFocusForward
+     */
+    @IdRes
+    @InspectableProperty(name = "nextFocusForward")
+    public int getNextFocusForwardId() {
+        return mNextFocusForwardId;
+    }
+
+    /**
+     * Sets the id of the view to use when the next focus is {@link #FOCUS_FORWARD}.
+     * @param nextFocusForwardId The next focus ID, or {@link #NO_ID} if the framework should
+     * decide automatically.
+     *
+     * @attr ref android.R.styleable#View_nextFocusForward
+     */
+    public void setNextFocusForwardId(@IdRes int nextFocusForwardId) {
+        mNextFocusForwardId = nextFocusForwardId;
+    }
+
+    /**
+     * Gets the id of the root of the next keyboard navigation cluster.
+     * @return The next keyboard navigation cluster ID, or {@link #NO_ID} if the framework should
+     * decide automatically.
+     *
+     * @attr ref android.R.styleable#View_nextClusterForward
+     */
+    @IdRes
+    @InspectableProperty(name = "nextClusterForward")
+    public int getNextClusterForwardId() {
+        return mNextClusterForwardId;
+    }
+
+    /**
+     * Sets the id of the view to use as the root of the next keyboard navigation cluster.
+     * @param nextClusterForwardId The next cluster ID, or {@link #NO_ID} if the framework should
+     * decide automatically.
+     *
+     * @attr ref android.R.styleable#View_nextClusterForward
+     */
+    public void setNextClusterForwardId(@IdRes int nextClusterForwardId) {
+        mNextClusterForwardId = nextClusterForwardId;
+    }
+
+    /**
+     * Returns the visibility of this view and all of its ancestors
+     *
+     * @return True if this view and all of its ancestors are {@link #VISIBLE}
+     */
+    public boolean isShown() {
+        View current = this;
+        //noinspection ConstantConditions
+        do {
+            if ((current.mViewFlags & VISIBILITY_MASK) != VISIBLE) {
+                return false;
+            }
+            ViewParent parent = current.mParent;
+            if (parent == null) {
+                return false; // We are not attached to the view root
+            }
+            if (!(parent instanceof View)) {
+                return true;
+            }
+            current = (View) parent;
+        } while (current != null);
+
+        return false;
+    }
+
+    private boolean detached() {
+        View current = this;
+        //noinspection ConstantConditions
+        do {
+            if ((current.mPrivateFlags4 & PFLAG4_DETACHED) != 0) {
+                return true;
+            }
+            ViewParent parent = current.mParent;
+            if (parent == null) {
+                return false;
+            }
+            if (!(parent instanceof View)) {
+                return false;
+            }
+            current = (View) parent;
+        } while (current != null);
+
+        return false;
+    }
+
+    /**
+     * Called by the view hierarchy when the content insets for a window have
+     * changed, to allow it to adjust its content to fit within those windows.
+     * The content insets tell you the space that the status bar, input method,
+     * and other system windows infringe on the application's window.
+     *
+     * <p>You do not normally need to deal with this function, since the default
+     * window decoration given to applications takes care of applying it to the
+     * content of the window.  If you use {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}
+     * or {@link #SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION} this will not be the case,
+     * and your content can be placed under those system elements.  You can then
+     * use this method within your view hierarchy if you have parts of your UI
+     * which you would like to ensure are not being covered.
+     *
+     * <p>The default implementation of this method simply applies the content
+     * insets to the view's padding, consuming that content (modifying the
+     * insets to be 0), and returning true.  This behavior is off by default, but can
+     * be enabled through {@link #setFitsSystemWindows(boolean)}.
+     *
+     * <p>This function's traversal down the hierarchy is depth-first.  The same content
+     * insets object is propagated down the hierarchy, so any changes made to it will
+     * be seen by all following views (including potentially ones above in
+     * the hierarchy since this is a depth-first traversal).  The first view
+     * that returns true will abort the entire traversal.
+     *
+     * <p>The default implementation works well for a situation where it is
+     * used with a container that covers the entire window, allowing it to
+     * apply the appropriate insets to its content on all edges.  If you need
+     * a more complicated layout (such as two different views fitting system
+     * windows, one on the top of the window, and one on the bottom),
+     * you can override the method and handle the insets however you would like.
+     * Note that the insets provided by the framework are always relative to the
+     * far edges of the window, not accounting for the location of the called view
+     * within that window.  (In fact when this method is called you do not yet know
+     * where the layout will place the view, as it is done before layout happens.)
+     *
+     * <p>Note: unlike many View methods, there is no dispatch phase to this
+     * call.  If you are overriding it in a ViewGroup and want to allow the
+     * call to continue to your children, you must be sure to call the super
+     * implementation.
+     *
+     * <p>Here is a sample layout that makes use of fitting system windows
+     * to have controls for a video view placed inside of the window decorations
+     * that it hides and shows.  This can be used with code like the second
+     * sample (video player) shown in {@link #setSystemUiVisibility(int)}.
+     *
+     * {@sample development/samples/ApiDemos/res/layout/video_player.xml complete}
+     *
+     * @param insets Current content insets of the window.  Prior to
+     * {@link android.os.Build.VERSION_CODES#JELLY_BEAN} you must not modify
+     * the insets or else you and Android will be unhappy.
+     *
+     * @return {@code true} if this view applied the insets and it should not
+     * continue propagating further down the hierarchy, {@code false} otherwise.
+     * @see #getFitsSystemWindows()
+     * @see #setFitsSystemWindows(boolean)
+     * @see #setSystemUiVisibility(int)
+     *
+     * @deprecated As of API 20 use {@link #dispatchApplyWindowInsets(WindowInsets)} to apply
+     * insets to views. Views should override {@link #onApplyWindowInsets(WindowInsets)} or use
+     * {@link #setOnApplyWindowInsetsListener(android.view.View.OnApplyWindowInsetsListener)}
+     * to implement handling their own insets.
+     */
+    @Deprecated
+    protected boolean fitSystemWindows(Rect insets) {
+        if ((mPrivateFlags3 & PFLAG3_APPLYING_INSETS) == 0) {
+            if (insets == null) {
+                // Null insets by definition have already been consumed.
+                // This call cannot apply insets since there are none to apply,
+                // so return false.
+                return false;
+            }
+            // If we're not in the process of dispatching the newer apply insets call,
+            // that means we're not in the compatibility path. Dispatch into the newer
+            // apply insets path and take things from there.
+            try {
+                mPrivateFlags3 |= PFLAG3_FITTING_SYSTEM_WINDOWS;
+                return dispatchApplyWindowInsets(new WindowInsets(insets)).isConsumed();
+            } finally {
+                mPrivateFlags3 &= ~PFLAG3_FITTING_SYSTEM_WINDOWS;
+            }
+        } else {
+            // We're being called from the newer apply insets path.
+            // Perform the standard fallback behavior.
+            return fitSystemWindowsInt(insets);
+        }
+    }
+
+    private boolean fitSystemWindowsInt(Rect insets) {
+        if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) {
+            Rect localInsets = sThreadLocal.get();
+            boolean res = computeFitSystemWindows(insets, localInsets);
+            applyInsets(localInsets);
+            return res;
+        }
+        return false;
+    }
+
+    private void applyInsets(Rect insets) {
+        mUserPaddingStart = UNDEFINED_PADDING;
+        mUserPaddingEnd = UNDEFINED_PADDING;
+        mUserPaddingLeftInitial = insets.left;
+        mUserPaddingRightInitial = insets.right;
+        internalSetPadding(insets.left, insets.top, insets.right, insets.bottom);
+    }
+
+    /**
+     * Called when the view should apply {@link WindowInsets} according to its internal policy.
+     *
+     * <p>This method should be overridden by views that wish to apply a policy different from or
+     * in addition to the default behavior. Clients that wish to force a view subtree
+     * to apply insets should call {@link #dispatchApplyWindowInsets(WindowInsets)}.</p>
+     *
+     * <p>Clients may supply an {@link OnApplyWindowInsetsListener} to a view. If one is set
+     * it will be called during dispatch instead of this method. The listener may optionally
+     * call this method from its own implementation if it wishes to apply the view's default
+     * insets policy in addition to its own.</p>
+     *
+     * <p>Implementations of this method should either return the insets parameter unchanged
+     * or a new {@link WindowInsets} cloned from the supplied insets with any insets consumed
+     * that this view applied itself. This allows new inset types added in future platform
+     * versions to pass through existing implementations unchanged without being erroneously
+     * consumed.</p>
+     *
+     * <p>By default if a view's {@link #setFitsSystemWindows(boolean) fitsSystemWindows}
+     * property is set then the view will consume the system window insets and apply them
+     * as padding for the view.</p>
+     *
+     * @param insets Insets to apply
+     * @return The supplied insets with any applied insets consumed
+     */
+    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+        if ((mPrivateFlags4 & PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS) != 0
+                && (mViewFlags & FITS_SYSTEM_WINDOWS) != 0) {
+            return onApplyFrameworkOptionalFitSystemWindows(insets);
+        }
+        if ((mPrivateFlags3 & PFLAG3_FITTING_SYSTEM_WINDOWS) == 0) {
+            // We weren't called from within a direct call to fitSystemWindows,
+            // call into it as a fallback in case we're in a class that overrides it
+            // and has logic to perform.
+            if (fitSystemWindows(insets.getSystemWindowInsetsAsRect())) {
+                return insets.consumeSystemWindowInsets();
+            }
+        } else {
+            // We were called from within a direct call to fitSystemWindows.
+            if (fitSystemWindowsInt(insets.getSystemWindowInsetsAsRect())) {
+                return insets.consumeSystemWindowInsets();
+            }
+        }
+        return insets;
+    }
+
+    private WindowInsets onApplyFrameworkOptionalFitSystemWindows(WindowInsets insets) {
+        Rect localInsets = sThreadLocal.get();
+        WindowInsets result = computeSystemWindowInsets(insets, localInsets);
+        applyInsets(localInsets);
+        return result;
+    }
+
+    /**
+     * Set an {@link OnApplyWindowInsetsListener} to take over the policy for applying
+     * window insets to this view. The listener's
+     * {@link OnApplyWindowInsetsListener#onApplyWindowInsets(View, WindowInsets) onApplyWindowInsets}
+     * method will be called instead of the view's
+     * {@link #onApplyWindowInsets(WindowInsets) onApplyWindowInsets} method.
+     *
+     * @param listener Listener to set
+     *
+     * @see #onApplyWindowInsets(WindowInsets)
+     */
+    public void setOnApplyWindowInsetsListener(OnApplyWindowInsetsListener listener) {
+        getListenerInfo().mOnApplyWindowInsetsListener = listener;
+    }
+
+    /**
+     * Request to apply the given window insets to this view or another view in its subtree.
+     *
+     * <p>This method should be called by clients wishing to apply insets corresponding to areas
+     * obscured by window decorations or overlays. This can include the status and navigation bars,
+     * action bars, input methods and more. New inset categories may be added in the future.
+     * The method returns the insets provided minus any that were applied by this view or its
+     * children.</p>
+     *
+     * <p>Clients wishing to provide custom behavior should override the
+     * {@link #onApplyWindowInsets(WindowInsets)} method or alternatively provide a
+     * {@link OnApplyWindowInsetsListener} via the
+     * {@link #setOnApplyWindowInsetsListener(View.OnApplyWindowInsetsListener) setOnApplyWindowInsetsListener}
+     * method.</p>
+     *
+     * <p>This method replaces the older {@link #fitSystemWindows(Rect) fitSystemWindows} method.
+     * </p>
+     *
+     * @param insets Insets to apply
+     * @return The provided insets minus the insets that were consumed
+     */
+    public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
+        try {
+            mPrivateFlags3 |= PFLAG3_APPLYING_INSETS;
+            if (mListenerInfo != null && mListenerInfo.mOnApplyWindowInsetsListener != null) {
+                return mListenerInfo.mOnApplyWindowInsetsListener.onApplyWindowInsets(this, insets);
+            } else {
+                return onApplyWindowInsets(insets);
+            }
+        } finally {
+            mPrivateFlags3 &= ~PFLAG3_APPLYING_INSETS;
+        }
+    }
+
+    /**
+     * Sets a {@link WindowInsetsAnimation.Callback} to be notified about animations of windows that
+     * cause insets.
+     * <p>
+     * The callback's {@link WindowInsetsAnimation.Callback#getDispatchMode()
+     * dispatch mode} will affect whether animation callbacks are dispatched to the children of
+     * this view.
+     * </p>
+     * @param callback The callback to set.
+     */
+    public void setWindowInsetsAnimationCallback(
+            @Nullable WindowInsetsAnimation.Callback callback) {
+        getListenerInfo().mWindowInsetsAnimationCallback = callback;
+    }
+
+    /**
+     * @return {@code true} if any {@link WindowInsetsAnimation.Callback} is registered on the view
+     *         or view tree of the sub-hierarchy {@code false} otherwise.
+     * @hide
+     */
+    public boolean hasWindowInsetsAnimationCallback() {
+        return getListenerInfo().mWindowInsetsAnimationCallback != null;
+    }
+
+    /**
+     * Dispatches {@link WindowInsetsAnimation.Callback#onPrepare(WindowInsetsAnimation)}
+     * when Window Insets animation is being prepared.
+     * @param animation current animation
+     *
+     * @see WindowInsetsAnimation.Callback#onPrepare(WindowInsetsAnimation)
+     */
+    public void dispatchWindowInsetsAnimationPrepare(
+            @NonNull WindowInsetsAnimation animation) {
+        if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
+            mListenerInfo.mWindowInsetsAnimationCallback.onPrepare(animation);
+        }
+    }
+
+    /**
+     * Dispatches {@link WindowInsetsAnimation.Callback#onStart(WindowInsetsAnimation, Bounds)}
+     * when Window Insets animation is started.
+     * @param animation current animation
+     * @param bounds the upper and lower {@link Bounds} that provides range of
+     *  {@link WindowInsetsAnimation}.
+     * @return the upper and lower {@link Bounds}.
+     */
+    @NonNull
+    public Bounds dispatchWindowInsetsAnimationStart(
+            @NonNull WindowInsetsAnimation animation, @NonNull Bounds bounds) {
+        if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
+            return mListenerInfo.mWindowInsetsAnimationCallback.onStart(animation, bounds);
+        }
+        return bounds;
+    }
+
+    /**
+     * Dispatches {@link WindowInsetsAnimation.Callback#onProgress(WindowInsets, List)}
+     * when Window Insets animation makes progress.
+     * @param insets The current {@link WindowInsets}.
+     * @param runningAnimations The currently running {@link WindowInsetsAnimation}s.
+     * @return current {@link WindowInsets}.
+     */
+    @NonNull
+    public WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull WindowInsets insets,
+            @NonNull List<WindowInsetsAnimation> runningAnimations) {
+        if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
+            return mListenerInfo.mWindowInsetsAnimationCallback.onProgress(insets,
+                    runningAnimations);
+        } else {
+            return insets;
+        }
+    }
+
+    /**
+     * Dispatches {@link WindowInsetsAnimation.Callback#onEnd(WindowInsetsAnimation)}
+     * when Window Insets animation ends.
+     * @param animation The current ongoing {@link WindowInsetsAnimation}.
+     */
+    public void dispatchWindowInsetsAnimationEnd(@NonNull WindowInsetsAnimation animation) {
+        if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
+            mListenerInfo.mWindowInsetsAnimationCallback.onEnd(animation);
+        }
+    }
+
+    /**
+     * Sets a list of areas within this view's post-layout coordinate space where the system
+     * should not intercept touch or other pointing device gestures. <em>This method should
+     * be called by {@link #onLayout(boolean, int, int, int, int)} or {@link #onDraw(Canvas)}.</em>
+     *
+     * <p>Use this to tell the system which specific sub-areas of a view need to receive gesture
+     * input in order to function correctly in the presence of global system gestures that may
+     * conflict. For example, if the system wishes to capture swipe-in-from-screen-edge gestures
+     * to provide system-level navigation functionality, a view such as a navigation drawer
+     * container can mark the left (or starting) edge of itself as requiring gesture capture
+     * priority using this API. The system may then choose to relax its own gesture recognition
+     * to allow the app to consume the user's gesture. It is not necessary for an app to register
+     * exclusion rects for broadly spanning regions such as the entirety of a
+     * <code>ScrollView</code> or for simple press and release click targets such as
+     * <code>Button</code>. Mark an exclusion rect when interacting with a view requires
+     * a precision touch gesture in a small area in either the X or Y dimension, such as
+     * an edge swipe or dragging a <code>SeekBar</code> thumb.</p>
+     *
+     * <p>Note: the system will put a limit of <code>200dp</code> on the vertical extent of the
+     * exclusions it takes into account. The limit does not apply while the navigation
+     * bar is {@link #SYSTEM_UI_FLAG_IMMERSIVE_STICKY stickily} hidden, nor to the
+     * {@link android.inputmethodservice.InputMethodService input method} and
+     * {@link Intent#CATEGORY_HOME home activity}.
+     * </p>
+     *
+     * @param rects A list of precision gesture regions that this view needs to function correctly
+     */
+    public void setSystemGestureExclusionRects(@NonNull List<Rect> rects) {
+        if (rects.isEmpty() && mListenerInfo == null) return;
+
+        final ListenerInfo info = getListenerInfo();
+        if (info.mSystemGestureExclusionRects != null) {
+            info.mSystemGestureExclusionRects.clear();
+            info.mSystemGestureExclusionRects.addAll(rects);
+        } else {
+            info.mSystemGestureExclusionRects = new ArrayList<>(rects);
+        }
+        if (rects.isEmpty()) {
+            if (info.mPositionUpdateListener != null) {
+                mRenderNode.removePositionUpdateListener(info.mPositionUpdateListener);
+            }
+        } else {
+            if (info.mPositionUpdateListener == null) {
+                info.mPositionUpdateListener = new RenderNode.PositionUpdateListener() {
+                    @Override
+                    public void positionChanged(long n, int l, int t, int r, int b) {
+                        postUpdateSystemGestureExclusionRects();
+                    }
+
+                    @Override
+                    public void positionLost(long frameNumber) {
+                        postUpdateSystemGestureExclusionRects();
+                    }
+                };
+                mRenderNode.addPositionUpdateListener(info.mPositionUpdateListener);
+            }
+        }
+        postUpdateSystemGestureExclusionRects();
+    }
+
+    /**
+     * WARNING: this can be called by a hwui worker thread, not just the UI thread!
+     */
+    void postUpdateSystemGestureExclusionRects() {
+        // Potentially racey from a background thread. It's ok if it's not perfect.
+        final Handler h = getHandler();
+        if (h != null) {
+            h.postAtFrontOfQueue(this::updateSystemGestureExclusionRects);
+        }
+    }
+
+    void updateSystemGestureExclusionRects() {
+        final AttachInfo ai = mAttachInfo;
+        if (ai != null) {
+            ai.mViewRootImpl.updateSystemGestureExclusionRectsForView(this);
+        }
+    }
+
+    /**
+     * Retrieve the list of areas within this view's post-layout coordinate space where the system
+     * should not intercept touch or other pointing device gestures.
+     *
+     * <p>Do not modify the returned list.</p>
+     *
+     * @return the list set by {@link #setSystemGestureExclusionRects(List)}
+     */
+    @NonNull
+    public List<Rect> getSystemGestureExclusionRects() {
+        final ListenerInfo info = mListenerInfo;
+        if (info != null) {
+            final List<Rect> list = info.mSystemGestureExclusionRects;
+            if (list != null) {
+                return list;
+            }
+        }
+        return Collections.emptyList();
+    }
+
+    /**
+     * Compute the view's coordinate within the surface.
+     *
+     * <p>Computes the coordinates of this view in its surface. The argument
+     * must be an array of two integers. After the method returns, the array
+     * contains the x and y location in that order.</p>
+     *
+     * @param location an array of two integers in which to hold the coordinates
+     */
+    public void getLocationInSurface(@NonNull @Size(2) int[] location) {
+        getLocationInWindow(location);
+        if (mAttachInfo != null && mAttachInfo.mViewRootImpl != null) {
+            location[0] += mAttachInfo.mViewRootImpl.mWindowAttributes.surfaceInsets.left;
+            location[1] += mAttachInfo.mViewRootImpl.mWindowAttributes.surfaceInsets.top;
+        }
+    }
+
+    /**
+     * Provide original WindowInsets that are dispatched to the view hierarchy. The insets are
+     * only available if the view is attached.
+     *
+     * @return WindowInsets from the top of the view hierarchy or null if View is detached
+     */
+    public WindowInsets getRootWindowInsets() {
+        if (mAttachInfo != null) {
+            return mAttachInfo.mViewRootImpl.getWindowInsets(false /* forceConstruct */);
+        }
+        return null;
+    }
+
+    /**
+     * Retrieves the single {@link WindowInsetsController} of the window this view is attached to.
+     *
+     * @return The {@link WindowInsetsController} or {@code null} if the view is neither attached to
+     *         a window nor a view tree with a decor.
+     * @see Window#getInsetsController()
+     */
+    public @Nullable WindowInsetsController getWindowInsetsController() {
+        if (mAttachInfo != null) {
+            return mAttachInfo.mViewRootImpl.getInsetsController();
+        }
+        ViewParent parent = getParent();
+        if (parent instanceof View) {
+            return ((View) parent).getWindowInsetsController();
+        } else if (parent instanceof ViewRootImpl) {
+            // Between WindowManager.addView() and the first traversal AttachInfo isn't set yet.
+            return ((ViewRootImpl) parent).getInsetsController();
+        }
+        return null;
+    }
+
+    /**
+     * @hide Compute the insets that should be consumed by this view and the ones
+     * that should propagate to those under it.
+     *
+     * Note: This is used by appcompat's ActionBarOverlayLayout through reflection.
+     *
+     * @param inoutInsets the insets given to this view
+     * @param outLocalInsets the insets that should be applied to this view
+     * @deprecated use {@link #computeSystemWindowInsets}
+     * @return
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    protected boolean computeFitSystemWindows(Rect inoutInsets, Rect outLocalInsets) {
+        WindowInsets innerInsets = computeSystemWindowInsets(new WindowInsets(inoutInsets),
+                outLocalInsets);
+        inoutInsets.set(innerInsets.getSystemWindowInsetsAsRect());
+        return innerInsets.isSystemWindowInsetsConsumed();
+    }
+
+    /**
+     * Compute insets that should be consumed by this view and the ones that should propagate
+     * to those under it.
+     *
+     * @param in Insets currently being processed by this View, likely received as a parameter
+     *           to {@link #onApplyWindowInsets(WindowInsets)}.
+     * @param outLocalInsets A Rect that will receive the insets that should be consumed
+     *                       by this view
+     * @return Insets that should be passed along to views under this one
+     */
+    public WindowInsets computeSystemWindowInsets(WindowInsets in, Rect outLocalInsets) {
+        boolean isOptionalFitSystemWindows = (mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS) != 0
+                || (mPrivateFlags4 & PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS) != 0;
+        if (isOptionalFitSystemWindows && mAttachInfo != null) {
+            OnContentApplyWindowInsetsListener listener =
+                    mAttachInfo.mContentOnApplyWindowInsetsListener;
+            if (listener == null) {
+                // The application wants to take care of fitting system window for
+                // the content.
+                outLocalInsets.setEmpty();
+                return in;
+            }
+            Pair<Insets, WindowInsets> result = listener.onContentApplyWindowInsets(this, in);
+            outLocalInsets.set(result.first.toRect());
+            return result.second;
+        } else {
+            outLocalInsets.set(in.getSystemWindowInsetsAsRect());
+            return in.consumeSystemWindowInsets().inset(outLocalInsets);
+        }
+    }
+
+    /**
+     * Sets whether or not this view should account for system screen decorations
+     * such as the status bar and inset its content; that is, controlling whether
+     * the default implementation of {@link #fitSystemWindows(Rect)} will be
+     * executed.  See that method for more details.
+     *
+     * <p>Note that if you are providing your own implementation of
+     * {@link #fitSystemWindows(Rect)}, then there is no need to set this
+     * flag to true -- your implementation will be overriding the default
+     * implementation that checks this flag.
+     *
+     * @param fitSystemWindows If true, then the default implementation of
+     * {@link #fitSystemWindows(Rect)} will be executed.
+     *
+     * @attr ref android.R.styleable#View_fitsSystemWindows
+     * @see #getFitsSystemWindows()
+     * @see #fitSystemWindows(Rect)
+     * @see #setSystemUiVisibility(int)
+     */
+    public void setFitsSystemWindows(boolean fitSystemWindows) {
+        setFlags(fitSystemWindows ? FITS_SYSTEM_WINDOWS : 0, FITS_SYSTEM_WINDOWS);
+    }
+
+    /**
+     * Check for state of {@link #setFitsSystemWindows(boolean)}. If this method
+     * returns {@code true}, the default implementation of {@link #fitSystemWindows(Rect)}
+     * will be executed.
+     *
+     * @return {@code true} if the default implementation of
+     * {@link #fitSystemWindows(Rect)} will be executed.
+     *
+     * @attr ref android.R.styleable#View_fitsSystemWindows
+     * @see #setFitsSystemWindows(boolean)
+     * @see #fitSystemWindows(Rect)
+     * @see #setSystemUiVisibility(int)
+     */
+    @ViewDebug.ExportedProperty
+    @InspectableProperty
+    public boolean getFitsSystemWindows() {
+        return (mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS;
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public boolean fitsSystemWindows() {
+        return getFitsSystemWindows();
+    }
+
+    /**
+     * Ask that a new dispatch of {@link #fitSystemWindows(Rect)} be performed.
+     * @deprecated Use {@link #requestApplyInsets()} for newer platform versions.
+     */
+    @Deprecated
+    public void requestFitSystemWindows() {
+        if (mParent != null) {
+            mParent.requestFitSystemWindows();
+        }
+    }
+
+    /**
+     * Ask that a new dispatch of {@link #onApplyWindowInsets(WindowInsets)} be performed.
+     */
+    public void requestApplyInsets() {
+        requestFitSystemWindows();
+    }
+
+    /**
+     * @see #OPTIONAL_FITS_SYSTEM_WINDOWS
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void makeOptionalFitsSystemWindows() {
+        setFlags(OPTIONAL_FITS_SYSTEM_WINDOWS, OPTIONAL_FITS_SYSTEM_WINDOWS);
+    }
+
+    /**
+     * @see #PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS
+     * @hide
+     */
+    public void makeFrameworkOptionalFitsSystemWindows() {
+        mPrivateFlags4 |= PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS;
+    }
+
+    /**
+     * @hide
+     */
+    public boolean isFrameworkOptionalFitsSystemWindows() {
+        return (mPrivateFlags4 & PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS) != 0;
+    }
+
+    /**
+     * Returns the visibility status for this view.
+     *
+     * @return One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
+     * @attr ref android.R.styleable#View_visibility
+     */
+    @ViewDebug.ExportedProperty(mapping = {
+        @ViewDebug.IntToString(from = VISIBLE,   to = "VISIBLE"),
+        @ViewDebug.IntToString(from = INVISIBLE, to = "INVISIBLE"),
+        @ViewDebug.IntToString(from = GONE,      to = "GONE")
+    })
+    @InspectableProperty(enumMapping = {
+            @EnumEntry(value = VISIBLE, name = "visible"),
+            @EnumEntry(value = INVISIBLE, name = "invisible"),
+            @EnumEntry(value = GONE, name = "gone")
+    })
+    @Visibility
+    public int getVisibility() {
+        return mViewFlags & VISIBILITY_MASK;
+    }
+
+    /**
+     * Set the visibility state of this view.
+     *
+     * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
+     * @attr ref android.R.styleable#View_visibility
+     */
+    @RemotableViewMethod
+    public void setVisibility(@Visibility int visibility) {
+        setFlags(visibility, VISIBILITY_MASK);
+    }
+
+    /**
+     * Returns the enabled status for this view. The interpretation of the
+     * enabled state varies by subclass.
+     *
+     * @return True if this view is enabled, false otherwise.
+     */
+    @ViewDebug.ExportedProperty
+    @InspectableProperty
+    public boolean isEnabled() {
+        return (mViewFlags & ENABLED_MASK) == ENABLED;
+    }
+
+    /**
+     * Set the enabled state of this view. The interpretation of the enabled
+     * state varies by subclass.
+     *
+     * @param enabled True if this view is enabled, false otherwise.
+     */
+    @RemotableViewMethod
+    public void setEnabled(boolean enabled) {
+        if (enabled == isEnabled()) return;
+
+        setFlags(enabled ? ENABLED : DISABLED, ENABLED_MASK);
+
+        /*
+         * The View most likely has to change its appearance, so refresh
+         * the drawable state.
+         */
+        refreshDrawableState();
+
+        // Invalidate too, since the default behavior for views is to be
+        // be drawn at 50% alpha rather than to change the drawable.
+        invalidate(true);
+
+        if (!enabled) {
+            cancelPendingInputEvents();
+        }
+    }
+
+    /**
+     * Set whether this view can receive the focus.
+     * <p>
+     * Setting this to false will also ensure that this view is not focusable
+     * in touch mode.
+     *
+     * @param focusable If true, this view can receive the focus.
+     *
+     * @see #setFocusableInTouchMode(boolean)
+     * @see #setFocusable(int)
+     * @attr ref android.R.styleable#View_focusable
+     */
+    @RemotableViewMethod
+    public void setFocusable(boolean focusable) {
+        setFocusable(focusable ? FOCUSABLE : NOT_FOCUSABLE);
+    }
+
+    /**
+     * Sets whether this view can receive focus.
+     * <p>
+     * Setting this to {@link #FOCUSABLE_AUTO} tells the framework to determine focusability
+     * automatically based on the view's interactivity. This is the default.
+     * <p>
+     * Setting this to NOT_FOCUSABLE will ensure that this view is also not focusable
+     * in touch mode.
+     *
+     * @param focusable One of {@link #NOT_FOCUSABLE}, {@link #FOCUSABLE},
+     *                  or {@link #FOCUSABLE_AUTO}.
+     * @see #setFocusableInTouchMode(boolean)
+     * @attr ref android.R.styleable#View_focusable
+     */
+    @RemotableViewMethod
+    public void setFocusable(@Focusable int focusable) {
+        if ((focusable & (FOCUSABLE_AUTO | FOCUSABLE)) == 0) {
+            setFlags(0, FOCUSABLE_IN_TOUCH_MODE);
+        }
+        setFlags(focusable, FOCUSABLE_MASK);
+    }
+
+    /**
+     * Set whether this view can receive focus while in touch mode.
+     *
+     * Setting this to true will also ensure that this view is focusable.
+     *
+     * @param focusableInTouchMode If true, this view can receive the focus while
+     *   in touch mode.
+     *
+     * @see #setFocusable(boolean)
+     * @attr ref android.R.styleable#View_focusableInTouchMode
+     */
+    @RemotableViewMethod
+    public void setFocusableInTouchMode(boolean focusableInTouchMode) {
+        // Focusable in touch mode should always be set before the focusable flag
+        // otherwise, setting the focusable flag will trigger a focusableViewAvailable()
+        // which, in touch mode, will not successfully request focus on this view
+        // because the focusable in touch mode flag is not set
+        setFlags(focusableInTouchMode ? FOCUSABLE_IN_TOUCH_MODE : 0, FOCUSABLE_IN_TOUCH_MODE);
+
+        // Clear FOCUSABLE_AUTO if set.
+        if (focusableInTouchMode) {
+            // Clears FOCUSABLE_AUTO if set.
+            setFlags(FOCUSABLE, FOCUSABLE_MASK);
+        }
+    }
+
+    /**
+     * Sets the hints that help an {@link android.service.autofill.AutofillService} determine how
+     * to autofill the view with the user's data.
+     *
+     * <p>Typically, there is only one way to autofill a view, but there could be more than one.
+     * For example, if the application accepts either an username or email address to identify
+     * an user.
+     *
+     * <p>These hints are not validated by the Android System, but passed "as is" to the service.
+     * Hence, they can have any value, but it's recommended to use the {@code AUTOFILL_HINT_}
+     * constants such as:
+     * {@link #AUTOFILL_HINT_USERNAME}, {@link #AUTOFILL_HINT_PASSWORD},
+     * {@link #AUTOFILL_HINT_EMAIL_ADDRESS},
+     * {@link #AUTOFILL_HINT_NAME},
+     * {@link #AUTOFILL_HINT_PHONE},
+     * {@link #AUTOFILL_HINT_POSTAL_ADDRESS}, {@link #AUTOFILL_HINT_POSTAL_CODE},
+     * {@link #AUTOFILL_HINT_CREDIT_CARD_NUMBER}, {@link #AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE},
+     * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE},
+     * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY},
+     * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH} or
+     * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR}.
+     *
+     * @param autofillHints The autofill hints to set. If the array is emtpy, {@code null} is set.
+     * @attr ref android.R.styleable#View_autofillHints
+     */
+    public void setAutofillHints(@Nullable String... autofillHints) {
+        if (autofillHints == null || autofillHints.length == 0) {
+            mAutofillHints = null;
+        } else {
+            mAutofillHints = autofillHints;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    public void setAutofilled(boolean isAutofilled, boolean hideHighlight) {
+        boolean wasChanged = isAutofilled != isAutofilled();
+
+        if (wasChanged) {
+            if (isAutofilled) {
+                mPrivateFlags3 |= PFLAG3_IS_AUTOFILLED;
+            } else {
+                mPrivateFlags3 &= ~PFLAG3_IS_AUTOFILLED;
+            }
+
+            if (hideHighlight) {
+                mPrivateFlags4 |= PFLAG4_AUTOFILL_HIDE_HIGHLIGHT;
+            } else {
+                mPrivateFlags4 &= ~PFLAG4_AUTOFILL_HIDE_HIGHLIGHT;
+            }
+
+            invalidate();
+        }
+    }
+
+    /**
+     * Set whether this view should have sound effects enabled for events such as
+     * clicking and touching.
+     *
+     * <p>You may wish to disable sound effects for a view if you already play sounds,
+     * for instance, a dial key that plays dtmf tones.
+     *
+     * @param soundEffectsEnabled whether sound effects are enabled for this view.
+     * @see #isSoundEffectsEnabled()
+     * @see #playSoundEffect(int)
+     * @attr ref android.R.styleable#View_soundEffectsEnabled
+     */
+    public void setSoundEffectsEnabled(boolean soundEffectsEnabled) {
+        setFlags(soundEffectsEnabled ? SOUND_EFFECTS_ENABLED: 0, SOUND_EFFECTS_ENABLED);
+    }
+
+    /**
+     * @return whether this view should have sound effects enabled for events such as
+     *     clicking and touching.
+     *
+     * @see #setSoundEffectsEnabled(boolean)
+     * @see #playSoundEffect(int)
+     * @attr ref android.R.styleable#View_soundEffectsEnabled
+     */
+    @ViewDebug.ExportedProperty
+    @InspectableProperty
+    public boolean isSoundEffectsEnabled() {
+        return SOUND_EFFECTS_ENABLED == (mViewFlags & SOUND_EFFECTS_ENABLED);
+    }
+
+    /**
+     * Set whether this view should have haptic feedback for events such as
+     * long presses.
+     *
+     * <p>You may wish to disable haptic feedback if your view already controls
+     * its own haptic feedback.
+     *
+     * @param hapticFeedbackEnabled whether haptic feedback enabled for this view.
+     * @see #isHapticFeedbackEnabled()
+     * @see #performHapticFeedback(int)
+     * @attr ref android.R.styleable#View_hapticFeedbackEnabled
+     */
+    public void setHapticFeedbackEnabled(boolean hapticFeedbackEnabled) {
+        setFlags(hapticFeedbackEnabled ? HAPTIC_FEEDBACK_ENABLED: 0, HAPTIC_FEEDBACK_ENABLED);
+    }
+
+    /**
+     * @return whether this view should have haptic feedback enabled for events
+     * long presses.
+     *
+     * @see #setHapticFeedbackEnabled(boolean)
+     * @see #performHapticFeedback(int)
+     * @attr ref android.R.styleable#View_hapticFeedbackEnabled
+     */
+    @ViewDebug.ExportedProperty
+    @InspectableProperty
+    public boolean isHapticFeedbackEnabled() {
+        return HAPTIC_FEEDBACK_ENABLED == (mViewFlags & HAPTIC_FEEDBACK_ENABLED);
+    }
+
+    /**
+     * Returns the layout direction for this view.
+     *
+     * @return One of {@link #LAYOUT_DIRECTION_LTR},
+     *   {@link #LAYOUT_DIRECTION_RTL},
+     *   {@link #LAYOUT_DIRECTION_INHERIT} or
+     *   {@link #LAYOUT_DIRECTION_LOCALE}.
+     *
+     * @attr ref android.R.styleable#View_layoutDirection
+     *
+     * @hide
+     */
+    @ViewDebug.ExportedProperty(category = "layout", mapping = {
+        @ViewDebug.IntToString(from = LAYOUT_DIRECTION_LTR,     to = "LTR"),
+        @ViewDebug.IntToString(from = LAYOUT_DIRECTION_RTL,     to = "RTL"),
+        @ViewDebug.IntToString(from = LAYOUT_DIRECTION_INHERIT, to = "INHERIT"),
+        @ViewDebug.IntToString(from = LAYOUT_DIRECTION_LOCALE,  to = "LOCALE")
+    })
+    @InspectableProperty(hasAttributeId = false, enumMapping = {
+            @EnumEntry(value = LAYOUT_DIRECTION_LTR, name = "ltr"),
+            @EnumEntry(value = LAYOUT_DIRECTION_RTL, name = "rtl"),
+            @EnumEntry(value = LAYOUT_DIRECTION_INHERIT, name = "inherit"),
+            @EnumEntry(value = LAYOUT_DIRECTION_LOCALE, name = "locale")
+    })
+    @LayoutDir
+    public int getRawLayoutDirection() {
+        return (mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_MASK) >> PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT;
+    }
+
+    /**
+     * Set the layout direction for this view. This will propagate a reset of layout direction
+     * resolution to the view's children and resolve layout direction for this view.
+     *
+     * @param layoutDirection the layout direction to set. Should be one of:
+     *
+     * {@link #LAYOUT_DIRECTION_LTR},
+     * {@link #LAYOUT_DIRECTION_RTL},
+     * {@link #LAYOUT_DIRECTION_INHERIT},
+     * {@link #LAYOUT_DIRECTION_LOCALE}.
+     *
+     * Resolution will be done if the value is set to LAYOUT_DIRECTION_INHERIT. The resolution
+     * proceeds up the parent chain of the view to get the value. If there is no parent, then it
+     * will return the default {@link #LAYOUT_DIRECTION_LTR}.
+     *
+     * @attr ref android.R.styleable#View_layoutDirection
+     */
+    @RemotableViewMethod
+    public void setLayoutDirection(@LayoutDir int layoutDirection) {
+        if (getRawLayoutDirection() != layoutDirection) {
+            // Reset the current layout direction and the resolved one
+            mPrivateFlags2 &= ~PFLAG2_LAYOUT_DIRECTION_MASK;
+            resetRtlProperties();
+            // Set the new layout direction (filtered)
+            mPrivateFlags2 |=
+                    ((layoutDirection << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) & PFLAG2_LAYOUT_DIRECTION_MASK);
+            // We need to resolve all RTL properties as they all depend on layout direction
+            resolveRtlPropertiesIfNeeded();
+            requestLayout();
+            invalidate(true);
+        }
+    }
+
+    /**
+     * Returns the resolved layout direction for this view.
+     *
+     * @return {@link #LAYOUT_DIRECTION_RTL} if the layout direction is RTL or returns
+     * {@link #LAYOUT_DIRECTION_LTR} if the layout direction is not RTL.
+     *
+     * For compatibility, this will return {@link #LAYOUT_DIRECTION_LTR} if API version
+     * is lower than {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}.
+     *
+     * @attr ref android.R.styleable#View_layoutDirection
+     */
+    @ViewDebug.ExportedProperty(category = "layout", mapping = {
+        @ViewDebug.IntToString(from = LAYOUT_DIRECTION_LTR, to = "RESOLVED_DIRECTION_LTR"),
+        @ViewDebug.IntToString(from = LAYOUT_DIRECTION_RTL, to = "RESOLVED_DIRECTION_RTL")
+    })
+    @InspectableProperty(enumMapping = {
+            @EnumEntry(value = LAYOUT_DIRECTION_LTR, name = "ltr"),
+            @EnumEntry(value = LAYOUT_DIRECTION_RTL, name = "rtl")
+    })
+    @ResolvedLayoutDir
+    public int getLayoutDirection() {
+        final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
+        if (targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED;
+            return LAYOUT_DIRECTION_RESOLVED_DEFAULT;
+        }
+        return ((mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) ==
+                PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) ? LAYOUT_DIRECTION_RTL : LAYOUT_DIRECTION_LTR;
+    }
+
+    /**
+     * Indicates whether or not this view's layout is right-to-left. This is resolved from
+     * layout attribute and/or the inherited value from the parent
+     *
+     * @return true if the layout is right-to-left.
+     *
+     * @hide
+     */
+    @ViewDebug.ExportedProperty(category = "layout")
+    @UnsupportedAppUsage
+    public boolean isLayoutRtl() {
+        return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
+    }
+
+    /**
+     * Indicates whether the view is currently tracking transient state that the
+     * app should not need to concern itself with saving and restoring, but that
+     * the framework should take special note to preserve when possible.
+     *
+     * <p>A view with transient state cannot be trivially rebound from an external
+     * data source, such as an adapter binding item views in a list. This may be
+     * because the view is performing an animation, tracking user selection
+     * of content, or similar.</p>
+     *
+     * @return true if the view has transient state
+     */
+    @ViewDebug.ExportedProperty(category = "layout")
+    public boolean hasTransientState() {
+        return (mPrivateFlags2 & PFLAG2_HAS_TRANSIENT_STATE) == PFLAG2_HAS_TRANSIENT_STATE;
+    }
+
+    /**
+     * Set whether this view is currently tracking transient state that the
+     * framework should attempt to preserve when possible. This flag is reference counted,
+     * so every call to setHasTransientState(true) should be paired with a later call
+     * to setHasTransientState(false).
+     *
+     * <p>A view with transient state cannot be trivially rebound from an external
+     * data source, such as an adapter binding item views in a list. This may be
+     * because the view is performing an animation, tracking user selection
+     * of content, or similar.</p>
+     *
+     * @param hasTransientState true if this view has transient state
+     */
+    public void setHasTransientState(boolean hasTransientState) {
+        final boolean oldHasTransientState = hasTransientState();
+        mTransientStateCount = hasTransientState ? mTransientStateCount + 1 :
+                mTransientStateCount - 1;
+        if (mTransientStateCount < 0) {
+            mTransientStateCount = 0;
+            Log.e(VIEW_LOG_TAG, "hasTransientState decremented below 0: " +
+                    "unmatched pair of setHasTransientState calls");
+        } else if ((hasTransientState && mTransientStateCount == 1) ||
+                (!hasTransientState && mTransientStateCount == 0)) {
+            // update flag if we've just incremented up from 0 or decremented down to 0
+            mPrivateFlags2 = (mPrivateFlags2 & ~PFLAG2_HAS_TRANSIENT_STATE) |
+                    (hasTransientState ? PFLAG2_HAS_TRANSIENT_STATE : 0);
+            final boolean newHasTransientState = hasTransientState();
+            if (mParent != null && newHasTransientState != oldHasTransientState) {
+                try {
+                    mParent.childHasTransientStateChanged(this, newHasTransientState);
+                } catch (AbstractMethodError e) {
+                    Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
+                            " does not fully implement ViewParent", e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Set the view is tracking translation transient state. This flag is used to check if the view
+     * need to call setHasTransientState(false) to reset transient state that set when starting
+     * translation.
+     *
+     * @param hasTranslationTransientState true if this view has translation transient state
+     * @hide
+     */
+    public void setHasTranslationTransientState(boolean hasTranslationTransientState) {
+        if (hasTranslationTransientState) {
+            mPrivateFlags4 |= PFLAG4_HAS_TRANSLATION_TRANSIENT_STATE;
+        } else {
+            mPrivateFlags4 &= ~PFLAG4_HAS_TRANSLATION_TRANSIENT_STATE;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public boolean hasTranslationTransientState() {
+        return (mPrivateFlags4 & PFLAG4_HAS_TRANSLATION_TRANSIENT_STATE)
+                == PFLAG4_HAS_TRANSLATION_TRANSIENT_STATE;
+    }
+
+    /**
+     * Returns true if this view is currently attached to a window.
+     */
+    public boolean isAttachedToWindow() {
+        return mAttachInfo != null;
+    }
+
+    /**
+     * Returns true if this view has been through at least one layout since it
+     * was last attached to or detached from a window.
+     */
+    public boolean isLaidOut() {
+        return (mPrivateFlags3 & PFLAG3_IS_LAID_OUT) == PFLAG3_IS_LAID_OUT;
+    }
+
+    /**
+     * @return {@code true} if laid-out and not about to do another layout.
+     */
+    boolean isLayoutValid() {
+        return isLaidOut() && ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == 0);
+    }
+
+    /**
+     * If this view doesn't do any drawing on its own, set this flag to
+     * allow further optimizations. By default, this flag is not set on
+     * View, but could be set on some View subclasses such as ViewGroup.
+     *
+     * Typically, if you override {@link #onDraw(android.graphics.Canvas)}
+     * you should clear this flag.
+     *
+     * @param willNotDraw whether or not this View draw on its own
+     */
+    public void setWillNotDraw(boolean willNotDraw) {
+        setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
+    }
+
+    /**
+     * Returns whether or not this View draws on its own.
+     *
+     * @return true if this view has nothing to draw, false otherwise
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    public boolean willNotDraw() {
+        return (mViewFlags & DRAW_MASK) == WILL_NOT_DRAW;
+    }
+
+    /**
+     * When a View's drawing cache is enabled, drawing is redirected to an
+     * offscreen bitmap. Some views, like an ImageView, must be able to
+     * bypass this mechanism if they already draw a single bitmap, to avoid
+     * unnecessary usage of the memory.
+     *
+     * @param willNotCacheDrawing true if this view does not cache its
+     *        drawing, false otherwise
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
+     */
+    @Deprecated
+    public void setWillNotCacheDrawing(boolean willNotCacheDrawing) {
+        setFlags(willNotCacheDrawing ? WILL_NOT_CACHE_DRAWING : 0, WILL_NOT_CACHE_DRAWING);
+    }
+
+    /**
+     * Returns whether or not this View can cache its drawing or not.
+     *
+     * @return true if this view does not cache its drawing, false otherwise
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    @Deprecated
+    public boolean willNotCacheDrawing() {
+        return (mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING;
+    }
+
+    /**
+     * Indicates whether this view reacts to click events or not.
+     *
+     * @return true if the view is clickable, false otherwise
+     *
+     * @see #setClickable(boolean)
+     * @attr ref android.R.styleable#View_clickable
+     */
+    @ViewDebug.ExportedProperty
+    @InspectableProperty
+    public boolean isClickable() {
+        return (mViewFlags & CLICKABLE) == CLICKABLE;
+    }
+
+    /**
+     * Enables or disables click events for this view. When a view
+     * is clickable it will change its state to "pressed" on every click.
+     * Subclasses should set the view clickable to visually react to
+     * user's clicks.
+     *
+     * @param clickable true to make the view clickable, false otherwise
+     *
+     * @see #isClickable()
+     * @attr ref android.R.styleable#View_clickable
+     */
+    public void setClickable(boolean clickable) {
+        setFlags(clickable ? CLICKABLE : 0, CLICKABLE);
+    }
+
+    /**
+     * Enables or disables click events for this view when disabled.
+     *
+     * @param clickableWhenDisabled true to make the view clickable, false otherwise
+     *
+     * @attr ref android.R.styleable#View_allowClickWhenDisabled
+     */
+    public void setAllowClickWhenDisabled(boolean clickableWhenDisabled) {
+        if (clickableWhenDisabled) {
+            mPrivateFlags4 |= PFLAG4_ALLOW_CLICK_WHEN_DISABLED;
+        } else {
+            mPrivateFlags4 &= ~PFLAG4_ALLOW_CLICK_WHEN_DISABLED;
+        }
+    }
+
+    /**
+     * Indicates whether this view reacts to long click events or not.
+     *
+     * @return true if the view is long clickable, false otherwise
+     *
+     * @see #setLongClickable(boolean)
+     * @attr ref android.R.styleable#View_longClickable
+     */
+    @InspectableProperty
+    public boolean isLongClickable() {
+        return (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE;
+    }
+
+    /**
+     * Enables or disables long click events for this view. When a view is long
+     * clickable it reacts to the user holding down the button for a longer
+     * duration than a tap. This event can either launch the listener or a
+     * context menu.
+     *
+     * @param longClickable true to make the view long clickable, false otherwise
+     * @see #isLongClickable()
+     * @attr ref android.R.styleable#View_longClickable
+     */
+    public void setLongClickable(boolean longClickable) {
+        setFlags(longClickable ? LONG_CLICKABLE : 0, LONG_CLICKABLE);
+    }
+
+    /**
+     * Indicates whether this view reacts to context clicks or not.
+     *
+     * @return true if the view is context clickable, false otherwise
+     * @see #setContextClickable(boolean)
+     * @attr ref android.R.styleable#View_contextClickable
+     */
+    @InspectableProperty
+    public boolean isContextClickable() {
+        return (mViewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
+    }
+
+    /**
+     * Enables or disables context clicking for this view. This event can launch the listener.
+     *
+     * @param contextClickable true to make the view react to a context click, false otherwise
+     * @see #isContextClickable()
+     * @attr ref android.R.styleable#View_contextClickable
+     */
+    public void setContextClickable(boolean contextClickable) {
+        setFlags(contextClickable ? CONTEXT_CLICKABLE : 0, CONTEXT_CLICKABLE);
+    }
+
+    /**
+     * Sets the pressed state for this view and provides a touch coordinate for
+     * animation hinting.
+     *
+     * @param pressed Pass true to set the View's internal state to "pressed",
+     *            or false to reverts the View's internal state from a
+     *            previously set "pressed" state.
+     * @param x The x coordinate of the touch that caused the press
+     * @param y The y coordinate of the touch that caused the press
+     */
+    private void setPressed(boolean pressed, float x, float y) {
+        if (pressed) {
+            drawableHotspotChanged(x, y);
+        }
+
+        setPressed(pressed);
+    }
+
+    /**
+     * Sets the pressed state for this view.
+     *
+     * @see #isClickable()
+     * @see #setClickable(boolean)
+     *
+     * @param pressed Pass true to set the View's internal state to "pressed", or false to reverts
+     *        the View's internal state from a previously set "pressed" state.
+     */
+    public void setPressed(boolean pressed) {
+        final boolean needsRefresh = pressed != ((mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED);
+
+        if (pressed) {
+            mPrivateFlags |= PFLAG_PRESSED;
+        } else {
+            mPrivateFlags &= ~PFLAG_PRESSED;
+        }
+
+        if (needsRefresh) {
+            refreshDrawableState();
+        }
+        dispatchSetPressed(pressed);
+    }
+
+    /**
+     * Dispatch setPressed to all of this View's children.
+     *
+     * @see #setPressed(boolean)
+     *
+     * @param pressed The new pressed state
+     */
+    protected void dispatchSetPressed(boolean pressed) {
+    }
+
+    /**
+     * Indicates whether the view is currently in pressed state. Unless
+     * {@link #setPressed(boolean)} is explicitly called, only clickable views can enter
+     * the pressed state.
+     *
+     * @see #setPressed(boolean)
+     * @see #isClickable()
+     * @see #setClickable(boolean)
+     *
+     * @return true if the view is currently pressed, false otherwise
+     */
+    @ViewDebug.ExportedProperty
+    @InspectableProperty(hasAttributeId = false)
+    public boolean isPressed() {
+        return (mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED;
+    }
+
+    /**
+     * @hide
+     * Indicates whether this view will participate in data collection through
+     * {@link ViewStructure}.  If true, it will not provide any data
+     * for itself or its children.  If false, the normal data collection will be allowed.
+     *
+     * @return Returns false if assist data collection is not blocked, else true.
+     *
+     * @see #setAssistBlocked(boolean)
+     * @attr ref android.R.styleable#View_assistBlocked
+     */
+    public boolean isAssistBlocked() {
+        return (mPrivateFlags3 & PFLAG3_ASSIST_BLOCKED) != 0;
+    }
+
+    /**
+     * @hide
+     * Controls whether assist data collection from this view and its children is enabled
+     * (that is, whether {@link #onProvideStructure} and
+     * {@link #onProvideVirtualStructure} will be called).  The default value is false,
+     * allowing normal assist collection.  Setting this to false will disable assist collection.
+     *
+     * @param enabled Set to true to <em>disable</em> assist data collection, or false
+     * (the default) to allow it.
+     *
+     * @see #isAssistBlocked()
+     * @see #onProvideStructure
+     * @see #onProvideVirtualStructure
+     * @attr ref android.R.styleable#View_assistBlocked
+     */
+    @UnsupportedAppUsage
+    public void setAssistBlocked(boolean enabled) {
+        if (enabled) {
+            mPrivateFlags3 |= PFLAG3_ASSIST_BLOCKED;
+        } else {
+            mPrivateFlags3 &= ~PFLAG3_ASSIST_BLOCKED;
+        }
+    }
+
+    /**
+     * Indicates whether this view will save its state (that is,
+     * whether its {@link #onSaveInstanceState} method will be called).
+     *
+     * @return Returns true if the view state saving is enabled, else false.
+     *
+     * @see #setSaveEnabled(boolean)
+     * @attr ref android.R.styleable#View_saveEnabled
+     */
+    @InspectableProperty
+    public boolean isSaveEnabled() {
+        return (mViewFlags & SAVE_DISABLED_MASK) != SAVE_DISABLED;
+    }
+
+    /**
+     * Controls whether the saving of this view's state is
+     * enabled (that is, whether its {@link #onSaveInstanceState} method
+     * will be called).  Note that even if freezing is enabled, the
+     * view still must have an id assigned to it (via {@link #setId(int)})
+     * for its state to be saved.  This flag can only disable the
+     * saving of this view; any child views may still have their state saved.
+     *
+     * @param enabled Set to false to <em>disable</em> state saving, or true
+     * (the default) to allow it.
+     *
+     * @see #isSaveEnabled()
+     * @see #setId(int)
+     * @see #onSaveInstanceState()
+     * @attr ref android.R.styleable#View_saveEnabled
+     */
+    public void setSaveEnabled(boolean enabled) {
+        setFlags(enabled ? 0 : SAVE_DISABLED, SAVE_DISABLED_MASK);
+    }
+
+    /**
+     * Gets whether the framework should discard touches when the view's
+     * window is obscured by another visible window.
+     * Refer to the {@link View} security documentation for more details.
+     *
+     * @return True if touch filtering is enabled.
+     *
+     * @see #setFilterTouchesWhenObscured(boolean)
+     * @attr ref android.R.styleable#View_filterTouchesWhenObscured
+     */
+    @ViewDebug.ExportedProperty
+    @InspectableProperty
+    public boolean getFilterTouchesWhenObscured() {
+        return (mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0;
+    }
+
+    /**
+     * Sets whether the framework should discard touches when the view's
+     * window is obscured by another visible window.
+     * Refer to the {@link View} security documentation for more details.
+     *
+     * @param enabled True if touch filtering should be enabled.
+     *
+     * @see #getFilterTouchesWhenObscured
+     * @attr ref android.R.styleable#View_filterTouchesWhenObscured
+     */
+    public void setFilterTouchesWhenObscured(boolean enabled) {
+        setFlags(enabled ? FILTER_TOUCHES_WHEN_OBSCURED : 0,
+                FILTER_TOUCHES_WHEN_OBSCURED);
+    }
+
+    /**
+     * Indicates whether the entire hierarchy under this view will save its
+     * state when a state saving traversal occurs from its parent.  The default
+     * is true; if false, these views will not be saved unless
+     * {@link #saveHierarchyState(SparseArray)} is called directly on this view.
+     *
+     * @return Returns true if the view state saving from parent is enabled, else false.
+     *
+     * @see #setSaveFromParentEnabled(boolean)
+     */
+    public boolean isSaveFromParentEnabled() {
+        return (mViewFlags & PARENT_SAVE_DISABLED_MASK) != PARENT_SAVE_DISABLED;
+    }
+
+    /**
+     * Controls whether the entire hierarchy under this view will save its
+     * state when a state saving traversal occurs from its parent.  The default
+     * is true; if false, these views will not be saved unless
+     * {@link #saveHierarchyState(SparseArray)} is called directly on this view.
+     *
+     * @param enabled Set to false to <em>disable</em> state saving, or true
+     * (the default) to allow it.
+     *
+     * @see #isSaveFromParentEnabled()
+     * @see #setId(int)
+     * @see #onSaveInstanceState()
+     */
+    public void setSaveFromParentEnabled(boolean enabled) {
+        setFlags(enabled ? 0 : PARENT_SAVE_DISABLED, PARENT_SAVE_DISABLED_MASK);
+    }
+
+
+    /**
+     * Returns whether this View is currently able to take focus.
+     *
+     * @return True if this view can take focus, or false otherwise.
+     */
+    @ViewDebug.ExportedProperty(category = "focus")
+    public final boolean isFocusable() {
+        return FOCUSABLE == (mViewFlags & FOCUSABLE);
+    }
+
+    /**
+     * Returns the focusable setting for this view.
+     *
+     * @return One of {@link #NOT_FOCUSABLE}, {@link #FOCUSABLE}, or {@link #FOCUSABLE_AUTO}.
+     * @attr ref android.R.styleable#View_focusable
+     */
+    @ViewDebug.ExportedProperty(mapping = {
+            @ViewDebug.IntToString(from = NOT_FOCUSABLE, to = "NOT_FOCUSABLE"),
+            @ViewDebug.IntToString(from = FOCUSABLE, to = "FOCUSABLE"),
+            @ViewDebug.IntToString(from = FOCUSABLE_AUTO, to = "FOCUSABLE_AUTO")
+            }, category = "focus")
+    @InspectableProperty(enumMapping = {
+            @EnumEntry(value = NOT_FOCUSABLE, name = "false"),
+            @EnumEntry(value = FOCUSABLE, name = "true"),
+            @EnumEntry(value = FOCUSABLE_AUTO, name = "auto")
+    })
+    @Focusable
+    public int getFocusable() {
+        return (mViewFlags & FOCUSABLE_AUTO) > 0 ? FOCUSABLE_AUTO : mViewFlags & FOCUSABLE;
+    }
+
+    /**
+     * When a view is focusable, it may not want to take focus when in touch mode.
+     * For example, a button would like focus when the user is navigating via a D-pad
+     * so that the user can click on it, but once the user starts touching the screen,
+     * the button shouldn't take focus
+     * @return Whether the view is focusable in touch mode.
+     * @attr ref android.R.styleable#View_focusableInTouchMode
+     */
+    @ViewDebug.ExportedProperty(category = "focus")
+    @InspectableProperty
+    public final boolean isFocusableInTouchMode() {
+        return FOCUSABLE_IN_TOUCH_MODE == (mViewFlags & FOCUSABLE_IN_TOUCH_MODE);
+    }
+
+    /**
+     * Returns whether the view should be treated as a focusable unit by screen reader
+     * accessibility tools.
+     * @see #setScreenReaderFocusable(boolean)
+     *
+     * @return Whether the view should be treated as a focusable unit by screen reader.
+     *
+     * @attr ref android.R.styleable#View_screenReaderFocusable
+     */
+    @InspectableProperty
+    public boolean isScreenReaderFocusable() {
+        return (mPrivateFlags3 & PFLAG3_SCREEN_READER_FOCUSABLE) != 0;
+    }
+
+    /**
+     * Sets whether this View should be a focusable element for screen readers
+     * and include non-focusable Views from its subtree when providing feedback.
+     * <p>
+     * Note: this is similar to using <a href="#attr_android:focusable">{@code android:focusable},
+     * but does not impact input focus behavior.
+     *
+     * @param screenReaderFocusable Whether the view should be treated as a unit by screen reader
+     *                              accessibility tools.
+     *
+     * @attr ref android.R.styleable#View_screenReaderFocusable
+     */
+    public void setScreenReaderFocusable(boolean screenReaderFocusable) {
+        updatePflags3AndNotifyA11yIfChanged(PFLAG3_SCREEN_READER_FOCUSABLE, screenReaderFocusable);
+    }
+
+    /**
+     * Gets whether this view is a heading for accessibility purposes.
+     *
+     * @return {@code true} if the view is a heading, {@code false} otherwise.
+     *
+     * @attr ref android.R.styleable#View_accessibilityHeading
+     */
+    @InspectableProperty
+    public boolean isAccessibilityHeading() {
+        return (mPrivateFlags3 & PFLAG3_ACCESSIBILITY_HEADING) != 0;
+    }
+
+    /**
+     * Set if view is a heading for a section of content for accessibility purposes.
+     *
+     * @param isHeading {@code true} if the view is a heading, {@code false} otherwise.
+     *
+     * @attr ref android.R.styleable#View_accessibilityHeading
+     */
+    public void setAccessibilityHeading(boolean isHeading) {
+        updatePflags3AndNotifyA11yIfChanged(PFLAG3_ACCESSIBILITY_HEADING, isHeading);
+    }
+
+    private void updatePflags3AndNotifyA11yIfChanged(int mask, boolean newValue) {
+        int pflags3 = mPrivateFlags3;
+        if (newValue) {
+            pflags3 |= mask;
+        } else {
+            pflags3 &= ~mask;
+        }
+
+        if (pflags3 != mPrivateFlags3) {
+            mPrivateFlags3 = pflags3;
+            notifyViewAccessibilityStateChangedIfNeeded(
+                    AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+        }
+    }
+
+    /**
+     * Find the nearest view in the specified direction that can take focus.
+     * This does not actually give focus to that view.
+     *
+     * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT
+     *
+     * @return The nearest focusable in the specified direction, or null if none
+     *         can be found.
+     */
+    public View focusSearch(@FocusRealDirection int direction) {
+        if (mParent != null) {
+            return mParent.focusSearch(this, direction);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns whether this View is a root of a keyboard navigation cluster.
+     *
+     * @return True if this view is a root of a cluster, or false otherwise.
+     * @attr ref android.R.styleable#View_keyboardNavigationCluster
+     */
+    @ViewDebug.ExportedProperty(category = "focus")
+    @InspectableProperty
+    public final boolean isKeyboardNavigationCluster() {
+        return (mPrivateFlags3 & PFLAG3_CLUSTER) != 0;
+    }
+
+    /**
+     * Searches up the view hierarchy to find the top-most cluster. All deeper/nested clusters
+     * will be ignored.
+     *
+     * @return the keyboard navigation cluster that this view is in (can be this view)
+     *         or {@code null} if not in one
+     */
+    View findKeyboardNavigationCluster() {
+        if (mParent instanceof View) {
+            View cluster = ((View) mParent).findKeyboardNavigationCluster();
+            if (cluster != null) {
+                return cluster;
+            } else if (isKeyboardNavigationCluster()) {
+                return this;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Set whether this view is a root of a keyboard navigation cluster.
+     *
+     * @param isCluster If true, this view is a root of a cluster.
+     *
+     * @attr ref android.R.styleable#View_keyboardNavigationCluster
+     */
+    public void setKeyboardNavigationCluster(boolean isCluster) {
+        if (isCluster) {
+            mPrivateFlags3 |= PFLAG3_CLUSTER;
+        } else {
+            mPrivateFlags3 &= ~PFLAG3_CLUSTER;
+        }
+    }
+
+    /**
+     * Sets this View as the one which receives focus the next time cluster navigation jumps
+     * to the cluster containing this View. This does NOT change focus even if the cluster
+     * containing this view is current.
+     *
+     * @hide
+     */
+    @TestApi
+    public final void setFocusedInCluster() {
+        setFocusedInCluster(findKeyboardNavigationCluster());
+    }
+
+    private void setFocusedInCluster(View cluster) {
+        if (this instanceof ViewGroup) {
+            ((ViewGroup) this).mFocusedInCluster = null;
+        }
+        if (cluster == this) {
+            return;
+        }
+        ViewParent parent = mParent;
+        View child = this;
+        while (parent instanceof ViewGroup) {
+            ((ViewGroup) parent).mFocusedInCluster = child;
+            if (parent == cluster) {
+                break;
+            }
+            child = (View) parent;
+            parent = parent.getParent();
+        }
+    }
+
+    private void updateFocusedInCluster(View oldFocus, @FocusDirection int direction) {
+        if (oldFocus != null) {
+            View oldCluster = oldFocus.findKeyboardNavigationCluster();
+            View cluster = findKeyboardNavigationCluster();
+            if (oldCluster != cluster) {
+                // Going from one cluster to another, so save last-focused.
+                // This covers cluster jumps because they are always FOCUS_DOWN
+                oldFocus.setFocusedInCluster(oldCluster);
+                if (!(oldFocus.mParent instanceof ViewGroup)) {
+                    return;
+                }
+                if (direction == FOCUS_FORWARD || direction == FOCUS_BACKWARD) {
+                    // This is a result of ordered navigation so consider navigation through
+                    // the previous cluster "complete" and clear its last-focused memory.
+                    ((ViewGroup) oldFocus.mParent).clearFocusedInCluster(oldFocus);
+                } else if (oldFocus instanceof ViewGroup
+                        && ((ViewGroup) oldFocus).getDescendantFocusability()
+                                == ViewGroup.FOCUS_AFTER_DESCENDANTS
+                        && ViewRootImpl.isViewDescendantOf(this, oldFocus)) {
+                    // This means oldFocus is not focusable since it obviously has a focusable
+                    // child (this). Don't restore focus to it in the future.
+                    ((ViewGroup) oldFocus.mParent).clearFocusedInCluster(oldFocus);
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns whether this View should receive focus when the focus is restored for the view
+     * hierarchy containing this view.
+     * <p>
+     * Focus gets restored for a view hierarchy when the root of the hierarchy gets added to a
+     * window or serves as a target of cluster navigation.
+     *
+     * @see #restoreDefaultFocus()
+     *
+     * @return {@code true} if this view is the default-focus view, {@code false} otherwise
+     * @attr ref android.R.styleable#View_focusedByDefault
+     */
+    @ViewDebug.ExportedProperty(category = "focus")
+    @InspectableProperty
+    public final boolean isFocusedByDefault() {
+        return (mPrivateFlags3 & PFLAG3_FOCUSED_BY_DEFAULT) != 0;
+    }
+
+    /**
+     * Sets whether this View should receive focus when the focus is restored for the view
+     * hierarchy containing this view.
+     * <p>
+     * Focus gets restored for a view hierarchy when the root of the hierarchy gets added to a
+     * window or serves as a target of cluster navigation.
+     *
+     * @param isFocusedByDefault {@code true} to set this view as the default-focus view,
+     *                           {@code false} otherwise.
+     *
+     * @see #restoreDefaultFocus()
+     *
+     * @attr ref android.R.styleable#View_focusedByDefault
+     */
+    @RemotableViewMethod
+    public void setFocusedByDefault(boolean isFocusedByDefault) {
+        if (isFocusedByDefault == ((mPrivateFlags3 & PFLAG3_FOCUSED_BY_DEFAULT) != 0)) {
+            return;
+        }
+
+        if (isFocusedByDefault) {
+            mPrivateFlags3 |= PFLAG3_FOCUSED_BY_DEFAULT;
+        } else {
+            mPrivateFlags3 &= ~PFLAG3_FOCUSED_BY_DEFAULT;
+        }
+
+        if (mParent instanceof ViewGroup) {
+            if (isFocusedByDefault) {
+                ((ViewGroup) mParent).setDefaultFocus(this);
+            } else {
+                ((ViewGroup) mParent).clearDefaultFocus(this);
+            }
+        }
+    }
+
+    /**
+     * Returns whether the view hierarchy with this view as a root contain a default-focus view.
+     *
+     * @return {@code true} if this view has default focus, {@code false} otherwise
+     */
+    boolean hasDefaultFocus() {
+        return isFocusedByDefault();
+    }
+
+    /**
+     * Find the nearest keyboard navigation cluster in the specified direction.
+     * This does not actually give focus to that cluster.
+     *
+     * @param currentCluster The starting point of the search. Null means the current cluster is not
+     *                       found yet
+     * @param direction Direction to look
+     *
+     * @return The nearest keyboard navigation cluster in the specified direction, or null if none
+     *         can be found
+     */
+    public View keyboardNavigationClusterSearch(View currentCluster,
+            @FocusDirection int direction) {
+        if (isKeyboardNavigationCluster()) {
+            currentCluster = this;
+        }
+        if (isRootNamespace()) {
+            // Root namespace means we should consider ourselves the top of the
+            // tree for group searching; otherwise we could be group searching
+            // into other tabs.  see LocalActivityManager and TabHost for more info.
+            return FocusFinder.getInstance().findNextKeyboardNavigationCluster(
+                    this, currentCluster, direction);
+        } else if (mParent != null) {
+            return mParent.keyboardNavigationClusterSearch(currentCluster, direction);
+        }
+        return null;
+    }
+
+    /**
+     * This method is the last chance for the focused view and its ancestors to
+     * respond to an arrow key. This is called when the focused view did not
+     * consume the key internally, nor could the view system find a new view in
+     * the requested direction to give focus to.
+     *
+     * @param focused The currently focused view.
+     * @param direction The direction focus wants to move. One of FOCUS_UP,
+     *        FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT.
+     * @return True if the this view consumed this unhandled move.
+     */
+    public boolean dispatchUnhandledMove(View focused, @FocusRealDirection int direction) {
+        return false;
+    }
+
+    /**
+     * Sets whether this View should use a default focus highlight when it gets focused but doesn't
+     * have {@link android.R.attr#state_focused} defined in its background.
+     *
+     * @param defaultFocusHighlightEnabled {@code true} to set this view to use a default focus
+     *                                      highlight, {@code false} otherwise.
+     *
+     * @attr ref android.R.styleable#View_defaultFocusHighlightEnabled
+     */
+    public void setDefaultFocusHighlightEnabled(boolean defaultFocusHighlightEnabled) {
+        mDefaultFocusHighlightEnabled = defaultFocusHighlightEnabled;
+    }
+
+    /**
+
+    /**
+     * Returns whether this View should use a default focus highlight when it gets focused but
+     * doesn't have {@link android.R.attr#state_focused} defined in its background.
+     *
+     * @return True if this View should use a default focus highlight.
+     * @attr ref android.R.styleable#View_defaultFocusHighlightEnabled
+     */
+    @ViewDebug.ExportedProperty(category = "focus")
+    @InspectableProperty
+    public final boolean getDefaultFocusHighlightEnabled() {
+        return mDefaultFocusHighlightEnabled;
+    }
+
+    /**
+     * If a user manually specified the next view id for a particular direction,
+     * use the root to look up the view.
+     * @param root The root view of the hierarchy containing this view.
+     * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, FOCUS_FORWARD,
+     * or FOCUS_BACKWARD.
+     * @return The user specified next view, or null if there is none.
+     */
+    View findUserSetNextFocus(View root, @FocusDirection int direction) {
+        switch (direction) {
+            case FOCUS_LEFT:
+                if (mNextFocusLeftId == View.NO_ID) return null;
+                return findViewInsideOutShouldExist(root, mNextFocusLeftId);
+            case FOCUS_RIGHT:
+                if (mNextFocusRightId == View.NO_ID) return null;
+                return findViewInsideOutShouldExist(root, mNextFocusRightId);
+            case FOCUS_UP:
+                if (mNextFocusUpId == View.NO_ID) return null;
+                return findViewInsideOutShouldExist(root, mNextFocusUpId);
+            case FOCUS_DOWN:
+                if (mNextFocusDownId == View.NO_ID) return null;
+                return findViewInsideOutShouldExist(root, mNextFocusDownId);
+            case FOCUS_FORWARD:
+                if (mNextFocusForwardId == View.NO_ID) return null;
+                return findViewInsideOutShouldExist(root, mNextFocusForwardId);
+            case FOCUS_BACKWARD: {
+                if (mID == View.NO_ID) return null;
+                final View rootView = root;
+                final View startView = this;
+                // Since we have forward links but no backward links, we need to find the view that
+                // forward links to this view. We can't just find the view with the specified ID
+                // because view IDs need not be unique throughout the tree.
+                return root.findViewByPredicateInsideOut(startView,
+                    t -> findViewInsideOutShouldExist(rootView, t, t.mNextFocusForwardId)
+                            == startView);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * If a user manually specified the next keyboard-navigation cluster for a particular direction,
+     * use the root to look up the view.
+     *
+     * @param root the root view of the hierarchy containing this view
+     * @param direction {@link #FOCUS_FORWARD} or {@link #FOCUS_BACKWARD}
+     * @return the user-specified next cluster, or {@code null} if there is none
+     */
+    View findUserSetNextKeyboardNavigationCluster(View root, @FocusDirection int direction) {
+        switch (direction) {
+            case FOCUS_FORWARD:
+                if (mNextClusterForwardId == View.NO_ID) return null;
+                return findViewInsideOutShouldExist(root, mNextClusterForwardId);
+            case FOCUS_BACKWARD: {
+                if (mID == View.NO_ID) return null;
+                final int id = mID;
+                return root.findViewByPredicateInsideOut(this,
+                        (Predicate<View>) t -> t.mNextClusterForwardId == id);
+            }
+        }
+        return null;
+    }
+
+    private View findViewInsideOutShouldExist(View root, int id) {
+        return findViewInsideOutShouldExist(root, this, id);
+    }
+
+    private View findViewInsideOutShouldExist(View root, View start, int id) {
+        if (mMatchIdPredicate == null) {
+            mMatchIdPredicate = new MatchIdPredicate();
+        }
+        mMatchIdPredicate.mId = id;
+        View result = root.findViewByPredicateInsideOut(start, mMatchIdPredicate);
+        if (result == null) {
+            Log.w(VIEW_LOG_TAG, "couldn't find view with id " + id);
+        }
+        return result;
+    }
+
+    /**
+     * Find and return all focusable views that are descendants of this view,
+     * possibly including this view if it is focusable itself.
+     *
+     * @param direction The direction of the focus
+     * @return A list of focusable views
+     */
+    public ArrayList<View> getFocusables(@FocusDirection int direction) {
+        ArrayList<View> result = new ArrayList<View>(24);
+        addFocusables(result, direction);
+        return result;
+    }
+
+    /**
+     * Add any focusable views that are descendants of this view (possibly
+     * including this view if it is focusable itself) to views.  If we are in touch mode,
+     * only add views that are also focusable in touch mode.
+     *
+     * @param views Focusable views found so far
+     * @param direction The direction of the focus
+     */
+    public void addFocusables(ArrayList<View> views, @FocusDirection int direction) {
+        addFocusables(views, direction, isInTouchMode() ? FOCUSABLES_TOUCH_MODE : FOCUSABLES_ALL);
+    }
+
+    /**
+     * Adds any focusable views that are descendants of this view (possibly
+     * including this view if it is focusable itself) to views. This method
+     * adds all focusable views regardless if we are in touch mode or
+     * only views focusable in touch mode if we are in touch mode or
+     * only views that can take accessibility focus if accessibility is enabled
+     * depending on the focusable mode parameter.
+     *
+     * @param views Focusable views found so far or null if all we are interested is
+     *        the number of focusables.
+     * @param direction The direction of the focus.
+     * @param focusableMode The type of focusables to be added.
+     *
+     * @see #FOCUSABLES_ALL
+     * @see #FOCUSABLES_TOUCH_MODE
+     */
+    public void addFocusables(ArrayList<View> views, @FocusDirection int direction,
+            @FocusableMode int focusableMode) {
+        if (views == null) {
+            return;
+        }
+        if (!canTakeFocus()) {
+            return;
+        }
+        if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE
+                && !isFocusableInTouchMode()) {
+            return;
+        }
+        views.add(this);
+    }
+
+    /**
+     * Adds any keyboard navigation cluster roots that are descendants of this view (possibly
+     * including this view if it is a cluster root itself) to views.
+     *
+     * @param views Keyboard navigation cluster roots found so far
+     * @param direction Direction to look
+     */
+    public void addKeyboardNavigationClusters(
+            @NonNull Collection<View> views,
+            int direction) {
+        if (!isKeyboardNavigationCluster()) {
+            return;
+        }
+        if (!hasFocusable()) {
+            return;
+        }
+        views.add(this);
+    }
+
+    /**
+     * Finds the Views that contain given text. The containment is case insensitive.
+     * The search is performed by either the text that the View renders or the content
+     * description that describes the view for accessibility purposes and the view does
+     * not render or both. Clients can specify how the search is to be performed via
+     * passing the {@link #FIND_VIEWS_WITH_TEXT} and
+     * {@link #FIND_VIEWS_WITH_CONTENT_DESCRIPTION} flags.
+     *
+     * @param outViews The output list of matching Views.
+     * @param searched The text to match against.
+     *
+     * @see #FIND_VIEWS_WITH_TEXT
+     * @see #FIND_VIEWS_WITH_CONTENT_DESCRIPTION
+     * @see #setContentDescription(CharSequence)
+     */
+    public void findViewsWithText(ArrayList<View> outViews, CharSequence searched,
+            @FindViewFlags int flags) {
+        if (getAccessibilityNodeProvider() != null) {
+            if ((flags & FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS) != 0) {
+                outViews.add(this);
+            }
+        } else if ((flags & FIND_VIEWS_WITH_CONTENT_DESCRIPTION) != 0
+                && (searched != null && searched.length() > 0)
+                && (mContentDescription != null && mContentDescription.length() > 0)) {
+            String searchedLowerCase = searched.toString().toLowerCase();
+            String contentDescriptionLowerCase = mContentDescription.toString().toLowerCase();
+            if (contentDescriptionLowerCase.contains(searchedLowerCase)) {
+                outViews.add(this);
+            }
+        }
+    }
+
+    /**
+     * Find and return all touchable views that are descendants of this view,
+     * possibly including this view if it is touchable itself.
+     *
+     * @return A list of touchable views
+     */
+    public ArrayList<View> getTouchables() {
+        ArrayList<View> result = new ArrayList<View>();
+        addTouchables(result);
+        return result;
+    }
+
+    /**
+     * Add any touchable views that are descendants of this view (possibly
+     * including this view if it is touchable itself) to views.
+     *
+     * @param views Touchable views found so far
+     */
+    public void addTouchables(ArrayList<View> views) {
+        final int viewFlags = mViewFlags;
+
+        if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE
+                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE)
+                && (viewFlags & ENABLED_MASK) == ENABLED) {
+            views.add(this);
+        }
+    }
+
+    /**
+     * Returns whether this View is accessibility focused.
+     *
+     * @return True if this View is accessibility focused.
+     */
+    @InspectableProperty(hasAttributeId = false)
+    public boolean isAccessibilityFocused() {
+        return (mPrivateFlags2 & PFLAG2_ACCESSIBILITY_FOCUSED) != 0;
+    }
+
+    /**
+     * Call this to try to give accessibility focus to this view.
+     *
+     * A view will not actually take focus if {@link AccessibilityManager#isEnabled()}
+     * returns false or the view is no visible or the view already has accessibility
+     * focus.
+     *
+     * See also {@link #focusSearch(int)}, which is what you call to say that you
+     * have focus, and you want your parent to look for the next one.
+     *
+     * @return Whether this view actually took accessibility focus.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public boolean requestAccessibilityFocus() {
+        AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
+        if (!manager.isEnabled() || !manager.isTouchExplorationEnabled()) {
+            return false;
+        }
+        if ((mViewFlags & VISIBILITY_MASK) != VISIBLE) {
+            return false;
+        }
+        if ((mPrivateFlags2 & PFLAG2_ACCESSIBILITY_FOCUSED) == 0) {
+            mPrivateFlags2 |= PFLAG2_ACCESSIBILITY_FOCUSED;
+            ViewRootImpl viewRootImpl = getViewRootImpl();
+            if (viewRootImpl != null) {
+                viewRootImpl.setAccessibilityFocus(this, null);
+            }
+            invalidate();
+            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Call this to try to clear accessibility focus of this view.
+     *
+     * See also {@link #focusSearch(int)}, which is what you call to say that you
+     * have focus, and you want your parent to look for the next one.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void clearAccessibilityFocus() {
+        clearAccessibilityFocusNoCallbacks(0);
+
+        // Clear the global reference of accessibility focus if this view or
+        // any of its descendants had accessibility focus. This will NOT send
+        // an event or update internal state if focus is cleared from a
+        // descendant view, which may leave views in inconsistent states.
+        final ViewRootImpl viewRootImpl = getViewRootImpl();
+        if (viewRootImpl != null) {
+            final View focusHost = viewRootImpl.getAccessibilityFocusedHost();
+            if (focusHost != null && ViewRootImpl.isViewDescendantOf(focusHost, this)) {
+                viewRootImpl.setAccessibilityFocus(null, null);
+            }
+        }
+    }
+
+    private void sendAccessibilityHoverEvent(int eventType) {
+        // Since we are not delivering to a client accessibility events from not
+        // important views (unless the clinet request that) we need to fire the
+        // event from the deepest view exposed to the client. As a consequence if
+        // the user crosses a not exposed view the client will see enter and exit
+        // of the exposed predecessor followed by and enter and exit of that same
+        // predecessor when entering and exiting the not exposed descendant. This
+        // is fine since the client has a clear idea which view is hovered at the
+        // price of a couple more events being sent. This is a simple and
+        // working solution.
+        View source = this;
+        while (true) {
+            if (source.includeForAccessibility()) {
+                source.sendAccessibilityEvent(eventType);
+                return;
+            }
+            ViewParent parent = source.getParent();
+            if (parent instanceof View) {
+                source = (View) parent;
+            } else {
+                return;
+            }
+        }
+    }
+
+    /**
+     * Clears accessibility focus without calling any callback methods
+     * normally invoked in {@link #clearAccessibilityFocus()}. This method
+     * is used separately from that one for clearing accessibility focus when
+     * giving this focus to another view.
+     *
+     * @param action The action, if any, that led to focus being cleared. Set to
+     * AccessibilityNodeInfo#ACTION_ACCESSIBILITY_FOCUS to specify that focus is moving within
+     * the window.
+     */
+    void clearAccessibilityFocusNoCallbacks(int action) {
+        if ((mPrivateFlags2 & PFLAG2_ACCESSIBILITY_FOCUSED) != 0) {
+            mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_FOCUSED;
+            invalidate();
+            if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+                AccessibilityEvent event = AccessibilityEvent.obtain(
+                        AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
+                event.setAction(action);
+                if (mAccessibilityDelegate != null) {
+                    mAccessibilityDelegate.sendAccessibilityEventUnchecked(this, event);
+                } else {
+                    sendAccessibilityEventUnchecked(event);
+                }
+            }
+        }
+    }
+
+    /**
+     * Call this to try to give focus to a specific view or to one of its
+     * descendants.
+     *
+     * A view will not actually take focus if it is not focusable ({@link #isFocusable} returns
+     * false), or if it can't be focused due to other conditions (not focusable in touch mode
+     * ({@link #isFocusableInTouchMode}) while the device is in touch mode, not visible, not
+     * enabled, or has no size).
+     *
+     * See also {@link #focusSearch(int)}, which is what you call to say that you
+     * have focus, and you want your parent to look for the next one.
+     *
+     * This is equivalent to calling {@link #requestFocus(int, Rect)} with arguments
+     * {@link #FOCUS_DOWN} and <code>null</code>.
+     *
+     * @return Whether this view or one of its descendants actually took focus.
+     */
+    public final boolean requestFocus() {
+        return requestFocus(View.FOCUS_DOWN);
+    }
+
+    /**
+     * This will request focus for whichever View was last focused within this
+     * cluster before a focus-jump out of it.
+     *
+     * @hide
+     */
+    @TestApi
+    public boolean restoreFocusInCluster(@FocusRealDirection int direction) {
+        // Prioritize focusableByDefault over algorithmic focus selection.
+        if (restoreDefaultFocus()) {
+            return true;
+        }
+        return requestFocus(direction);
+    }
+
+    /**
+     * This will request focus for whichever View not in a cluster was last focused before a
+     * focus-jump to a cluster. If no non-cluster View has previously had focus, this will focus
+     * the "first" focusable view it finds.
+     *
+     * @hide
+     */
+    @TestApi
+    public boolean restoreFocusNotInCluster() {
+        return requestFocus(View.FOCUS_DOWN);
+    }
+
+    /**
+     * Gives focus to the default-focus view in the view hierarchy that has this view as a root.
+     * If the default-focus view cannot be found, falls back to calling {@link #requestFocus(int)}.
+     *
+     * @return Whether this view or one of its descendants actually took focus
+     */
+    public boolean restoreDefaultFocus() {
+        return requestFocus(View.FOCUS_DOWN);
+    }
+
+    /**
+     * Call this to try to give focus to a specific view or to one of its
+     * descendants and give it a hint about what direction focus is heading.
+     *
+     * A view will not actually take focus if it is not focusable ({@link #isFocusable} returns
+     * false), or if it is focusable and it is not focusable in touch mode
+     * ({@link #isFocusableInTouchMode}) while the device is in touch mode.
+     *
+     * See also {@link #focusSearch(int)}, which is what you call to say that you
+     * have focus, and you want your parent to look for the next one.
+     *
+     * This is equivalent to calling {@link #requestFocus(int, Rect)} with
+     * <code>null</code> set for the previously focused rectangle.
+     *
+     * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT
+     * @return Whether this view or one of its descendants actually took focus.
+     */
+    public final boolean requestFocus(int direction) {
+        return requestFocus(direction, null);
+    }
+
+    /**
+     * Call this to try to give focus to a specific view or to one of its descendants
+     * and give it hints about the direction and a specific rectangle that the focus
+     * is coming from.  The rectangle can help give larger views a finer grained hint
+     * about where focus is coming from, and therefore, where to show selection, or
+     * forward focus change internally.
+     *
+     * A view will not actually take focus if it is not focusable ({@link #isFocusable} returns
+     * false), or if it is focusable and it is not focusable in touch mode
+     * ({@link #isFocusableInTouchMode}) while the device is in touch mode.
+     *
+     * A View will not take focus if it is not visible.
+     *
+     * A View will not take focus if one of its parents has
+     * {@link android.view.ViewGroup#getDescendantFocusability()} equal to
+     * {@link ViewGroup#FOCUS_BLOCK_DESCENDANTS}.
+     *
+     * See also {@link #focusSearch(int)}, which is what you call to say that you
+     * have focus, and you want your parent to look for the next one.
+     *
+     * You may wish to override this method if your custom {@link View} has an internal
+     * {@link View} that it wishes to forward the request to.
+     *
+     * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT
+     * @param previouslyFocusedRect The rectangle (in this View's coordinate system)
+     *        to give a finer grained hint about where focus is coming from.  May be null
+     *        if there is no hint.
+     * @return Whether this view or one of its descendants actually took focus.
+     */
+    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
+        return requestFocusNoSearch(direction, previouslyFocusedRect);
+    }
+
+    private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
+        // need to be focusable
+        if (!canTakeFocus()) {
+            return false;
+        }
+
+        // need to be focusable in touch mode if in touch mode
+        if (isInTouchMode() &&
+            (FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {
+               return false;
+        }
+
+        // need to not have any parents blocking us
+        if (hasAncestorThatBlocksDescendantFocus()) {
+            return false;
+        }
+
+        if (!isLayoutValid()) {
+            mPrivateFlags |= PFLAG_WANTS_FOCUS;
+        } else {
+            clearParentsWantFocus();
+        }
+
+        handleFocusGainInternal(direction, previouslyFocusedRect);
+        return true;
+    }
+
+    void clearParentsWantFocus() {
+        if (mParent instanceof View) {
+            ((View) mParent).mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
+            ((View) mParent).clearParentsWantFocus();
+        }
+    }
+
+    /**
+     * Call this to try to give focus to a specific view or to one of its descendants. This is a
+     * special variant of {@link #requestFocus() } that will allow views that are not focusable in
+     * touch mode to request focus when they are touched.
+     *
+     * @return Whether this view or one of its descendants actually took focus.
+     *
+     * @see #isInTouchMode()
+     *
+     */
+    public final boolean requestFocusFromTouch() {
+        // Leave touch mode if we need to
+        if (isInTouchMode()) {
+            ViewRootImpl viewRoot = getViewRootImpl();
+            if (viewRoot != null) {
+                viewRoot.ensureTouchMode(false);
+            }
+        }
+        return requestFocus(View.FOCUS_DOWN);
+    }
+
+    /**
+     * @return Whether any ancestor of this view blocks descendant focus.
+     */
+    private boolean hasAncestorThatBlocksDescendantFocus() {
+        final boolean focusableInTouchMode = isFocusableInTouchMode();
+        ViewParent ancestor = mParent;
+        while (ancestor instanceof ViewGroup) {
+            final ViewGroup vgAncestor = (ViewGroup) ancestor;
+            if (vgAncestor.getDescendantFocusability() == ViewGroup.FOCUS_BLOCK_DESCENDANTS
+                    || (!focusableInTouchMode && vgAncestor.shouldBlockFocusForTouchscreen())) {
+                return true;
+            } else {
+                ancestor = vgAncestor.getParent();
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Gets the mode for determining whether this View is important for accessibility.
+     * A view is important for accessibility if it fires accessibility events and if it
+     * is reported to accessibility services that query the screen.
+     *
+     * @return The mode for determining whether a view is important for accessibility, one
+     * of {@link #IMPORTANT_FOR_ACCESSIBILITY_AUTO}, {@link #IMPORTANT_FOR_ACCESSIBILITY_YES},
+     * {@link #IMPORTANT_FOR_ACCESSIBILITY_NO}, or
+     * {@link #IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS}.
+     *
+     * @attr ref android.R.styleable#View_importantForAccessibility
+     *
+     * @see #IMPORTANT_FOR_ACCESSIBILITY_YES
+     * @see #IMPORTANT_FOR_ACCESSIBILITY_NO
+     * @see #IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+     * @see #IMPORTANT_FOR_ACCESSIBILITY_AUTO
+     */
+    @ViewDebug.ExportedProperty(category = "accessibility", mapping = {
+            @ViewDebug.IntToString(from = IMPORTANT_FOR_ACCESSIBILITY_AUTO, to = "auto"),
+            @ViewDebug.IntToString(from = IMPORTANT_FOR_ACCESSIBILITY_YES, to = "yes"),
+            @ViewDebug.IntToString(from = IMPORTANT_FOR_ACCESSIBILITY_NO, to = "no"),
+            @ViewDebug.IntToString(from = IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS,
+                    to = "noHideDescendants")
+        })
+    @InspectableProperty(enumMapping = {
+            @EnumEntry(value = IMPORTANT_FOR_ACCESSIBILITY_AUTO, name = "auto"),
+            @EnumEntry(value = IMPORTANT_FOR_ACCESSIBILITY_YES, name = "yes"),
+            @EnumEntry(value = IMPORTANT_FOR_ACCESSIBILITY_NO, name = "no"),
+            @EnumEntry(value = IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS,
+                    name = "noHideDescendants"),
+    })
+    public int getImportantForAccessibility() {
+        return (mPrivateFlags2 & PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK)
+                >> PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT;
+    }
+
+    /**
+     * Sets the live region mode for this view. This indicates to accessibility
+     * services whether they should automatically notify the user about changes
+     * to the view's content description or text, or to the content descriptions
+     * or text of the view's children (where applicable).
+     * <p>
+     * For example, in a login screen with a TextView that displays an "incorrect
+     * password" notification, that view should be marked as a live region with
+     * mode {@link #ACCESSIBILITY_LIVE_REGION_POLITE}.
+     * <p>
+     * To disable change notifications for this view, use
+     * {@link #ACCESSIBILITY_LIVE_REGION_NONE}. This is the default live region
+     * mode for most views.
+     * <p>
+     * To indicate that the user should be notified of changes, use
+     * {@link #ACCESSIBILITY_LIVE_REGION_POLITE}.
+     * <p>
+     * If the view's changes should interrupt ongoing speech and notify the user
+     * immediately, use {@link #ACCESSIBILITY_LIVE_REGION_ASSERTIVE}.
+     *
+     * @param mode The live region mode for this view, one of:
+     *        <ul>
+     *        <li>{@link #ACCESSIBILITY_LIVE_REGION_NONE}
+     *        <li>{@link #ACCESSIBILITY_LIVE_REGION_POLITE}
+     *        <li>{@link #ACCESSIBILITY_LIVE_REGION_ASSERTIVE}
+     *        </ul>
+     * @attr ref android.R.styleable#View_accessibilityLiveRegion
+     */
+    public void setAccessibilityLiveRegion(int mode) {
+        if (mode != getAccessibilityLiveRegion()) {
+            mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK;
+            mPrivateFlags2 |= (mode << PFLAG2_ACCESSIBILITY_LIVE_REGION_SHIFT)
+                    & PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK;
+            notifyViewAccessibilityStateChangedIfNeeded(
+                    AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+        }
+    }
+
+    /**
+     * Gets the live region mode for this View.
+     *
+     * @return The live region mode for the view.
+     *
+     * @attr ref android.R.styleable#View_accessibilityLiveRegion
+     *
+     * @see #setAccessibilityLiveRegion(int)
+     */
+    @InspectableProperty(enumMapping = {
+            @EnumEntry(value = ACCESSIBILITY_LIVE_REGION_NONE, name = "none"),
+            @EnumEntry(value = ACCESSIBILITY_LIVE_REGION_POLITE, name = "polite"),
+            @EnumEntry(value = ACCESSIBILITY_LIVE_REGION_ASSERTIVE, name = "assertive")
+    })
+    public int getAccessibilityLiveRegion() {
+        return (mPrivateFlags2 & PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK)
+                >> PFLAG2_ACCESSIBILITY_LIVE_REGION_SHIFT;
+    }
+
+    /**
+     * Sets how to determine whether this view is important for accessibility
+     * which is if it fires accessibility events and if it is reported to
+     * accessibility services that query the screen.
+     *
+     * @param mode How to determine whether this view is important for accessibility.
+     *
+     * @attr ref android.R.styleable#View_importantForAccessibility
+     *
+     * @see #IMPORTANT_FOR_ACCESSIBILITY_YES
+     * @see #IMPORTANT_FOR_ACCESSIBILITY_NO
+     * @see #IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+     * @see #IMPORTANT_FOR_ACCESSIBILITY_AUTO
+     */
+    public void setImportantForAccessibility(int mode) {
+        final int oldMode = getImportantForAccessibility();
+        if (mode != oldMode) {
+            final boolean hideDescendants =
+                    mode == IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
+
+            // If this node or its descendants are no longer important, try to
+            // clear accessibility focus.
+            if (mode == IMPORTANT_FOR_ACCESSIBILITY_NO || hideDescendants) {
+                final View focusHost = findAccessibilityFocusHost(hideDescendants);
+                if (focusHost != null) {
+                    focusHost.clearAccessibilityFocus();
+                }
+            }
+
+            // If we're moving between AUTO and another state, we might not need
+            // to send a subtree changed notification. We'll store the computed
+            // importance, since we'll need to check it later to make sure.
+            final boolean maySkipNotify = oldMode == IMPORTANT_FOR_ACCESSIBILITY_AUTO
+                    || mode == IMPORTANT_FOR_ACCESSIBILITY_AUTO;
+            final boolean oldIncludeForAccessibility = maySkipNotify && includeForAccessibility();
+            mPrivateFlags2 &= ~PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK;
+            mPrivateFlags2 |= (mode << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT)
+                    & PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK;
+            if (!maySkipNotify || oldIncludeForAccessibility != includeForAccessibility()) {
+                notifySubtreeAccessibilityStateChangedIfNeeded();
+            } else {
+                notifyViewAccessibilityStateChangedIfNeeded(
+                        AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+            }
+        }
+    }
+
+    /**
+     * Returns the view within this view's hierarchy that is hosting
+     * accessibility focus.
+     *
+     * @param searchDescendants whether to search for focus in descendant views
+     * @return the view hosting accessibility focus, or {@code null}
+     */
+    private View findAccessibilityFocusHost(boolean searchDescendants) {
+        if (isAccessibilityFocusedViewOrHost()) {
+            return this;
+        }
+
+        if (searchDescendants) {
+            final ViewRootImpl viewRoot = getViewRootImpl();
+            if (viewRoot != null) {
+                final View focusHost = viewRoot.getAccessibilityFocusedHost();
+                if (focusHost != null && ViewRootImpl.isViewDescendantOf(focusHost, this)) {
+                    return focusHost;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Computes whether this view should be exposed for accessibility. In
+     * general, views that are interactive or provide information are exposed
+     * while views that serve only as containers are hidden.
+     * <p>
+     * If an ancestor of this view has importance
+     * {@link #IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS}, this method
+     * returns <code>false</code>.
+     * <p>
+     * Otherwise, the value is computed according to the view's
+     * {@link #getImportantForAccessibility()} value:
+     * <ol>
+     * <li>{@link #IMPORTANT_FOR_ACCESSIBILITY_NO} or
+     * {@link #IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS}, return <code>false
+     * </code>
+     * <li>{@link #IMPORTANT_FOR_ACCESSIBILITY_YES}, return <code>true</code>
+     * <li>{@link #IMPORTANT_FOR_ACCESSIBILITY_AUTO}, return <code>true</code> if
+     * view satisfies any of the following:
+     * <ul>
+     * <li>Is actionable, e.g. {@link #isClickable()},
+     * {@link #isLongClickable()}, or {@link #isFocusable()}
+     * <li>Has an {@link AccessibilityDelegate}
+     * <li>Has an interaction listener, e.g. {@link OnTouchListener},
+     * {@link OnKeyListener}, etc.
+     * <li>Is an accessibility live region, e.g.
+     * {@link #getAccessibilityLiveRegion()} is not
+     * {@link #ACCESSIBILITY_LIVE_REGION_NONE}.
+     * </ul>
+     * <li>Has an accessibility pane title, see {@link #setAccessibilityPaneTitle}</li>
+     * </ol>
+     *
+     * @return Whether the view is exposed for accessibility.
+     * @see #setImportantForAccessibility(int)
+     * @see #getImportantForAccessibility()
+     */
+    public boolean isImportantForAccessibility() {
+        final int mode = getImportantForAccessibility();
+        if (mode == IMPORTANT_FOR_ACCESSIBILITY_NO
+                || mode == IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) {
+            return false;
+        }
+
+        // Check parent mode to ensure we're not hidden.
+        ViewParent parent = mParent;
+        while (parent instanceof View) {
+            if (((View) parent).getImportantForAccessibility()
+                    == IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) {
+                return false;
+            }
+            parent = parent.getParent();
+        }
+
+        return mode == IMPORTANT_FOR_ACCESSIBILITY_YES || isActionableForAccessibility()
+                || hasListenersForAccessibility() || getAccessibilityNodeProvider() != null
+                || getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE
+                || isAccessibilityPane();
+    }
+
+    /**
+     * Gets the parent for accessibility purposes. Note that the parent for
+     * accessibility is not necessary the immediate parent. It is the first
+     * predecessor that is important for accessibility.
+     *
+     * @return The parent for accessibility purposes.
+     */
+    public ViewParent getParentForAccessibility() {
+        if (mParent instanceof View) {
+            View parentView = (View) mParent;
+            if (parentView.includeForAccessibility()) {
+                return mParent;
+            } else {
+                return mParent.getParentForAccessibility();
+            }
+        }
+        return null;
+    }
+
+    /** @hide */
+    View getSelfOrParentImportantForA11y() {
+        if (isImportantForAccessibility()) return this;
+        ViewParent parent = getParentForAccessibility();
+        if (parent instanceof View) return (View) parent;
+        return null;
+    }
+
+    /**
+     * Adds the children of this View relevant for accessibility to the given list
+     * as output. Since some Views are not important for accessibility the added
+     * child views are not necessarily direct children of this view, rather they are
+     * the first level of descendants important for accessibility.
+     *
+     * @param outChildren The output list that will receive children for accessibility.
+     */
+    public void addChildrenForAccessibility(ArrayList<View> outChildren) {
+
+    }
+
+    /**
+     * Whether to regard this view for accessibility. A view is regarded for
+     * accessibility if it is important for accessibility or the querying
+     * accessibility service has explicitly requested that view not
+     * important for accessibility are regarded.
+     *
+     * @return Whether to regard the view for accessibility.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public boolean includeForAccessibility() {
+        if (mAttachInfo != null) {
+            return (mAttachInfo.mAccessibilityFetchFlags
+                    & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0
+                    || isImportantForAccessibility();
+        }
+        return false;
+    }
+
+    /**
+     * Returns whether the View is considered actionable from
+     * accessibility perspective. Such view are important for
+     * accessibility.
+     *
+     * @return True if the view is actionable for accessibility.
+     *
+     * @hide
+     */
+    public boolean isActionableForAccessibility() {
+        return (isClickable() || isLongClickable() || isFocusable());
+    }
+
+    /**
+     * Returns whether the View has registered callbacks which makes it
+     * important for accessibility.
+     *
+     * @return True if the view is actionable for accessibility.
+     */
+    private boolean hasListenersForAccessibility() {
+        ListenerInfo info = getListenerInfo();
+        return mTouchDelegate != null || info.mOnKeyListener != null
+                || info.mOnTouchListener != null || info.mOnGenericMotionListener != null
+                || info.mOnHoverListener != null || info.mOnDragListener != null;
+    }
+
+    /**
+     * Notifies that the accessibility state of this view changed. The change
+     * is local to this view and does not represent structural changes such
+     * as children and parent. For example, the view became focusable. The
+     * notification is at at most once every
+     * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}
+     * to avoid unnecessary load to the system. Also once a view has a pending
+     * notification this method is a NOP until the notification has been sent.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public void notifyViewAccessibilityStateChangedIfNeeded(int changeType) {
+        if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) {
+            return;
+        }
+
+        // Changes to views with a pane title count as window state changes, as the pane title
+        // marks them as significant parts of the UI.
+        if ((changeType != AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE)
+                && isAccessibilityPane()) {
+            // If the pane isn't visible, content changed events are sufficient unless we're
+            // reporting that the view just disappeared
+            if ((getVisibility() == VISIBLE)
+                    || (changeType == AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED)) {
+                final AccessibilityEvent event = AccessibilityEvent.obtain();
+                onInitializeAccessibilityEvent(event);
+                event.setEventType(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+                event.setContentChangeTypes(changeType);
+                event.setSource(this);
+                onPopulateAccessibilityEvent(event);
+                if (mParent != null) {
+                    try {
+                        mParent.requestSendAccessibilityEvent(this, event);
+                    } catch (AbstractMethodError e) {
+                        Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName()
+                                + " does not fully implement ViewParent", e);
+                    }
+                }
+                return;
+            }
+        }
+
+        // If this is a live region, we should send a subtree change event
+        // from this view immediately. Otherwise, we can let it propagate up.
+        if (getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE) {
+            final AccessibilityEvent event = AccessibilityEvent.obtain();
+            event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+            event.setContentChangeTypes(changeType);
+            sendAccessibilityEventUnchecked(event);
+        } else if (mParent != null) {
+            try {
+                mParent.notifySubtreeAccessibilityStateChanged(this, this, changeType);
+            } catch (AbstractMethodError e) {
+                Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
+                        " does not fully implement ViewParent", e);
+            }
+        }
+    }
+
+    /**
+     * Notifies that the accessibility state of this view changed. The change
+     * is *not* local to this view and does represent structural changes such
+     * as children and parent. For example, the view size changed. The
+     * notification is at at most once every
+     * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}
+     * to avoid unnecessary load to the system. Also once a view has a pending
+     * notification this method is a NOP until the notification has been sent.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void notifySubtreeAccessibilityStateChangedIfNeeded() {
+        if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) {
+            return;
+        }
+
+        if ((mPrivateFlags2 & PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED) == 0) {
+            mPrivateFlags2 |= PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED;
+            if (mParent != null) {
+                try {
+                    mParent.notifySubtreeAccessibilityStateChanged(
+                            this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
+                } catch (AbstractMethodError e) {
+                    Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
+                            " does not fully implement ViewParent", e);
+                }
+            }
+        }
+    }
+
+    private void notifySubtreeAccessibilityStateChangedByParentIfNeeded() {
+        if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
+            return;
+        }
+
+        final View sendA11yEventView = (View) getParentForAccessibility();
+        if (sendA11yEventView != null && sendA11yEventView.isShown()) {
+            sendA11yEventView.notifySubtreeAccessibilityStateChangedIfNeeded();
+        }
+    }
+
+    /**
+     * Changes the visibility of this View without triggering any other changes. This should only
+     * be used by animation frameworks, such as {@link android.transition.Transition}, where
+     * visibility changes should not adjust focus or trigger a new layout. Application developers
+     * should use {@link #setVisibility} instead to ensure that the hierarchy is correctly updated.
+     *
+     * <p>Only call this method when a temporary visibility must be applied during an
+     * animation and the original visibility value is guaranteed to be reset after the
+     * animation completes. Use {@link #setVisibility} in all other cases.</p>
+     *
+     * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
+     * @see #setVisibility(int)
+     */
+    public void setTransitionVisibility(@Visibility int visibility) {
+        mViewFlags = (mViewFlags & ~View.VISIBILITY_MASK) | visibility;
+    }
+
+    /**
+     * Reset the flag indicating the accessibility state of the subtree rooted
+     * at this view changed.
+     */
+    void resetSubtreeAccessibilityStateChanged() {
+        mPrivateFlags2 &= ~PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED;
+    }
+
+    /**
+     * Report an accessibility action to this view's parents for delegated processing.
+     *
+     * <p>Implementations of {@link #performAccessibilityAction(int, Bundle)} may internally
+     * call this method to delegate an accessibility action to a supporting parent. If the parent
+     * returns true from its
+     * {@link ViewParent#onNestedPrePerformAccessibilityAction(View, int, android.os.Bundle)}
+     * method this method will return true to signify that the action was consumed.</p>
+     *
+     * <p>This method is useful for implementing nested scrolling child views. If
+     * {@link #isNestedScrollingEnabled()} returns true and the action is a scrolling action
+     * a custom view implementation may invoke this method to allow a parent to consume the
+     * scroll first. If this method returns true the custom view should skip its own scrolling
+     * behavior.</p>
+     *
+     * @param action Accessibility action to delegate
+     * @param arguments Optional action arguments
+     * @return true if the action was consumed by a parent
+     */
+    public boolean dispatchNestedPrePerformAccessibilityAction(int action, Bundle arguments) {
+        for (ViewParent p = getParent(); p != null; p = p.getParent()) {
+            if (p.onNestedPrePerformAccessibilityAction(this, action, arguments)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Performs the specified accessibility action on the view. For
+     * possible accessibility actions look at {@link AccessibilityNodeInfo}.
+     * <p>
+     * If an {@link AccessibilityDelegate} has been specified via calling
+     * {@link #setAccessibilityDelegate(AccessibilityDelegate)} its
+     * {@link AccessibilityDelegate#performAccessibilityAction(View, int, Bundle)}
+     * is responsible for handling this call.
+     * </p>
+     *
+     * <p>The default implementation will delegate
+     * {@link AccessibilityNodeInfo#ACTION_SCROLL_BACKWARD} and
+     * {@link AccessibilityNodeInfo#ACTION_SCROLL_FORWARD} to nested scrolling parents if
+     * {@link #isNestedScrollingEnabled() nested scrolling is enabled} on this view.</p>
+     *
+     * @param action The action to perform.
+     * @param arguments Optional action arguments.
+     * @return Whether the action was performed.
+     */
+    public boolean performAccessibilityAction(int action, Bundle arguments) {
+      if (mAccessibilityDelegate != null) {
+          return mAccessibilityDelegate.performAccessibilityAction(this, action, arguments);
+      } else {
+          return performAccessibilityActionInternal(action, arguments);
+      }
+    }
+
+   /**
+    * @see #performAccessibilityAction(int, Bundle)
+    *
+    * Note: Called from the default {@link AccessibilityDelegate}.
+    *
+    * @hide
+    */
+    @UnsupportedAppUsage
+    public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+        if (isNestedScrollingEnabled()
+                && (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD
+                || action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD
+                || action == R.id.accessibilityActionScrollUp
+                || action == R.id.accessibilityActionScrollLeft
+                || action == R.id.accessibilityActionScrollDown
+                || action == R.id.accessibilityActionScrollRight)) {
+            if (dispatchNestedPrePerformAccessibilityAction(action, arguments)) {
+                return true;
+            }
+        }
+
+        switch (action) {
+            case AccessibilityNodeInfo.ACTION_CLICK: {
+                if (isClickable()) {
+                    performClickInternal();
+                    return true;
+                }
+            } break;
+            case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
+                if (isLongClickable()) {
+                    performLongClick();
+                    return true;
+                }
+            } break;
+            case AccessibilityNodeInfo.ACTION_FOCUS: {
+                if (!hasFocus()) {
+                    // Get out of touch mode since accessibility
+                    // wants to move focus around.
+                    getViewRootImpl().ensureTouchMode(false);
+                    return requestFocus();
+                }
+            } break;
+            case AccessibilityNodeInfo.ACTION_CLEAR_FOCUS: {
+                if (hasFocus()) {
+                    clearFocus();
+                    return !isFocused();
+                }
+            } break;
+            case AccessibilityNodeInfo.ACTION_SELECT: {
+                if (!isSelected()) {
+                    setSelected(true);
+                    return isSelected();
+                }
+            } break;
+            case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: {
+                if (isSelected()) {
+                    setSelected(false);
+                    return !isSelected();
+                }
+            } break;
+            case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
+                if (!isAccessibilityFocused()) {
+                    return requestAccessibilityFocus();
+                }
+            } break;
+            case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
+                if (isAccessibilityFocused()) {
+                    clearAccessibilityFocus();
+                    return true;
+                }
+            } break;
+            case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY: {
+                if (arguments != null) {
+                    final int granularity = arguments.getInt(
+                            AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);
+                    final boolean extendSelection = arguments.getBoolean(
+                            AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN);
+                    return traverseAtGranularity(granularity, true, extendSelection);
+                }
+            } break;
+            case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
+                if (arguments != null) {
+                    final int granularity = arguments.getInt(
+                            AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);
+                    final boolean extendSelection = arguments.getBoolean(
+                            AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN);
+                    return traverseAtGranularity(granularity, false, extendSelection);
+                }
+            } break;
+            case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
+                CharSequence text = getIterableTextForAccessibility();
+                if (text == null) {
+                    return false;
+                }
+                final int start = (arguments != null) ? arguments.getInt(
+                        AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
+                final int end = (arguments != null) ? arguments.getInt(
+                AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
+                // Only cursor position can be specified (selection length == 0)
+                if ((getAccessibilitySelectionStart() != start
+                        || getAccessibilitySelectionEnd() != end)
+                        && (start == end)) {
+                    setAccessibilitySelection(start, end);
+                    notifyViewAccessibilityStateChangedIfNeeded(
+                            AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+                    return true;
+                }
+            } break;
+            case R.id.accessibilityActionShowOnScreen: {
+                if (mAttachInfo != null) {
+                    final Rect r = mAttachInfo.mTmpInvalRect;
+                    getDrawingRect(r);
+                    return requestRectangleOnScreen(r, true);
+                }
+            } break;
+            case R.id.accessibilityActionContextClick: {
+                if (isContextClickable()) {
+                    performContextClick();
+                    return true;
+                }
+            } break;
+            case R.id.accessibilityActionShowTooltip: {
+                if ((mTooltipInfo != null) && (mTooltipInfo.mTooltipPopup != null)) {
+                    // Tooltip already showing
+                    return false;
+                }
+                return showLongClickTooltip(0, 0);
+            }
+            case R.id.accessibilityActionHideTooltip: {
+                if ((mTooltipInfo == null) || (mTooltipInfo.mTooltipPopup == null)) {
+                    // No tooltip showing
+                    return false;
+                }
+                hideTooltip();
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean traverseAtGranularity(int granularity, boolean forward,
+            boolean extendSelection) {
+        CharSequence text = getIterableTextForAccessibility();
+        if (text == null || text.length() == 0) {
+            return false;
+        }
+        TextSegmentIterator iterator = getIteratorForGranularity(granularity);
+        if (iterator == null) {
+            return false;
+        }
+        int current = getAccessibilitySelectionEnd();
+        if (current == ACCESSIBILITY_CURSOR_POSITION_UNDEFINED) {
+            current = forward ? 0 : text.length();
+        }
+        final int[] range = forward ? iterator.following(current) : iterator.preceding(current);
+        if (range == null) {
+            return false;
+        }
+        final int segmentStart = range[0];
+        final int segmentEnd = range[1];
+        int selectionStart;
+        int selectionEnd;
+        if (extendSelection && isAccessibilitySelectionExtendable()) {
+            selectionStart = getAccessibilitySelectionStart();
+            if (selectionStart == ACCESSIBILITY_CURSOR_POSITION_UNDEFINED) {
+                selectionStart = forward ? segmentStart : segmentEnd;
+            }
+            selectionEnd = forward ? segmentEnd : segmentStart;
+        } else {
+            selectionStart = selectionEnd= forward ? segmentEnd : segmentStart;
+        }
+        setAccessibilitySelection(selectionStart, selectionEnd);
+        final int action = forward ? AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                : AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY;
+        sendViewTextTraversedAtGranularityEvent(action, granularity, segmentStart, segmentEnd);
+        return true;
+    }
+
+    /**
+     * Gets the text reported for accessibility purposes.
+     *
+     * @return The accessibility text.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public CharSequence getIterableTextForAccessibility() {
+        return getContentDescription();
+    }
+
+    /**
+     * Gets whether accessibility selection can be extended.
+     *
+     * @return If selection is extensible.
+     *
+     * @hide
+     */
+    public boolean isAccessibilitySelectionExtendable() {
+        return false;
+    }
+
+    /**
+     * @hide
+     */
+    public int getAccessibilitySelectionStart() {
+        return mAccessibilityCursorPosition;
+    }
+
+    /**
+     * @hide
+     */
+    public int getAccessibilitySelectionEnd() {
+        return getAccessibilitySelectionStart();
+    }
+
+    /**
+     * @hide
+     */
+    public void setAccessibilitySelection(int start, int end) {
+        if (start ==  end && end == mAccessibilityCursorPosition) {
+            return;
+        }
+        if (start >= 0 && start == end && end <= getIterableTextForAccessibility().length()) {
+            mAccessibilityCursorPosition = start;
+        } else {
+            mAccessibilityCursorPosition = ACCESSIBILITY_CURSOR_POSITION_UNDEFINED;
+        }
+        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
+    }
+
+    private void sendViewTextTraversedAtGranularityEvent(int action, int granularity,
+            int fromIndex, int toIndex) {
+        if (mParent == null) {
+            return;
+        }
+        AccessibilityEvent event = AccessibilityEvent.obtain(
+                AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY);
+        onInitializeAccessibilityEvent(event);
+        onPopulateAccessibilityEvent(event);
+        event.setFromIndex(fromIndex);
+        event.setToIndex(toIndex);
+        event.setAction(action);
+        event.setMovementGranularity(granularity);
+        mParent.requestSendAccessibilityEvent(this, event);
+    }
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public TextSegmentIterator getIteratorForGranularity(int granularity) {
+        switch (granularity) {
+            case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER: {
+                CharSequence text = getIterableTextForAccessibility();
+                if (text != null && text.length() > 0) {
+                    CharacterTextSegmentIterator iterator =
+                        CharacterTextSegmentIterator.getInstance(
+                                mContext.getResources().getConfiguration().locale);
+                    iterator.initialize(text.toString());
+                    return iterator;
+                }
+            } break;
+            case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD: {
+                CharSequence text = getIterableTextForAccessibility();
+                if (text != null && text.length() > 0) {
+                    WordTextSegmentIterator iterator =
+                        WordTextSegmentIterator.getInstance(
+                                mContext.getResources().getConfiguration().locale);
+                    iterator.initialize(text.toString());
+                    return iterator;
+                }
+            } break;
+            case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH: {
+                CharSequence text = getIterableTextForAccessibility();
+                if (text != null && text.length() > 0) {
+                    ParagraphTextSegmentIterator iterator =
+                        ParagraphTextSegmentIterator.getInstance();
+                    iterator.initialize(text.toString());
+                    return iterator;
+                }
+            } break;
+        }
+        return null;
+    }
+
+    /**
+     * Tells whether the {@link View} is in the state between {@link #onStartTemporaryDetach()}
+     * and {@link #onFinishTemporaryDetach()}.
+     *
+     * <p>This method always returns {@code true} when called directly or indirectly from
+     * {@link #onStartTemporaryDetach()}. The return value when called directly or indirectly from
+     * {@link #onFinishTemporaryDetach()}, however, depends on the OS version.
+     * <ul>
+     *     <li>{@code true} on {@link android.os.Build.VERSION_CODES#N API 24}</li>
+     *     <li>{@code false} on {@link android.os.Build.VERSION_CODES#N_MR1 API 25}} and later</li>
+     * </ul>
+     * </p>
+     *
+     * @return {@code true} when the View is in the state between {@link #onStartTemporaryDetach()}
+     * and {@link #onFinishTemporaryDetach()}.
+     */
+    public final boolean isTemporarilyDetached() {
+        return (mPrivateFlags3 & PFLAG3_TEMPORARY_DETACH) != 0;
+    }
+
+    /**
+     * Dispatch {@link #onStartTemporaryDetach()} to this View and its direct children if this is
+     * a container View.
+     */
+    @CallSuper
+    public void dispatchStartTemporaryDetach() {
+        mPrivateFlags3 |= PFLAG3_TEMPORARY_DETACH;
+        notifyEnterOrExitForAutoFillIfNeeded(false);
+        notifyAppearedOrDisappearedForContentCaptureIfNeeded(false);
+        onStartTemporaryDetach();
+    }
+
+    /**
+     * This is called when a container is going to temporarily detach a child, with
+     * {@link ViewGroup#detachViewFromParent(View) ViewGroup.detachViewFromParent}.
+     * It will either be followed by {@link #onFinishTemporaryDetach()} or
+     * {@link #onDetachedFromWindow()} when the container is done.
+     */
+    public void onStartTemporaryDetach() {
+        removeUnsetPressCallback();
+        mPrivateFlags |= PFLAG_CANCEL_NEXT_UP_EVENT;
+    }
+
+    /**
+     * Dispatch {@link #onFinishTemporaryDetach()} to this View and its direct children if this is
+     * a container View.
+     */
+    @CallSuper
+    public void dispatchFinishTemporaryDetach() {
+        mPrivateFlags3 &= ~PFLAG3_TEMPORARY_DETACH;
+        onFinishTemporaryDetach();
+        if (hasWindowFocus() && hasFocus()) {
+            notifyFocusChangeToImeFocusController(true /* hasFocus */);
+        }
+        notifyEnterOrExitForAutoFillIfNeeded(true);
+        notifyAppearedOrDisappearedForContentCaptureIfNeeded(true);
+    }
+
+    /**
+     * Called after {@link #onStartTemporaryDetach} when the container is done
+     * changing the view.
+     */
+    public void onFinishTemporaryDetach() {
+    }
+
+    /**
+     * Return the global {@link KeyEvent.DispatcherState KeyEvent.DispatcherState}
+     * for this view's window.  Returns null if the view is not currently attached
+     * to the window.  Normally you will not need to use this directly, but
+     * just use the standard high-level event callbacks like
+     * {@link #onKeyDown(int, KeyEvent)}.
+     */
+    public KeyEvent.DispatcherState getKeyDispatcherState() {
+        return mAttachInfo != null ? mAttachInfo.mKeyDispatchState : null;
+    }
+
+    /**
+     * Dispatch a key event before it is processed by any input method
+     * associated with the view hierarchy.  This can be used to intercept
+     * key events in special situations before the IME consumes them; a
+     * typical example would be handling the BACK key to update the application's
+     * UI instead of allowing the IME to see it and close itself.
+     *
+     * @param event The key event to be dispatched.
+     * @return True if the event was handled, false otherwise.
+     */
+    public boolean dispatchKeyEventPreIme(KeyEvent event) {
+        return onKeyPreIme(event.getKeyCode(), event);
+    }
+
+    /**
+     * Dispatch a key event to the next view on the focus path. This path runs
+     * from the top of the view tree down to the currently focused view. If this
+     * view has focus, it will dispatch to itself. Otherwise it will dispatch
+     * the next node down the focus path. This method also fires any key
+     * listeners.
+     *
+     * @param event The key event to be dispatched.
+     * @return True if the event was handled, false otherwise.
+     */
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        if (mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onKeyEvent(event, 0);
+        }
+
+        // Give any attached key listener a first crack at the event.
+        //noinspection SimplifiableIfStatement
+        ListenerInfo li = mListenerInfo;
+        if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
+                && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
+            return true;
+        }
+
+        if (event.dispatch(this, mAttachInfo != null
+                ? mAttachInfo.mKeyDispatchState : null, this)) {
+            return true;
+        }
+
+        if (mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+        }
+        return false;
+    }
+
+    /**
+     * Dispatches a key shortcut event.
+     *
+     * @param event The key event to be dispatched.
+     * @return True if the event was handled by the view, false otherwise.
+     */
+    public boolean dispatchKeyShortcutEvent(KeyEvent event) {
+        return onKeyShortcut(event.getKeyCode(), event);
+    }
+
+    /**
+     * Pass the touch screen motion event down to the target view, or this
+     * view if it is the target.
+     *
+     * @param event The motion event to be dispatched.
+     * @return True if the event was handled by the view, false otherwise.
+     */
+    public boolean dispatchTouchEvent(MotionEvent event) {
+        // If the event should be handled by accessibility focus first.
+        if (event.isTargetAccessibilityFocus()) {
+            // We don't have focus or no virtual descendant has it, do not handle the event.
+            if (!isAccessibilityFocusedViewOrHost()) {
+                return false;
+            }
+            // We have focus and got the event, then use normal event dispatch.
+            event.setTargetAccessibilityFocus(false);
+        }
+        boolean result = false;
+
+        if (mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
+        }
+
+        final int actionMasked = event.getActionMasked();
+        if (actionMasked == MotionEvent.ACTION_DOWN) {
+            // Defensive cleanup for new gesture
+            stopNestedScroll();
+        }
+
+        if (onFilterTouchEventForSecurity(event)) {
+            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
+                result = true;
+            }
+            //noinspection SimplifiableIfStatement
+            ListenerInfo li = mListenerInfo;
+            if (li != null && li.mOnTouchListener != null
+                    && (mViewFlags & ENABLED_MASK) == ENABLED
+                    && li.mOnTouchListener.onTouch(this, event)) {
+                result = true;
+            }
+
+            if (!result && onTouchEvent(event)) {
+                result = true;
+            }
+        }
+
+        if (!result && mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+        }
+
+        // Clean up after nested scrolls if this is the end of a gesture;
+        // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
+        // of the gesture.
+        if (actionMasked == MotionEvent.ACTION_UP ||
+                actionMasked == MotionEvent.ACTION_CANCEL ||
+                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
+            stopNestedScroll();
+        }
+
+        return result;
+    }
+
+    boolean isAccessibilityFocusedViewOrHost() {
+        return isAccessibilityFocused() || (getViewRootImpl() != null && getViewRootImpl()
+                .getAccessibilityFocusedHost() == this);
+    }
+
+    /**
+     * Returns whether this view can receive pointer events.
+     *
+     * @return {@code true} if this view can receive pointer events.
+     * @hide
+     */
+    protected boolean canReceivePointerEvents() {
+        return (mViewFlags & VISIBILITY_MASK) == VISIBLE || getAnimation() != null;
+    }
+
+    /**
+     * Filter the touch event to apply security policies.
+     *
+     * @param event The motion event to be filtered.
+     * @return True if the event should be dispatched, false if the event should be dropped.
+     *
+     * @see #getFilterTouchesWhenObscured
+     */
+    public boolean onFilterTouchEventForSecurity(MotionEvent event) {
+        //noinspection RedundantIfStatement
+        if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
+                && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
+            // Window is obscured, drop this touch.
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Pass a trackball motion event down to the focused view.
+     *
+     * @param event The motion event to be dispatched.
+     * @return True if the event was handled by the view, false otherwise.
+     */
+    public boolean dispatchTrackballEvent(MotionEvent event) {
+        if (mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onTrackballEvent(event, 0);
+        }
+
+        return onTrackballEvent(event);
+    }
+
+    /**
+     * Pass a captured pointer event down to the focused view.
+     *
+     * @param event The motion event to be dispatched.
+     * @return True if the event was handled by the view, false otherwise.
+     */
+    public boolean dispatchCapturedPointerEvent(MotionEvent event) {
+        if (!hasPointerCapture()) {
+            return false;
+        }
+        //noinspection SimplifiableIfStatement
+        ListenerInfo li = mListenerInfo;
+        if (li != null && li.mOnCapturedPointerListener != null
+                && li.mOnCapturedPointerListener.onCapturedPointer(this, event)) {
+            return true;
+        }
+        return onCapturedPointerEvent(event);
+    }
+
+    /**
+     * Dispatch a generic motion event.
+     * <p>
+     * Generic motion events with source class {@link InputDevice#SOURCE_CLASS_POINTER}
+     * are delivered to the view under the pointer.  All other generic motion events are
+     * delivered to the focused view.  Hover events are handled specially and are delivered
+     * to {@link #onHoverEvent(MotionEvent)}.
+     * </p>
+     *
+     * @param event The motion event to be dispatched.
+     * @return True if the event was handled by the view, false otherwise.
+     */
+    public boolean dispatchGenericMotionEvent(MotionEvent event) {
+        if (mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onGenericMotionEvent(event, 0);
+        }
+
+        final int source = event.getSource();
+        if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+            final int action = event.getAction();
+            if (action == MotionEvent.ACTION_HOVER_ENTER
+                    || action == MotionEvent.ACTION_HOVER_MOVE
+                    || action == MotionEvent.ACTION_HOVER_EXIT) {
+                if (dispatchHoverEvent(event)) {
+                    return true;
+                }
+            } else if (dispatchGenericPointerEvent(event)) {
+                return true;
+            }
+        } else if (dispatchGenericFocusedEvent(event)) {
+            return true;
+        }
+
+        if (dispatchGenericMotionEventInternal(event)) {
+            return true;
+        }
+
+        if (mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+        }
+        return false;
+    }
+
+    private boolean dispatchGenericMotionEventInternal(MotionEvent event) {
+        //noinspection SimplifiableIfStatement
+        ListenerInfo li = mListenerInfo;
+        if (li != null && li.mOnGenericMotionListener != null
+                && (mViewFlags & ENABLED_MASK) == ENABLED
+                && li.mOnGenericMotionListener.onGenericMotion(this, event)) {
+            return true;
+        }
+
+        if (onGenericMotionEvent(event)) {
+            return true;
+        }
+
+        final int actionButton = event.getActionButton();
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_BUTTON_PRESS:
+                if (isContextClickable() && !mInContextButtonPress && !mHasPerformedLongPress
+                        && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
+                        || actionButton == MotionEvent.BUTTON_SECONDARY)) {
+                    if (performContextClick(event.getX(), event.getY())) {
+                        mInContextButtonPress = true;
+                        setPressed(true, event.getX(), event.getY());
+                        removeTapCallback();
+                        removeLongPressCallback();
+                        return true;
+                    }
+                }
+                break;
+
+            case MotionEvent.ACTION_BUTTON_RELEASE:
+                if (mInContextButtonPress && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
+                        || actionButton == MotionEvent.BUTTON_SECONDARY)) {
+                    mInContextButtonPress = false;
+                    mIgnoreNextUpEvent = true;
+                }
+                break;
+        }
+
+        if (mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+        }
+        return false;
+    }
+
+    /**
+     * Dispatch a hover event.
+     * <p>
+     * Do not call this method directly.
+     * Call {@link #dispatchGenericMotionEvent(MotionEvent)} instead.
+     * </p>
+     *
+     * @param event The motion event to be dispatched.
+     * @return True if the event was handled by the view, false otherwise.
+     */
+    protected boolean dispatchHoverEvent(MotionEvent event) {
+        ListenerInfo li = mListenerInfo;
+        //noinspection SimplifiableIfStatement
+        if (li != null && li.mOnHoverListener != null
+                && (mViewFlags & ENABLED_MASK) == ENABLED
+                && li.mOnHoverListener.onHover(this, event)) {
+            return true;
+        }
+
+        return onHoverEvent(event);
+    }
+
+    /**
+     * Returns true if the view has a child to which it has recently sent
+     * {@link MotionEvent#ACTION_HOVER_ENTER}.  If this view is hovered and
+     * it does not have a hovered child, then it must be the innermost hovered view.
+     * @hide
+     */
+    protected boolean hasHoveredChild() {
+        return false;
+    }
+
+    /**
+     * Returns true if the given point, in local coordinates, is inside the hovered child.
+     *
+     * @hide
+     */
+    protected boolean pointInHoveredChild(MotionEvent event) {
+        return false;
+    }
+
+    /**
+     * Dispatch a generic motion event to the view under the first pointer.
+     * <p>
+     * Do not call this method directly.
+     * Call {@link #dispatchGenericMotionEvent(MotionEvent)} instead.
+     * </p>
+     *
+     * @param event The motion event to be dispatched.
+     * @return True if the event was handled by the view, false otherwise.
+     */
+    protected boolean dispatchGenericPointerEvent(MotionEvent event) {
+        return false;
+    }
+
+    /**
+     * Dispatch a generic motion event to the currently focused view.
+     * <p>
+     * Do not call this method directly.
+     * Call {@link #dispatchGenericMotionEvent(MotionEvent)} instead.
+     * </p>
+     *
+     * @param event The motion event to be dispatched.
+     * @return True if the event was handled by the view, false otherwise.
+     */
+    protected boolean dispatchGenericFocusedEvent(MotionEvent event) {
+        return false;
+    }
+
+    /**
+     * Dispatch a pointer event.
+     * <p>
+     * Dispatches touch related pointer events to {@link #onTouchEvent(MotionEvent)} and all
+     * other events to {@link #onGenericMotionEvent(MotionEvent)}.  This separation of concerns
+     * reinforces the invariant that {@link #onTouchEvent(MotionEvent)} is really about touches
+     * and should not be expected to handle other pointing device features.
+     * </p>
+     *
+     * @param event The motion event to be dispatched.
+     * @return True if the event was handled by the view, false otherwise.
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public final boolean dispatchPointerEvent(MotionEvent event) {
+        if (event.isTouchEvent()) {
+            return dispatchTouchEvent(event);
+        } else {
+            return dispatchGenericMotionEvent(event);
+        }
+    }
+
+    /**
+     * Called when the window containing this view gains or loses window focus.
+     * ViewGroups should override to route to their children.
+     *
+     * @param hasFocus True if the window containing this view now has focus,
+     *        false otherwise.
+     */
+    public void dispatchWindowFocusChanged(boolean hasFocus) {
+        onWindowFocusChanged(hasFocus);
+    }
+
+    /**
+     * Called when the window containing this view gains or loses focus.  Note
+     * that this is separate from view focus: to receive key events, both
+     * your view and its window must have focus.  If a window is displayed
+     * on top of yours that takes input focus, then your own window will lose
+     * focus but the view focus will remain unchanged.
+     *
+     * @param hasWindowFocus True if the window containing this view now has
+     *        focus, false otherwise.
+     */
+    public void onWindowFocusChanged(boolean hasWindowFocus) {
+        if (!hasWindowFocus) {
+            if (isPressed()) {
+                setPressed(false);
+            }
+            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
+            if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
+                notifyFocusChangeToImeFocusController(false /* hasFocus */);
+            }
+            removeLongPressCallback();
+            removeTapCallback();
+            onFocusLost();
+        } else if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
+            notifyFocusChangeToImeFocusController(true /* hasFocus */);
+        }
+
+        refreshDrawableState();
+    }
+
+    /**
+     * Returns true if this view is in a window that currently has window focus.
+     * Note that this is not the same as the view itself having focus.
+     *
+     * @return True if this view is in a window that currently has window focus.
+     */
+    public boolean hasWindowFocus() {
+        return mAttachInfo != null && mAttachInfo.mHasWindowFocus;
+    }
+
+    /**
+     * @return {@code true} if this view is in a window that currently has IME focusable state.
+     * @hide
+     */
+    public boolean hasImeFocus() {
+        return getViewRootImpl() != null && getViewRootImpl().getImeFocusController().hasImeFocus();
+    }
+
+    /**
+     * Dispatch a view visibility change down the view hierarchy.
+     * ViewGroups should override to route to their children.
+     * @param changedView The view whose visibility changed. Could be 'this' or
+     * an ancestor view.
+     * @param visibility The new visibility of changedView: {@link #VISIBLE},
+     * {@link #INVISIBLE} or {@link #GONE}.
+     */
+    protected void dispatchVisibilityChanged(@NonNull View changedView,
+            @Visibility int visibility) {
+        onVisibilityChanged(changedView, visibility);
+    }
+
+    /**
+     * Called when the visibility of the view or an ancestor of the view has
+     * changed.
+     *
+     * @param changedView The view whose visibility changed. May be
+     *                    {@code this} or an ancestor view.
+     * @param visibility The new visibility, one of {@link #VISIBLE},
+     *                   {@link #INVISIBLE} or {@link #GONE}.
+     */
+    protected void onVisibilityChanged(@NonNull View changedView, @Visibility int visibility) {
+    }
+
+    /**
+     * Dispatch a hint about whether this view is displayed. For instance, when
+     * a View moves out of the screen, it might receives a display hint indicating
+     * the view is not displayed. Applications should not <em>rely</em> on this hint
+     * as there is no guarantee that they will receive one.
+     *
+     * @param hint A hint about whether or not this view is displayed:
+     * {@link #VISIBLE} or {@link #INVISIBLE}.
+     */
+    public void dispatchDisplayHint(@Visibility int hint) {
+        onDisplayHint(hint);
+    }
+
+    /**
+     * Gives this view a hint about whether is displayed or not. For instance, when
+     * a View moves out of the screen, it might receives a display hint indicating
+     * the view is not displayed. Applications should not <em>rely</em> on this hint
+     * as there is no guarantee that they will receive one.
+     *
+     * @param hint A hint about whether or not this view is displayed:
+     * {@link #VISIBLE} or {@link #INVISIBLE}.
+     */
+    protected void onDisplayHint(@Visibility int hint) {
+    }
+
+    /**
+     * Dispatch a window visibility change down the view hierarchy.
+     * ViewGroups should override to route to their children.
+     *
+     * @param visibility The new visibility of the window.
+     *
+     * @see #onWindowVisibilityChanged(int)
+     */
+    public void dispatchWindowVisibilityChanged(@Visibility int visibility) {
+        onWindowVisibilityChanged(visibility);
+    }
+
+    /**
+     * Called when the window containing has change its visibility
+     * (between {@link #GONE}, {@link #INVISIBLE}, and {@link #VISIBLE}).  Note
+     * that this tells you whether or not your window is being made visible
+     * to the window manager; this does <em>not</em> tell you whether or not
+     * your window is obscured by other windows on the screen, even if it
+     * is itself visible.
+     *
+     * @param visibility The new visibility of the window.
+     */
+    protected void onWindowVisibilityChanged(@Visibility int visibility) {
+        if (visibility == VISIBLE) {
+            initialAwakenScrollBars();
+        }
+    }
+
+    /**
+     * @return true if this view and all ancestors are visible as of the last
+     * {@link #onVisibilityAggregated(boolean)} call.
+     */
+    boolean isAggregatedVisible() {
+        return (mPrivateFlags3 & PFLAG3_AGGREGATED_VISIBLE) != 0;
+    }
+
+    /**
+     * Internal dispatching method for {@link #onVisibilityAggregated}. Overridden by
+     * ViewGroup. Intended to only be called when {@link #isAttachedToWindow()},
+     * {@link #getWindowVisibility()} is {@link #VISIBLE} and this view's parent {@link #isShown()}.
+     *
+     * @param isVisible true if this view's visibility to the user is uninterrupted by its
+     *                  ancestors or by window visibility
+     * @return true if this view is visible to the user, not counting clipping or overlapping
+     */
+    boolean dispatchVisibilityAggregated(boolean isVisible) {
+        final boolean thisVisible = getVisibility() == VISIBLE;
+        // If we're not visible but something is telling us we are, ignore it.
+        if (thisVisible || !isVisible) {
+            onVisibilityAggregated(isVisible);
+        }
+        return thisVisible && isVisible;
+    }
+
+    /**
+     * Called when the user-visibility of this View is potentially affected by a change
+     * to this view itself, an ancestor view or the window this view is attached to.
+     *
+     * @param isVisible true if this view and all of its ancestors are {@link #VISIBLE}
+     *                  and this view's window is also visible
+     */
+    @CallSuper
+    public void onVisibilityAggregated(boolean isVisible) {
+        // Update our internal visibility tracking so we can detect changes
+        boolean oldVisible = isAggregatedVisible();
+        mPrivateFlags3 = isVisible ? (mPrivateFlags3 | PFLAG3_AGGREGATED_VISIBLE)
+                : (mPrivateFlags3 & ~PFLAG3_AGGREGATED_VISIBLE);
+        if (isVisible && mAttachInfo != null) {
+            initialAwakenScrollBars();
+        }
+
+        final Drawable dr = mBackground;
+        if (dr != null && isVisible != dr.isVisible()) {
+            dr.setVisible(isVisible, false);
+        }
+        final Drawable hl = mDefaultFocusHighlight;
+        if (hl != null && isVisible != hl.isVisible()) {
+            hl.setVisible(isVisible, false);
+        }
+        final Drawable fg = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
+        if (fg != null && isVisible != fg.isVisible()) {
+            fg.setVisible(isVisible, false);
+        }
+
+        if (isAutofillable()) {
+            AutofillManager afm = getAutofillManager();
+
+            if (afm != null && getAutofillViewId() > LAST_APP_AUTOFILL_ID) {
+                if (mVisibilityChangeForAutofillHandler != null) {
+                    mVisibilityChangeForAutofillHandler.removeMessages(0);
+                }
+
+                // If the view is in the background but still part of the hierarchy this is called
+                // with isVisible=false. Hence visibility==false requires further checks
+                if (isVisible) {
+                    afm.notifyViewVisibilityChanged(this, true);
+                } else {
+                    if (mVisibilityChangeForAutofillHandler == null) {
+                        mVisibilityChangeForAutofillHandler =
+                                new VisibilityChangeForAutofillHandler(afm, this);
+                    }
+                    // Let current operation (e.g. removal of the view from the hierarchy)
+                    // finish before checking state
+                    mVisibilityChangeForAutofillHandler.obtainMessage(0, this).sendToTarget();
+                }
+            }
+        }
+
+        if (isVisible != oldVisible) {
+            if (isAccessibilityPane()) {
+                notifyViewAccessibilityStateChangedIfNeeded(isVisible
+                        ? AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_APPEARED
+                        : AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED);
+            }
+
+            notifyAppearedOrDisappearedForContentCaptureIfNeeded(isVisible);
+
+            if (!getSystemGestureExclusionRects().isEmpty()) {
+                postUpdateSystemGestureExclusionRects();
+            }
+        }
+    }
+
+    /**
+     * Returns the current visibility of the window this view is attached to
+     * (either {@link #GONE}, {@link #INVISIBLE}, or {@link #VISIBLE}).
+     *
+     * @return Returns the current visibility of the view's window.
+     */
+    @Visibility
+    public int getWindowVisibility() {
+        return mAttachInfo != null ? mAttachInfo.mWindowVisibility : GONE;
+    }
+
+    /**
+     * Retrieve the overall visible display size in which the window this view is
+     * attached to has been positioned in.  This takes into account screen
+     * decorations above the window, for both cases where the window itself
+     * is being position inside of them or the window is being placed under
+     * then and covered insets are used for the window to position its content
+     * inside.  In effect, this tells you the available area where content can
+     * be placed and remain visible to users.
+     *
+     * @param outRect Filled in with the visible display frame.  If the view
+     * is not attached to a window, this is simply the raw display size.
+     */
+    public void getWindowVisibleDisplayFrame(Rect outRect) {
+        if (mAttachInfo != null) {
+            mAttachInfo.mViewRootImpl.getWindowVisibleDisplayFrame(outRect);
+            return;
+        }
+        // The view is not attached to a display so we don't have a context.
+        // Make a best guess about the display size.
+        Display d = DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
+        d.getRectSize(outRect);
+    }
+
+    /**
+     * Like {@link #getWindowVisibleDisplayFrame}, but returns the "full" display frame this window
+     * is currently in without any insets.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void getWindowDisplayFrame(Rect outRect) {
+        if (mAttachInfo != null) {
+            mAttachInfo.mViewRootImpl.getDisplayFrame(outRect);
+            return;
+        }
+        // The view is not attached to a display so we don't have a context.
+        // Make a best guess about the display size.
+        Display d = DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
+        d.getRectSize(outRect);
+    }
+
+    /**
+     * Dispatch a notification about a resource configuration change down
+     * the view hierarchy.
+     * ViewGroups should override to route to their children.
+     *
+     * @param newConfig The new resource configuration.
+     *
+     * @see #onConfigurationChanged(android.content.res.Configuration)
+     */
+    public void dispatchConfigurationChanged(Configuration newConfig) {
+        onConfigurationChanged(newConfig);
+    }
+
+    /**
+     * Called when the current configuration of the resources being used
+     * by the application have changed.  You can use this to decide when
+     * to reload resources that can changed based on orientation and other
+     * configuration characteristics.  You only need to use this if you are
+     * not relying on the normal {@link android.app.Activity} mechanism of
+     * recreating the activity instance upon a configuration change.
+     *
+     * @param newConfig The new resource configuration.
+     */
+    protected void onConfigurationChanged(Configuration newConfig) {
+    }
+
+    /**
+     * Private function to aggregate all per-view attributes in to the view
+     * root.
+     */
+    void dispatchCollectViewAttributes(AttachInfo attachInfo, int visibility) {
+        performCollectViewAttributes(attachInfo, visibility);
+    }
+
+    void performCollectViewAttributes(AttachInfo attachInfo, int visibility) {
+        if ((visibility & VISIBILITY_MASK) == VISIBLE) {
+            if ((mViewFlags & KEEP_SCREEN_ON) == KEEP_SCREEN_ON) {
+                attachInfo.mKeepScreenOn = true;
+            }
+            attachInfo.mSystemUiVisibility |= mSystemUiVisibility;
+            ListenerInfo li = mListenerInfo;
+            if (li != null && li.mOnSystemUiVisibilityChangeListener != null) {
+                attachInfo.mHasSystemUiListeners = true;
+            }
+        }
+    }
+
+    void needGlobalAttributesUpdate(boolean force) {
+        final AttachInfo ai = mAttachInfo;
+        if (ai != null && !ai.mRecomputeGlobalAttributes) {
+            if (force || ai.mKeepScreenOn || (ai.mSystemUiVisibility != 0)
+                    || ai.mHasSystemUiListeners) {
+                ai.mRecomputeGlobalAttributes = true;
+            }
+        }
+    }
+
+    /**
+     * Returns whether the device is currently in touch mode.  Touch mode is entered
+     * once the user begins interacting with the device by touch, and affects various
+     * things like whether focus is always visible to the user.
+     *
+     * @return Whether the device is in touch mode.
+     */
+    @ViewDebug.ExportedProperty
+    public boolean isInTouchMode() {
+        if (mAttachInfo != null) {
+            return mAttachInfo.mInTouchMode;
+        } else {
+            return ViewRootImpl.isInTouchMode();
+        }
+    }
+
+    /**
+     * Returns the context the view is running in, through which it can
+     * access the current theme, resources, etc.
+     *
+     * @return The view's Context.
+     */
+    @ViewDebug.CapturedViewProperty
+    @UiContext
+    public final Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * Handle a key event before it is processed by any input method
+     * associated with the view hierarchy.  This can be used to intercept
+     * key events in special situations before the IME consumes them; a
+     * typical example would be handling the BACK key to update the application's
+     * UI instead of allowing the IME to see it and close itself.
+     *
+     * @param keyCode The value in event.getKeyCode().
+     * @param event Description of the key event.
+     * @return If you handled the event, return true. If you want to allow the
+     *         event to be handled by the next receiver, return false.
+     */
+    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+        return false;
+    }
+
+    /**
+     * Default implementation of {@link KeyEvent.Callback#onKeyDown(int, KeyEvent)
+     * KeyEvent.Callback.onKeyDown()}: perform press of the view
+     * when {@link KeyEvent#KEYCODE_DPAD_CENTER} or {@link KeyEvent#KEYCODE_ENTER}
+     * is released, if the view is enabled and clickable.
+     * <p>
+     * Key presses in software keyboards will generally NOT trigger this
+     * listener, although some may elect to do so in some situations. Do not
+     * rely on this to catch software key presses.
+     *
+     * @param keyCode a key code that represents the button pressed, from
+     *                {@link android.view.KeyEvent}
+     * @param event the KeyEvent object that defines the button action
+     */
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (KeyEvent.isConfirmKey(keyCode)) {
+            if ((mViewFlags & ENABLED_MASK) == DISABLED) {
+                return true;
+            }
+
+            if (event.getRepeatCount() == 0) {
+                // Long clickable items don't necessarily have to be clickable.
+                final boolean clickable = (mViewFlags & CLICKABLE) == CLICKABLE
+                        || (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE;
+                if (clickable || (mViewFlags & TOOLTIP) == TOOLTIP) {
+                    // For the purposes of menu anchoring and drawable hotspots,
+                    // key events are considered to be at the center of the view.
+                    final float x = getWidth() / 2f;
+                    final float y = getHeight() / 2f;
+                    if (clickable) {
+                        setPressed(true, x, y);
+                    }
+                    checkForLongClick(
+                            ViewConfiguration.getLongPressTimeout(),
+                            x,
+                            y,
+                            // This is not a touch gesture -- do not classify it as one.
+                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION);
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Default implementation of {@link KeyEvent.Callback#onKeyLongPress(int, KeyEvent)
+     * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle
+     * the event).
+     * <p>Key presses in software keyboards will generally NOT trigger this listener,
+     * although some may elect to do so in some situations. Do not rely on this to
+     * catch software key presses.
+     */
+    public boolean onKeyLongPress(int keyCode, KeyEvent event) {
+        return false;
+    }
+
+    /**
+     * Default implementation of {@link KeyEvent.Callback#onKeyUp(int, KeyEvent)
+     * KeyEvent.Callback.onKeyUp()}: perform clicking of the view
+     * when {@link KeyEvent#KEYCODE_DPAD_CENTER}, {@link KeyEvent#KEYCODE_ENTER}
+     * or {@link KeyEvent#KEYCODE_SPACE} is released.
+     * <p>Key presses in software keyboards will generally NOT trigger this listener,
+     * although some may elect to do so in some situations. Do not rely on this to
+     * catch software key presses.
+     *
+     * @param keyCode A key code that represents the button pressed, from
+     *                {@link android.view.KeyEvent}.
+     * @param event   The KeyEvent object that defines the button action.
+     */
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (KeyEvent.isConfirmKey(keyCode)) {
+            if ((mViewFlags & ENABLED_MASK) == DISABLED) {
+                return true;
+            }
+            if ((mViewFlags & CLICKABLE) == CLICKABLE && isPressed()) {
+                setPressed(false);
+
+                if (!mHasPerformedLongPress) {
+                    // This is a tap, so remove the longpress check
+                    removeLongPressCallback();
+                    if (!event.isCanceled()) {
+                        return performClickInternal();
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Default implementation of {@link KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
+     * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle
+     * the event).
+     * <p>Key presses in software keyboards will generally NOT trigger this listener,
+     * although some may elect to do so in some situations. Do not rely on this to
+     * catch software key presses.
+     *
+     * @param keyCode     A key code that represents the button pressed, from
+     *                    {@link android.view.KeyEvent}.
+     * @param repeatCount The number of times the action was made.
+     * @param event       The KeyEvent object that defines the button action.
+     */
+    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
+        return false;
+    }
+
+    /**
+     * Called on the focused view when a key shortcut event is not handled.
+     * Override this method to implement local key shortcuts for the View.
+     * Key shortcuts can also be implemented by setting the
+     * {@link MenuItem#setShortcut(char, char) shortcut} property of menu items.
+     *
+     * @param keyCode The value in event.getKeyCode().
+     * @param event Description of the key event.
+     * @return If you handled the event, return true. If you want to allow the
+     *         event to be handled by the next receiver, return false.
+     */
+    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
+        return false;
+    }
+
+    /**
+     * Check whether the called view is a text editor, in which case it
+     * would make sense to automatically display a soft input window for
+     * it.  Subclasses should override this if they implement
+     * {@link #onCreateInputConnection(EditorInfo)} to return true if
+     * a call on that method would return a non-null InputConnection, and
+     * they are really a first-class editor that the user would normally
+     * start typing on when the go into a window containing your view.
+     *
+     * <p>The default implementation always returns false.  This does
+     * <em>not</em> mean that its {@link #onCreateInputConnection(EditorInfo)}
+     * will not be called or the user can not otherwise perform edits on your
+     * view; it is just a hint to the system that this is not the primary
+     * purpose of this view.
+     *
+     * @return Returns true if this view is a text editor, else false.
+     */
+    public boolean onCheckIsTextEditor() {
+        return false;
+    }
+
+    /**
+     * Create a new InputConnection for an InputMethod to interact
+     * with the view.  The default implementation returns null, since it doesn't
+     * support input methods.  You can override this to implement such support.
+     * This is only needed for views that take focus and text input.
+     *
+     * <p>When implementing this, you probably also want to implement
+     * {@link #onCheckIsTextEditor()} to indicate you will return a
+     * non-null InputConnection.</p>
+     *
+     * <p>Also, take good care to fill in the {@link android.view.inputmethod.EditorInfo}
+     * object correctly and in its entirety, so that the connected IME can rely
+     * on its values. For example, {@link android.view.inputmethod.EditorInfo#initialSelStart}
+     * and  {@link android.view.inputmethod.EditorInfo#initialSelEnd} members
+     * must be filled in with the correct cursor position for IMEs to work correctly
+     * with your application.</p>
+     *
+     * @param outAttrs Fill in with attribute information about the connection.
+     */
+    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+        return null;
+    }
+
+    /**
+     * Called by the {@link android.view.inputmethod.InputMethodManager} to notify the application
+     * that the system has successfully initialized an {@link InputConnection} and it is ready for
+     * use.
+     *
+     * <p>The default implementation does nothing, since a view doesn't support input methods by
+     * default (see {@link #onCreateInputConnection}).
+     *
+     * @param inputConnection The {@link InputConnection} from {@link #onCreateInputConnection},
+     * after it's been fully initialized by the system.
+     * @param editorInfo The {@link EditorInfo} that was used to create the {@link InputConnection}.
+     * @param handler The dedicated {@link Handler} on which IPC method calls from input methods
+     * will be dispatched. This is the handler returned by {@link InputConnection#getHandler()}. If
+     * that method returns null, this parameter will be null also.
+     *
+     * @hide
+     */
+    public void onInputConnectionOpenedInternal(@NonNull InputConnection inputConnection,
+            @NonNull EditorInfo editorInfo, @Nullable Handler handler) {}
+
+    /**
+     * Called by the {@link android.view.inputmethod.InputMethodManager} to notify the application
+     * that the {@link InputConnection} has been closed.
+     *
+     * <p>The default implementation does nothing, since a view doesn't support input methods by
+     * default (see {@link #onCreateInputConnection}).
+     *
+     * <p><strong>Note:</strong> This callback is not invoked if the view is already detached when
+     * the {@link InputConnection} is closed or the connection is not valid and managed by
+     * {@link com.android.server.inputmethod.InputMethodManagerService}.
+     * TODO(b/170645312): Before un-hiding this API, handle the detached view scenario.
+     *
+     * @hide
+     */
+    public void onInputConnectionClosedInternal() {}
+
+    /**
+     * Called by the {@link android.view.inputmethod.InputMethodManager}
+     * when a view who is not the current
+     * input connection target is trying to make a call on the manager.  The
+     * default implementation returns false; you can override this to return
+     * true for certain views if you are performing InputConnection proxying
+     * to them.
+     * @param view The View that is making the InputMethodManager call.
+     * @return Return true to allow the call, false to reject.
+     */
+    public boolean checkInputConnectionProxy(View view) {
+        return false;
+    }
+
+    /**
+     * Show the context menu for this view. It is not safe to hold on to the
+     * menu after returning from this method.
+     *
+     * You should normally not overload this method. Overload
+     * {@link #onCreateContextMenu(ContextMenu)} or define an
+     * {@link OnCreateContextMenuListener} to add items to the context menu.
+     *
+     * @param menu The context menu to populate
+     */
+    public void createContextMenu(ContextMenu menu) {
+        ContextMenuInfo menuInfo = getContextMenuInfo();
+
+        // Sets the current menu info so all items added to menu will have
+        // my extra info set.
+        ((MenuBuilder)menu).setCurrentMenuInfo(menuInfo);
+
+        onCreateContextMenu(menu);
+        ListenerInfo li = mListenerInfo;
+        if (li != null && li.mOnCreateContextMenuListener != null) {
+            li.mOnCreateContextMenuListener.onCreateContextMenu(menu, this, menuInfo);
+        }
+
+        // Clear the extra information so subsequent items that aren't mine don't
+        // have my extra info.
+        ((MenuBuilder)menu).setCurrentMenuInfo(null);
+
+        if (mParent != null) {
+            mParent.createContextMenu(menu);
+        }
+    }
+
+    /**
+     * Views should implement this if they have extra information to associate
+     * with the context menu. The return result is supplied as a parameter to
+     * the {@link OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo)}
+     * callback.
+     *
+     * @return Extra information about the item for which the context menu
+     *         should be shown. This information will vary across different
+     *         subclasses of View.
+     */
+    protected ContextMenuInfo getContextMenuInfo() {
+        return null;
+    }
+
+    /**
+     * Views should implement this if the view itself is going to add items to
+     * the context menu.
+     *
+     * @param menu the context menu to populate
+     */
+    protected void onCreateContextMenu(ContextMenu menu) {
+    }
+
+    /**
+     * Implement this method to handle trackball motion events.  The
+     * <em>relative</em> movement of the trackball since the last event
+     * can be retrieve with {@link MotionEvent#getX MotionEvent.getX()} and
+     * {@link MotionEvent#getY MotionEvent.getY()}.  These are normalized so
+     * that a movement of 1 corresponds to the user pressing one DPAD key (so
+     * they will often be fractional values, representing the more fine-grained
+     * movement information available from a trackball).
+     *
+     * @param event The motion event.
+     * @return True if the event was handled, false otherwise.
+     */
+    public boolean onTrackballEvent(MotionEvent event) {
+        return false;
+    }
+
+    /**
+     * Implement this method to handle generic motion events.
+     * <p>
+     * Generic motion events describe joystick movements, mouse hovers, track pad
+     * touches, scroll wheel movements and other input events.  The
+     * {@link MotionEvent#getSource() source} of the motion event specifies
+     * the class of input that was received.  Implementations of this method
+     * must examine the bits in the source before processing the event.
+     * The following code example shows how this is done.
+     * </p><p>
+     * Generic motion events with source class {@link InputDevice#SOURCE_CLASS_POINTER}
+     * are delivered to the view under the pointer.  All other generic motion events are
+     * delivered to the focused view.
+     * </p>
+     * <pre> public boolean onGenericMotionEvent(MotionEvent event) {
+     *     if (event.isFromSource(InputDevice.SOURCE_CLASS_JOYSTICK)) {
+     *         if (event.getAction() == MotionEvent.ACTION_MOVE) {
+     *             // process the joystick movement...
+     *             return true;
+     *         }
+     *     }
+     *     if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
+     *         switch (event.getAction()) {
+     *             case MotionEvent.ACTION_HOVER_MOVE:
+     *                 // process the mouse hover movement...
+     *                 return true;
+     *             case MotionEvent.ACTION_SCROLL:
+     *                 // process the scroll wheel movement...
+     *                 return true;
+     *         }
+     *     }
+     *     return super.onGenericMotionEvent(event);
+     * }</pre>
+     *
+     * @param event The generic motion event being processed.
+     * @return True if the event was handled, false otherwise.
+     */
+    public boolean onGenericMotionEvent(MotionEvent event) {
+        return false;
+    }
+
+    /**
+     * Dispatching hover events to {@link TouchDelegate} to improve accessibility.
+     * <p>
+     * This method is dispatching hover events to the delegate target to support explore by touch.
+     * Similar to {@link ViewGroup#dispatchTouchEvent}, this method send proper hover events to
+     * the delegate target according to the pointer and the touch area of the delegate while touch
+     * exploration enabled.
+     * </p>
+     *
+     * @param event The motion event dispatch to the delegate target.
+     * @return True if the event was handled, false otherwise.
+     *
+     * @see #onHoverEvent
+     */
+    private boolean dispatchTouchExplorationHoverEvent(MotionEvent event) {
+        final AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
+        if (!manager.isEnabled() || !manager.isTouchExplorationEnabled()) {
+            return false;
+        }
+
+        final boolean oldHoveringTouchDelegate = mHoveringTouchDelegate;
+        final int action = event.getActionMasked();
+        boolean pointInDelegateRegion = false;
+        boolean handled = false;
+
+        final AccessibilityNodeInfo.TouchDelegateInfo info = mTouchDelegate.getTouchDelegateInfo();
+        for (int i = 0; i < info.getRegionCount(); i++) {
+            Region r = info.getRegionAt(i);
+            if (r.contains((int) event.getX(), (int) event.getY())) {
+                pointInDelegateRegion = true;
+            }
+        }
+
+        // Explore by touch should dispatch events to children under the pointer first if any
+        // before dispatching to TouchDelegate. For non-hoverable views that do not consume
+        // hover events but receive accessibility focus, it should also not delegate to these
+        // views when hovered.
+        if (!oldHoveringTouchDelegate) {
+            if ((action == MotionEvent.ACTION_HOVER_ENTER
+                    || action == MotionEvent.ACTION_HOVER_MOVE)
+                    && !pointInHoveredChild(event)
+                    && pointInDelegateRegion) {
+                mHoveringTouchDelegate = true;
+            }
+        } else {
+            if (action == MotionEvent.ACTION_HOVER_EXIT
+                    || (action == MotionEvent.ACTION_HOVER_MOVE
+                        && (pointInHoveredChild(event) || !pointInDelegateRegion))) {
+                mHoveringTouchDelegate = false;
+            }
+        }
+        switch (action) {
+            case MotionEvent.ACTION_HOVER_MOVE:
+                if (oldHoveringTouchDelegate && mHoveringTouchDelegate) {
+                    // Inside bounds, dispatch as is.
+                    handled = mTouchDelegate.onTouchExplorationHoverEvent(event);
+                } else if (!oldHoveringTouchDelegate && mHoveringTouchDelegate) {
+                    // Moving inbound, synthesize hover enter.
+                    MotionEvent eventNoHistory = (event.getHistorySize() == 0)
+                            ? event : MotionEvent.obtainNoHistory(event);
+                    eventNoHistory.setAction(MotionEvent.ACTION_HOVER_ENTER);
+                    handled = mTouchDelegate.onTouchExplorationHoverEvent(eventNoHistory);
+                    eventNoHistory.setAction(action);
+                    handled |= mTouchDelegate.onTouchExplorationHoverEvent(eventNoHistory);
+                } else if (oldHoveringTouchDelegate && !mHoveringTouchDelegate) {
+                    // Moving outbound, synthesize hover exit.
+                    final boolean hoverExitPending = event.isHoverExitPending();
+                    event.setHoverExitPending(true);
+                    mTouchDelegate.onTouchExplorationHoverEvent(event);
+                    MotionEvent eventNoHistory = (event.getHistorySize() == 0)
+                            ? event : MotionEvent.obtainNoHistory(event);
+                    eventNoHistory.setHoverExitPending(hoverExitPending);
+                    eventNoHistory.setAction(MotionEvent.ACTION_HOVER_EXIT);
+                    mTouchDelegate.onTouchExplorationHoverEvent(eventNoHistory);
+                }  // else: outside bounds, do nothing.
+                break;
+            case MotionEvent.ACTION_HOVER_ENTER:
+                if (!oldHoveringTouchDelegate && mHoveringTouchDelegate) {
+                    handled = mTouchDelegate.onTouchExplorationHoverEvent(event);
+                }
+                break;
+            case MotionEvent.ACTION_HOVER_EXIT:
+                if (oldHoveringTouchDelegate) {
+                    mTouchDelegate.onTouchExplorationHoverEvent(event);
+                }
+                break;
+        }
+        return handled;
+    }
+
+    /**
+     * Implement this method to handle hover events.
+     * <p>
+     * This method is called whenever a pointer is hovering into, over, or out of the
+     * bounds of a view and the view is not currently being touched.
+     * Hover events are represented as pointer events with action
+     * {@link MotionEvent#ACTION_HOVER_ENTER}, {@link MotionEvent#ACTION_HOVER_MOVE},
+     * or {@link MotionEvent#ACTION_HOVER_EXIT}.
+     * </p>
+     * <ul>
+     * <li>The view receives a hover event with action {@link MotionEvent#ACTION_HOVER_ENTER}
+     * when the pointer enters the bounds of the view.</li>
+     * <li>The view receives a hover event with action {@link MotionEvent#ACTION_HOVER_MOVE}
+     * when the pointer has already entered the bounds of the view and has moved.</li>
+     * <li>The view receives a hover event with action {@link MotionEvent#ACTION_HOVER_EXIT}
+     * when the pointer has exited the bounds of the view or when the pointer is
+     * about to go down due to a button click, tap, or similar user action that
+     * causes the view to be touched.</li>
+     * </ul>
+     * <p>
+     * The view should implement this method to return true to indicate that it is
+     * handling the hover event, such as by changing its drawable state.
+     * </p><p>
+     * The default implementation calls {@link #setHovered} to update the hovered state
+     * of the view when a hover enter or hover exit event is received, if the view
+     * is enabled and is clickable.  The default implementation also sends hover
+     * accessibility events.
+     * </p>
+     *
+     * @param event The motion event that describes the hover.
+     * @return True if the view handled the hover event.
+     *
+     * @see #isHovered
+     * @see #setHovered
+     * @see #onHoverChanged
+     */
+    public boolean onHoverEvent(MotionEvent event) {
+        if (mTouchDelegate != null && dispatchTouchExplorationHoverEvent(event)) {
+            return true;
+        }
+
+        // The root view may receive hover (or touch) events that are outside the bounds of
+        // the window.  This code ensures that we only send accessibility events for
+        // hovers that are actually within the bounds of the root view.
+        final int action = event.getActionMasked();
+        if (!mSendingHoverAccessibilityEvents) {
+            if ((action == MotionEvent.ACTION_HOVER_ENTER
+                    || action == MotionEvent.ACTION_HOVER_MOVE)
+                    && !hasHoveredChild()
+                    && pointInView(event.getX(), event.getY())) {
+                sendAccessibilityHoverEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
+                mSendingHoverAccessibilityEvents = true;
+            }
+        } else {
+            if (action == MotionEvent.ACTION_HOVER_EXIT
+                    || (action == MotionEvent.ACTION_HOVER_MOVE
+                            && !pointInView(event.getX(), event.getY()))) {
+                mSendingHoverAccessibilityEvents = false;
+                sendAccessibilityHoverEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
+            }
+        }
+
+        if ((action == MotionEvent.ACTION_HOVER_ENTER || action == MotionEvent.ACTION_HOVER_MOVE)
+                && event.isFromSource(InputDevice.SOURCE_MOUSE)
+                && isOnScrollbar(event.getX(), event.getY())) {
+            awakenScrollBars();
+        }
+
+        // If we consider ourself hoverable, or if we we're already hovered,
+        // handle changing state in response to ENTER and EXIT events.
+        if (isHoverable() || isHovered()) {
+            switch (action) {
+                case MotionEvent.ACTION_HOVER_ENTER:
+                    setHovered(true);
+                    break;
+                case MotionEvent.ACTION_HOVER_EXIT:
+                    setHovered(false);
+                    break;
+            }
+
+            // Dispatch the event to onGenericMotionEvent before returning true.
+            // This is to provide compatibility with existing applications that
+            // handled HOVER_MOVE events in onGenericMotionEvent and that would
+            // break because of the new default handling for hoverable views
+            // in onHoverEvent.
+            // Note that onGenericMotionEvent will be called by default when
+            // onHoverEvent returns false (refer to dispatchGenericMotionEvent).
+            dispatchGenericMotionEventInternal(event);
+            // The event was already handled by calling setHovered(), so always
+            // return true.
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns true if the view should handle {@link #onHoverEvent}
+     * by calling {@link #setHovered} to change its hovered state.
+     *
+     * @return True if the view is hoverable.
+     */
+    private boolean isHoverable() {
+        final int viewFlags = mViewFlags;
+        if ((viewFlags & ENABLED_MASK) == DISABLED) {
+            return false;
+        }
+
+        return (viewFlags & CLICKABLE) == CLICKABLE
+                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE
+                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
+    }
+
+    /**
+     * Returns true if the view is currently hovered.
+     *
+     * @return True if the view is currently hovered.
+     *
+     * @see #setHovered
+     * @see #onHoverChanged
+     */
+    @ViewDebug.ExportedProperty
+    public boolean isHovered() {
+        return (mPrivateFlags & PFLAG_HOVERED) != 0;
+    }
+
+    /**
+     * Sets whether the view is currently hovered.
+     * <p>
+     * Calling this method also changes the drawable state of the view.  This
+     * enables the view to react to hover by using different drawable resources
+     * to change its appearance.
+     * </p><p>
+     * The {@link #onHoverChanged} method is called when the hovered state changes.
+     * </p>
+     *
+     * @param hovered True if the view is hovered.
+     *
+     * @see #isHovered
+     * @see #onHoverChanged
+     */
+    public void setHovered(boolean hovered) {
+        if (hovered) {
+            if ((mPrivateFlags & PFLAG_HOVERED) == 0) {
+                mPrivateFlags |= PFLAG_HOVERED;
+                refreshDrawableState();
+                onHoverChanged(true);
+            }
+        } else {
+            if ((mPrivateFlags & PFLAG_HOVERED) != 0) {
+                mPrivateFlags &= ~PFLAG_HOVERED;
+                refreshDrawableState();
+                onHoverChanged(false);
+            }
+        }
+    }
+
+    /**
+     * Implement this method to handle hover state changes.
+     * <p>
+     * This method is called whenever the hover state changes as a result of a
+     * call to {@link #setHovered}.
+     * </p>
+     *
+     * @param hovered The current hover state, as returned by {@link #isHovered}.
+     *
+     * @see #isHovered
+     * @see #setHovered
+     */
+    public void onHoverChanged(boolean hovered) {
+    }
+
+    /**
+     * Handles scroll bar dragging by mouse input.
+     *
+     * @hide
+     * @param event The motion event.
+     *
+     * @return true if the event was handled as a scroll bar dragging, false otherwise.
+     */
+    protected boolean handleScrollBarDragging(MotionEvent event) {
+        if (mScrollCache == null) {
+            return false;
+        }
+        final float x = event.getX();
+        final float y = event.getY();
+        final int action = event.getAction();
+        if ((mScrollCache.mScrollBarDraggingState == ScrollabilityCache.NOT_DRAGGING
+                && action != MotionEvent.ACTION_DOWN)
+                    || !event.isFromSource(InputDevice.SOURCE_MOUSE)
+                    || !event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) {
+            mScrollCache.mScrollBarDraggingState = ScrollabilityCache.NOT_DRAGGING;
+            return false;
+        }
+
+        switch (action) {
+            case MotionEvent.ACTION_MOVE:
+                if (mScrollCache.mScrollBarDraggingState == ScrollabilityCache.NOT_DRAGGING) {
+                    return false;
+                }
+                if (mScrollCache.mScrollBarDraggingState
+                        == ScrollabilityCache.DRAGGING_VERTICAL_SCROLL_BAR) {
+                    final Rect bounds = mScrollCache.mScrollBarBounds;
+                    getVerticalScrollBarBounds(bounds, null);
+                    final int range = computeVerticalScrollRange();
+                    final int offset = computeVerticalScrollOffset();
+                    final int extent = computeVerticalScrollExtent();
+
+                    final int thumbLength = ScrollBarUtils.getThumbLength(
+                            bounds.height(), bounds.width(), extent, range);
+                    final int thumbOffset = ScrollBarUtils.getThumbOffset(
+                            bounds.height(), thumbLength, extent, range, offset);
+
+                    final float diff = y - mScrollCache.mScrollBarDraggingPos;
+                    final float maxThumbOffset = bounds.height() - thumbLength;
+                    final float newThumbOffset =
+                            Math.min(Math.max(thumbOffset + diff, 0.0f), maxThumbOffset);
+                    final int height = getHeight();
+                    if (Math.round(newThumbOffset) != thumbOffset && maxThumbOffset > 0
+                            && height > 0 && extent > 0) {
+                        final int newY = Math.round((range - extent)
+                                / ((float)extent / height) * (newThumbOffset / maxThumbOffset));
+                        if (newY != getScrollY()) {
+                            mScrollCache.mScrollBarDraggingPos = y;
+                            setScrollY(newY);
+                        }
+                    }
+                    return true;
+                }
+                if (mScrollCache.mScrollBarDraggingState
+                        == ScrollabilityCache.DRAGGING_HORIZONTAL_SCROLL_BAR) {
+                    final Rect bounds = mScrollCache.mScrollBarBounds;
+                    getHorizontalScrollBarBounds(bounds, null);
+                    final int range = computeHorizontalScrollRange();
+                    final int offset = computeHorizontalScrollOffset();
+                    final int extent = computeHorizontalScrollExtent();
+
+                    final int thumbLength = ScrollBarUtils.getThumbLength(
+                            bounds.width(), bounds.height(), extent, range);
+                    final int thumbOffset = ScrollBarUtils.getThumbOffset(
+                            bounds.width(), thumbLength, extent, range, offset);
+
+                    final float diff = x - mScrollCache.mScrollBarDraggingPos;
+                    final float maxThumbOffset = bounds.width() - thumbLength;
+                    final float newThumbOffset =
+                            Math.min(Math.max(thumbOffset + diff, 0.0f), maxThumbOffset);
+                    final int width = getWidth();
+                    if (Math.round(newThumbOffset) != thumbOffset && maxThumbOffset > 0
+                            && width > 0 && extent > 0) {
+                        final int newX = Math.round((range - extent)
+                                / ((float)extent / width) * (newThumbOffset / maxThumbOffset));
+                        if (newX != getScrollX()) {
+                            mScrollCache.mScrollBarDraggingPos = x;
+                            setScrollX(newX);
+                        }
+                    }
+                    return true;
+                }
+            case MotionEvent.ACTION_DOWN:
+                if (mScrollCache.state == ScrollabilityCache.OFF) {
+                    return false;
+                }
+                if (isOnVerticalScrollbarThumb(x, y)) {
+                    mScrollCache.mScrollBarDraggingState =
+                            ScrollabilityCache.DRAGGING_VERTICAL_SCROLL_BAR;
+                    mScrollCache.mScrollBarDraggingPos = y;
+                    return true;
+                }
+                if (isOnHorizontalScrollbarThumb(x, y)) {
+                    mScrollCache.mScrollBarDraggingState =
+                            ScrollabilityCache.DRAGGING_HORIZONTAL_SCROLL_BAR;
+                    mScrollCache.mScrollBarDraggingPos = x;
+                    return true;
+                }
+        }
+        mScrollCache.mScrollBarDraggingState = ScrollabilityCache.NOT_DRAGGING;
+        return false;
+    }
+
+    /**
+     * Implement this method to handle touch screen motion events.
+     * <p>
+     * If this method is used to detect click actions, it is recommended that
+     * the actions be performed by implementing and calling
+     * {@link #performClick()}. This will ensure consistent system behavior,
+     * including:
+     * <ul>
+     * <li>obeying click sound preferences
+     * <li>dispatching OnClickListener calls
+     * <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
+     * accessibility features are enabled
+     * </ul>
+     *
+     * @param event The motion event.
+     * @return True if the event was handled, false otherwise.
+     */
+    public boolean onTouchEvent(MotionEvent event) {
+        final float x = event.getX();
+        final float y = event.getY();
+        final int viewFlags = mViewFlags;
+        final int action = event.getAction();
+
+        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
+                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
+                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
+
+        if ((viewFlags & ENABLED_MASK) == DISABLED
+                && (mPrivateFlags4 & PFLAG4_ALLOW_CLICK_WHEN_DISABLED) == 0) {
+            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
+                setPressed(false);
+            }
+            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
+            // A disabled view that is clickable still consumes the touch
+            // events, it just doesn't respond to them.
+            return clickable;
+        }
+        if (mTouchDelegate != null) {
+            if (mTouchDelegate.onTouchEvent(event)) {
+                return true;
+            }
+        }
+
+        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
+            switch (action) {
+                case MotionEvent.ACTION_UP:
+                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
+                    if ((viewFlags & TOOLTIP) == TOOLTIP) {
+                        handleTooltipUp();
+                    }
+                    if (!clickable) {
+                        removeTapCallback();
+                        removeLongPressCallback();
+                        mInContextButtonPress = false;
+                        mHasPerformedLongPress = false;
+                        mIgnoreNextUpEvent = false;
+                        break;
+                    }
+                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
+                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
+                        // take focus if we don't have it already and we should in
+                        // touch mode.
+                        boolean focusTaken = false;
+                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
+                            focusTaken = requestFocus();
+                        }
+
+                        if (prepressed) {
+                            // The button is being released before we actually
+                            // showed it as pressed.  Make it show the pressed
+                            // state now (before scheduling the click) to ensure
+                            // the user sees it.
+                            setPressed(true, x, y);
+                        }
+
+                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
+                            // This is a tap, so remove the longpress check
+                            removeLongPressCallback();
+
+                            // Only perform take click actions if we were in the pressed state
+                            if (!focusTaken) {
+                                // Use a Runnable and post this rather than calling
+                                // performClick directly. This lets other visual state
+                                // of the view update before click actions start.
+                                if (mPerformClick == null) {
+                                    mPerformClick = new PerformClick();
+                                }
+                                if (!post(mPerformClick)) {
+                                    performClickInternal();
+                                }
+                            }
+                        }
+
+                        if (mUnsetPressedState == null) {
+                            mUnsetPressedState = new UnsetPressedState();
+                        }
+
+                        if (prepressed) {
+                            postDelayed(mUnsetPressedState,
+                                    ViewConfiguration.getPressedStateDuration());
+                        } else if (!post(mUnsetPressedState)) {
+                            // If the post failed, unpress right now
+                            mUnsetPressedState.run();
+                        }
+
+                        removeTapCallback();
+                    }
+                    mIgnoreNextUpEvent = false;
+                    break;
+
+                case MotionEvent.ACTION_DOWN:
+                    if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
+                        mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
+                    }
+                    mHasPerformedLongPress = false;
+
+                    if (!clickable) {
+                        checkForLongClick(
+                                ViewConfiguration.getLongPressTimeout(),
+                                x,
+                                y,
+                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
+                        break;
+                    }
+
+                    if (performButtonActionOnTouchDown(event)) {
+                        break;
+                    }
+
+                    // Walk up the hierarchy to determine if we're inside a scrolling container.
+                    boolean isInScrollingContainer = isInScrollingContainer();
+
+                    // For views inside a scrolling container, delay the pressed feedback for
+                    // a short period in case this is a scroll.
+                    if (isInScrollingContainer) {
+                        mPrivateFlags |= PFLAG_PREPRESSED;
+                        if (mPendingCheckForTap == null) {
+                            mPendingCheckForTap = new CheckForTap();
+                        }
+                        mPendingCheckForTap.x = event.getX();
+                        mPendingCheckForTap.y = event.getY();
+                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
+                    } else {
+                        // Not inside a scrolling container, so show the feedback right away
+                        setPressed(true, x, y);
+                        checkForLongClick(
+                                ViewConfiguration.getLongPressTimeout(),
+                                x,
+                                y,
+                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
+                    }
+                    break;
+
+                case MotionEvent.ACTION_CANCEL:
+                    if (clickable) {
+                        setPressed(false);
+                    }
+                    removeTapCallback();
+                    removeLongPressCallback();
+                    mInContextButtonPress = false;
+                    mHasPerformedLongPress = false;
+                    mIgnoreNextUpEvent = false;
+                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
+                    break;
+
+                case MotionEvent.ACTION_MOVE:
+                    if (clickable) {
+                        drawableHotspotChanged(x, y);
+                    }
+
+                    final int motionClassification = event.getClassification();
+                    final boolean ambiguousGesture =
+                            motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
+                    int touchSlop = mTouchSlop;
+                    if (ambiguousGesture && hasPendingLongPressCallback()) {
+                        if (!pointInView(x, y, touchSlop)) {
+                            // The default action here is to cancel long press. But instead, we
+                            // just extend the timeout here, in case the classification
+                            // stays ambiguous.
+                            removeLongPressCallback();
+                            long delay = (long) (ViewConfiguration.getLongPressTimeout()
+                                    * mAmbiguousGestureMultiplier);
+                            // Subtract the time already spent
+                            delay -= event.getEventTime() - event.getDownTime();
+                            checkForLongClick(
+                                    delay,
+                                    x,
+                                    y,
+                                    TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
+                        }
+                        touchSlop *= mAmbiguousGestureMultiplier;
+                    }
+
+                    // Be lenient about moving outside of buttons
+                    if (!pointInView(x, y, touchSlop)) {
+                        // Outside button
+                        // Remove any future long press/tap checks
+                        removeTapCallback();
+                        removeLongPressCallback();
+                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
+                            setPressed(false);
+                        }
+                        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
+                    }
+
+                    final boolean deepPress =
+                            motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
+                    if (deepPress && hasPendingLongPressCallback()) {
+                        // process the long click action immediately
+                        removeLongPressCallback();
+                        checkForLongClick(
+                                0 /* send immediately */,
+                                x,
+                                y,
+                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
+                    }
+
+                    break;
+            }
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public boolean isInScrollingContainer() {
+        ViewParent p = getParent();
+        while (p != null && p instanceof ViewGroup) {
+            if (((ViewGroup) p).shouldDelayChildPressedState()) {
+                return true;
+            }
+            p = p.getParent();
+        }
+        return false;
+    }
+
+    /**
+     * Remove the longpress detection timer.
+     */
+    private void removeLongPressCallback() {
+        if (mPendingCheckForLongPress != null) {
+            removeCallbacks(mPendingCheckForLongPress);
+        }
+    }
+
+    /**
+     * Return true if the long press callback is scheduled to run sometime in the future.
+     * Return false if there is no scheduled long press callback at the moment.
+     */
+    private boolean hasPendingLongPressCallback() {
+        if (mPendingCheckForLongPress == null) {
+            return false;
+        }
+        final AttachInfo attachInfo = mAttachInfo;
+        if (attachInfo == null) {
+            return false;
+        }
+        return attachInfo.mHandler.hasCallbacks(mPendingCheckForLongPress);
+    }
+
+   /**
+     * Remove the pending click action
+     */
+    @UnsupportedAppUsage
+    private void removePerformClickCallback() {
+        if (mPerformClick != null) {
+            removeCallbacks(mPerformClick);
+        }
+    }
+
+    /**
+     * Remove the prepress detection timer.
+     */
+    private void removeUnsetPressCallback() {
+        if ((mPrivateFlags & PFLAG_PRESSED) != 0 && mUnsetPressedState != null) {
+            setPressed(false);
+            removeCallbacks(mUnsetPressedState);
+        }
+    }
+
+    /**
+     * Remove the tap detection timer.
+     */
+    private void removeTapCallback() {
+        if (mPendingCheckForTap != null) {
+            mPrivateFlags &= ~PFLAG_PREPRESSED;
+            removeCallbacks(mPendingCheckForTap);
+        }
+    }
+
+    /**
+     * Cancels a pending long press.  Your subclass can use this if you
+     * want the context menu to come up if the user presses and holds
+     * at the same place, but you don't want it to come up if they press
+     * and then move around enough to cause scrolling.
+     */
+    public void cancelLongPress() {
+        removeLongPressCallback();
+
+        /*
+         * The prepressed state handled by the tap callback is a display
+         * construct, but the tap callback will post a long press callback
+         * less its own timeout. Remove it here.
+         */
+        removeTapCallback();
+    }
+
+    /**
+     * Sets the TouchDelegate for this View.
+     */
+    public void setTouchDelegate(TouchDelegate delegate) {
+        mTouchDelegate = delegate;
+    }
+
+    /**
+     * Gets the TouchDelegate for this View.
+     */
+    public TouchDelegate getTouchDelegate() {
+        return mTouchDelegate;
+    }
+
+    /**
+     * Request unbuffered dispatch of the given stream of MotionEvents to this View.
+     *
+     * Until this View receives a corresponding {@link MotionEvent#ACTION_UP}, ask that the input
+     * system not batch {@link MotionEvent}s but instead deliver them as soon as they're
+     * available. This method should only be called for touch events.
+     *
+     * <p class="note">This API is not intended for most applications. Buffered dispatch
+     * provides many of benefits, and just requesting unbuffered dispatch on most MotionEvent
+     * streams will not improve your input latency. Side effects include: increased latency,
+     * jittery scrolls and inability to take advantage of system resampling. Talk to your input
+     * professional to see if {@link #requestUnbufferedDispatch(MotionEvent)} is right for
+     * you.</p>
+     *
+     * To receive unbuffered events for arbitrary input device source classes, use
+     * {@link #requestUnbufferedDispatch(int)},
+     *
+     * @see View#requestUnbufferedDispatch(int)
+     */
+    public final void requestUnbufferedDispatch(MotionEvent event) {
+        final int action = event.getAction();
+        if (mAttachInfo == null
+                || action != MotionEvent.ACTION_DOWN && action != MotionEvent.ACTION_MOVE
+                || !event.isTouchEvent()) {
+            return;
+        }
+        mAttachInfo.mUnbufferedDispatchRequested = true;
+    }
+
+    /**
+     * Request unbuffered dispatch of the given event source class to this view.
+     * This is similar to {@link View#requestUnbufferedDispatch(MotionEvent)}, but does not
+     * automatically terminate, and allows the specification of arbitrary input source classes.
+     *
+     * @param source The combined input source class to request unbuffered dispatch for. All
+     *               events coming from these source classes will not be buffered. Set to
+     *               {@link InputDevice#SOURCE_CLASS_NONE} in order to return to default behaviour.
+     *
+     * @see View#requestUnbufferedDispatch(MotionEvent)
+     */
+    public final void requestUnbufferedDispatch(@InputSourceClass int source) {
+        if (mUnbufferedInputSource == source) {
+            return;
+        }
+        mUnbufferedInputSource = source;
+        if (mParent != null) {
+            mParent.onDescendantUnbufferedRequested();
+        }
+    }
+
+    private boolean hasSize() {
+        return (mBottom > mTop) && (mRight > mLeft);
+    }
+
+    private boolean canTakeFocus() {
+        return ((mViewFlags & VISIBILITY_MASK) == VISIBLE)
+                && ((mViewFlags & FOCUSABLE) == FOCUSABLE)
+                && ((mViewFlags & ENABLED_MASK) == ENABLED)
+                && (sCanFocusZeroSized || !isLayoutValid() || hasSize());
+    }
+
+    /**
+     * Set flags controlling behavior of this view.
+     *
+     * @param flags Constant indicating the value which should be set
+     * @param mask Constant indicating the bit range that should be changed
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    void setFlags(int flags, int mask) {
+        final boolean accessibilityEnabled =
+                AccessibilityManager.getInstance(mContext).isEnabled();
+        final boolean oldIncludeForAccessibility = accessibilityEnabled && includeForAccessibility();
+
+        int old = mViewFlags;
+        mViewFlags = (mViewFlags & ~mask) | (flags & mask);
+
+        int changed = mViewFlags ^ old;
+        if (changed == 0) {
+            return;
+        }
+        int privateFlags = mPrivateFlags;
+        boolean shouldNotifyFocusableAvailable = false;
+
+        // If focusable is auto, update the FOCUSABLE bit.
+        int focusableChangedByAuto = 0;
+        if (((mViewFlags & FOCUSABLE_AUTO) != 0)
+                && (changed & (FOCUSABLE_MASK | CLICKABLE)) != 0) {
+            // Heuristic only takes into account whether view is clickable.
+            final int newFocus;
+            if ((mViewFlags & CLICKABLE) != 0) {
+                newFocus = FOCUSABLE;
+            } else {
+                newFocus = NOT_FOCUSABLE;
+            }
+            mViewFlags = (mViewFlags & ~FOCUSABLE) | newFocus;
+            focusableChangedByAuto = (old & FOCUSABLE) ^ (newFocus & FOCUSABLE);
+            changed = (changed & ~FOCUSABLE) | focusableChangedByAuto;
+        }
+
+        /* Check if the FOCUSABLE bit has changed */
+        if (((changed & FOCUSABLE) != 0) && ((privateFlags & PFLAG_HAS_BOUNDS) != 0)) {
+            if (((old & FOCUSABLE) == FOCUSABLE)
+                    && ((privateFlags & PFLAG_FOCUSED) != 0)) {
+                /* Give up focus if we are no longer focusable */
+                clearFocus();
+                if (mParent instanceof ViewGroup) {
+                    ((ViewGroup) mParent).clearFocusedInCluster();
+                }
+            } else if (((old & FOCUSABLE) == NOT_FOCUSABLE)
+                    && ((privateFlags & PFLAG_FOCUSED) == 0)) {
+                /*
+                 * Tell the view system that we are now available to take focus
+                 * if no one else already has it.
+                 */
+                if (mParent != null) {
+                    ViewRootImpl viewRootImpl = getViewRootImpl();
+                    if (!sAutoFocusableOffUIThreadWontNotifyParents
+                            || focusableChangedByAuto == 0
+                            || viewRootImpl == null
+                            || viewRootImpl.mThread == Thread.currentThread()) {
+                        shouldNotifyFocusableAvailable = canTakeFocus();
+                    }
+                }
+            }
+        }
+
+        final int newVisibility = flags & VISIBILITY_MASK;
+        if (newVisibility == VISIBLE) {
+            if ((changed & VISIBILITY_MASK) != 0) {
+                /*
+                 * If this view is becoming visible, invalidate it in case it changed while
+                 * it was not visible. Marking it drawn ensures that the invalidation will
+                 * go through.
+                 */
+                mPrivateFlags |= PFLAG_DRAWN;
+                invalidate(true);
+
+                needGlobalAttributesUpdate(true);
+
+                // a view becoming visible is worth notifying the parent about in case nothing has
+                // focus. Even if this specific view isn't focusable, it may contain something that
+                // is, so let the root view try to give this focus if nothing else does.
+                shouldNotifyFocusableAvailable = hasSize();
+            }
+        }
+
+        if ((changed & ENABLED_MASK) != 0) {
+            if ((mViewFlags & ENABLED_MASK) == ENABLED) {
+                // a view becoming enabled should notify the parent as long as the view is also
+                // visible and the parent wasn't already notified by becoming visible during this
+                // setFlags invocation.
+                shouldNotifyFocusableAvailable = canTakeFocus();
+            } else {
+                if (isFocused()) clearFocus();
+            }
+        }
+
+        if (shouldNotifyFocusableAvailable && mParent != null) {
+            mParent.focusableViewAvailable(this);
+        }
+
+        /* Check if the GONE bit has changed */
+        if ((changed & GONE) != 0) {
+            needGlobalAttributesUpdate(false);
+            requestLayout();
+
+            if (((mViewFlags & VISIBILITY_MASK) == GONE)) {
+                if (hasFocus()) {
+                    clearFocus();
+                    if (mParent instanceof ViewGroup) {
+                        ((ViewGroup) mParent).clearFocusedInCluster();
+                    }
+                }
+                clearAccessibilityFocus();
+                destroyDrawingCache();
+                if (mParent instanceof View) {
+                    // GONE views noop invalidation, so invalidate the parent
+                    ((View) mParent).invalidate(true);
+                }
+                // Mark the view drawn to ensure that it gets invalidated properly the next
+                // time it is visible and gets invalidated
+                mPrivateFlags |= PFLAG_DRAWN;
+            }
+            if (mAttachInfo != null) {
+                mAttachInfo.mViewVisibilityChanged = true;
+            }
+        }
+
+        /* Check if the VISIBLE bit has changed */
+        if ((changed & INVISIBLE) != 0) {
+            needGlobalAttributesUpdate(false);
+            /*
+             * If this view is becoming invisible, set the DRAWN flag so that
+             * the next invalidate() will not be skipped.
+             */
+            mPrivateFlags |= PFLAG_DRAWN;
+
+            if (((mViewFlags & VISIBILITY_MASK) == INVISIBLE)) {
+                // root view becoming invisible shouldn't clear focus and accessibility focus
+                if (getRootView() != this) {
+                    if (hasFocus()) {
+                        clearFocus();
+                        if (mParent instanceof ViewGroup) {
+                            ((ViewGroup) mParent).clearFocusedInCluster();
+                        }
+                    }
+                    clearAccessibilityFocus();
+                }
+            }
+            if (mAttachInfo != null) {
+                mAttachInfo.mViewVisibilityChanged = true;
+            }
+        }
+
+        if ((changed & VISIBILITY_MASK) != 0) {
+            // If the view is invisible, cleanup its display list to free up resources
+            if (newVisibility != VISIBLE && mAttachInfo != null) {
+                cleanupDraw();
+            }
+
+            if (mParent instanceof ViewGroup) {
+                ViewGroup parent = (ViewGroup) mParent;
+                parent.onChildVisibilityChanged(this, (changed & VISIBILITY_MASK),
+                        newVisibility);
+                parent.invalidate(true);
+            } else if (mParent != null) {
+                mParent.invalidateChild(this, null);
+            }
+
+            if (mAttachInfo != null) {
+                dispatchVisibilityChanged(this, newVisibility);
+
+                // Aggregated visibility changes are dispatched to attached views
+                // in visible windows where the parent is currently shown/drawn
+                // or the parent is not a ViewGroup (and therefore assumed to be a ViewRoot),
+                // discounting clipping or overlapping. This makes it a good place
+                // to change animation states.
+                if (mParent != null && getWindowVisibility() == VISIBLE &&
+                        ((!(mParent instanceof ViewGroup)) || ((ViewGroup) mParent).isShown())) {
+                    dispatchVisibilityAggregated(newVisibility == VISIBLE);
+                }
+                // If this view is invisible from visible, then sending the A11y event by its
+                // parent which is shown and has the accessibility important.
+                if ((old & VISIBILITY_MASK) == VISIBLE) {
+                    notifySubtreeAccessibilityStateChangedByParentIfNeeded();
+                } else {
+                    notifySubtreeAccessibilityStateChangedIfNeeded();
+                }
+            }
+        }
+
+        if ((changed & WILL_NOT_CACHE_DRAWING) != 0) {
+            destroyDrawingCache();
+        }
+
+        if ((changed & DRAWING_CACHE_ENABLED) != 0) {
+            destroyDrawingCache();
+            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
+            invalidateParentCaches();
+        }
+
+        if ((changed & DRAWING_CACHE_QUALITY_MASK) != 0) {
+            destroyDrawingCache();
+            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
+        }
+
+        if ((changed & DRAW_MASK) != 0) {
+            if ((mViewFlags & WILL_NOT_DRAW) != 0) {
+                if (mBackground != null
+                        || mDefaultFocusHighlight != null
+                        || (mForegroundInfo != null && mForegroundInfo.mDrawable != null)) {
+                    mPrivateFlags &= ~PFLAG_SKIP_DRAW;
+                } else {
+                    mPrivateFlags |= PFLAG_SKIP_DRAW;
+                }
+            } else {
+                mPrivateFlags &= ~PFLAG_SKIP_DRAW;
+            }
+            requestLayout();
+            invalidate(true);
+        }
+
+        if ((changed & KEEP_SCREEN_ON) != 0) {
+            if (mParent != null && mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) {
+                mParent.recomputeViewAttributes(this);
+            }
+        }
+
+        if (accessibilityEnabled) {
+            // If we're an accessibility pane and the visibility changed, we already have sent
+            // a state change, so we really don't need to report other changes.
+            if (isAccessibilityPane()) {
+                changed &= ~VISIBILITY_MASK;
+            }
+            if ((changed & FOCUSABLE) != 0 || (changed & VISIBILITY_MASK) != 0
+                    || (changed & CLICKABLE) != 0 || (changed & LONG_CLICKABLE) != 0
+                    || (changed & CONTEXT_CLICKABLE) != 0) {
+                if (oldIncludeForAccessibility != includeForAccessibility()) {
+                    notifySubtreeAccessibilityStateChangedIfNeeded();
+                } else {
+                    notifyViewAccessibilityStateChangedIfNeeded(
+                            AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+                }
+            } else if ((changed & ENABLED_MASK) != 0) {
+                notifyViewAccessibilityStateChangedIfNeeded(
+                        AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+            }
+        }
+    }
+
+    /**
+     * Change the view's z order in the tree, so it's on top of other sibling
+     * views. This ordering change may affect layout, if the parent container
+     * uses an order-dependent layout scheme (e.g., LinearLayout). Prior
+     * to {@link android.os.Build.VERSION_CODES#KITKAT} this
+     * method should be followed by calls to {@link #requestLayout()} and
+     * {@link View#invalidate()} on the view's parent to force the parent to redraw
+     * with the new child ordering.
+     *
+     * @see ViewGroup#bringChildToFront(View)
+     */
+    public void bringToFront() {
+        if (mParent != null) {
+            mParent.bringChildToFront(this);
+        }
+    }
+
+    /**
+     * This is called in response to an internal scroll in this view (i.e., the
+     * view scrolled its own contents). This is typically as a result of
+     * {@link #scrollBy(int, int)} or {@link #scrollTo(int, int)} having been
+     * called.
+     *
+     * @param l Current horizontal scroll origin.
+     * @param t Current vertical scroll origin.
+     * @param oldl Previous horizontal scroll origin.
+     * @param oldt Previous vertical scroll origin.
+     */
+    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+        notifySubtreeAccessibilityStateChangedIfNeeded();
+        postSendViewScrolledAccessibilityEventCallback(l - oldl, t - oldt);
+
+        mBackgroundSizeChanged = true;
+        mDefaultFocusHighlightSizeChanged = true;
+        if (mForegroundInfo != null) {
+            mForegroundInfo.mBoundsChanged = true;
+        }
+
+        final AttachInfo ai = mAttachInfo;
+        if (ai != null) {
+            ai.mViewScrollChanged = true;
+        }
+
+        if (mListenerInfo != null && mListenerInfo.mOnScrollChangeListener != null) {
+            mListenerInfo.mOnScrollChangeListener.onScrollChange(this, l, t, oldl, oldt);
+        }
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when the scroll
+     * X or Y positions of a view change.
+     * <p>
+     * <b>Note:</b> Some views handle scrolling independently from View and may
+     * have their own separate listeners for scroll-type events. For example,
+     * {@link android.widget.ListView ListView} allows clients to register an
+     * {@link android.widget.ListView#setOnScrollListener(android.widget.AbsListView.OnScrollListener) AbsListView.OnScrollListener}
+     * to listen for changes in list scroll position.
+     *
+     * @see #setOnScrollChangeListener(View.OnScrollChangeListener)
+     */
+    public interface OnScrollChangeListener {
+        /**
+         * Called when the scroll position of a view changes.
+         *
+         * @param v The view whose scroll position has changed.
+         * @param scrollX Current horizontal scroll origin.
+         * @param scrollY Current vertical scroll origin.
+         * @param oldScrollX Previous horizontal scroll origin.
+         * @param oldScrollY Previous vertical scroll origin.
+         */
+        void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when the layout bounds of a view
+     * changes due to layout processing.
+     */
+    public interface OnLayoutChangeListener {
+        /**
+         * Called when the layout bounds of a view changes due to layout processing.
+         *
+         * @param v The view whose bounds have changed.
+         * @param left The new value of the view's left property.
+         * @param top The new value of the view's top property.
+         * @param right The new value of the view's right property.
+         * @param bottom The new value of the view's bottom property.
+         * @param oldLeft The previous value of the view's left property.
+         * @param oldTop The previous value of the view's top property.
+         * @param oldRight The previous value of the view's right property.
+         * @param oldBottom The previous value of the view's bottom property.
+         */
+        void onLayoutChange(View v, int left, int top, int right, int bottom,
+            int oldLeft, int oldTop, int oldRight, int oldBottom);
+    }
+
+    /**
+     * This is called during layout when the size of this view has changed. If
+     * you were just added to the view hierarchy, you're called with the old
+     * values of 0.
+     *
+     * @param w Current width of this view.
+     * @param h Current height of this view.
+     * @param oldw Old width of this view.
+     * @param oldh Old height of this view.
+     */
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+    }
+
+    /**
+     * Called by draw to draw the child views. This may be overridden
+     * by derived classes to gain control just before its children are drawn
+     * (but after its own view has been drawn).
+     * @param canvas the canvas on which to draw the view
+     */
+    protected void dispatchDraw(Canvas canvas) {
+
+    }
+
+    /**
+     * Gets the parent of this view. Note that the parent is a
+     * ViewParent and not necessarily a View.
+     *
+     * @return Parent of this view.
+     */
+    public final ViewParent getParent() {
+        return mParent;
+    }
+
+    /**
+     * Set the horizontal scrolled position of your view. This will cause a call to
+     * {@link #onScrollChanged(int, int, int, int)} and the view will be
+     * invalidated.
+     * @param value the x position to scroll to
+     */
+    public void setScrollX(int value) {
+        scrollTo(value, mScrollY);
+    }
+
+    /**
+     * Set the vertical scrolled position of your view. This will cause a call to
+     * {@link #onScrollChanged(int, int, int, int)} and the view will be
+     * invalidated.
+     * @param value the y position to scroll to
+     */
+    public void setScrollY(int value) {
+        scrollTo(mScrollX, value);
+    }
+
+    /**
+     * Return the scrolled left position of this view. This is the left edge of
+     * the displayed part of your view. You do not need to draw any pixels
+     * farther left, since those are outside of the frame of your view on
+     * screen.
+     *
+     * @return The left edge of the displayed part of your view, in pixels.
+     */
+    @InspectableProperty
+    public final int getScrollX() {
+        return mScrollX;
+    }
+
+    /**
+     * Return the scrolled top position of this view. This is the top edge of
+     * the displayed part of your view. You do not need to draw any pixels above
+     * it, since those are outside of the frame of your view on screen.
+     *
+     * @return The top edge of the displayed part of your view, in pixels.
+     */
+    @InspectableProperty
+    public final int getScrollY() {
+        return mScrollY;
+    }
+
+    /**
+     * Return the width of your view.
+     *
+     * @return The width of your view, in pixels.
+     */
+    @ViewDebug.ExportedProperty(category = "layout")
+    public final int getWidth() {
+        return mRight - mLeft;
+    }
+
+    /**
+     * Return the height of your view.
+     *
+     * @return The height of your view, in pixels.
+     */
+    @ViewDebug.ExportedProperty(category = "layout")
+    public final int getHeight() {
+        return mBottom - mTop;
+    }
+
+    /**
+     * Return the visible drawing bounds of your view. Fills in the output
+     * rectangle with the values from getScrollX(), getScrollY(),
+     * getWidth(), and getHeight(). These bounds do not account for any
+     * transformation properties currently set on the view, such as
+     * {@link #setScaleX(float)} or {@link #setRotation(float)}.
+     *
+     * @param outRect The (scrolled) drawing bounds of the view.
+     */
+    public void getDrawingRect(Rect outRect) {
+        outRect.left = mScrollX;
+        outRect.top = mScrollY;
+        outRect.right = mScrollX + (mRight - mLeft);
+        outRect.bottom = mScrollY + (mBottom - mTop);
+    }
+
+    /**
+     * Like {@link #getMeasuredWidthAndState()}, but only returns the
+     * raw width component (that is the result is masked by
+     * {@link #MEASURED_SIZE_MASK}).
+     *
+     * @return The raw measured width of this view.
+     */
+    public final int getMeasuredWidth() {
+        return mMeasuredWidth & MEASURED_SIZE_MASK;
+    }
+
+    /**
+     * Return the full width measurement information for this view as computed
+     * by the most recent call to {@link #measure(int, int)}.  This result is a bit mask
+     * as defined by {@link #MEASURED_SIZE_MASK} and {@link #MEASURED_STATE_TOO_SMALL}.
+     * This should be used during measurement and layout calculations only. Use
+     * {@link #getWidth()} to see how wide a view is after layout.
+     *
+     * @return The measured width of this view as a bit mask.
+     */
+    @ViewDebug.ExportedProperty(category = "measurement", flagMapping = {
+            @ViewDebug.FlagToString(mask = MEASURED_STATE_MASK, equals = MEASURED_STATE_TOO_SMALL,
+                    name = "MEASURED_STATE_TOO_SMALL"),
+    })
+    public final int getMeasuredWidthAndState() {
+        return mMeasuredWidth;
+    }
+
+    /**
+     * Like {@link #getMeasuredHeightAndState()}, but only returns the
+     * raw height component (that is the result is masked by
+     * {@link #MEASURED_SIZE_MASK}).
+     *
+     * @return The raw measured height of this view.
+     */
+    public final int getMeasuredHeight() {
+        return mMeasuredHeight & MEASURED_SIZE_MASK;
+    }
+
+    /**
+     * Return the full height measurement information for this view as computed
+     * by the most recent call to {@link #measure(int, int)}.  This result is a bit mask
+     * as defined by {@link #MEASURED_SIZE_MASK} and {@link #MEASURED_STATE_TOO_SMALL}.
+     * This should be used during measurement and layout calculations only. Use
+     * {@link #getHeight()} to see how high a view is after layout.
+     *
+     * @return The measured height of this view as a bit mask.
+     */
+    @ViewDebug.ExportedProperty(category = "measurement", flagMapping = {
+            @ViewDebug.FlagToString(mask = MEASURED_STATE_MASK, equals = MEASURED_STATE_TOO_SMALL,
+                    name = "MEASURED_STATE_TOO_SMALL"),
+    })
+    public final int getMeasuredHeightAndState() {
+        return mMeasuredHeight;
+    }
+
+    /**
+     * Return only the state bits of {@link #getMeasuredWidthAndState()}
+     * and {@link #getMeasuredHeightAndState()}, combined into one integer.
+     * The width component is in the regular bits {@link #MEASURED_STATE_MASK}
+     * and the height component is at the shifted bits
+     * {@link #MEASURED_HEIGHT_STATE_SHIFT}>>{@link #MEASURED_STATE_MASK}.
+     */
+    public final int getMeasuredState() {
+        return (mMeasuredWidth&MEASURED_STATE_MASK)
+                | ((mMeasuredHeight>>MEASURED_HEIGHT_STATE_SHIFT)
+                        & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
+    }
+
+    /**
+     * The transform matrix of this view, which is calculated based on the current
+     * rotation, scale, and pivot properties.
+     *
+     * @see #getRotation()
+     * @see #getScaleX()
+     * @see #getScaleY()
+     * @see #getPivotX()
+     * @see #getPivotY()
+     * @return The current transform matrix for the view
+     */
+    public Matrix getMatrix() {
+        ensureTransformationInfo();
+        final Matrix matrix = mTransformationInfo.mMatrix;
+        mRenderNode.getMatrix(matrix);
+        return matrix;
+    }
+
+    /**
+     * Returns true if the transform matrix is the identity matrix.
+     * Recomputes the matrix if necessary.
+     *
+     * @return True if the transform matrix is the identity matrix, false otherwise.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public final boolean hasIdentityMatrix() {
+        return mRenderNode.hasIdentityMatrix();
+    }
+
+    @UnsupportedAppUsage
+    void ensureTransformationInfo() {
+        if (mTransformationInfo == null) {
+            mTransformationInfo = new TransformationInfo();
+        }
+    }
+
+    /**
+     * Utility method to retrieve the inverse of the current mMatrix property.
+     * We cache the matrix to avoid recalculating it when transform properties
+     * have not changed.
+     *
+     * @return The inverse of the current matrix of this view.
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public final Matrix getInverseMatrix() {
+        ensureTransformationInfo();
+        if (mTransformationInfo.mInverseMatrix == null) {
+            mTransformationInfo.mInverseMatrix = new Matrix();
+        }
+        final Matrix matrix = mTransformationInfo.mInverseMatrix;
+        mRenderNode.getInverseMatrix(matrix);
+        return matrix;
+    }
+
+    /**
+     * Gets the distance along the Z axis from the camera to this view.
+     *
+     * @see #setCameraDistance(float)
+     *
+     * @return The distance along the Z axis.
+     */
+    public float getCameraDistance() {
+        final float dpi = mResources.getDisplayMetrics().densityDpi;
+        return mRenderNode.getCameraDistance() * dpi;
+    }
+
+    /**
+     * <p>Sets the distance along the Z axis (orthogonal to the X/Y plane on which
+     * views are drawn) from the camera to this view. The camera's distance
+     * affects 3D transformations, for instance rotations around the X and Y
+     * axis. If the rotationX or rotationY properties are changed and this view is
+     * large (more than half the size of the screen), it is recommended to always
+     * use a camera distance that's greater than the height (X axis rotation) or
+     * the width (Y axis rotation) of this view.</p>
+     *
+     * <p>The distance of the camera from the view plane can have an affect on the
+     * perspective distortion of the view when it is rotated around the x or y axis.
+     * For example, a large distance will result in a large viewing angle, and there
+     * will not be much perspective distortion of the view as it rotates. A short
+     * distance may cause much more perspective distortion upon rotation, and can
+     * also result in some drawing artifacts if the rotated view ends up partially
+     * behind the camera (which is why the recommendation is to use a distance at
+     * least as far as the size of the view, if the view is to be rotated.)</p>
+     *
+     * <p>The distance is expressed in "depth pixels." The default distance depends
+     * on the screen density. For instance, on a medium density display, the
+     * default distance is 1280. On a high density display, the default distance
+     * is 1920.</p>
+     *
+     * <p>If you want to specify a distance that leads to visually consistent
+     * results across various densities, use the following formula:</p>
+     * <pre>
+     * float scale = context.getResources().getDisplayMetrics().density;
+     * view.setCameraDistance(distance * scale);
+     * </pre>
+     *
+     * <p>The density scale factor of a high density display is 1.5,
+     * and 1920 = 1280 * 1.5.</p>
+     *
+     * @param distance The distance in "depth pixels", if negative the opposite
+     *        value is used
+     *
+     * @see #setRotationX(float)
+     * @see #setRotationY(float)
+     */
+    public void setCameraDistance(float distance) {
+        final float dpi = mResources.getDisplayMetrics().densityDpi;
+
+        invalidateViewProperty(true, false);
+        mRenderNode.setCameraDistance(Math.abs(distance) / dpi);
+        invalidateViewProperty(false, false);
+
+        invalidateParentIfNeededAndWasQuickRejected();
+    }
+
+    /**
+     * The degrees that the view is rotated around the pivot point.
+     *
+     * @see #setRotation(float)
+     * @see #getPivotX()
+     * @see #getPivotY()
+     *
+     * @return The degrees of rotation.
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    @InspectableProperty
+    public float getRotation() {
+        return mRenderNode.getRotationZ();
+    }
+
+    /**
+     * Sets the degrees that the view is rotated around the pivot point. Increasing values
+     * result in clockwise rotation.
+     *
+     * @param rotation The degrees of rotation.
+     *
+     * @see #getRotation()
+     * @see #getPivotX()
+     * @see #getPivotY()
+     * @see #setRotationX(float)
+     * @see #setRotationY(float)
+     *
+     * @attr ref android.R.styleable#View_rotation
+     */
+    @RemotableViewMethod
+    public void setRotation(float rotation) {
+        if (rotation != getRotation()) {
+            // Double-invalidation is necessary to capture view's old and new areas
+            invalidateViewProperty(true, false);
+            mRenderNode.setRotationZ(rotation);
+            invalidateViewProperty(false, true);
+
+            invalidateParentIfNeededAndWasQuickRejected();
+            notifySubtreeAccessibilityStateChangedIfNeeded();
+        }
+    }
+
+    /**
+     * The degrees that the view is rotated around the vertical axis through the pivot point.
+     *
+     * @see #getPivotX()
+     * @see #getPivotY()
+     * @see #setRotationY(float)
+     *
+     * @return The degrees of Y rotation.
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    @InspectableProperty
+    public float getRotationY() {
+        return mRenderNode.getRotationY();
+    }
+
+    /**
+     * Sets the degrees that the view is rotated around the vertical axis through the pivot point.
+     * Increasing values result in counter-clockwise rotation from the viewpoint of looking
+     * down the y axis.
+     *
+     * When rotating large views, it is recommended to adjust the camera distance
+     * accordingly. Refer to {@link #setCameraDistance(float)} for more information.
+     *
+     * @param rotationY The degrees of Y rotation.
+     *
+     * @see #getRotationY()
+     * @see #getPivotX()
+     * @see #getPivotY()
+     * @see #setRotation(float)
+     * @see #setRotationX(float)
+     * @see #setCameraDistance(float)
+     *
+     * @attr ref android.R.styleable#View_rotationY
+     */
+    @RemotableViewMethod
+    public void setRotationY(float rotationY) {
+        if (rotationY != getRotationY()) {
+            invalidateViewProperty(true, false);
+            mRenderNode.setRotationY(rotationY);
+            invalidateViewProperty(false, true);
+
+            invalidateParentIfNeededAndWasQuickRejected();
+            notifySubtreeAccessibilityStateChangedIfNeeded();
+        }
+    }
+
+    /**
+     * The degrees that the view is rotated around the horizontal axis through the pivot point.
+     *
+     * @see #getPivotX()
+     * @see #getPivotY()
+     * @see #setRotationX(float)
+     *
+     * @return The degrees of X rotation.
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    @InspectableProperty
+    public float getRotationX() {
+        return mRenderNode.getRotationX();
+    }
+
+    /**
+     * Sets the degrees that the view is rotated around the horizontal axis through the pivot point.
+     * Increasing values result in clockwise rotation from the viewpoint of looking down the
+     * x axis.
+     *
+     * When rotating large views, it is recommended to adjust the camera distance
+     * accordingly. Refer to {@link #setCameraDistance(float)} for more information.
+     *
+     * @param rotationX The degrees of X rotation.
+     *
+     * @see #getRotationX()
+     * @see #getPivotX()
+     * @see #getPivotY()
+     * @see #setRotation(float)
+     * @see #setRotationY(float)
+     * @see #setCameraDistance(float)
+     *
+     * @attr ref android.R.styleable#View_rotationX
+     */
+    @RemotableViewMethod
+    public void setRotationX(float rotationX) {
+        if (rotationX != getRotationX()) {
+            invalidateViewProperty(true, false);
+            mRenderNode.setRotationX(rotationX);
+            invalidateViewProperty(false, true);
+
+            invalidateParentIfNeededAndWasQuickRejected();
+            notifySubtreeAccessibilityStateChangedIfNeeded();
+        }
+    }
+
+    /**
+     * The amount that the view is scaled in x around the pivot point, as a proportion of
+     * the view's unscaled width. A value of 1, the default, means that no scaling is applied.
+     *
+     * <p>By default, this is 1.0f.
+     *
+     * @see #getPivotX()
+     * @see #getPivotY()
+     * @return The scaling factor.
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    @InspectableProperty
+    public float getScaleX() {
+        return mRenderNode.getScaleX();
+    }
+
+    /**
+     * Sets the amount that the view is scaled in x around the pivot point, as a proportion of
+     * the view's unscaled width. A value of 1 means that no scaling is applied.
+     *
+     * @param scaleX The scaling factor.
+     * @see #getPivotX()
+     * @see #getPivotY()
+     *
+     * @attr ref android.R.styleable#View_scaleX
+     */
+    @RemotableViewMethod
+    public void setScaleX(float scaleX) {
+        if (scaleX != getScaleX()) {
+            scaleX = sanitizeFloatPropertyValue(scaleX, "scaleX");
+            invalidateViewProperty(true, false);
+            mRenderNode.setScaleX(scaleX);
+            invalidateViewProperty(false, true);
+
+            invalidateParentIfNeededAndWasQuickRejected();
+            notifySubtreeAccessibilityStateChangedIfNeeded();
+        }
+    }
+
+    /**
+     * The amount that the view is scaled in y around the pivot point, as a proportion of
+     * the view's unscaled height. A value of 1, the default, means that no scaling is applied.
+     *
+     * <p>By default, this is 1.0f.
+     *
+     * @see #getPivotX()
+     * @see #getPivotY()
+     * @return The scaling factor.
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    @InspectableProperty
+    public float getScaleY() {
+        return mRenderNode.getScaleY();
+    }
+
+    /**
+     * Sets the amount that the view is scaled in Y around the pivot point, as a proportion of
+     * the view's unscaled width. A value of 1 means that no scaling is applied.
+     *
+     * @param scaleY The scaling factor.
+     * @see #getPivotX()
+     * @see #getPivotY()
+     *
+     * @attr ref android.R.styleable#View_scaleY
+     */
+    @RemotableViewMethod
+    public void setScaleY(float scaleY) {
+        if (scaleY != getScaleY()) {
+            scaleY = sanitizeFloatPropertyValue(scaleY, "scaleY");
+            invalidateViewProperty(true, false);
+            mRenderNode.setScaleY(scaleY);
+            invalidateViewProperty(false, true);
+
+            invalidateParentIfNeededAndWasQuickRejected();
+            notifySubtreeAccessibilityStateChangedIfNeeded();
+        }
+    }
+
+    /**
+     * The x location of the point around which the view is {@link #setRotation(float) rotated}
+     * and {@link #setScaleX(float) scaled}.
+     *
+     * @see #getRotation()
+     * @see #getScaleX()
+     * @see #getScaleY()
+     * @see #getPivotY()
+     * @return The x location of the pivot point.
+     *
+     * @attr ref android.R.styleable#View_transformPivotX
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    @InspectableProperty(name = "transformPivotX")
+    public float getPivotX() {
+        return mRenderNode.getPivotX();
+    }
+
+    /**
+     * Sets the x location of the point around which the view is
+     * {@link #setRotation(float) rotated} and {@link #setScaleX(float) scaled}.
+     * By default, the pivot point is centered on the object.
+     * Setting this property disables this behavior and causes the view to use only the
+     * explicitly set pivotX and pivotY values.
+     *
+     * @param pivotX The x location of the pivot point.
+     * @see #getRotation()
+     * @see #getScaleX()
+     * @see #getScaleY()
+     * @see #getPivotY()
+     *
+     * @attr ref android.R.styleable#View_transformPivotX
+     */
+    @RemotableViewMethod
+    public void setPivotX(float pivotX) {
+        if (!mRenderNode.isPivotExplicitlySet() || pivotX != getPivotX()) {
+            invalidateViewProperty(true, false);
+            mRenderNode.setPivotX(pivotX);
+            invalidateViewProperty(false, true);
+
+            invalidateParentIfNeededAndWasQuickRejected();
+        }
+    }
+
+    /**
+     * The y location of the point around which the view is {@link #setRotation(float) rotated}
+     * and {@link #setScaleY(float) scaled}.
+     *
+     * @see #getRotation()
+     * @see #getScaleX()
+     * @see #getScaleY()
+     * @see #getPivotY()
+     * @return The y location of the pivot point.
+     *
+     * @attr ref android.R.styleable#View_transformPivotY
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    @InspectableProperty(name = "transformPivotY")
+    public float getPivotY() {
+        return mRenderNode.getPivotY();
+    }
+
+    /**
+     * Sets the y location of the point around which the view is {@link #setRotation(float) rotated}
+     * and {@link #setScaleY(float) scaled}. By default, the pivot point is centered on the object.
+     * Setting this property disables this behavior and causes the view to use only the
+     * explicitly set pivotX and pivotY values.
+     *
+     * @param pivotY The y location of the pivot point.
+     * @see #getRotation()
+     * @see #getScaleX()
+     * @see #getScaleY()
+     * @see #getPivotY()
+     *
+     * @attr ref android.R.styleable#View_transformPivotY
+     */
+    @RemotableViewMethod
+    public void setPivotY(float pivotY) {
+        if (!mRenderNode.isPivotExplicitlySet() || pivotY != getPivotY()) {
+            invalidateViewProperty(true, false);
+            mRenderNode.setPivotY(pivotY);
+            invalidateViewProperty(false, true);
+
+            invalidateParentIfNeededAndWasQuickRejected();
+        }
+    }
+
+    /**
+     * Returns whether or not a pivot has been set by a call to {@link #setPivotX(float)} or
+     * {@link #setPivotY(float)}. If no pivot has been set then the pivot will be the center
+     * of the view.
+     *
+     * @return True if a pivot has been set, false if the default pivot is being used
+     */
+    public boolean isPivotSet() {
+        return mRenderNode.isPivotExplicitlySet();
+    }
+
+    /**
+     * Clears any pivot previously set by a call to  {@link #setPivotX(float)} or
+     * {@link #setPivotY(float)}. After calling this {@link #isPivotSet()} will be false
+     * and the pivot used for rotation will return to default of being centered on the view.
+     */
+    public void resetPivot() {
+        if (mRenderNode.resetPivot()) {
+            invalidateViewProperty(false, false);
+        }
+    }
+
+    /**
+     * The opacity of the view. This is a value from 0 to 1, where 0 means the view is
+     * completely transparent and 1 means the view is completely opaque.
+     *
+     * <p>By default this is 1.0f.
+     * @return The opacity of the view.
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    @InspectableProperty
+    public float getAlpha() {
+        return mTransformationInfo != null ? mTransformationInfo.mAlpha : 1;
+    }
+
+    /**
+     * Sets the behavior for overlapping rendering for this view (see {@link
+     * #hasOverlappingRendering()} for more details on this behavior). Calling this method
+     * is an alternative to overriding {@link #hasOverlappingRendering()} in a subclass,
+     * providing the value which is then used internally. That is, when {@link
+     * #forceHasOverlappingRendering(boolean)} is called, the value of {@link
+     * #hasOverlappingRendering()} is ignored and the value passed into this method is used
+     * instead.
+     *
+     * @param hasOverlappingRendering The value for overlapping rendering to be used internally
+     * instead of that returned by {@link #hasOverlappingRendering()}.
+     *
+     * @attr ref android.R.styleable#View_forceHasOverlappingRendering
+     */
+    public void forceHasOverlappingRendering(boolean hasOverlappingRendering) {
+        mPrivateFlags3 |= PFLAG3_HAS_OVERLAPPING_RENDERING_FORCED;
+        if (hasOverlappingRendering) {
+            mPrivateFlags3 |= PFLAG3_OVERLAPPING_RENDERING_FORCED_VALUE;
+        } else {
+            mPrivateFlags3 &= ~PFLAG3_OVERLAPPING_RENDERING_FORCED_VALUE;
+        }
+    }
+
+    /**
+     * Returns the value for overlapping rendering that is used internally. This is either
+     * the value passed into {@link #forceHasOverlappingRendering(boolean)}, if called, or
+     * the return value of {@link #hasOverlappingRendering()}, otherwise.
+     *
+     * @return The value for overlapping rendering being used internally.
+     */
+    public final boolean getHasOverlappingRendering() {
+        return (mPrivateFlags3 & PFLAG3_HAS_OVERLAPPING_RENDERING_FORCED) != 0 ?
+                (mPrivateFlags3 & PFLAG3_OVERLAPPING_RENDERING_FORCED_VALUE) != 0 :
+                hasOverlappingRendering();
+    }
+
+    /**
+     * Returns whether this View has content which overlaps.
+     *
+     * <p>This function, intended to be overridden by specific View types, is an optimization when
+     * alpha is set on a view. If rendering overlaps in a view with alpha < 1, that view is drawn to
+     * an offscreen buffer and then composited into place, which can be expensive. If the view has
+     * no overlapping rendering, the view can draw each primitive with the appropriate alpha value
+     * directly. An example of overlapping rendering is a TextView with a background image, such as
+     * a Button. An example of non-overlapping rendering is a TextView with no background, or an
+     * ImageView with only the foreground image. The default implementation returns true; subclasses
+     * should override if they have cases which can be optimized.</p>
+     *
+     * <p><strong>Note:</strong> The return value of this method is ignored if {@link
+     * #forceHasOverlappingRendering(boolean)} has been called on this view.</p>
+     *
+     * @return true if the content in this view might overlap, false otherwise.
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    public boolean hasOverlappingRendering() {
+        return true;
+    }
+
+    /**
+     * Sets the opacity of the view to a value from 0 to 1, where 0 means the view is
+     * completely transparent and 1 means the view is completely opaque.
+     *
+     * <p class="note"><strong>Note:</strong> setting alpha to a translucent value (0 < alpha < 1)
+     * can have significant performance implications, especially for large views. It is best to use
+     * the alpha property sparingly and transiently, as in the case of fading animations.</p>
+     *
+     * <p>For a view with a frequently changing alpha, such as during a fading animation, it is
+     * strongly recommended for performance reasons to either override
+     * {@link #hasOverlappingRendering()} to return <code>false</code> if appropriate, or setting a
+     * {@link #setLayerType(int, android.graphics.Paint) layer type} on the view for the duration
+     * of the animation. On versions {@link android.os.Build.VERSION_CODES#M} and below,
+     * the default path for rendering an unlayered View with alpha could add multiple milliseconds
+     * of rendering cost, even for simple or small views. Starting with
+     * {@link android.os.Build.VERSION_CODES#M}, {@link #LAYER_TYPE_HARDWARE} is automatically
+     * applied to the view at the rendering level.</p>
+     *
+     * <p>If this view overrides {@link #onSetAlpha(int)} to return true, then this view is
+     * responsible for applying the opacity itself.</p>
+     *
+     * <p>On versions {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1} and below, note that if
+     * the view is backed by a {@link #setLayerType(int, android.graphics.Paint) layer} and is
+     * associated with a {@link #setLayerPaint(android.graphics.Paint) layer paint}, setting an
+     * alpha value less than 1.0 will supersede the alpha of the layer paint.</p>
+     *
+     * <p>Starting with {@link android.os.Build.VERSION_CODES#M}, setting a translucent alpha
+     * value will clip a View to its bounds, unless the View returns <code>false</code> from
+     * {@link #hasOverlappingRendering}.</p>
+     *
+     * @param alpha The opacity of the view.
+     *
+     * @see #hasOverlappingRendering()
+     * @see #setLayerType(int, android.graphics.Paint)
+     *
+     * @attr ref android.R.styleable#View_alpha
+     */
+    @RemotableViewMethod
+    public void setAlpha(@FloatRange(from=0.0, to=1.0) float alpha) {
+        ensureTransformationInfo();
+        if (mTransformationInfo.mAlpha != alpha) {
+            setAlphaInternal(alpha);
+            if (onSetAlpha((int) (alpha * 255))) {
+                mPrivateFlags |= PFLAG_ALPHA_SET;
+                // subclass is handling alpha - don't optimize rendering cache invalidation
+                invalidateParentCaches();
+                invalidate(true);
+            } else {
+                mPrivateFlags &= ~PFLAG_ALPHA_SET;
+                invalidateViewProperty(true, false);
+                mRenderNode.setAlpha(getFinalAlpha());
+            }
+        }
+    }
+
+    /**
+     * Faster version of setAlpha() which performs the same steps except there are
+     * no calls to invalidate(). The caller of this function should perform proper invalidation
+     * on the parent and this object. The return value indicates whether the subclass handles
+     * alpha (the return value for onSetAlpha()).
+     *
+     * @param alpha The new value for the alpha property
+     * @return true if the View subclass handles alpha (the return value for onSetAlpha()) and
+     *         the new value for the alpha property is different from the old value
+     */
+    @UnsupportedAppUsage(maxTargetSdk  = Build.VERSION_CODES.P, trackingBug = 123768435)
+    boolean setAlphaNoInvalidation(float alpha) {
+        ensureTransformationInfo();
+        if (mTransformationInfo.mAlpha != alpha) {
+            setAlphaInternal(alpha);
+            boolean subclassHandlesAlpha = onSetAlpha((int) (alpha * 255));
+            if (subclassHandlesAlpha) {
+                mPrivateFlags |= PFLAG_ALPHA_SET;
+                return true;
+            } else {
+                mPrivateFlags &= ~PFLAG_ALPHA_SET;
+                mRenderNode.setAlpha(getFinalAlpha());
+            }
+        }
+        return false;
+    }
+
+    void setAlphaInternal(float alpha) {
+        float oldAlpha = mTransformationInfo.mAlpha;
+        mTransformationInfo.mAlpha = alpha;
+        // Report visibility changes, which can affect children, to accessibility
+        if ((alpha == 0) ^ (oldAlpha == 0)) {
+            notifySubtreeAccessibilityStateChangedIfNeeded();
+        }
+    }
+
+    /**
+     * This property is intended only for use by the Fade transition, which animates it
+     * to produce a visual translucency that does not side-effect (or get affected by)
+     * the real alpha property. This value is composited with the other alpha value
+     * (and the AlphaAnimation value, when that is present) to produce a final visual
+     * translucency result, which is what is passed into the DisplayList.
+     */
+    public void setTransitionAlpha(float alpha) {
+        ensureTransformationInfo();
+        if (mTransformationInfo.mTransitionAlpha != alpha) {
+            mTransformationInfo.mTransitionAlpha = alpha;
+            mPrivateFlags &= ~PFLAG_ALPHA_SET;
+            invalidateViewProperty(true, false);
+            mRenderNode.setAlpha(getFinalAlpha());
+        }
+    }
+
+    /**
+     * Calculates the visual alpha of this view, which is a combination of the actual
+     * alpha value and the transitionAlpha value (if set).
+     */
+    private float getFinalAlpha() {
+        if (mTransformationInfo != null) {
+            return mTransformationInfo.mAlpha * mTransformationInfo.mTransitionAlpha;
+        }
+        return 1;
+    }
+
+    /**
+     * This property is intended only for use by the Fade transition, which animates
+     * it to produce a visual translucency that does not side-effect (or get affected
+     * by) the real alpha property. This value is composited with the other alpha
+     * value (and the AlphaAnimation value, when that is present) to produce a final
+     * visual translucency result, which is what is passed into the DisplayList.
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    public float getTransitionAlpha() {
+        return mTransformationInfo != null ? mTransformationInfo.mTransitionAlpha : 1;
+    }
+
+    /**
+     * Sets whether or not to allow force dark to apply to this view.
+     *
+     * Setting this to false will disable the auto-dark feature on everything this view
+     * draws, including any descendants.
+     *
+     * Setting this to true will allow this view to be automatically made dark, however
+     * a value of 'true' will not override any 'false' value in its parent chain nor will
+     * it prevent any 'false' in any of its children.
+     *
+     * The default behavior of force dark is also influenced by the Theme's
+     * {@link android.R.styleable#Theme_isLightTheme isLightTheme} attribute.
+     * If a theme is isLightTheme="false", then force dark is globally disabled for that theme.
+     *
+     * @param allow Whether or not to allow force dark.
+     */
+    public void setForceDarkAllowed(boolean allow) {
+        if (mRenderNode.setForceDarkAllowed(allow)) {
+            // Currently toggling force-dark requires a new display list push to apply
+            // TODO: Make it not clobber the display list so this is just a damageSelf() instead
+            invalidate();
+        }
+    }
+
+    /**
+     * See {@link #setForceDarkAllowed(boolean)}
+     *
+     * @return true if force dark is allowed (default), false if it is disabled
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    @InspectableProperty
+    public boolean isForceDarkAllowed() {
+        return mRenderNode.isForceDarkAllowed();
+    }
+
+    /**
+     * Top position of this view relative to its parent.
+     *
+     * @return The top of this view, in pixels.
+     */
+    @ViewDebug.CapturedViewProperty
+    public final int getTop() {
+        return mTop;
+    }
+
+    /**
+     * Sets the top position of this view relative to its parent. This method is meant to be called
+     * by the layout system and should not generally be called otherwise, because the property
+     * may be changed at any time by the layout.
+     *
+     * @param top The top of this view, in pixels.
+     */
+    public final void setTop(int top) {
+        if (top != mTop) {
+            final boolean matrixIsIdentity = hasIdentityMatrix();
+            if (matrixIsIdentity) {
+                if (mAttachInfo != null) {
+                    int minTop;
+                    int yLoc;
+                    if (top < mTop) {
+                        minTop = top;
+                        yLoc = top - mTop;
+                    } else {
+                        minTop = mTop;
+                        yLoc = 0;
+                    }
+                    invalidate(0, yLoc, mRight - mLeft, mBottom - minTop);
+                }
+            } else {
+                // Double-invalidation is necessary to capture view's old and new areas
+                invalidate(true);
+            }
+
+            int width = mRight - mLeft;
+            int oldHeight = mBottom - mTop;
+
+            mTop = top;
+            mRenderNode.setTop(mTop);
+
+            sizeChange(width, mBottom - mTop, width, oldHeight);
+
+            if (!matrixIsIdentity) {
+                mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation
+                invalidate(true);
+            }
+            mBackgroundSizeChanged = true;
+            mDefaultFocusHighlightSizeChanged = true;
+            if (mForegroundInfo != null) {
+                mForegroundInfo.mBoundsChanged = true;
+            }
+            invalidateParentIfNeeded();
+        }
+    }
+
+    /**
+     * Bottom position of this view relative to its parent.
+     *
+     * @return The bottom of this view, in pixels.
+     */
+    @ViewDebug.CapturedViewProperty
+    public final int getBottom() {
+        return mBottom;
+    }
+
+    /**
+     * True if this view has changed since the last time being drawn.
+     *
+     * @return The dirty state of this view.
+     */
+    public boolean isDirty() {
+        return (mPrivateFlags & PFLAG_DIRTY_MASK) != 0;
+    }
+
+    /**
+     * Sets the bottom position of this view relative to its parent. This method is meant to be
+     * called by the layout system and should not generally be called otherwise, because the
+     * property may be changed at any time by the layout.
+     *
+     * @param bottom The bottom of this view, in pixels.
+     */
+    public final void setBottom(int bottom) {
+        if (bottom != mBottom) {
+            final boolean matrixIsIdentity = hasIdentityMatrix();
+            if (matrixIsIdentity) {
+                if (mAttachInfo != null) {
+                    int maxBottom;
+                    if (bottom < mBottom) {
+                        maxBottom = mBottom;
+                    } else {
+                        maxBottom = bottom;
+                    }
+                    invalidate(0, 0, mRight - mLeft, maxBottom - mTop);
+                }
+            } else {
+                // Double-invalidation is necessary to capture view's old and new areas
+                invalidate(true);
+            }
+
+            int width = mRight - mLeft;
+            int oldHeight = mBottom - mTop;
+
+            mBottom = bottom;
+            mRenderNode.setBottom(mBottom);
+
+            sizeChange(width, mBottom - mTop, width, oldHeight);
+
+            if (!matrixIsIdentity) {
+                mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation
+                invalidate(true);
+            }
+            mBackgroundSizeChanged = true;
+            mDefaultFocusHighlightSizeChanged = true;
+            if (mForegroundInfo != null) {
+                mForegroundInfo.mBoundsChanged = true;
+            }
+            invalidateParentIfNeeded();
+        }
+    }
+
+    /**
+     * Left position of this view relative to its parent.
+     *
+     * @return The left edge of this view, in pixels.
+     */
+    @ViewDebug.CapturedViewProperty
+    public final int getLeft() {
+        return mLeft;
+    }
+
+    /**
+     * Sets the left position of this view relative to its parent. This method is meant to be called
+     * by the layout system and should not generally be called otherwise, because the property
+     * may be changed at any time by the layout.
+     *
+     * @param left The left of this view, in pixels.
+     */
+    public final void setLeft(int left) {
+        if (left != mLeft) {
+            final boolean matrixIsIdentity = hasIdentityMatrix();
+            if (matrixIsIdentity) {
+                if (mAttachInfo != null) {
+                    int minLeft;
+                    int xLoc;
+                    if (left < mLeft) {
+                        minLeft = left;
+                        xLoc = left - mLeft;
+                    } else {
+                        minLeft = mLeft;
+                        xLoc = 0;
+                    }
+                    invalidate(xLoc, 0, mRight - minLeft, mBottom - mTop);
+                }
+            } else {
+                // Double-invalidation is necessary to capture view's old and new areas
+                invalidate(true);
+            }
+
+            int oldWidth = mRight - mLeft;
+            int height = mBottom - mTop;
+
+            mLeft = left;
+            mRenderNode.setLeft(left);
+
+            sizeChange(mRight - mLeft, height, oldWidth, height);
+
+            if (!matrixIsIdentity) {
+                mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation
+                invalidate(true);
+            }
+            mBackgroundSizeChanged = true;
+            mDefaultFocusHighlightSizeChanged = true;
+            if (mForegroundInfo != null) {
+                mForegroundInfo.mBoundsChanged = true;
+            }
+            invalidateParentIfNeeded();
+        }
+    }
+
+    /**
+     * Right position of this view relative to its parent.
+     *
+     * @return The right edge of this view, in pixels.
+     */
+    @ViewDebug.CapturedViewProperty
+    public final int getRight() {
+        return mRight;
+    }
+
+    /**
+     * Sets the right position of this view relative to its parent. This method is meant to be called
+     * by the layout system and should not generally be called otherwise, because the property
+     * may be changed at any time by the layout.
+     *
+     * @param right The right of this view, in pixels.
+     */
+    public final void setRight(int right) {
+        if (right != mRight) {
+            final boolean matrixIsIdentity = hasIdentityMatrix();
+            if (matrixIsIdentity) {
+                if (mAttachInfo != null) {
+                    int maxRight;
+                    if (right < mRight) {
+                        maxRight = mRight;
+                    } else {
+                        maxRight = right;
+                    }
+                    invalidate(0, 0, maxRight - mLeft, mBottom - mTop);
+                }
+            } else {
+                // Double-invalidation is necessary to capture view's old and new areas
+                invalidate(true);
+            }
+
+            int oldWidth = mRight - mLeft;
+            int height = mBottom - mTop;
+
+            mRight = right;
+            mRenderNode.setRight(mRight);
+
+            sizeChange(mRight - mLeft, height, oldWidth, height);
+
+            if (!matrixIsIdentity) {
+                mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation
+                invalidate(true);
+            }
+            mBackgroundSizeChanged = true;
+            mDefaultFocusHighlightSizeChanged = true;
+            if (mForegroundInfo != null) {
+                mForegroundInfo.mBoundsChanged = true;
+            }
+            invalidateParentIfNeeded();
+        }
+    }
+
+    private static float sanitizeFloatPropertyValue(float value, String propertyName) {
+        return sanitizeFloatPropertyValue(value, propertyName, -Float.MAX_VALUE, Float.MAX_VALUE);
+    }
+
+    private static float sanitizeFloatPropertyValue(float value, String propertyName,
+            float min, float max) {
+        // The expected "nothing bad happened" path
+        if (value >= min && value <= max) return value;
+
+        if (value < min || value == Float.NEGATIVE_INFINITY) {
+            if (sThrowOnInvalidFloatProperties) {
+                throw new IllegalArgumentException("Cannot set '" + propertyName + "' to "
+                        + value + ", the value must be >= " + min);
+            }
+            return min;
+        }
+
+        if (value > max || value == Float.POSITIVE_INFINITY) {
+            if (sThrowOnInvalidFloatProperties) {
+                throw new IllegalArgumentException("Cannot set '" + propertyName + "' to "
+                        + value + ", the value must be <= " + max);
+            }
+            return max;
+        }
+
+        if (Float.isNaN(value)) {
+            if (sThrowOnInvalidFloatProperties) {
+                throw new IllegalArgumentException(
+                        "Cannot set '" + propertyName + "' to Float.NaN");
+            }
+            return 0; // Unclear which direction this NaN went so... 0?
+        }
+
+        // Shouldn't be possible to reach this.
+        throw new IllegalStateException("How do you get here?? " + value);
+    }
+
+    /**
+     * The visual x position of this view, in pixels. This is equivalent to the
+     * {@link #setTranslationX(float) translationX} property plus the current
+     * {@link #getLeft() left} property.
+     *
+     * @return The visual x position of this view, in pixels.
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    public float getX() {
+        return mLeft + getTranslationX();
+    }
+
+    /**
+     * Sets the visual x position of this view, in pixels. This is equivalent to setting the
+     * {@link #setTranslationX(float) translationX} property to be the difference between
+     * the x value passed in and the current {@link #getLeft() left} property.
+     *
+     * @param x The visual x position of this view, in pixels.
+     */
+    public void setX(float x) {
+        setTranslationX(x - mLeft);
+    }
+
+    /**
+     * The visual y position of this view, in pixels. This is equivalent to the
+     * {@link #setTranslationY(float) translationY} property plus the current
+     * {@link #getTop() top} property.
+     *
+     * @return The visual y position of this view, in pixels.
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    public float getY() {
+        return mTop + getTranslationY();
+    }
+
+    /**
+     * Sets the visual y position of this view, in pixels. This is equivalent to setting the
+     * {@link #setTranslationY(float) translationY} property to be the difference between
+     * the y value passed in and the current {@link #getTop() top} property.
+     *
+     * @param y The visual y position of this view, in pixels.
+     */
+    public void setY(float y) {
+        setTranslationY(y - mTop);
+    }
+
+    /**
+     * The visual z position of this view, in pixels. This is equivalent to the
+     * {@link #setTranslationZ(float) translationZ} property plus the current
+     * {@link #getElevation() elevation} property.
+     *
+     * @return The visual z position of this view, in pixels.
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    public float getZ() {
+        return getElevation() + getTranslationZ();
+    }
+
+    /**
+     * Sets the visual z position of this view, in pixels. This is equivalent to setting the
+     * {@link #setTranslationZ(float) translationZ} property to be the difference between
+     * the z value passed in and the current {@link #getElevation() elevation} property.
+     *
+     * @param z The visual z position of this view, in pixels.
+     */
+    public void setZ(float z) {
+        setTranslationZ(z - getElevation());
+    }
+
+    /**
+     * The base elevation of this view relative to its parent, in pixels.
+     *
+     * @return The base depth position of the view, in pixels.
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    @InspectableProperty
+    public float getElevation() {
+        return mRenderNode.getElevation();
+    }
+
+    /**
+     * Sets the base elevation of this view, in pixels.
+     *
+     * @attr ref android.R.styleable#View_elevation
+     */
+    @RemotableViewMethod
+    public void setElevation(float elevation) {
+        if (elevation != getElevation()) {
+            elevation = sanitizeFloatPropertyValue(elevation, "elevation");
+            invalidateViewProperty(true, false);
+            mRenderNode.setElevation(elevation);
+            invalidateViewProperty(false, true);
+
+            invalidateParentIfNeededAndWasQuickRejected();
+        }
+    }
+
+    /**
+     * The horizontal location of this view relative to its {@link #getLeft() left} position.
+     * This position is post-layout, in addition to wherever the object's
+     * layout placed it.
+     *
+     * @return The horizontal position of this view relative to its left position, in pixels.
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    @InspectableProperty
+    public float getTranslationX() {
+        return mRenderNode.getTranslationX();
+    }
+
+    /**
+     * Sets the horizontal location of this view relative to its {@link #getLeft() left} position.
+     * This effectively positions the object post-layout, in addition to wherever the object's
+     * layout placed it.
+     *
+     * @param translationX The horizontal position of this view relative to its left position,
+     * in pixels.
+     *
+     * @attr ref android.R.styleable#View_translationX
+     */
+    @RemotableViewMethod
+    public void setTranslationX(float translationX) {
+        if (translationX != getTranslationX()) {
+            invalidateViewProperty(true, false);
+            mRenderNode.setTranslationX(translationX);
+            invalidateViewProperty(false, true);
+
+            invalidateParentIfNeededAndWasQuickRejected();
+            notifySubtreeAccessibilityStateChangedIfNeeded();
+        }
+    }
+
+    /**
+     * The vertical location of this view relative to its {@link #getTop() top} position.
+     * This position is post-layout, in addition to wherever the object's
+     * layout placed it.
+     *
+     * @return The vertical position of this view relative to its top position,
+     * in pixels.
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    @InspectableProperty
+    public float getTranslationY() {
+        return mRenderNode.getTranslationY();
+    }
+
+    /**
+     * Sets the vertical location of this view relative to its {@link #getTop() top} position.
+     * This effectively positions the object post-layout, in addition to wherever the object's
+     * layout placed it.
+     *
+     * @param translationY The vertical position of this view relative to its top position,
+     * in pixels.
+     *
+     * @attr ref android.R.styleable#View_translationY
+     */
+    @RemotableViewMethod
+    public void setTranslationY(float translationY) {
+        if (translationY != getTranslationY()) {
+            invalidateViewProperty(true, false);
+            mRenderNode.setTranslationY(translationY);
+            invalidateViewProperty(false, true);
+
+            invalidateParentIfNeededAndWasQuickRejected();
+            notifySubtreeAccessibilityStateChangedIfNeeded();
+        }
+    }
+
+    /**
+     * The depth location of this view relative to its {@link #getElevation() elevation}.
+     *
+     * @return The depth of this view relative to its elevation.
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    @InspectableProperty
+    public float getTranslationZ() {
+        return mRenderNode.getTranslationZ();
+    }
+
+    /**
+     * Sets the depth location of this view relative to its {@link #getElevation() elevation}.
+     *
+     * @attr ref android.R.styleable#View_translationZ
+     */
+    @RemotableViewMethod
+    public void setTranslationZ(float translationZ) {
+        if (translationZ != getTranslationZ()) {
+            translationZ = sanitizeFloatPropertyValue(translationZ, "translationZ");
+            invalidateViewProperty(true, false);
+            mRenderNode.setTranslationZ(translationZ);
+            invalidateViewProperty(false, true);
+
+            invalidateParentIfNeededAndWasQuickRejected();
+        }
+    }
+
+    /**
+     * Changes the transformation matrix on the view. This is used in animation frameworks,
+     * such as {@link android.transition.Transition}. When the animation finishes, the matrix
+     * should be cleared by calling this method with <code>null</code> as the matrix parameter.
+     * Application developers should use transformation methods like {@link #setRotation(float)},
+     * {@link #setScaleX(float)}, {@link #setScaleX(float)}, {@link #setTranslationX(float)}}
+     * and {@link #setTranslationY(float)} (float)}} instead.
+     *
+     * @param matrix The matrix, null indicates that the matrix should be cleared.
+     * @see #getAnimationMatrix()
+     */
+    public void setAnimationMatrix(@Nullable Matrix matrix) {
+        invalidateViewProperty(true, false);
+        mRenderNode.setAnimationMatrix(matrix);
+        invalidateViewProperty(false, true);
+
+        invalidateParentIfNeededAndWasQuickRejected();
+    }
+
+    /**
+     * Return the current transformation matrix of the view. This is used in animation frameworks,
+     * such as {@link android.transition.Transition}. Returns <code>null</code> when there is no
+     * transformation provided by {@link #setAnimationMatrix(Matrix)}.
+     * Application developers should use transformation methods like {@link #setRotation(float)},
+     * {@link #setScaleX(float)}, {@link #setScaleX(float)}, {@link #setTranslationX(float)}}
+     * and {@link #setTranslationY(float)} (float)}} instead.
+     *
+     * @return the Matrix, null indicates there is no transformation
+     * @see #setAnimationMatrix(Matrix)
+     */
+    @Nullable
+    public Matrix getAnimationMatrix() {
+        return mRenderNode.getAnimationMatrix();
+    }
+
+    /**
+     * Returns the current StateListAnimator if exists.
+     *
+     * @return StateListAnimator or null if it does not exists
+     * @see    #setStateListAnimator(android.animation.StateListAnimator)
+     */
+    @InspectableProperty
+    public StateListAnimator getStateListAnimator() {
+        return mStateListAnimator;
+    }
+
+    /**
+     * Attaches the provided StateListAnimator to this View.
+     * <p>
+     * Any previously attached StateListAnimator will be detached.
+     *
+     * @param stateListAnimator The StateListAnimator to update the view
+     * @see android.animation.StateListAnimator
+     */
+    public void setStateListAnimator(StateListAnimator stateListAnimator) {
+        if (mStateListAnimator == stateListAnimator) {
+            return;
+        }
+        if (mStateListAnimator != null) {
+            mStateListAnimator.setTarget(null);
+        }
+        mStateListAnimator = stateListAnimator;
+        if (stateListAnimator != null) {
+            stateListAnimator.setTarget(this);
+            if (isAttachedToWindow()) {
+                stateListAnimator.setState(getDrawableState());
+            }
+        }
+    }
+
+    /**
+     * Returns whether the Outline should be used to clip the contents of the View.
+     * <p>
+     * Note that this flag will only be respected if the View's Outline returns true from
+     * {@link Outline#canClip()}.
+     *
+     * @see #setOutlineProvider(ViewOutlineProvider)
+     * @see #setClipToOutline(boolean)
+     */
+    public final boolean getClipToOutline() {
+        return mRenderNode.getClipToOutline();
+    }
+
+    /**
+     * Sets whether the View's Outline should be used to clip the contents of the View.
+     * <p>
+     * Only a single non-rectangular clip can be applied on a View at any time.
+     * Circular clips from a {@link ViewAnimationUtils#createCircularReveal(View, int, int, float, float)
+     * circular reveal} animation take priority over Outline clipping, and
+     * child Outline clipping takes priority over Outline clipping done by a
+     * parent.
+     * <p>
+     * Note that this flag will only be respected if the View's Outline returns true from
+     * {@link Outline#canClip()}.
+     *
+     * @see #setOutlineProvider(ViewOutlineProvider)
+     * @see #getClipToOutline()
+     *
+     * @attr ref android.R.styleable#View_clipToOutline
+     */
+    @RemotableViewMethod
+    public void setClipToOutline(boolean clipToOutline) {
+        damageInParent();
+        if (getClipToOutline() != clipToOutline) {
+            mRenderNode.setClipToOutline(clipToOutline);
+        }
+    }
+
+    // correspond to the enum values of View_outlineProvider
+    private static final int PROVIDER_BACKGROUND = 0;
+    private static final int PROVIDER_NONE = 1;
+    private static final int PROVIDER_BOUNDS = 2;
+    private static final int PROVIDER_PADDED_BOUNDS = 3;
+    private void setOutlineProviderFromAttribute(int providerInt) {
+        switch (providerInt) {
+            case PROVIDER_BACKGROUND:
+                setOutlineProvider(ViewOutlineProvider.BACKGROUND);
+                break;
+            case PROVIDER_NONE:
+                setOutlineProvider(null);
+                break;
+            case PROVIDER_BOUNDS:
+                setOutlineProvider(ViewOutlineProvider.BOUNDS);
+                break;
+            case PROVIDER_PADDED_BOUNDS:
+                setOutlineProvider(ViewOutlineProvider.PADDED_BOUNDS);
+                break;
+        }
+    }
+
+    /**
+     * Sets the {@link ViewOutlineProvider} of the view, which generates the Outline that defines
+     * the shape of the shadow it casts, and enables outline clipping.
+     * <p>
+     * The default ViewOutlineProvider, {@link ViewOutlineProvider#BACKGROUND}, queries the Outline
+     * from the View's background drawable, via {@link Drawable#getOutline(Outline)}. Changing the
+     * outline provider with this method allows this behavior to be overridden.
+     * <p>
+     * If the ViewOutlineProvider is null, if querying it for an outline returns false,
+     * or if the produced Outline is {@link Outline#isEmpty()}, shadows will not be cast.
+     * <p>
+     * Only outlines that return true from {@link Outline#canClip()} may be used for clipping.
+     *
+     * @see #setClipToOutline(boolean)
+     * @see #getClipToOutline()
+     * @see #getOutlineProvider()
+     */
+    public void setOutlineProvider(ViewOutlineProvider provider) {
+        mOutlineProvider = provider;
+        invalidateOutline();
+    }
+
+    /**
+     * Returns the current {@link ViewOutlineProvider} of the view, which generates the Outline
+     * that defines the shape of the shadow it casts, and enables outline clipping.
+     *
+     * @see #setOutlineProvider(ViewOutlineProvider)
+     */
+    @InspectableProperty
+    public ViewOutlineProvider getOutlineProvider() {
+        return mOutlineProvider;
+    }
+
+    /**
+     * Called to rebuild this View's Outline from its {@link ViewOutlineProvider outline provider}
+     *
+     * @see #setOutlineProvider(ViewOutlineProvider)
+     */
+    public void invalidateOutline() {
+        rebuildOutline();
+
+        notifySubtreeAccessibilityStateChangedIfNeeded();
+        invalidateViewProperty(false, false);
+    }
+
+    /**
+     * Internal version of {@link #invalidateOutline()} which invalidates the
+     * outline without invalidating the view itself. This is intended to be called from
+     * within methods in the View class itself which are the result of the view being
+     * invalidated already. For example, when we are drawing the background of a View,
+     * we invalidate the outline in case it changed in the meantime, but we do not
+     * need to invalidate the view because we're already drawing the background as part
+     * of drawing the view in response to an earlier invalidation of the view.
+     */
+    private void rebuildOutline() {
+        // Unattached views ignore this signal, and outline is recomputed in onAttachedToWindow()
+        if (mAttachInfo == null) return;
+
+        if (mOutlineProvider == null) {
+            // no provider, remove outline
+            mRenderNode.setOutline(null);
+        } else {
+            final Outline outline = mAttachInfo.mTmpOutline;
+            outline.setEmpty();
+            outline.setAlpha(1.0f);
+
+            mOutlineProvider.getOutline(this, outline);
+            mRenderNode.setOutline(outline);
+        }
+    }
+
+    /**
+     * HierarchyViewer only
+     *
+     * @hide
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    public boolean hasShadow() {
+        return mRenderNode.hasShadow();
+    }
+
+    /**
+     * Sets the color of the spot shadow that is drawn when the view has a positive Z or
+     * elevation value.
+     * <p>
+     * By default the shadow color is black. Generally, this color will be opaque so the intensity
+     * of the shadow is consistent between different views with different colors.
+     * <p>
+     * The opacity of the final spot shadow is a function of the shadow caster height, the
+     * alpha channel of the outlineSpotShadowColor (typically opaque), and the
+     * {@link android.R.attr#spotShadowAlpha} theme attribute.
+     *
+     * @attr ref android.R.styleable#View_outlineSpotShadowColor
+     * @param color The color this View will cast for its elevation spot shadow.
+     */
+    public void setOutlineSpotShadowColor(@ColorInt int color) {
+        if (mRenderNode.setSpotShadowColor(color)) {
+            invalidateViewProperty(true, true);
+        }
+    }
+
+    /**
+     * @return The shadow color set by {@link #setOutlineSpotShadowColor(int)}, or black if nothing
+     * was set
+     */
+    @InspectableProperty
+    public @ColorInt int getOutlineSpotShadowColor() {
+        return mRenderNode.getSpotShadowColor();
+    }
+
+    /**
+     * Sets the color of the ambient shadow that is drawn when the view has a positive Z or
+     * elevation value.
+     * <p>
+     * By default the shadow color is black. Generally, this color will be opaque so the intensity
+     * of the shadow is consistent between different views with different colors.
+     * <p>
+     * The opacity of the final ambient shadow is a function of the shadow caster height, the
+     * alpha channel of the outlineAmbientShadowColor (typically opaque), and the
+     * {@link android.R.attr#ambientShadowAlpha} theme attribute.
+     *
+     * @attr ref android.R.styleable#View_outlineAmbientShadowColor
+     * @param color The color this View will cast for its elevation shadow.
+     */
+    public void setOutlineAmbientShadowColor(@ColorInt int color) {
+        if (mRenderNode.setAmbientShadowColor(color)) {
+            invalidateViewProperty(true, true);
+        }
+    }
+
+    /**
+     * @return The shadow color set by {@link #setOutlineAmbientShadowColor(int)}, or black if
+     * nothing was set
+     */
+    @InspectableProperty
+    public @ColorInt int getOutlineAmbientShadowColor() {
+        return mRenderNode.getAmbientShadowColor();
+    }
+
+
+    /** @hide */
+    public void setRevealClip(boolean shouldClip, float x, float y, float radius) {
+        mRenderNode.setRevealClip(shouldClip, x, y, radius);
+        invalidateViewProperty(false, false);
+    }
+
+    /**
+     * Hit rectangle in parent's coordinates
+     *
+     * @param outRect The hit rectangle of the view.
+     */
+    public void getHitRect(Rect outRect) {
+        if (hasIdentityMatrix() || mAttachInfo == null) {
+            outRect.set(mLeft, mTop, mRight, mBottom);
+        } else {
+            final RectF tmpRect = mAttachInfo.mTmpTransformRect;
+            tmpRect.set(0, 0, getWidth(), getHeight());
+            getMatrix().mapRect(tmpRect); // TODO: mRenderNode.mapRect(tmpRect)
+            outRect.set((int) tmpRect.left + mLeft, (int) tmpRect.top + mTop,
+                    (int) tmpRect.right + mLeft, (int) tmpRect.bottom + mTop);
+        }
+    }
+
+    /**
+     * Determines whether the given point, in local coordinates is inside the view.
+     */
+    /*package*/ final boolean pointInView(float localX, float localY) {
+        return pointInView(localX, localY, 0);
+    }
+
+    /**
+     * Utility method to determine whether the given point, in local coordinates,
+     * is inside the view, where the area of the view is expanded by the slop factor.
+     * This method is called while processing touch-move events to determine if the event
+     * is still within the view.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public boolean pointInView(float localX, float localY, float slop) {
+        return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) &&
+                localY < ((mBottom - mTop) + slop);
+    }
+
+    /**
+     * When a view has focus and the user navigates away from it, the next view is searched for
+     * starting from the rectangle filled in by this method.
+     *
+     * By default, the rectangle is the {@link #getDrawingRect(android.graphics.Rect)})
+     * of the view.  However, if your view maintains some idea of internal selection,
+     * such as a cursor, or a selected row or column, you should override this method and
+     * fill in a more specific rectangle.
+     *
+     * @param r The rectangle to fill in, in this view's coordinates.
+     */
+    public void getFocusedRect(Rect r) {
+        getDrawingRect(r);
+    }
+
+    /**
+     * If some part of this view is not clipped by any of its parents, then
+     * return that area in r in global (root) coordinates. To convert r to local
+     * coordinates (without taking possible View rotations into account), offset
+     * it by -globalOffset (e.g. r.offset(-globalOffset.x, -globalOffset.y)).
+     * If the view is completely clipped or translated out, return false.
+     *
+     * @param r If true is returned, r holds the global coordinates of the
+     *        visible portion of this view.
+     * @param globalOffset If true is returned, globalOffset holds the dx,dy
+     *        between this view and its root. globalOffet may be null.
+     * @return true if r is non-empty (i.e. part of the view is visible at the
+     *         root level.
+     */
+    public boolean getGlobalVisibleRect(Rect r, Point globalOffset) {
+        int width = mRight - mLeft;
+        int height = mBottom - mTop;
+        if (width > 0 && height > 0) {
+            r.set(0, 0, width, height);
+            if (globalOffset != null) {
+                globalOffset.set(-mScrollX, -mScrollY);
+            }
+            return mParent == null || mParent.getChildVisibleRect(this, r, globalOffset);
+        }
+        return false;
+    }
+
+    public final boolean getGlobalVisibleRect(Rect r) {
+        return getGlobalVisibleRect(r, null);
+    }
+
+    public final boolean getLocalVisibleRect(Rect r) {
+        final Point offset = mAttachInfo != null ? mAttachInfo.mPoint : new Point();
+        if (getGlobalVisibleRect(r, offset)) {
+            r.offset(-offset.x, -offset.y); // make r local
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Offset this view's vertical location by the specified number of pixels.
+     *
+     * @param offset the number of pixels to offset the view by
+     */
+    public void offsetTopAndBottom(int offset) {
+        if (offset != 0) {
+            final boolean matrixIsIdentity = hasIdentityMatrix();
+            if (matrixIsIdentity) {
+                if (isHardwareAccelerated()) {
+                    invalidateViewProperty(false, false);
+                } else {
+                    final ViewParent p = mParent;
+                    if (p != null && mAttachInfo != null) {
+                        final Rect r = mAttachInfo.mTmpInvalRect;
+                        int minTop;
+                        int maxBottom;
+                        int yLoc;
+                        if (offset < 0) {
+                            minTop = mTop + offset;
+                            maxBottom = mBottom;
+                            yLoc = offset;
+                        } else {
+                            minTop = mTop;
+                            maxBottom = mBottom + offset;
+                            yLoc = 0;
+                        }
+                        r.set(0, yLoc, mRight - mLeft, maxBottom - minTop);
+                        p.invalidateChild(this, r);
+                    }
+                }
+            } else {
+                invalidateViewProperty(false, false);
+            }
+
+            mTop += offset;
+            mBottom += offset;
+            mRenderNode.offsetTopAndBottom(offset);
+            if (isHardwareAccelerated()) {
+                invalidateViewProperty(false, false);
+                invalidateParentIfNeededAndWasQuickRejected();
+            } else {
+                if (!matrixIsIdentity) {
+                    invalidateViewProperty(false, true);
+                }
+                invalidateParentIfNeeded();
+            }
+            notifySubtreeAccessibilityStateChangedIfNeeded();
+        }
+    }
+
+    /**
+     * Offset this view's horizontal location by the specified amount of pixels.
+     *
+     * @param offset the number of pixels to offset the view by
+     */
+    public void offsetLeftAndRight(int offset) {
+        if (offset != 0) {
+            final boolean matrixIsIdentity = hasIdentityMatrix();
+            if (matrixIsIdentity) {
+                if (isHardwareAccelerated()) {
+                    invalidateViewProperty(false, false);
+                } else {
+                    final ViewParent p = mParent;
+                    if (p != null && mAttachInfo != null) {
+                        final Rect r = mAttachInfo.mTmpInvalRect;
+                        int minLeft;
+                        int maxRight;
+                        if (offset < 0) {
+                            minLeft = mLeft + offset;
+                            maxRight = mRight;
+                        } else {
+                            minLeft = mLeft;
+                            maxRight = mRight + offset;
+                        }
+                        r.set(0, 0, maxRight - minLeft, mBottom - mTop);
+                        p.invalidateChild(this, r);
+                    }
+                }
+            } else {
+                invalidateViewProperty(false, false);
+            }
+
+            mLeft += offset;
+            mRight += offset;
+            mRenderNode.offsetLeftAndRight(offset);
+            if (isHardwareAccelerated()) {
+                invalidateViewProperty(false, false);
+                invalidateParentIfNeededAndWasQuickRejected();
+            } else {
+                if (!matrixIsIdentity) {
+                    invalidateViewProperty(false, true);
+                }
+                invalidateParentIfNeeded();
+            }
+            notifySubtreeAccessibilityStateChangedIfNeeded();
+        }
+    }
+
+    /**
+     * Get the LayoutParams associated with this view. All views should have
+     * layout parameters. These supply parameters to the <i>parent</i> of this
+     * view specifying how it should be arranged. There are many subclasses of
+     * ViewGroup.LayoutParams, and these correspond to the different subclasses
+     * of ViewGroup that are responsible for arranging their children.
+     *
+     * This method may return null if this View is not attached to a parent
+     * ViewGroup or {@link #setLayoutParams(android.view.ViewGroup.LayoutParams)}
+     * was not invoked successfully. When a View is attached to a parent
+     * ViewGroup, this method must not return null.
+     *
+     * @return The LayoutParams associated with this view, or null if no
+     *         parameters have been set yet
+     */
+    @ViewDebug.ExportedProperty(deepExport = true, prefix = "layout_")
+    public ViewGroup.LayoutParams getLayoutParams() {
+        return mLayoutParams;
+    }
+
+    /**
+     * Set the layout parameters associated with this view. These supply
+     * parameters to the <i>parent</i> of this view specifying how it should be
+     * arranged. There are many subclasses of ViewGroup.LayoutParams, and these
+     * correspond to the different subclasses of ViewGroup that are responsible
+     * for arranging their children.
+     *
+     * @param params The layout parameters for this view, cannot be null
+     */
+    public void setLayoutParams(ViewGroup.LayoutParams params) {
+        if (params == null) {
+            throw new NullPointerException("Layout parameters cannot be null");
+        }
+        mLayoutParams = params;
+        resolveLayoutParams();
+        if (mParent instanceof ViewGroup) {
+            ((ViewGroup) mParent).onSetLayoutParams(this, params);
+        }
+        requestLayout();
+    }
+
+    /**
+     * Resolve the layout parameters depending on the resolved layout direction
+     *
+     * @hide
+     */
+    public void resolveLayoutParams() {
+        if (mLayoutParams != null) {
+            mLayoutParams.resolveLayoutDirection(getLayoutDirection());
+        }
+    }
+
+    /**
+     * Set the scrolled position of your view. This will cause a call to
+     * {@link #onScrollChanged(int, int, int, int)} and the view will be
+     * invalidated.
+     * @param x the x position to scroll to
+     * @param y the y position to scroll to
+     */
+    public void scrollTo(int x, int y) {
+        if (mScrollX != x || mScrollY != y) {
+            int oldX = mScrollX;
+            int oldY = mScrollY;
+            mScrollX = x;
+            mScrollY = y;
+            invalidateParentCaches();
+            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
+            if (!awakenScrollBars()) {
+                postInvalidateOnAnimation();
+            }
+        }
+    }
+
+    /**
+     * Move the scrolled position of your view. This will cause a call to
+     * {@link #onScrollChanged(int, int, int, int)} and the view will be
+     * invalidated.
+     * @param x the amount of pixels to scroll by horizontally
+     * @param y the amount of pixels to scroll by vertically
+     */
+    public void scrollBy(int x, int y) {
+        scrollTo(mScrollX + x, mScrollY + y);
+    }
+
+    /**
+     * <p>Trigger the scrollbars to draw. When invoked this method starts an
+     * animation to fade the scrollbars out after a default delay. If a subclass
+     * provides animated scrolling, the start delay should equal the duration
+     * of the scrolling animation.</p>
+     *
+     * <p>The animation starts only if at least one of the scrollbars is
+     * enabled, as specified by {@link #isHorizontalScrollBarEnabled()} and
+     * {@link #isVerticalScrollBarEnabled()}. When the animation is started,
+     * this method returns true, and false otherwise. If the animation is
+     * started, this method calls {@link #invalidate()}; in that case the
+     * caller should not call {@link #invalidate()}.</p>
+     *
+     * <p>This method should be invoked every time a subclass directly updates
+     * the scroll parameters.</p>
+     *
+     * <p>This method is automatically invoked by {@link #scrollBy(int, int)}
+     * and {@link #scrollTo(int, int)}.</p>
+     *
+     * @return true if the animation is played, false otherwise
+     *
+     * @see #awakenScrollBars(int)
+     * @see #scrollBy(int, int)
+     * @see #scrollTo(int, int)
+     * @see #isHorizontalScrollBarEnabled()
+     * @see #isVerticalScrollBarEnabled()
+     * @see #setHorizontalScrollBarEnabled(boolean)
+     * @see #setVerticalScrollBarEnabled(boolean)
+     */
+    protected boolean awakenScrollBars() {
+        return mScrollCache != null &&
+                awakenScrollBars(mScrollCache.scrollBarDefaultDelayBeforeFade, true);
+    }
+
+    /**
+     * Trigger the scrollbars to draw.
+     * This method differs from awakenScrollBars() only in its default duration.
+     * initialAwakenScrollBars() will show the scroll bars for longer than
+     * usual to give the user more of a chance to notice them.
+     *
+     * @return true if the animation is played, false otherwise.
+     */
+    private boolean initialAwakenScrollBars() {
+        return mScrollCache != null &&
+                awakenScrollBars(mScrollCache.scrollBarDefaultDelayBeforeFade * 4, true);
+    }
+
+    /**
+     * <p>
+     * Trigger the scrollbars to draw. When invoked this method starts an
+     * animation to fade the scrollbars out after a fixed delay. If a subclass
+     * provides animated scrolling, the start delay should equal the duration of
+     * the scrolling animation.
+     * </p>
+     *
+     * <p>
+     * The animation starts only if at least one of the scrollbars is enabled,
+     * as specified by {@link #isHorizontalScrollBarEnabled()} and
+     * {@link #isVerticalScrollBarEnabled()}. When the animation is started,
+     * this method returns true, and false otherwise. If the animation is
+     * started, this method calls {@link #invalidate()}; in that case the caller
+     * should not call {@link #invalidate()}.
+     * </p>
+     *
+     * <p>
+     * This method should be invoked every time a subclass directly updates the
+     * scroll parameters.
+     * </p>
+     *
+     * @param startDelay the delay, in milliseconds, after which the animation
+     *        should start; when the delay is 0, the animation starts
+     *        immediately
+     * @return true if the animation is played, false otherwise
+     *
+     * @see #scrollBy(int, int)
+     * @see #scrollTo(int, int)
+     * @see #isHorizontalScrollBarEnabled()
+     * @see #isVerticalScrollBarEnabled()
+     * @see #setHorizontalScrollBarEnabled(boolean)
+     * @see #setVerticalScrollBarEnabled(boolean)
+     */
+    protected boolean awakenScrollBars(int startDelay) {
+        return awakenScrollBars(startDelay, true);
+    }
+
+    /**
+     * <p>
+     * Trigger the scrollbars to draw. When invoked this method starts an
+     * animation to fade the scrollbars out after a fixed delay. If a subclass
+     * provides animated scrolling, the start delay should equal the duration of
+     * the scrolling animation.
+     * </p>
+     *
+     * <p>
+     * The animation starts only if at least one of the scrollbars is enabled,
+     * as specified by {@link #isHorizontalScrollBarEnabled()} and
+     * {@link #isVerticalScrollBarEnabled()}. When the animation is started,
+     * this method returns true, and false otherwise. If the animation is
+     * started, this method calls {@link #invalidate()} if the invalidate parameter
+     * is set to true; in that case the caller
+     * should not call {@link #invalidate()}.
+     * </p>
+     *
+     * <p>
+     * This method should be invoked every time a subclass directly updates the
+     * scroll parameters.
+     * </p>
+     *
+     * @param startDelay the delay, in milliseconds, after which the animation
+     *        should start; when the delay is 0, the animation starts
+     *        immediately
+     *
+     * @param invalidate Whether this method should call invalidate
+     *
+     * @return true if the animation is played, false otherwise
+     *
+     * @see #scrollBy(int, int)
+     * @see #scrollTo(int, int)
+     * @see #isHorizontalScrollBarEnabled()
+     * @see #isVerticalScrollBarEnabled()
+     * @see #setHorizontalScrollBarEnabled(boolean)
+     * @see #setVerticalScrollBarEnabled(boolean)
+     */
+    protected boolean awakenScrollBars(int startDelay, boolean invalidate) {
+        final ScrollabilityCache scrollCache = mScrollCache;
+
+        if (scrollCache == null || !scrollCache.fadeScrollBars) {
+            return false;
+        }
+
+        if (scrollCache.scrollBar == null) {
+            scrollCache.scrollBar = new ScrollBarDrawable();
+            scrollCache.scrollBar.setState(getDrawableState());
+            scrollCache.scrollBar.setCallback(this);
+        }
+
+        if (isHorizontalScrollBarEnabled() || isVerticalScrollBarEnabled()) {
+
+            if (invalidate) {
+                // Invalidate to show the scrollbars
+                postInvalidateOnAnimation();
+            }
+
+            if (scrollCache.state == ScrollabilityCache.OFF) {
+                // FIXME: this is copied from WindowManagerService.
+                // We should get this value from the system when it
+                // is possible to do so.
+                final int KEY_REPEAT_FIRST_DELAY = 750;
+                startDelay = Math.max(KEY_REPEAT_FIRST_DELAY, startDelay);
+            }
+
+            // Tell mScrollCache when we should start fading. This may
+            // extend the fade start time if one was already scheduled
+            long fadeStartTime = AnimationUtils.currentAnimationTimeMillis() + startDelay;
+            scrollCache.fadeStartTime = fadeStartTime;
+            scrollCache.state = ScrollabilityCache.ON;
+
+            // Schedule our fader to run, unscheduling any old ones first
+            if (mAttachInfo != null) {
+                mAttachInfo.mHandler.removeCallbacks(scrollCache);
+                mAttachInfo.mHandler.postAtTime(scrollCache, fadeStartTime);
+            }
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Do not invalidate views which are not visible and which are not running an animation. They
+     * will not get drawn and they should not set dirty flags as if they will be drawn
+     */
+    private boolean skipInvalidate() {
+        return (mViewFlags & VISIBILITY_MASK) != VISIBLE && mCurrentAnimation == null &&
+                (!(mParent instanceof ViewGroup) ||
+                        !((ViewGroup) mParent).isViewTransitioning(this));
+    }
+
+    /**
+     * Mark the area defined by dirty as needing to be drawn. If the view is
+     * visible, {@link #onDraw(android.graphics.Canvas)} will be called at some
+     * point in the future.
+     * <p>
+     * This must be called from a UI thread. To call from a non-UI thread, call
+     * {@link #postInvalidate()}.
+     * <p>
+     * <b>WARNING:</b> In API 19 and below, this method may be destructive to
+     * {@code dirty}.
+     *
+     * @param dirty the rectangle representing the bounds of the dirty region
+     *
+     * @deprecated The switch to hardware accelerated rendering in API 14 reduced
+     * the importance of the dirty rectangle. In API 21 the given rectangle is
+     * ignored entirely in favor of an internally-calculated area instead.
+     * Because of this, clients are encouraged to just call {@link #invalidate()}.
+     */
+    @Deprecated
+    public void invalidate(Rect dirty) {
+        final int scrollX = mScrollX;
+        final int scrollY = mScrollY;
+        invalidateInternal(dirty.left - scrollX, dirty.top - scrollY,
+                dirty.right - scrollX, dirty.bottom - scrollY, true, false);
+    }
+
+    /**
+     * Mark the area defined by the rect (l,t,r,b) as needing to be drawn. The
+     * coordinates of the dirty rect are relative to the view. If the view is
+     * visible, {@link #onDraw(android.graphics.Canvas)} will be called at some
+     * point in the future.
+     * <p>
+     * This must be called from a UI thread. To call from a non-UI thread, call
+     * {@link #postInvalidate()}.
+     *
+     * @param l the left position of the dirty region
+     * @param t the top position of the dirty region
+     * @param r the right position of the dirty region
+     * @param b the bottom position of the dirty region
+     *
+     * @deprecated The switch to hardware accelerated rendering in API 14 reduced
+     * the importance of the dirty rectangle. In API 21 the given rectangle is
+     * ignored entirely in favor of an internally-calculated area instead.
+     * Because of this, clients are encouraged to just call {@link #invalidate()}.
+     */
+    @Deprecated
+    public void invalidate(int l, int t, int r, int b) {
+        final int scrollX = mScrollX;
+        final int scrollY = mScrollY;
+        invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false);
+    }
+
+    /**
+     * Invalidate the whole view. If the view is visible,
+     * {@link #onDraw(android.graphics.Canvas)} will be called at some point in
+     * the future.
+     * <p>
+     * This must be called from a UI thread. To call from a non-UI thread, call
+     * {@link #postInvalidate()}.
+     */
+    public void invalidate() {
+        invalidate(true);
+    }
+
+    /**
+     * This is where the invalidate() work actually happens. A full invalidate()
+     * causes the drawing cache to be invalidated, but this function can be
+     * called with invalidateCache set to false to skip that invalidation step
+     * for cases that do not need it (for example, a component that remains at
+     * the same dimensions with the same content).
+     *
+     * @param invalidateCache Whether the drawing cache for this view should be
+     *            invalidated as well. This is usually true for a full
+     *            invalidate, but may be set to false if the View's contents or
+     *            dimensions have not changed.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void invalidate(boolean invalidateCache) {
+        invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
+    }
+
+    void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
+            boolean fullInvalidate) {
+        if (mGhostView != null) {
+            mGhostView.invalidate(true);
+            return;
+        }
+
+        if (skipInvalidate()) {
+            return;
+        }
+
+        // Reset content capture caches
+        mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK;
+        mContentCaptureSessionCached = false;
+
+        if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
+                || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
+                || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
+                || (fullInvalidate && isOpaque() != mLastIsOpaque)) {
+            if (fullInvalidate) {
+                mLastIsOpaque = isOpaque();
+                mPrivateFlags &= ~PFLAG_DRAWN;
+            }
+
+            mPrivateFlags |= PFLAG_DIRTY;
+
+            if (invalidateCache) {
+                mPrivateFlags |= PFLAG_INVALIDATED;
+                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
+            }
+
+            // Propagate the damage rectangle to the parent view.
+            final AttachInfo ai = mAttachInfo;
+            final ViewParent p = mParent;
+            if (p != null && ai != null && l < r && t < b) {
+                final Rect damage = ai.mTmpInvalRect;
+                damage.set(l, t, r, b);
+                p.invalidateChild(this, damage);
+            }
+
+            // Damage the entire projection receiver, if necessary.
+            if (mBackground != null && mBackground.isProjected()) {
+                final View receiver = getProjectionReceiver();
+                if (receiver != null) {
+                    receiver.damageInParent();
+                }
+            }
+        }
+    }
+
+    /**
+     * @return this view's projection receiver, or {@code null} if none exists
+     */
+    private View getProjectionReceiver() {
+        ViewParent p = getParent();
+        while (p != null && p instanceof View) {
+            final View v = (View) p;
+            if (v.isProjectionReceiver()) {
+                return v;
+            }
+            p = p.getParent();
+        }
+
+        return null;
+    }
+
+    /**
+     * @return whether the view is a projection receiver
+     */
+    private boolean isProjectionReceiver() {
+        return mBackground != null;
+    }
+
+    /**
+     * Quick invalidation for View property changes (alpha, translationXY, etc.). We don't want to
+     * set any flags or handle all of the cases handled by the default invalidation methods.
+     * Instead, we just want to schedule a traversal in ViewRootImpl with the appropriate
+     * dirty rect. This method calls into fast invalidation methods in ViewGroup that
+     * walk up the hierarchy, transforming the dirty rect as necessary.
+     *
+     * The method also handles normal invalidation logic if display list properties are not
+     * being used in this view. The invalidateParent and forceRedraw flags are used by that
+     * backup approach, to handle these cases used in the various property-setting methods.
+     *
+     * @param invalidateParent Force a call to invalidateParentCaches() if display list properties
+     * are not being used in this view
+     * @param forceRedraw Mark the view as DRAWN to force the invalidation to propagate, if display
+     * list properties are not being used in this view
+     */
+    @UnsupportedAppUsage
+    void invalidateViewProperty(boolean invalidateParent, boolean forceRedraw) {
+        if (!isHardwareAccelerated()
+                || !mRenderNode.hasDisplayList()
+                || (mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
+            if (invalidateParent) {
+                invalidateParentCaches();
+            }
+            if (forceRedraw) {
+                mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation
+            }
+            invalidate(false);
+        } else {
+            damageInParent();
+        }
+    }
+
+    /**
+     * Tells the parent view to damage this view's bounds.
+     *
+     * @hide
+     */
+    protected void damageInParent() {
+        if (mParent != null && mAttachInfo != null) {
+            mParent.onDescendantInvalidated(this, this);
+        }
+    }
+
+    /**
+     * Used to indicate that the parent of this view should clear its caches. This functionality
+     * is used to force the parent to rebuild its display list (when hardware-accelerated),
+     * which is necessary when various parent-managed properties of the view change, such as
+     * alpha, translationX/Y, scrollX/Y, scaleX/Y, and rotation/X/Y. This method only
+     * clears the parent caches and does not causes an invalidate event.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    protected void invalidateParentCaches() {
+        if (mParent instanceof View) {
+            ((View) mParent).mPrivateFlags |= PFLAG_INVALIDATED;
+        }
+    }
+
+    /**
+     * Used to indicate that the parent of this view should be invalidated. This functionality
+     * is used to force the parent to rebuild its display list (when hardware-accelerated),
+     * which is necessary when various parent-managed properties of the view change, such as
+     * alpha, translationX/Y, scrollX/Y, scaleX/Y, and rotation/X/Y. This method will propagate
+     * an invalidation event to the parent.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    protected void invalidateParentIfNeeded() {
+        if (isHardwareAccelerated() && mParent instanceof View) {
+            ((View) mParent).invalidate(true);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    protected void invalidateParentIfNeededAndWasQuickRejected() {
+        if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) != 0) {
+            // View was rejected last time it was drawn by its parent; this may have changed
+            invalidateParentIfNeeded();
+        }
+    }
+
+    /**
+     * Indicates whether this View is opaque. An opaque View guarantees that it will
+     * draw all the pixels overlapping its bounds using a fully opaque color.
+     *
+     * Subclasses of View should override this method whenever possible to indicate
+     * whether an instance is opaque. Opaque Views are treated in a special way by
+     * the View hierarchy, possibly allowing it to perform optimizations during
+     * invalidate/draw passes.
+     *
+     * @return True if this View is guaranteed to be fully opaque, false otherwise.
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    public boolean isOpaque() {
+        return (mPrivateFlags & PFLAG_OPAQUE_MASK) == PFLAG_OPAQUE_MASK &&
+                getFinalAlpha() >= 1.0f;
+    }
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    protected void computeOpaqueFlags() {
+        // Opaque if:
+        //   - Has a background
+        //   - Background is opaque
+        //   - Doesn't have scrollbars or scrollbars overlay
+
+        if (mBackground != null && mBackground.getOpacity() == PixelFormat.OPAQUE) {
+            mPrivateFlags |= PFLAG_OPAQUE_BACKGROUND;
+        } else {
+            mPrivateFlags &= ~PFLAG_OPAQUE_BACKGROUND;
+        }
+
+        final int flags = mViewFlags;
+        if (((flags & SCROLLBARS_VERTICAL) == 0 && (flags & SCROLLBARS_HORIZONTAL) == 0) ||
+                (flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_INSIDE_OVERLAY ||
+                (flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_OUTSIDE_OVERLAY) {
+            mPrivateFlags |= PFLAG_OPAQUE_SCROLLBARS;
+        } else {
+            mPrivateFlags &= ~PFLAG_OPAQUE_SCROLLBARS;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    protected boolean hasOpaqueScrollbars() {
+        return (mPrivateFlags & PFLAG_OPAQUE_SCROLLBARS) == PFLAG_OPAQUE_SCROLLBARS;
+    }
+
+    /**
+     * @return A handler associated with the thread running the View. This
+     * handler can be used to pump events in the UI events queue.
+     */
+    public Handler getHandler() {
+        final AttachInfo attachInfo = mAttachInfo;
+        if (attachInfo != null) {
+            return attachInfo.mHandler;
+        }
+        return null;
+    }
+
+    /**
+     * Returns the queue of runnable for this view.
+     *
+     * @return the queue of runnables for this view
+     */
+    private HandlerActionQueue getRunQueue() {
+        if (mRunQueue == null) {
+            mRunQueue = new HandlerActionQueue();
+        }
+        return mRunQueue;
+    }
+
+    /**
+     * Gets the view root associated with the View.
+     * @return The view root, or null if none.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public ViewRootImpl getViewRootImpl() {
+        if (mAttachInfo != null) {
+            return mAttachInfo.mViewRootImpl;
+        }
+        return null;
+    }
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public ThreadedRenderer getThreadedRenderer() {
+        return mAttachInfo != null ? mAttachInfo.mThreadedRenderer : null;
+    }
+
+    /**
+     * <p>Causes the Runnable to be added to the message queue.
+     * The runnable will be run on the user interface thread.</p>
+     *
+     * @param action The Runnable that will be executed.
+     *
+     * @return Returns true if the Runnable was successfully placed in to the
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.
+     *
+     * @see #postDelayed
+     * @see #removeCallbacks
+     */
+    public boolean post(Runnable action) {
+        final AttachInfo attachInfo = mAttachInfo;
+        if (attachInfo != null) {
+            return attachInfo.mHandler.post(action);
+        }
+
+        // Postpone the runnable until we know on which thread it needs to run.
+        // Assume that the runnable will be successfully placed after attach.
+        getRunQueue().post(action);
+        return true;
+    }
+
+    /**
+     * <p>Causes the Runnable to be added to the message queue, to be run
+     * after the specified amount of time elapses.
+     * The runnable will be run on the user interface thread.</p>
+     *
+     * @param action The Runnable that will be executed.
+     * @param delayMillis The delay (in milliseconds) until the Runnable
+     *        will be executed.
+     *
+     * @return true if the Runnable was successfully placed in to the
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.  Note that a
+     *         result of true does not mean the Runnable will be processed --
+     *         if the looper is quit before the delivery time of the message
+     *         occurs then the message will be dropped.
+     *
+     * @see #post
+     * @see #removeCallbacks
+     */
+    public boolean postDelayed(Runnable action, long delayMillis) {
+        final AttachInfo attachInfo = mAttachInfo;
+        if (attachInfo != null) {
+            return attachInfo.mHandler.postDelayed(action, delayMillis);
+        }
+
+        // Postpone the runnable until we know on which thread it needs to run.
+        // Assume that the runnable will be successfully placed after attach.
+        getRunQueue().postDelayed(action, delayMillis);
+        return true;
+    }
+
+    /**
+     * <p>Causes the Runnable to execute on the next animation time step.
+     * The runnable will be run on the user interface thread.</p>
+     *
+     * @param action The Runnable that will be executed.
+     *
+     * @see #postOnAnimationDelayed
+     * @see #removeCallbacks
+     */
+    public void postOnAnimation(Runnable action) {
+        final AttachInfo attachInfo = mAttachInfo;
+        if (attachInfo != null) {
+            attachInfo.mViewRootImpl.mChoreographer.postCallback(
+                    Choreographer.CALLBACK_ANIMATION, action, null);
+        } else {
+            // Postpone the runnable until we know
+            // on which thread it needs to run.
+            getRunQueue().post(action);
+        }
+    }
+
+    /**
+     * <p>Causes the Runnable to execute on the next animation time step,
+     * after the specified amount of time elapses.
+     * The runnable will be run on the user interface thread.</p>
+     *
+     * @param action The Runnable that will be executed.
+     * @param delayMillis The delay (in milliseconds) until the Runnable
+     *        will be executed.
+     *
+     * @see #postOnAnimation
+     * @see #removeCallbacks
+     */
+    public void postOnAnimationDelayed(Runnable action, long delayMillis) {
+        final AttachInfo attachInfo = mAttachInfo;
+        if (attachInfo != null) {
+            attachInfo.mViewRootImpl.mChoreographer.postCallbackDelayed(
+                    Choreographer.CALLBACK_ANIMATION, action, null, delayMillis);
+        } else {
+            // Postpone the runnable until we know
+            // on which thread it needs to run.
+            getRunQueue().postDelayed(action, delayMillis);
+        }
+    }
+
+    /**
+     * <p>Removes the specified Runnable from the message queue.</p>
+     *
+     * @param action The Runnable to remove from the message handling queue
+     *
+     * @return true if this view could ask the Handler to remove the Runnable,
+     *         false otherwise. When the returned value is true, the Runnable
+     *         may or may not have been actually removed from the message queue
+     *         (for instance, if the Runnable was not in the queue already.)
+     *
+     * @see #post
+     * @see #postDelayed
+     * @see #postOnAnimation
+     * @see #postOnAnimationDelayed
+     */
+    public boolean removeCallbacks(Runnable action) {
+        if (action != null) {
+            final AttachInfo attachInfo = mAttachInfo;
+            if (attachInfo != null) {
+                attachInfo.mHandler.removeCallbacks(action);
+                attachInfo.mViewRootImpl.mChoreographer.removeCallbacks(
+                        Choreographer.CALLBACK_ANIMATION, action, null);
+            }
+            getRunQueue().removeCallbacks(action);
+        }
+        return true;
+    }
+
+    /**
+     * <p>Cause an invalidate to happen on a subsequent cycle through the event loop.
+     * Use this to invalidate the View from a non-UI thread.</p>
+     *
+     * <p>This method can be invoked from outside of the UI thread
+     * only when this View is attached to a window.</p>
+     *
+     * @see #invalidate()
+     * @see #postInvalidateDelayed(long)
+     */
+    public void postInvalidate() {
+        postInvalidateDelayed(0);
+    }
+
+    /**
+     * <p>Cause an invalidate of the specified area to happen on a subsequent cycle
+     * through the event loop. Use this to invalidate the View from a non-UI thread.</p>
+     *
+     * <p>This method can be invoked from outside of the UI thread
+     * only when this View is attached to a window.</p>
+     *
+     * @param left The left coordinate of the rectangle to invalidate.
+     * @param top The top coordinate of the rectangle to invalidate.
+     * @param right The right coordinate of the rectangle to invalidate.
+     * @param bottom The bottom coordinate of the rectangle to invalidate.
+     *
+     * @see #invalidate(int, int, int, int)
+     * @see #invalidate(Rect)
+     * @see #postInvalidateDelayed(long, int, int, int, int)
+     */
+    public void postInvalidate(int left, int top, int right, int bottom) {
+        postInvalidateDelayed(0, left, top, right, bottom);
+    }
+
+    /**
+     * <p>Cause an invalidate to happen on a subsequent cycle through the event
+     * loop. Waits for the specified amount of time.</p>
+     *
+     * <p>This method can be invoked from outside of the UI thread
+     * only when this View is attached to a window.</p>
+     *
+     * @param delayMilliseconds the duration in milliseconds to delay the
+     *         invalidation by
+     *
+     * @see #invalidate()
+     * @see #postInvalidate()
+     */
+    public void postInvalidateDelayed(long delayMilliseconds) {
+        // We try only with the AttachInfo because there's no point in invalidating
+        // if we are not attached to our window
+        final AttachInfo attachInfo = mAttachInfo;
+        if (attachInfo != null) {
+            attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
+        }
+    }
+
+    /**
+     * <p>Cause an invalidate of the specified area to happen on a subsequent cycle
+     * through the event loop. Waits for the specified amount of time.</p>
+     *
+     * <p>This method can be invoked from outside of the UI thread
+     * only when this View is attached to a window.</p>
+     *
+     * @param delayMilliseconds the duration in milliseconds to delay the
+     *         invalidation by
+     * @param left The left coordinate of the rectangle to invalidate.
+     * @param top The top coordinate of the rectangle to invalidate.
+     * @param right The right coordinate of the rectangle to invalidate.
+     * @param bottom The bottom coordinate of the rectangle to invalidate.
+     *
+     * @see #invalidate(int, int, int, int)
+     * @see #invalidate(Rect)
+     * @see #postInvalidate(int, int, int, int)
+     */
+    public void postInvalidateDelayed(long delayMilliseconds, int left, int top,
+            int right, int bottom) {
+
+        // We try only with the AttachInfo because there's no point in invalidating
+        // if we are not attached to our window
+        final AttachInfo attachInfo = mAttachInfo;
+        if (attachInfo != null) {
+            final AttachInfo.InvalidateInfo info = AttachInfo.InvalidateInfo.obtain();
+            info.target = this;
+            info.left = left;
+            info.top = top;
+            info.right = right;
+            info.bottom = bottom;
+
+            attachInfo.mViewRootImpl.dispatchInvalidateRectDelayed(info, delayMilliseconds);
+        }
+    }
+
+    /**
+     * <p>Cause an invalidate to happen on the next animation time step, typically the
+     * next display frame.</p>
+     *
+     * <p>This method can be invoked from outside of the UI thread
+     * only when this View is attached to a window.</p>
+     *
+     * @see #invalidate()
+     */
+    public void postInvalidateOnAnimation() {
+        // We try only with the AttachInfo because there's no point in invalidating
+        // if we are not attached to our window
+        final AttachInfo attachInfo = mAttachInfo;
+        if (attachInfo != null) {
+            attachInfo.mViewRootImpl.dispatchInvalidateOnAnimation(this);
+        }
+    }
+
+    /**
+     * <p>Cause an invalidate of the specified area to happen on the next animation
+     * time step, typically the next display frame.</p>
+     *
+     * <p>This method can be invoked from outside of the UI thread
+     * only when this View is attached to a window.</p>
+     *
+     * @param left The left coordinate of the rectangle to invalidate.
+     * @param top The top coordinate of the rectangle to invalidate.
+     * @param right The right coordinate of the rectangle to invalidate.
+     * @param bottom The bottom coordinate of the rectangle to invalidate.
+     *
+     * @see #invalidate(int, int, int, int)
+     * @see #invalidate(Rect)
+     */
+    public void postInvalidateOnAnimation(int left, int top, int right, int bottom) {
+        // We try only with the AttachInfo because there's no point in invalidating
+        // if we are not attached to our window
+        final AttachInfo attachInfo = mAttachInfo;
+        if (attachInfo != null) {
+            final AttachInfo.InvalidateInfo info = AttachInfo.InvalidateInfo.obtain();
+            info.target = this;
+            info.left = left;
+            info.top = top;
+            info.right = right;
+            info.bottom = bottom;
+
+            attachInfo.mViewRootImpl.dispatchInvalidateRectOnAnimation(info);
+        }
+    }
+
+    /**
+     * Post a callback to send a {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
+     * This event is sent at most once every
+     * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}.
+     */
+    private void postSendViewScrolledAccessibilityEventCallback(int dx, int dy) {
+        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+            AccessibilityEvent event =
+                    AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED);
+            event.setScrollDeltaX(dx);
+            event.setScrollDeltaY(dy);
+            sendAccessibilityEventUnchecked(event);
+        }
+    }
+
+    /**
+     * Called by a parent to request that a child update its values for mScrollX
+     * and mScrollY if necessary. This will typically be done if the child is
+     * animating a scroll using a {@link android.widget.Scroller Scroller}
+     * object.
+     */
+    public void computeScroll() {
+    }
+
+    /**
+     * <p>Indicate whether the horizontal edges are faded when the view is
+     * scrolled horizontally.</p>
+     *
+     * @return true if the horizontal edges should are faded on scroll, false
+     *         otherwise
+     *
+     * @see #setHorizontalFadingEdgeEnabled(boolean)
+     *
+     * @attr ref android.R.styleable#View_requiresFadingEdge
+     */
+    public boolean isHorizontalFadingEdgeEnabled() {
+        return (mViewFlags & FADING_EDGE_HORIZONTAL) == FADING_EDGE_HORIZONTAL;
+    }
+
+    /**
+     * <p>Define whether the horizontal edges should be faded when this view
+     * is scrolled horizontally.</p>
+     *
+     * @param horizontalFadingEdgeEnabled true if the horizontal edges should
+     *                                    be faded when the view is scrolled
+     *                                    horizontally
+     *
+     * @see #isHorizontalFadingEdgeEnabled()
+     *
+     * @attr ref android.R.styleable#View_requiresFadingEdge
+     */
+    public void setHorizontalFadingEdgeEnabled(boolean horizontalFadingEdgeEnabled) {
+        if (isHorizontalFadingEdgeEnabled() != horizontalFadingEdgeEnabled) {
+            if (horizontalFadingEdgeEnabled) {
+                initScrollCache();
+            }
+
+            mViewFlags ^= FADING_EDGE_HORIZONTAL;
+        }
+    }
+
+    /**
+     * <p>Indicate whether the vertical edges are faded when the view is
+     * scrolled horizontally.</p>
+     *
+     * @return true if the vertical edges should are faded on scroll, false
+     *         otherwise
+     *
+     * @see #setVerticalFadingEdgeEnabled(boolean)
+     *
+     * @attr ref android.R.styleable#View_requiresFadingEdge
+     */
+    public boolean isVerticalFadingEdgeEnabled() {
+        return (mViewFlags & FADING_EDGE_VERTICAL) == FADING_EDGE_VERTICAL;
+    }
+
+    /**
+     * <p>Define whether the vertical edges should be faded when this view
+     * is scrolled vertically.</p>
+     *
+     * @param verticalFadingEdgeEnabled true if the vertical edges should
+     *                                  be faded when the view is scrolled
+     *                                  vertically
+     *
+     * @see #isVerticalFadingEdgeEnabled()
+     *
+     * @attr ref android.R.styleable#View_requiresFadingEdge
+     */
+    public void setVerticalFadingEdgeEnabled(boolean verticalFadingEdgeEnabled) {
+        if (isVerticalFadingEdgeEnabled() != verticalFadingEdgeEnabled) {
+            if (verticalFadingEdgeEnabled) {
+                initScrollCache();
+            }
+
+            mViewFlags ^= FADING_EDGE_VERTICAL;
+        }
+    }
+
+    /**
+     * Get the fading edge flags, used for inspection.
+     *
+     * @return One of {@link #FADING_EDGE_NONE}, {@link #FADING_EDGE_VERTICAL},
+     *         or {@link #FADING_EDGE_HORIZONTAL}
+     * @hide
+     */
+    @InspectableProperty(name = "requiresFadingEdge", flagMapping = {
+            @FlagEntry(target = FADING_EDGE_NONE, mask = FADING_EDGE_MASK, name = "none"),
+            @FlagEntry(target = FADING_EDGE_VERTICAL, name = "vertical"),
+            @FlagEntry(target = FADING_EDGE_HORIZONTAL, name = "horizontal")
+    })
+    public int getFadingEdge() {
+        return mViewFlags & FADING_EDGE_MASK;
+    }
+
+    /**
+     * Get the fading edge length, used for inspection
+     *
+     * @return The fading edge length or 0
+     * @hide
+     */
+    @InspectableProperty
+    public int getFadingEdgeLength() {
+        if (mScrollCache != null && (mViewFlags & FADING_EDGE_MASK) != FADING_EDGE_NONE) {
+            return mScrollCache.fadingEdgeLength;
+        }
+        return 0;
+    }
+
+    /**
+     * Returns the strength, or intensity, of the top faded edge. The strength is
+     * a value between 0.0 (no fade) and 1.0 (full fade). The default implementation
+     * returns 0.0 or 1.0 but no value in between.
+     *
+     * Subclasses should override this method to provide a smoother fade transition
+     * when scrolling occurs.
+     *
+     * @return the intensity of the top fade as a float between 0.0f and 1.0f
+     */
+    protected float getTopFadingEdgeStrength() {
+        return computeVerticalScrollOffset() > 0 ? 1.0f : 0.0f;
+    }
+
+    /**
+     * Returns the strength, or intensity, of the bottom faded edge. The strength is
+     * a value between 0.0 (no fade) and 1.0 (full fade). The default implementation
+     * returns 0.0 or 1.0 but no value in between.
+     *
+     * Subclasses should override this method to provide a smoother fade transition
+     * when scrolling occurs.
+     *
+     * @return the intensity of the bottom fade as a float between 0.0f and 1.0f
+     */
+    protected float getBottomFadingEdgeStrength() {
+        return computeVerticalScrollOffset() + computeVerticalScrollExtent() <
+                computeVerticalScrollRange() ? 1.0f : 0.0f;
+    }
+
+    /**
+     * Returns the strength, or intensity, of the left faded edge. The strength is
+     * a value between 0.0 (no fade) and 1.0 (full fade). The default implementation
+     * returns 0.0 or 1.0 but no value in between.
+     *
+     * Subclasses should override this method to provide a smoother fade transition
+     * when scrolling occurs.
+     *
+     * @return the intensity of the left fade as a float between 0.0f and 1.0f
+     */
+    protected float getLeftFadingEdgeStrength() {
+        return computeHorizontalScrollOffset() > 0 ? 1.0f : 0.0f;
+    }
+
+    /**
+     * Returns the strength, or intensity, of the right faded edge. The strength is
+     * a value between 0.0 (no fade) and 1.0 (full fade). The default implementation
+     * returns 0.0 or 1.0 but no value in between.
+     *
+     * Subclasses should override this method to provide a smoother fade transition
+     * when scrolling occurs.
+     *
+     * @return the intensity of the right fade as a float between 0.0f and 1.0f
+     */
+    protected float getRightFadingEdgeStrength() {
+        return computeHorizontalScrollOffset() + computeHorizontalScrollExtent() <
+                computeHorizontalScrollRange() ? 1.0f : 0.0f;
+    }
+
+    /**
+     * <p>Indicate whether the horizontal scrollbar should be drawn or not. The
+     * scrollbar is not drawn by default.</p>
+     *
+     * @return true if the horizontal scrollbar should be painted, false
+     *         otherwise
+     *
+     * @see #setHorizontalScrollBarEnabled(boolean)
+     */
+    public boolean isHorizontalScrollBarEnabled() {
+        return (mViewFlags & SCROLLBARS_HORIZONTAL) == SCROLLBARS_HORIZONTAL;
+    }
+
+    /**
+     * <p>Define whether the horizontal scrollbar should be drawn or not. The
+     * scrollbar is not drawn by default.</p>
+     *
+     * @param horizontalScrollBarEnabled true if the horizontal scrollbar should
+     *                                   be painted
+     *
+     * @see #isHorizontalScrollBarEnabled()
+     */
+    public void setHorizontalScrollBarEnabled(boolean horizontalScrollBarEnabled) {
+        if (isHorizontalScrollBarEnabled() != horizontalScrollBarEnabled) {
+            mViewFlags ^= SCROLLBARS_HORIZONTAL;
+            computeOpaqueFlags();
+            resolvePadding();
+        }
+    }
+
+    /**
+     * <p>Indicate whether the vertical scrollbar should be drawn or not. The
+     * scrollbar is not drawn by default.</p>
+     *
+     * @return true if the vertical scrollbar should be painted, false
+     *         otherwise
+     *
+     * @see #setVerticalScrollBarEnabled(boolean)
+     */
+    public boolean isVerticalScrollBarEnabled() {
+        return (mViewFlags & SCROLLBARS_VERTICAL) == SCROLLBARS_VERTICAL;
+    }
+
+    /**
+     * <p>Define whether the vertical scrollbar should be drawn or not. The
+     * scrollbar is not drawn by default.</p>
+     *
+     * @param verticalScrollBarEnabled true if the vertical scrollbar should
+     *                                 be painted
+     *
+     * @see #isVerticalScrollBarEnabled()
+     */
+    public void setVerticalScrollBarEnabled(boolean verticalScrollBarEnabled) {
+        if (isVerticalScrollBarEnabled() != verticalScrollBarEnabled) {
+            mViewFlags ^= SCROLLBARS_VERTICAL;
+            computeOpaqueFlags();
+            resolvePadding();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    protected void recomputePadding() {
+        internalSetPadding(mUserPaddingLeft, mPaddingTop, mUserPaddingRight, mUserPaddingBottom);
+    }
+
+    /**
+     * Define whether scrollbars will fade when the view is not scrolling.
+     *
+     * @param fadeScrollbars whether to enable fading
+     *
+     * @attr ref android.R.styleable#View_fadeScrollbars
+     */
+    public void setScrollbarFadingEnabled(boolean fadeScrollbars) {
+        initScrollCache();
+        final ScrollabilityCache scrollabilityCache = mScrollCache;
+        scrollabilityCache.fadeScrollBars = fadeScrollbars;
+        if (fadeScrollbars) {
+            scrollabilityCache.state = ScrollabilityCache.OFF;
+        } else {
+            scrollabilityCache.state = ScrollabilityCache.ON;
+        }
+    }
+
+    /**
+     *
+     * Returns true if scrollbars will fade when this view is not scrolling
+     *
+     * @return true if scrollbar fading is enabled
+     *
+     * @attr ref android.R.styleable#View_fadeScrollbars
+     */
+    public boolean isScrollbarFadingEnabled() {
+        return mScrollCache != null && mScrollCache.fadeScrollBars;
+    }
+
+    /**
+     *
+     * Returns the delay before scrollbars fade.
+     *
+     * @return the delay before scrollbars fade
+     *
+     * @attr ref android.R.styleable#View_scrollbarDefaultDelayBeforeFade
+     */
+    @InspectableProperty(name = "scrollbarDefaultDelayBeforeFade")
+    public int getScrollBarDefaultDelayBeforeFade() {
+        return mScrollCache == null ? ViewConfiguration.getScrollDefaultDelay() :
+                mScrollCache.scrollBarDefaultDelayBeforeFade;
+    }
+
+    /**
+     * Define the delay before scrollbars fade.
+     *
+     * @param scrollBarDefaultDelayBeforeFade - the delay before scrollbars fade
+     *
+     * @attr ref android.R.styleable#View_scrollbarDefaultDelayBeforeFade
+     */
+    public void setScrollBarDefaultDelayBeforeFade(int scrollBarDefaultDelayBeforeFade) {
+        getScrollCache().scrollBarDefaultDelayBeforeFade = scrollBarDefaultDelayBeforeFade;
+    }
+
+    /**
+     *
+     * Returns the scrollbar fade duration.
+     *
+     * @return the scrollbar fade duration, in milliseconds
+     *
+     * @attr ref android.R.styleable#View_scrollbarFadeDuration
+     */
+    @InspectableProperty(name = "scrollbarFadeDuration")
+    public int getScrollBarFadeDuration() {
+        return mScrollCache == null ? ViewConfiguration.getScrollBarFadeDuration() :
+                mScrollCache.scrollBarFadeDuration;
+    }
+
+    /**
+     * Define the scrollbar fade duration.
+     *
+     * @param scrollBarFadeDuration - the scrollbar fade duration, in milliseconds
+     *
+     * @attr ref android.R.styleable#View_scrollbarFadeDuration
+     */
+    public void setScrollBarFadeDuration(int scrollBarFadeDuration) {
+        getScrollCache().scrollBarFadeDuration = scrollBarFadeDuration;
+    }
+
+    /**
+     *
+     * Returns the scrollbar size.
+     *
+     * @return the scrollbar size
+     *
+     * @attr ref android.R.styleable#View_scrollbarSize
+     */
+    @InspectableProperty(name = "scrollbarSize")
+    public int getScrollBarSize() {
+        return mScrollCache == null ? ViewConfiguration.get(mContext).getScaledScrollBarSize() :
+                mScrollCache.scrollBarSize;
+    }
+
+    /**
+     * Define the scrollbar size.
+     *
+     * @param scrollBarSize - the scrollbar size
+     *
+     * @attr ref android.R.styleable#View_scrollbarSize
+     */
+    public void setScrollBarSize(int scrollBarSize) {
+        getScrollCache().scrollBarSize = scrollBarSize;
+    }
+
+    /**
+     * <p>Specify the style of the scrollbars. The scrollbars can be overlaid or
+     * inset. When inset, they add to the padding of the view. And the scrollbars
+     * can be drawn inside the padding area or on the edge of the view. For example,
+     * if a view has a background drawable and you want to draw the scrollbars
+     * inside the padding specified by the drawable, you can use
+     * SCROLLBARS_INSIDE_OVERLAY or SCROLLBARS_INSIDE_INSET. If you want them to
+     * appear at the edge of the view, ignoring the padding, then you can use
+     * SCROLLBARS_OUTSIDE_OVERLAY or SCROLLBARS_OUTSIDE_INSET.</p>
+     * @param style the style of the scrollbars. Should be one of
+     * SCROLLBARS_INSIDE_OVERLAY, SCROLLBARS_INSIDE_INSET,
+     * SCROLLBARS_OUTSIDE_OVERLAY or SCROLLBARS_OUTSIDE_INSET.
+     * @see #SCROLLBARS_INSIDE_OVERLAY
+     * @see #SCROLLBARS_INSIDE_INSET
+     * @see #SCROLLBARS_OUTSIDE_OVERLAY
+     * @see #SCROLLBARS_OUTSIDE_INSET
+     *
+     * @attr ref android.R.styleable#View_scrollbarStyle
+     */
+    public void setScrollBarStyle(@ScrollBarStyle int style) {
+        if (style != (mViewFlags & SCROLLBARS_STYLE_MASK)) {
+            mViewFlags = (mViewFlags & ~SCROLLBARS_STYLE_MASK) | (style & SCROLLBARS_STYLE_MASK);
+            computeOpaqueFlags();
+            resolvePadding();
+        }
+    }
+
+    /**
+     * <p>Returns the current scrollbar style.</p>
+     * @return the current scrollbar style
+     * @see #SCROLLBARS_INSIDE_OVERLAY
+     * @see #SCROLLBARS_INSIDE_INSET
+     * @see #SCROLLBARS_OUTSIDE_OVERLAY
+     * @see #SCROLLBARS_OUTSIDE_INSET
+     *
+     * @attr ref android.R.styleable#View_scrollbarStyle
+     */
+    @ViewDebug.ExportedProperty(mapping = {
+            @ViewDebug.IntToString(from = SCROLLBARS_INSIDE_OVERLAY, to = "INSIDE_OVERLAY"),
+            @ViewDebug.IntToString(from = SCROLLBARS_INSIDE_INSET, to = "INSIDE_INSET"),
+            @ViewDebug.IntToString(from = SCROLLBARS_OUTSIDE_OVERLAY, to = "OUTSIDE_OVERLAY"),
+            @ViewDebug.IntToString(from = SCROLLBARS_OUTSIDE_INSET, to = "OUTSIDE_INSET")
+    })
+    @InspectableProperty(name = "scrollbarStyle", enumMapping = {
+            @EnumEntry(value = SCROLLBARS_INSIDE_OVERLAY, name = "insideOverlay"),
+            @EnumEntry(value = SCROLLBARS_INSIDE_INSET, name = "insideInset"),
+            @EnumEntry(value = SCROLLBARS_OUTSIDE_OVERLAY, name = "outsideOverlay"),
+            @EnumEntry(value = SCROLLBARS_OUTSIDE_INSET, name = "outsideInset")
+    })
+    @ScrollBarStyle
+    public int getScrollBarStyle() {
+        return mViewFlags & SCROLLBARS_STYLE_MASK;
+    }
+
+    /**
+     * <p>Compute the horizontal range that the horizontal scrollbar
+     * represents.</p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the
+     * units used by {@link #computeHorizontalScrollExtent()} and
+     * {@link #computeHorizontalScrollOffset()}.</p>
+     *
+     * <p>The default range is the drawing width of this view.</p>
+     *
+     * @return the total horizontal range represented by the horizontal
+     *         scrollbar
+     *
+     * @see #computeHorizontalScrollExtent()
+     * @see #computeHorizontalScrollOffset()
+     */
+    protected int computeHorizontalScrollRange() {
+        return getWidth();
+    }
+
+    /**
+     * <p>Compute the horizontal offset of the horizontal scrollbar's thumb
+     * within the horizontal range. This value is used to compute the position
+     * of the thumb within the scrollbar's track.</p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the
+     * units used by {@link #computeHorizontalScrollRange()} and
+     * {@link #computeHorizontalScrollExtent()}.</p>
+     *
+     * <p>The default offset is the scroll offset of this view.</p>
+     *
+     * @return the horizontal offset of the scrollbar's thumb
+     *
+     * @see #computeHorizontalScrollRange()
+     * @see #computeHorizontalScrollExtent()
+     */
+    protected int computeHorizontalScrollOffset() {
+        return mScrollX;
+    }
+
+    /**
+     * <p>Compute the horizontal extent of the horizontal scrollbar's thumb
+     * within the horizontal range. This value is used to compute the length
+     * of the thumb within the scrollbar's track.</p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the
+     * units used by {@link #computeHorizontalScrollRange()} and
+     * {@link #computeHorizontalScrollOffset()}.</p>
+     *
+     * <p>The default extent is the drawing width of this view.</p>
+     *
+     * @return the horizontal extent of the scrollbar's thumb
+     *
+     * @see #computeHorizontalScrollRange()
+     * @see #computeHorizontalScrollOffset()
+     */
+    protected int computeHorizontalScrollExtent() {
+        return getWidth();
+    }
+
+    /**
+     * <p>Compute the vertical range that the vertical scrollbar represents.</p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the
+     * units used by {@link #computeVerticalScrollExtent()} and
+     * {@link #computeVerticalScrollOffset()}.</p>
+     *
+     * @return the total vertical range represented by the vertical scrollbar
+     *
+     * <p>The default range is the drawing height of this view.</p>
+     *
+     * @see #computeVerticalScrollExtent()
+     * @see #computeVerticalScrollOffset()
+     */
+    protected int computeVerticalScrollRange() {
+        return getHeight();
+    }
+
+    /**
+     * <p>Compute the vertical offset of the vertical scrollbar's thumb
+     * within the horizontal range. This value is used to compute the position
+     * of the thumb within the scrollbar's track.</p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the
+     * units used by {@link #computeVerticalScrollRange()} and
+     * {@link #computeVerticalScrollExtent()}.</p>
+     *
+     * <p>The default offset is the scroll offset of this view.</p>
+     *
+     * @return the vertical offset of the scrollbar's thumb
+     *
+     * @see #computeVerticalScrollRange()
+     * @see #computeVerticalScrollExtent()
+     */
+    protected int computeVerticalScrollOffset() {
+        return mScrollY;
+    }
+
+    /**
+     * <p>Compute the vertical extent of the vertical scrollbar's thumb
+     * within the vertical range. This value is used to compute the length
+     * of the thumb within the scrollbar's track.</p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the
+     * units used by {@link #computeVerticalScrollRange()} and
+     * {@link #computeVerticalScrollOffset()}.</p>
+     *
+     * <p>The default extent is the drawing height of this view.</p>
+     *
+     * @return the vertical extent of the scrollbar's thumb
+     *
+     * @see #computeVerticalScrollRange()
+     * @see #computeVerticalScrollOffset()
+     */
+    protected int computeVerticalScrollExtent() {
+        return getHeight();
+    }
+
+    /**
+     * Check if this view can be scrolled horizontally in a certain direction.
+     *
+     * @param direction Negative to check scrolling left, positive to check scrolling right.
+     * @return true if this view can be scrolled in the specified direction, false otherwise.
+     */
+    public boolean canScrollHorizontally(int direction) {
+        final int offset = computeHorizontalScrollOffset();
+        final int range = computeHorizontalScrollRange() - computeHorizontalScrollExtent();
+        if (range == 0) return false;
+        if (direction < 0) {
+            return offset > 0;
+        } else {
+            return offset < range - 1;
+        }
+    }
+
+    /**
+     * Check if this view can be scrolled vertically in a certain direction.
+     *
+     * @param direction Negative to check scrolling up, positive to check scrolling down.
+     * @return true if this view can be scrolled in the specified direction, false otherwise.
+     */
+    public boolean canScrollVertically(int direction) {
+        final int offset = computeVerticalScrollOffset();
+        final int range = computeVerticalScrollRange() - computeVerticalScrollExtent();
+        if (range == 0) return false;
+        if (direction < 0) {
+            return offset > 0;
+        } else {
+            return offset < range - 1;
+        }
+    }
+
+    void getScrollIndicatorBounds(@NonNull Rect out) {
+        out.left = mScrollX;
+        out.right = mScrollX + mRight - mLeft;
+        out.top = mScrollY;
+        out.bottom = mScrollY + mBottom - mTop;
+    }
+
+    private void onDrawScrollIndicators(Canvas c) {
+        if ((mPrivateFlags3 & SCROLL_INDICATORS_PFLAG3_MASK) == 0) {
+            // No scroll indicators enabled.
+            return;
+        }
+
+        final Drawable dr = mScrollIndicatorDrawable;
+        if (dr == null) {
+            // Scroll indicators aren't supported here.
+            return;
+        }
+
+        if (mAttachInfo == null) {
+            // View is not attached.
+            return;
+        }
+
+        final int h = dr.getIntrinsicHeight();
+        final int w = dr.getIntrinsicWidth();
+        final Rect rect = mAttachInfo.mTmpInvalRect;
+        getScrollIndicatorBounds(rect);
+
+        if ((mPrivateFlags3 & PFLAG3_SCROLL_INDICATOR_TOP) != 0) {
+            final boolean canScrollUp = canScrollVertically(-1);
+            if (canScrollUp) {
+                dr.setBounds(rect.left, rect.top, rect.right, rect.top + h);
+                dr.draw(c);
+            }
+        }
+
+        if ((mPrivateFlags3 & PFLAG3_SCROLL_INDICATOR_BOTTOM) != 0) {
+            final boolean canScrollDown = canScrollVertically(1);
+            if (canScrollDown) {
+                dr.setBounds(rect.left, rect.bottom - h, rect.right, rect.bottom);
+                dr.draw(c);
+            }
+        }
+
+        final int leftRtl;
+        final int rightRtl;
+        if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
+            leftRtl = PFLAG3_SCROLL_INDICATOR_END;
+            rightRtl = PFLAG3_SCROLL_INDICATOR_START;
+        } else {
+            leftRtl = PFLAG3_SCROLL_INDICATOR_START;
+            rightRtl = PFLAG3_SCROLL_INDICATOR_END;
+        }
+
+        final int leftMask = PFLAG3_SCROLL_INDICATOR_LEFT | leftRtl;
+        if ((mPrivateFlags3 & leftMask) != 0) {
+            final boolean canScrollLeft = canScrollHorizontally(-1);
+            if (canScrollLeft) {
+                dr.setBounds(rect.left, rect.top, rect.left + w, rect.bottom);
+                dr.draw(c);
+            }
+        }
+
+        final int rightMask = PFLAG3_SCROLL_INDICATOR_RIGHT | rightRtl;
+        if ((mPrivateFlags3 & rightMask) != 0) {
+            final boolean canScrollRight = canScrollHorizontally(1);
+            if (canScrollRight) {
+                dr.setBounds(rect.right - w, rect.top, rect.right, rect.bottom);
+                dr.draw(c);
+            }
+        }
+    }
+
+    private void getHorizontalScrollBarBounds(@Nullable Rect drawBounds,
+            @Nullable Rect touchBounds) {
+        final Rect bounds = drawBounds != null ? drawBounds : touchBounds;
+        if (bounds == null) {
+            return;
+        }
+        final int inside = (mViewFlags & SCROLLBARS_OUTSIDE_MASK) == 0 ? ~0 : 0;
+        final boolean drawVerticalScrollBar = isVerticalScrollBarEnabled()
+                && !isVerticalScrollBarHidden();
+        final int size = getHorizontalScrollbarHeight();
+        final int verticalScrollBarGap = drawVerticalScrollBar ?
+                getVerticalScrollbarWidth() : 0;
+        final int width = mRight - mLeft;
+        final int height = mBottom - mTop;
+        bounds.top = mScrollY + height - size - (mUserPaddingBottom & inside);
+        bounds.left = mScrollX + (mPaddingLeft & inside);
+        bounds.right = mScrollX + width - (mUserPaddingRight & inside) - verticalScrollBarGap;
+        bounds.bottom = bounds.top + size;
+
+        if (touchBounds == null) {
+            return;
+        }
+        if (touchBounds != bounds) {
+            touchBounds.set(bounds);
+        }
+        final int minTouchTarget = mScrollCache.scrollBarMinTouchTarget;
+        if (touchBounds.height() < minTouchTarget) {
+            final int adjust = (minTouchTarget - touchBounds.height()) / 2;
+            touchBounds.bottom = Math.min(touchBounds.bottom + adjust, mScrollY + height);
+            touchBounds.top = touchBounds.bottom - minTouchTarget;
+        }
+        if (touchBounds.width() < minTouchTarget) {
+            final int adjust = (minTouchTarget - touchBounds.width()) / 2;
+            touchBounds.left -= adjust;
+            touchBounds.right = touchBounds.left + minTouchTarget;
+        }
+    }
+
+    private void getVerticalScrollBarBounds(@Nullable Rect bounds, @Nullable Rect touchBounds) {
+        if (mRoundScrollbarRenderer == null) {
+            getStraightVerticalScrollBarBounds(bounds, touchBounds);
+        } else {
+            getRoundVerticalScrollBarBounds(bounds != null ? bounds : touchBounds);
+        }
+    }
+
+    private void getRoundVerticalScrollBarBounds(Rect bounds) {
+        final int width = mRight - mLeft;
+        final int height = mBottom - mTop;
+        // Do not take padding into account as we always want the scrollbars
+        // to hug the screen for round wearable devices.
+        bounds.left = mScrollX;
+        bounds.top = mScrollY;
+        bounds.right = bounds.left + width;
+        bounds.bottom = mScrollY + height;
+    }
+
+    private void getStraightVerticalScrollBarBounds(@Nullable Rect drawBounds,
+            @Nullable Rect touchBounds) {
+        final Rect bounds = drawBounds != null ? drawBounds : touchBounds;
+        if (bounds == null) {
+            return;
+        }
+        final int inside = (mViewFlags & SCROLLBARS_OUTSIDE_MASK) == 0 ? ~0 : 0;
+        final int size = getVerticalScrollbarWidth();
+        int verticalScrollbarPosition = mVerticalScrollbarPosition;
+        if (verticalScrollbarPosition == SCROLLBAR_POSITION_DEFAULT) {
+            verticalScrollbarPosition = isLayoutRtl() ?
+                    SCROLLBAR_POSITION_LEFT : SCROLLBAR_POSITION_RIGHT;
+        }
+        final int width = mRight - mLeft;
+        final int height = mBottom - mTop;
+        switch (verticalScrollbarPosition) {
+            default:
+            case SCROLLBAR_POSITION_RIGHT:
+                bounds.left = mScrollX + width - size - (mUserPaddingRight & inside);
+                break;
+            case SCROLLBAR_POSITION_LEFT:
+                bounds.left = mScrollX + (mUserPaddingLeft & inside);
+                break;
+        }
+        bounds.top = mScrollY + (mPaddingTop & inside);
+        bounds.right = bounds.left + size;
+        bounds.bottom = mScrollY + height - (mUserPaddingBottom & inside);
+
+        if (touchBounds == null) {
+            return;
+        }
+        if (touchBounds != bounds) {
+            touchBounds.set(bounds);
+        }
+        final int minTouchTarget = mScrollCache.scrollBarMinTouchTarget;
+        if (touchBounds.width() < minTouchTarget) {
+            final int adjust = (minTouchTarget - touchBounds.width()) / 2;
+            if (verticalScrollbarPosition == SCROLLBAR_POSITION_RIGHT) {
+                touchBounds.right = Math.min(touchBounds.right + adjust, mScrollX + width);
+                touchBounds.left = touchBounds.right - minTouchTarget;
+            } else {
+                touchBounds.left = Math.max(touchBounds.left + adjust, mScrollX);
+                touchBounds.right = touchBounds.left + minTouchTarget;
+            }
+        }
+        if (touchBounds.height() < minTouchTarget) {
+            final int adjust = (minTouchTarget - touchBounds.height()) / 2;
+            touchBounds.top -= adjust;
+            touchBounds.bottom = touchBounds.top + minTouchTarget;
+        }
+    }
+
+    /**
+     * <p>Request the drawing of the horizontal and the vertical scrollbar. The
+     * scrollbars are painted only if they have been awakened first.</p>
+     *
+     * @param canvas the canvas on which to draw the scrollbars
+     *
+     * @see #awakenScrollBars(int)
+     */
+    protected final void onDrawScrollBars(Canvas canvas) {
+        // scrollbars are drawn only when the animation is running
+        final ScrollabilityCache cache = mScrollCache;
+
+        if (cache != null) {
+
+            int state = cache.state;
+
+            if (state == ScrollabilityCache.OFF) {
+                return;
+            }
+
+            boolean invalidate = false;
+
+            if (state == ScrollabilityCache.FADING) {
+                // We're fading -- get our fade interpolation
+                if (cache.interpolatorValues == null) {
+                    cache.interpolatorValues = new float[1];
+                }
+
+                float[] values = cache.interpolatorValues;
+
+                // Stops the animation if we're done
+                if (cache.scrollBarInterpolator.timeToValues(values) ==
+                        Interpolator.Result.FREEZE_END) {
+                    cache.state = ScrollabilityCache.OFF;
+                } else {
+                    cache.scrollBar.mutate().setAlpha(Math.round(values[0]));
+                }
+
+                // This will make the scroll bars inval themselves after
+                // drawing. We only want this when we're fading so that
+                // we prevent excessive redraws
+                invalidate = true;
+            } else {
+                // We're just on -- but we may have been fading before so
+                // reset alpha
+                cache.scrollBar.mutate().setAlpha(255);
+            }
+
+            final boolean drawHorizontalScrollBar = isHorizontalScrollBarEnabled();
+            final boolean drawVerticalScrollBar = isVerticalScrollBarEnabled()
+                    && !isVerticalScrollBarHidden();
+
+            // Fork out the scroll bar drawing for round wearable devices.
+            if (mRoundScrollbarRenderer != null) {
+                if (drawVerticalScrollBar) {
+                    final Rect bounds = cache.mScrollBarBounds;
+                    getVerticalScrollBarBounds(bounds, null);
+                    mRoundScrollbarRenderer.drawRoundScrollbars(
+                            canvas, (float) cache.scrollBar.getAlpha() / 255f, bounds);
+                    if (invalidate) {
+                        invalidate();
+                    }
+                }
+                // Do not draw horizontal scroll bars for round wearable devices.
+            } else if (drawVerticalScrollBar || drawHorizontalScrollBar) {
+                final ScrollBarDrawable scrollBar = cache.scrollBar;
+
+                if (drawHorizontalScrollBar) {
+                    scrollBar.setParameters(computeHorizontalScrollRange(),
+                            computeHorizontalScrollOffset(),
+                            computeHorizontalScrollExtent(), false);
+                    final Rect bounds = cache.mScrollBarBounds;
+                    getHorizontalScrollBarBounds(bounds, null);
+                    onDrawHorizontalScrollBar(canvas, scrollBar, bounds.left, bounds.top,
+                            bounds.right, bounds.bottom);
+                    if (invalidate) {
+                        invalidate(bounds);
+                    }
+                }
+
+                if (drawVerticalScrollBar) {
+                    scrollBar.setParameters(computeVerticalScrollRange(),
+                            computeVerticalScrollOffset(),
+                            computeVerticalScrollExtent(), true);
+                    final Rect bounds = cache.mScrollBarBounds;
+                    getVerticalScrollBarBounds(bounds, null);
+                    onDrawVerticalScrollBar(canvas, scrollBar, bounds.left, bounds.top,
+                            bounds.right, bounds.bottom);
+                    if (invalidate) {
+                        invalidate(bounds);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Override this if the vertical scrollbar needs to be hidden in a subclass, like when
+     * FastScroller is visible.
+     * @return whether to temporarily hide the vertical scrollbar
+     * @hide
+     */
+    protected boolean isVerticalScrollBarHidden() {
+        return false;
+    }
+
+    /**
+     * <p>Draw the horizontal scrollbar if
+     * {@link #isHorizontalScrollBarEnabled()} returns true.</p>
+     *
+     * @param canvas the canvas on which to draw the scrollbar
+     * @param scrollBar the scrollbar's drawable
+     *
+     * @see #isHorizontalScrollBarEnabled()
+     * @see #computeHorizontalScrollRange()
+     * @see #computeHorizontalScrollExtent()
+     * @see #computeHorizontalScrollOffset()
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    protected void onDrawHorizontalScrollBar(Canvas canvas, Drawable scrollBar,
+            int l, int t, int r, int b) {
+        scrollBar.setBounds(l, t, r, b);
+        scrollBar.draw(canvas);
+    }
+
+    /**
+     * <p>Draw the vertical scrollbar if {@link #isVerticalScrollBarEnabled()}
+     * returns true.</p>
+     *
+     * @param canvas the canvas on which to draw the scrollbar
+     * @param scrollBar the scrollbar's drawable
+     *
+     * @see #isVerticalScrollBarEnabled()
+     * @see #computeVerticalScrollRange()
+     * @see #computeVerticalScrollExtent()
+     * @see #computeVerticalScrollOffset()
+     * @hide
+     */
+    @UnsupportedAppUsage
+    protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar,
+            int l, int t, int r, int b) {
+        scrollBar.setBounds(l, t, r, b);
+        scrollBar.draw(canvas);
+    }
+
+    /**
+     * Implement this to do your drawing.
+     *
+     * @param canvas the canvas on which the background will be drawn
+     */
+    protected void onDraw(Canvas canvas) {
+    }
+
+    /*
+     * Caller is responsible for calling requestLayout if necessary.
+     * (This allows addViewInLayout to not request a new layout.)
+     */
+    @UnsupportedAppUsage
+    void assignParent(ViewParent parent) {
+        if (mParent == null) {
+            mParent = parent;
+        } else if (parent == null) {
+            mParent = null;
+        } else {
+            throw new RuntimeException("view " + this + " being added, but"
+                    + " it already has a parent");
+        }
+    }
+
+    /**
+     * This is called when the view is attached to a window.  At this point it
+     * has a Surface and will start drawing.  Note that this function is
+     * guaranteed to be called before {@link #onDraw(android.graphics.Canvas)},
+     * however it may be called any time before the first onDraw -- including
+     * before or after {@link #onMeasure(int, int)}.
+     *
+     * @see #onDetachedFromWindow()
+     */
+    @CallSuper
+    protected void onAttachedToWindow() {
+        if ((mPrivateFlags & PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
+            mParent.requestTransparentRegion(this);
+        }
+
+        mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT;
+
+        jumpDrawablesToCurrentState();
+
+        AccessibilityNodeIdManager.getInstance().registerViewWithId(this, getAccessibilityViewId());
+        resetSubtreeAccessibilityStateChanged();
+
+        // rebuild, since Outline not maintained while View is detached
+        rebuildOutline();
+
+        if (isFocused()) {
+            notifyFocusChangeToImeFocusController(true /* hasFocus */);
+        }
+    }
+
+    /**
+     * Resolve all RTL related properties.
+     *
+     * @return true if resolution of RTL properties has been done
+     *
+     * @hide
+     */
+    public boolean resolveRtlPropertiesIfNeeded() {
+        if (!needRtlPropertiesResolution()) return false;
+
+        // Order is important here: LayoutDirection MUST be resolved first
+        if (!isLayoutDirectionResolved()) {
+            resolveLayoutDirection();
+            resolveLayoutParams();
+        }
+        // ... then we can resolve the others properties depending on the resolved LayoutDirection.
+        if (!isTextDirectionResolved()) {
+            resolveTextDirection();
+        }
+        if (!isTextAlignmentResolved()) {
+            resolveTextAlignment();
+        }
+        // Should resolve Drawables before Padding because we need the layout direction of the
+        // Drawable to correctly resolve Padding.
+        if (!areDrawablesResolved()) {
+            resolveDrawables();
+        }
+        if (!isPaddingResolved()) {
+            resolvePadding();
+        }
+        onRtlPropertiesChanged(getLayoutDirection());
+        return true;
+    }
+
+    /**
+     * Reset resolution of all RTL related properties.
+     *
+     * @hide
+     */
+    @TestApi
+    public void resetRtlProperties() {
+        resetResolvedLayoutDirection();
+        resetResolvedTextDirection();
+        resetResolvedTextAlignment();
+        resetResolvedPadding();
+        resetResolvedDrawables();
+    }
+
+    /**
+     * @see #onScreenStateChanged(int)
+     */
+    void dispatchScreenStateChanged(int screenState) {
+        onScreenStateChanged(screenState);
+    }
+
+    /**
+     * This method is called whenever the state of the screen this view is
+     * attached to changes. A state change will usually occurs when the screen
+     * turns on or off (whether it happens automatically or the user does it
+     * manually.)
+     *
+     * @param screenState The new state of the screen. Can be either
+     *                    {@link #SCREEN_STATE_ON} or {@link #SCREEN_STATE_OFF}
+     */
+    public void onScreenStateChanged(int screenState) {
+    }
+
+    /**
+     * @see #onMovedToDisplay(int, Configuration)
+     */
+    void dispatchMovedToDisplay(Display display, Configuration config) {
+        mAttachInfo.mDisplay = display;
+        mAttachInfo.mDisplayState = display.getState();
+        onMovedToDisplay(display.getDisplayId(), config);
+    }
+
+    /**
+     * Called by the system when the hosting activity is moved from one display to another without
+     * recreation. This means that the activity is declared to handle all changes to configuration
+     * that happened when it was switched to another display, so it wasn't destroyed and created
+     * again.
+     *
+     * <p>This call will be followed by {@link #onConfigurationChanged(Configuration)} if the
+     * applied configuration actually changed. It is up to app developer to choose whether to handle
+     * the change in this method or in the following {@link #onConfigurationChanged(Configuration)}
+     * call.
+     *
+     * <p>Use this callback to track changes to the displays if some functionality relies on an
+     * association with some display properties.
+     *
+     * @param displayId The id of the display to which the view was moved.
+     * @param config Configuration of the resources on new display after move.
+     *
+     * @see #onConfigurationChanged(Configuration)
+     * @hide
+     */
+    public void onMovedToDisplay(int displayId, Configuration config) {
+    }
+
+    /**
+     * Return true if the application tag in the AndroidManifest has set "supportRtl" to true
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private boolean hasRtlSupport() {
+        return mContext.getApplicationInfo().hasRtlSupport();
+    }
+
+    /**
+     * Return true if we are in RTL compatibility mode (either before Jelly Bean MR1 or
+     * RTL not supported)
+     */
+    private boolean isRtlCompatibilityMode() {
+        final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
+        return targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR1 || !hasRtlSupport();
+    }
+
+    /**
+     * @return true if RTL properties need resolution.
+     *
+     */
+    private boolean needRtlPropertiesResolution() {
+        return (mPrivateFlags2 & ALL_RTL_PROPERTIES_RESOLVED) != ALL_RTL_PROPERTIES_RESOLVED;
+    }
+
+    /**
+     * Called when any RTL property (layout direction or text direction or text alignment) has
+     * been changed.
+     *
+     * Subclasses need to override this method to take care of cached information that depends on the
+     * resolved layout direction, or to inform child views that inherit their layout direction.
+     *
+     * The default implementation does nothing.
+     *
+     * @param layoutDirection the direction of the layout
+     *
+     * @see #LAYOUT_DIRECTION_LTR
+     * @see #LAYOUT_DIRECTION_RTL
+     */
+    public void onRtlPropertiesChanged(@ResolvedLayoutDir int layoutDirection) {
+    }
+
+    /**
+     * Resolve and cache the layout direction. LTR is set initially. This is implicitly supposing
+     * that the parent directionality can and will be resolved before its children.
+     *
+     * @return true if resolution has been done, false otherwise.
+     *
+     * @hide
+     */
+    public boolean resolveLayoutDirection() {
+        // Clear any previous layout direction resolution
+        mPrivateFlags2 &= ~PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK;
+
+        if (hasRtlSupport()) {
+            // Set resolved depending on layout direction
+            switch ((mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_MASK) >>
+                    PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) {
+                case LAYOUT_DIRECTION_INHERIT:
+                    // We cannot resolve yet. LTR is by default and let the resolution happen again
+                    // later to get the correct resolved value
+                    if (!canResolveLayoutDirection()) return false;
+
+                    // Parent has not yet resolved, LTR is still the default
+                    try {
+                        if (!mParent.isLayoutDirectionResolved()) return false;
+
+                        if (mParent.getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
+                            mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL;
+                        }
+                    } catch (AbstractMethodError e) {
+                        Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
+                                " does not fully implement ViewParent", e);
+                    }
+                    break;
+                case LAYOUT_DIRECTION_RTL:
+                    mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL;
+                    break;
+                case LAYOUT_DIRECTION_LOCALE:
+                    if((LAYOUT_DIRECTION_RTL ==
+                            TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()))) {
+                        mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL;
+                    }
+                    break;
+                default:
+                    // Nothing to do, LTR by default
+            }
+        }
+
+        // Set to resolved
+        mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED;
+        return true;
+    }
+
+    /**
+     * Check if layout direction resolution can be done.
+     *
+     * @return true if layout direction resolution can be done otherwise return false.
+     */
+    public boolean canResolveLayoutDirection() {
+        switch (getRawLayoutDirection()) {
+            case LAYOUT_DIRECTION_INHERIT:
+                if (mParent != null) {
+                    try {
+                        return mParent.canResolveLayoutDirection();
+                    } catch (AbstractMethodError e) {
+                        Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
+                                " does not fully implement ViewParent", e);
+                    }
+                }
+                return false;
+
+            default:
+                return true;
+        }
+    }
+
+    /**
+     * Reset the resolved layout direction. Layout direction will be resolved during a call to
+     * {@link #onMeasure(int, int)}.
+     *
+     * @hide
+     */
+    @TestApi
+    public void resetResolvedLayoutDirection() {
+        // Reset the current resolved bits
+        mPrivateFlags2 &= ~PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK;
+    }
+
+    /**
+     * @return true if the layout direction is inherited.
+     *
+     * @hide
+     */
+    public boolean isLayoutDirectionInherited() {
+        return (getRawLayoutDirection() == LAYOUT_DIRECTION_INHERIT);
+    }
+
+    /**
+     * @return true if layout direction has been resolved.
+     */
+    public boolean isLayoutDirectionResolved() {
+        return (mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_RESOLVED) == PFLAG2_LAYOUT_DIRECTION_RESOLVED;
+    }
+
+    /**
+     * Return if padding has been resolved
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    boolean isPaddingResolved() {
+        return (mPrivateFlags2 & PFLAG2_PADDING_RESOLVED) == PFLAG2_PADDING_RESOLVED;
+    }
+
+    /**
+     * Resolves padding depending on layout direction, if applicable, and
+     * recomputes internal padding values to adjust for scroll bars.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public void resolvePadding() {
+        final int resolvedLayoutDirection = getLayoutDirection();
+
+        if (!isRtlCompatibilityMode()) {
+            // Post Jelly Bean MR1 case: we need to take the resolved layout direction into account.
+            // If start / end padding are defined, they will be resolved (hence overriding) to
+            // left / right or right / left depending on the resolved layout direction.
+            // If start / end padding are not defined, use the left / right ones.
+            if (mBackground != null && (!mLeftPaddingDefined || !mRightPaddingDefined)) {
+                Rect padding = sThreadLocal.get();
+                if (padding == null) {
+                    padding = new Rect();
+                    sThreadLocal.set(padding);
+                }
+                mBackground.getPadding(padding);
+                if (!mLeftPaddingDefined) {
+                    mUserPaddingLeftInitial = padding.left;
+                }
+                if (!mRightPaddingDefined) {
+                    mUserPaddingRightInitial = padding.right;
+                }
+            }
+            switch (resolvedLayoutDirection) {
+                case LAYOUT_DIRECTION_RTL:
+                    if (mUserPaddingStart != UNDEFINED_PADDING) {
+                        mUserPaddingRight = mUserPaddingStart;
+                    } else {
+                        mUserPaddingRight = mUserPaddingRightInitial;
+                    }
+                    if (mUserPaddingEnd != UNDEFINED_PADDING) {
+                        mUserPaddingLeft = mUserPaddingEnd;
+                    } else {
+                        mUserPaddingLeft = mUserPaddingLeftInitial;
+                    }
+                    break;
+                case LAYOUT_DIRECTION_LTR:
+                default:
+                    if (mUserPaddingStart != UNDEFINED_PADDING) {
+                        mUserPaddingLeft = mUserPaddingStart;
+                    } else {
+                        mUserPaddingLeft = mUserPaddingLeftInitial;
+                    }
+                    if (mUserPaddingEnd != UNDEFINED_PADDING) {
+                        mUserPaddingRight = mUserPaddingEnd;
+                    } else {
+                        mUserPaddingRight = mUserPaddingRightInitial;
+                    }
+            }
+
+            mUserPaddingBottom = (mUserPaddingBottom >= 0) ? mUserPaddingBottom : mPaddingBottom;
+        }
+
+        internalSetPadding(mUserPaddingLeft, mPaddingTop, mUserPaddingRight, mUserPaddingBottom);
+        onRtlPropertiesChanged(resolvedLayoutDirection);
+
+        mPrivateFlags2 |= PFLAG2_PADDING_RESOLVED;
+    }
+
+    /**
+     * Reset the resolved layout direction.
+     *
+     * @hide
+     */
+    @TestApi
+    public void resetResolvedPadding() {
+        resetResolvedPaddingInternal();
+    }
+
+    /**
+     * Used when we only want to reset *this* view's padding and not trigger overrides
+     * in ViewGroup that reset children too.
+     */
+    void resetResolvedPaddingInternal() {
+        mPrivateFlags2 &= ~PFLAG2_PADDING_RESOLVED;
+    }
+
+    /**
+     * This is called when the view is detached from a window.  At this point it
+     * no longer has a surface for drawing.
+     *
+     * @see #onAttachedToWindow()
+     */
+    @CallSuper
+    protected void onDetachedFromWindow() {
+    }
+
+    /**
+     * This is a framework-internal mirror of onDetachedFromWindow() that's called
+     * after onDetachedFromWindow().
+     *
+     * If you override this you *MUST* call super.onDetachedFromWindowInternal()!
+     * The super method should be called at the end of the overridden method to ensure
+     * subclasses are destroyed first
+     *
+     * @hide
+     */
+    @CallSuper
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    protected void onDetachedFromWindowInternal() {
+        mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
+        mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT;
+        mPrivateFlags3 &= ~PFLAG3_TEMPORARY_DETACH;
+
+        removeUnsetPressCallback();
+        removeLongPressCallback();
+        removePerformClickCallback();
+        clearAccessibilityThrottles();
+        stopNestedScroll();
+
+        // Anything that started animating right before detach should already
+        // be in its final state when re-attached.
+        jumpDrawablesToCurrentState();
+
+        destroyDrawingCache();
+
+        cleanupDraw();
+        mCurrentAnimation = null;
+
+        if ((mViewFlags & TOOLTIP) == TOOLTIP) {
+            hideTooltip();
+        }
+
+        AccessibilityNodeIdManager.getInstance().unregisterViewWithId(getAccessibilityViewId());
+    }
+
+    private void cleanupDraw() {
+        resetDisplayList();
+        if (mAttachInfo != null) {
+            mAttachInfo.mViewRootImpl.cancelInvalidate(this);
+        }
+    }
+
+    void invalidateInheritedLayoutMode(int layoutModeOfRoot) {
+    }
+
+    /**
+     * @return The number of times this view has been attached to a window
+     */
+    protected int getWindowAttachCount() {
+        return mWindowAttachCount;
+    }
+
+    /**
+     * Retrieve a unique token identifying the window this view is attached to.
+     * @return Return the window's token for use in
+     * {@link WindowManager.LayoutParams#token WindowManager.LayoutParams.token}.
+     */
+    public IBinder getWindowToken() {
+        return mAttachInfo != null ? mAttachInfo.mWindowToken : null;
+    }
+
+    /**
+     * Retrieve the {@link WindowId} for the window this view is
+     * currently attached to.
+     */
+    public WindowId getWindowId() {
+        AttachInfo ai = mAttachInfo;
+        if (ai == null) {
+            return null;
+        }
+        if (ai.mWindowId == null) {
+            try {
+                ai.mIWindowId = ai.mSession.getWindowId(ai.mWindowToken);
+                if (ai.mIWindowId != null) {
+                    ai.mWindowId = new WindowId(ai.mIWindowId);
+                }
+            } catch (RemoteException e) {
+            }
+        }
+        return ai.mWindowId;
+    }
+
+    /**
+     * Retrieve a unique token identifying the top-level "real" window of
+     * the window that this view is attached to.  That is, this is like
+     * {@link #getWindowToken}, except if the window this view in is a panel
+     * window (attached to another containing window), then the token of
+     * the containing window is returned instead.
+     *
+     * @return Returns the associated window token, either
+     * {@link #getWindowToken()} or the containing window's token.
+     */
+    public IBinder getApplicationWindowToken() {
+        AttachInfo ai = mAttachInfo;
+        if (ai != null) {
+            IBinder appWindowToken = ai.mPanelParentWindowToken;
+            if (appWindowToken == null) {
+                appWindowToken = ai.mWindowToken;
+            }
+            return appWindowToken;
+        }
+        return null;
+    }
+
+    /**
+     * Gets the logical display to which the view's window has been attached.
+     *
+     * @return The logical display, or null if the view is not currently attached to a window.
+     */
+    public Display getDisplay() {
+        return mAttachInfo != null ? mAttachInfo.mDisplay : null;
+    }
+
+    /**
+     * Retrieve private session object this view hierarchy is using to
+     * communicate with the window manager.
+     * @return the session object to communicate with the window manager
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    /*package*/ IWindowSession getWindowSession() {
+        return mAttachInfo != null ? mAttachInfo.mSession : null;
+    }
+
+    /**
+     * Return the window this view is currently attached to.
+     * @hide
+     */
+    protected IWindow getWindow() {
+        return mAttachInfo != null ? mAttachInfo.mWindow : null;
+    }
+
+    /**
+     * Return the visibility value of the least visible component passed.
+     */
+    int combineVisibility(int vis1, int vis2) {
+        // This works because VISIBLE < INVISIBLE < GONE.
+        return Math.max(vis1, vis2);
+    }
+
+    /**
+     * @param info the {@link android.view.View.AttachInfo} to associated with
+     *        this view
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
+        mAttachInfo = info;
+        if (mOverlay != null) {
+            mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
+        }
+        mWindowAttachCount++;
+        // We will need to evaluate the drawable state at least once.
+        mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
+        if (mFloatingTreeObserver != null) {
+            info.mTreeObserver.merge(mFloatingTreeObserver);
+            mFloatingTreeObserver = null;
+        }
+
+        registerPendingFrameMetricsObservers();
+
+        if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {
+            mAttachInfo.mScrollContainers.add(this);
+            mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
+        }
+        // Transfer all pending runnables.
+        if (mRunQueue != null) {
+            mRunQueue.executeActions(info.mHandler);
+            mRunQueue = null;
+        }
+        performCollectViewAttributes(mAttachInfo, visibility);
+        onAttachedToWindow();
+
+        ListenerInfo li = mListenerInfo;
+        final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
+                li != null ? li.mOnAttachStateChangeListeners : null;
+        if (listeners != null && listeners.size() > 0) {
+            // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
+            // perform the dispatching. The iterator is a safe guard against listeners that
+            // could mutate the list by calling the various add/remove methods. This prevents
+            // the array from being modified while we iterate it.
+            for (OnAttachStateChangeListener listener : listeners) {
+                listener.onViewAttachedToWindow(this);
+            }
+        }
+
+        int vis = info.mWindowVisibility;
+        if (vis != GONE) {
+            onWindowVisibilityChanged(vis);
+            if (isShown()) {
+                // Calling onVisibilityAggregated directly here since the subtree will also
+                // receive dispatchAttachedToWindow and this same call
+                onVisibilityAggregated(vis == VISIBLE);
+            }
+        }
+
+        // Send onVisibilityChanged directly instead of dispatchVisibilityChanged.
+        // As all views in the subtree will already receive dispatchAttachedToWindow
+        // traversing the subtree again here is not desired.
+        onVisibilityChanged(this, visibility);
+
+        if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) != 0) {
+            // If nobody has evaluated the drawable state yet, then do it now.
+            refreshDrawableState();
+        }
+        needGlobalAttributesUpdate(false);
+
+        notifyEnterOrExitForAutoFillIfNeeded(true);
+        notifyAppearedOrDisappearedForContentCaptureIfNeeded(true);
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    void dispatchDetachedFromWindow() {
+        AttachInfo info = mAttachInfo;
+        if (info != null) {
+            int vis = info.mWindowVisibility;
+            if (vis != GONE) {
+                onWindowVisibilityChanged(GONE);
+                if (isShown()) {
+                    // Invoking onVisibilityAggregated directly here since the subtree
+                    // will also receive detached from window
+                    onVisibilityAggregated(false);
+                }
+            }
+        }
+
+        onDetachedFromWindow();
+        onDetachedFromWindowInternal();
+
+        if (info != null) {
+            info.mViewRootImpl.getImeFocusController().onViewDetachedFromWindow(this);
+        }
+
+        ListenerInfo li = mListenerInfo;
+        final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
+                li != null ? li.mOnAttachStateChangeListeners : null;
+        if (listeners != null && listeners.size() > 0) {
+            // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
+            // perform the dispatching. The iterator is a safe guard against listeners that
+            // could mutate the list by calling the various add/remove methods. This prevents
+            // the array from being modified while we iterate it.
+            for (OnAttachStateChangeListener listener : listeners) {
+                listener.onViewDetachedFromWindow(this);
+            }
+        }
+
+        if ((mPrivateFlags & PFLAG_SCROLL_CONTAINER_ADDED) != 0) {
+            mAttachInfo.mScrollContainers.remove(this);
+            mPrivateFlags &= ~PFLAG_SCROLL_CONTAINER_ADDED;
+        }
+
+        mAttachInfo = null;
+        if (mOverlay != null) {
+            mOverlay.getOverlayView().dispatchDetachedFromWindow();
+        }
+
+        notifyEnterOrExitForAutoFillIfNeeded(false);
+        notifyAppearedOrDisappearedForContentCaptureIfNeeded(false);
+    }
+
+    /**
+     * Cancel any deferred high-level input events that were previously posted to the event queue.
+     *
+     * <p>Many views post high-level events such as click handlers to the event queue
+     * to run deferred in order to preserve a desired user experience - clearing visible
+     * pressed states before executing, etc. This method will abort any events of this nature
+     * that are currently in flight.</p>
+     *
+     * <p>Custom views that generate their own high-level deferred input events should override
+     * {@link #onCancelPendingInputEvents()} and remove those pending events from the queue.</p>
+     *
+     * <p>This will also cancel pending input events for any child views.</p>
+     *
+     * <p>Note that this may not be sufficient as a debouncing strategy for clicks in all cases.
+     * This will not impact newer events posted after this call that may occur as a result of
+     * lower-level input events still waiting in the queue. If you are trying to prevent
+     * double-submitted  events for the duration of some sort of asynchronous transaction
+     * you should also take other steps to protect against unexpected double inputs e.g. calling
+     * {@link #setEnabled(boolean) setEnabled(false)} and re-enabling the view when
+     * the transaction completes, tracking already submitted transaction IDs, etc.</p>
+     */
+    public final void cancelPendingInputEvents() {
+        dispatchCancelPendingInputEvents();
+    }
+
+    /**
+     * Called by {@link #cancelPendingInputEvents()} to cancel input events in flight.
+     * Overridden by ViewGroup to dispatch. Package scoped to prevent app-side meddling.
+     */
+    void dispatchCancelPendingInputEvents() {
+        mPrivateFlags3 &= ~PFLAG3_CALLED_SUPER;
+        onCancelPendingInputEvents();
+        if ((mPrivateFlags3 & PFLAG3_CALLED_SUPER) != PFLAG3_CALLED_SUPER) {
+            throw new SuperNotCalledException("View " + getClass().getSimpleName() +
+                    " did not call through to super.onCancelPendingInputEvents()");
+        }
+    }
+
+    /**
+     * Called as the result of a call to {@link #cancelPendingInputEvents()} on this view or
+     * a parent view.
+     *
+     * <p>This method is responsible for removing any pending high-level input events that were
+     * posted to the event queue to run later. Custom view classes that post their own deferred
+     * high-level events via {@link #post(Runnable)}, {@link #postDelayed(Runnable, long)} or
+     * {@link android.os.Handler} should override this method, call
+     * <code>super.onCancelPendingInputEvents()</code> and remove those callbacks as appropriate.
+     * </p>
+     */
+    public void onCancelPendingInputEvents() {
+        removePerformClickCallback();
+        cancelLongPress();
+        mPrivateFlags3 |= PFLAG3_CALLED_SUPER;
+    }
+
+    /**
+     * Store this view hierarchy's frozen state into the given container.
+     *
+     * @param container The SparseArray in which to save the view's state.
+     *
+     * @see #restoreHierarchyState(android.util.SparseArray)
+     * @see #dispatchSaveInstanceState(android.util.SparseArray)
+     * @see #onSaveInstanceState()
+     */
+    public void saveHierarchyState(SparseArray<Parcelable> container) {
+        dispatchSaveInstanceState(container);
+    }
+
+    /**
+     * Called by {@link #saveHierarchyState(android.util.SparseArray)} to store the state for
+     * this view and its children. May be overridden to modify how freezing happens to a
+     * view's children; for example, some views may want to not store state for their children.
+     *
+     * @param container The SparseArray in which to save the view's state.
+     *
+     * @see #dispatchRestoreInstanceState(android.util.SparseArray)
+     * @see #saveHierarchyState(android.util.SparseArray)
+     * @see #onSaveInstanceState()
+     */
+    protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
+        if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
+            mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
+            Parcelable state = onSaveInstanceState();
+            if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
+                throw new IllegalStateException(
+                        "Derived class did not call super.onSaveInstanceState()");
+            }
+            if (state != null) {
+                // Log.i("View", "Freezing #" + Integer.toHexString(mID)
+                // + ": " + state);
+                container.put(mID, state);
+            }
+        }
+    }
+
+    /**
+     * Hook allowing a view to generate a representation of its internal state
+     * that can later be used to create a new instance with that same state.
+     * This state should only contain information that is not persistent or can
+     * not be reconstructed later. For example, you will never store your
+     * current position on screen because that will be computed again when a
+     * new instance of the view is placed in its view hierarchy.
+     * <p>
+     * Some examples of things you may store here: the current cursor position
+     * in a text view (but usually not the text itself since that is stored in a
+     * content provider or other persistent storage), the currently selected
+     * item in a list view.
+     *
+     * @return Returns a Parcelable object containing the view's current dynamic
+     *         state, or null if there is nothing interesting to save.
+     * @see #onRestoreInstanceState(Parcelable)
+     * @see #saveHierarchyState(SparseArray)
+     * @see #dispatchSaveInstanceState(SparseArray)
+     * @see #setSaveEnabled(boolean)
+     */
+    @CallSuper
+    @Nullable protected Parcelable onSaveInstanceState() {
+        mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
+        if (mStartActivityRequestWho != null || isAutofilled()
+                || mAutofillViewId > LAST_APP_AUTOFILL_ID) {
+            BaseSavedState state = new BaseSavedState(AbsSavedState.EMPTY_STATE);
+
+            if (mStartActivityRequestWho != null) {
+                state.mSavedData |= BaseSavedState.START_ACTIVITY_REQUESTED_WHO_SAVED;
+            }
+
+            if (isAutofilled()) {
+                state.mSavedData |= BaseSavedState.IS_AUTOFILLED;
+            }
+
+            if (mAutofillViewId > LAST_APP_AUTOFILL_ID) {
+                state.mSavedData |= BaseSavedState.AUTOFILL_ID;
+            }
+
+            state.mStartActivityRequestWhoSaved = mStartActivityRequestWho;
+            state.mIsAutofilled = isAutofilled();
+            state.mHideHighlight = hideAutofillHighlight();
+            state.mAutofillViewId = mAutofillViewId;
+            return state;
+        }
+        return BaseSavedState.EMPTY_STATE;
+    }
+
+    /**
+     * Restore this view hierarchy's frozen state from the given container.
+     *
+     * @param container The SparseArray which holds previously frozen states.
+     *
+     * @see #saveHierarchyState(android.util.SparseArray)
+     * @see #dispatchRestoreInstanceState(android.util.SparseArray)
+     * @see #onRestoreInstanceState(android.os.Parcelable)
+     */
+    public void restoreHierarchyState(SparseArray<Parcelable> container) {
+        dispatchRestoreInstanceState(container);
+    }
+
+    /**
+     * Called by {@link #restoreHierarchyState(android.util.SparseArray)} to retrieve the
+     * state for this view and its children. May be overridden to modify how restoring
+     * happens to a view's children; for example, some views may want to not store state
+     * for their children.
+     *
+     * @param container The SparseArray which holds previously saved state.
+     *
+     * @see #dispatchSaveInstanceState(android.util.SparseArray)
+     * @see #restoreHierarchyState(android.util.SparseArray)
+     * @see #onRestoreInstanceState(android.os.Parcelable)
+     */
+    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
+        if (mID != NO_ID) {
+            Parcelable state = container.get(mID);
+            if (state != null) {
+                // Log.i("View", "Restoreing #" + Integer.toHexString(mID)
+                // + ": " + state);
+                mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
+                onRestoreInstanceState(state);
+                if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
+                    throw new IllegalStateException(
+                            "Derived class did not call super.onRestoreInstanceState()");
+                }
+            }
+        }
+    }
+
+    /**
+     * Hook allowing a view to re-apply a representation of its internal state that had previously
+     * been generated by {@link #onSaveInstanceState}. This function will never be called with a
+     * null state.
+     *
+     * @param state The frozen state that had previously been returned by
+     *        {@link #onSaveInstanceState}.
+     *
+     * @see #onSaveInstanceState()
+     * @see #restoreHierarchyState(android.util.SparseArray)
+     * @see #dispatchRestoreInstanceState(android.util.SparseArray)
+     */
+    @CallSuper
+    protected void onRestoreInstanceState(Parcelable state) {
+        mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
+        if (state != null && !(state instanceof AbsSavedState)) {
+            throw new IllegalArgumentException("Wrong state class, expecting View State but "
+                    + "received " + state.getClass().toString() + " instead. This usually happens "
+                    + "when two views of different type have the same id in the same hierarchy. "
+                    + "This view's id is " + ViewDebug.resolveId(mContext, getId()) + ". Make sure "
+                    + "other views do not use the same id.");
+        }
+        if (state != null && state instanceof BaseSavedState) {
+            BaseSavedState baseState = (BaseSavedState) state;
+
+            if ((baseState.mSavedData & BaseSavedState.START_ACTIVITY_REQUESTED_WHO_SAVED) != 0) {
+                mStartActivityRequestWho = baseState.mStartActivityRequestWhoSaved;
+            }
+            if ((baseState.mSavedData & BaseSavedState.IS_AUTOFILLED) != 0) {
+                setAutofilled(baseState.mIsAutofilled, baseState.mHideHighlight);
+            }
+            if ((baseState.mSavedData & BaseSavedState.AUTOFILL_ID) != 0) {
+                // It can happen that views have the same view id and the restoration path will not
+                // be able to distinguish between them. The autofill id needs to be unique though.
+                // Hence prevent the same autofill view id from being restored multiple times.
+                ((BaseSavedState) state).mSavedData &= ~BaseSavedState.AUTOFILL_ID;
+
+                if ((mPrivateFlags3 & PFLAG3_AUTOFILLID_EXPLICITLY_SET) != 0) {
+                    // Ignore when view already set it through setAutofillId();
+                    if (Log.isLoggable(AUTOFILL_LOG_TAG, Log.DEBUG)) {
+                        Log.d(AUTOFILL_LOG_TAG, "onRestoreInstanceState(): not setting autofillId "
+                                + "to " + baseState.mAutofillViewId + " because view explicitly set"
+                                + " it to " + mAutofillId);
+                    }
+                } else {
+                    mAutofillViewId = baseState.mAutofillViewId;
+                    mAutofillId = null; // will be set on demand by getAutofillId()
+                }
+            }
+        }
+    }
+
+    /**
+     * <p>Return the time at which the drawing of the view hierarchy started.</p>
+     *
+     * @return the drawing start time in milliseconds
+     */
+    public long getDrawingTime() {
+        return mAttachInfo != null ? mAttachInfo.mDrawingTime : 0;
+    }
+
+    /**
+     * <p>Enables or disables the duplication of the parent's state into this view. When
+     * duplication is enabled, this view gets its drawable state from its parent rather
+     * than from its own internal properties.</p>
+     *
+     * <p>Note: in the current implementation, setting this property to true after the
+     * view was added to a ViewGroup might have no effect at all. This property should
+     * always be used from XML or set to true before adding this view to a ViewGroup.</p>
+     *
+     * <p>Note: if this view's parent addStateFromChildren property is enabled and this
+     * property is enabled, an exception will be thrown.</p>
+     *
+     * <p>Note: if the child view uses and updates additional states which are unknown to the
+     * parent, these states should not be affected by this method.</p>
+     *
+     * @param enabled True to enable duplication of the parent's drawable state, false
+     *                to disable it.
+     *
+     * @see #getDrawableState()
+     * @see #isDuplicateParentStateEnabled()
+     */
+    public void setDuplicateParentStateEnabled(boolean enabled) {
+        setFlags(enabled ? DUPLICATE_PARENT_STATE : 0, DUPLICATE_PARENT_STATE);
+    }
+
+    /**
+     * <p>Indicates whether this duplicates its drawable state from its parent.</p>
+     *
+     * @return True if this view's drawable state is duplicated from the parent,
+     *         false otherwise
+     *
+     * @see #getDrawableState()
+     * @see #setDuplicateParentStateEnabled(boolean)
+     */
+    @InspectableProperty(name = "duplicateParentState")
+    public boolean isDuplicateParentStateEnabled() {
+        return (mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE;
+    }
+
+    /**
+     * <p>Specifies the type of layer backing this view. The layer can be
+     * {@link #LAYER_TYPE_NONE}, {@link #LAYER_TYPE_SOFTWARE} or
+     * {@link #LAYER_TYPE_HARDWARE}.</p>
+     *
+     * <p>A layer is associated with an optional {@link android.graphics.Paint}
+     * instance that controls how the layer is composed on screen. The following
+     * properties of the paint are taken into account when composing the layer:</p>
+     * <ul>
+     * <li>{@link android.graphics.Paint#getAlpha() Translucency (alpha)}</li>
+     * <li>{@link android.graphics.Paint#getXfermode() Blending mode}</li>
+     * <li>{@link android.graphics.Paint#getColorFilter() Color filter}</li>
+     * </ul>
+     *
+     * <p>If this view has an alpha value set to < 1.0 by calling
+     * {@link #setAlpha(float)}, the alpha value of the layer's paint is superseded
+     * by this view's alpha value.</p>
+     *
+     * <p>Refer to the documentation of {@link #LAYER_TYPE_NONE},
+     * {@link #LAYER_TYPE_SOFTWARE} and {@link #LAYER_TYPE_HARDWARE}
+     * for more information on when and how to use layers.</p>
+     *
+     * @param layerType The type of layer to use with this view, must be one of
+     *        {@link #LAYER_TYPE_NONE}, {@link #LAYER_TYPE_SOFTWARE} or
+     *        {@link #LAYER_TYPE_HARDWARE}
+     * @param paint The paint used to compose the layer. This argument is optional
+     *        and can be null. It is ignored when the layer type is
+     *        {@link #LAYER_TYPE_NONE}
+     *
+     * @see #getLayerType()
+     * @see #LAYER_TYPE_NONE
+     * @see #LAYER_TYPE_SOFTWARE
+     * @see #LAYER_TYPE_HARDWARE
+     * @see #setAlpha(float)
+     *
+     * @attr ref android.R.styleable#View_layerType
+     */
+    public void setLayerType(@LayerType int layerType, @Nullable Paint paint) {
+        if (layerType < LAYER_TYPE_NONE || layerType > LAYER_TYPE_HARDWARE) {
+            throw new IllegalArgumentException("Layer type can only be one of: LAYER_TYPE_NONE, "
+                    + "LAYER_TYPE_SOFTWARE or LAYER_TYPE_HARDWARE");
+        }
+
+        boolean typeChanged = mRenderNode.setLayerType(layerType);
+
+        if (!typeChanged) {
+            setLayerPaint(paint);
+            return;
+        }
+
+        if (layerType != LAYER_TYPE_SOFTWARE) {
+            // Destroy any previous software drawing cache if present
+            // NOTE: even if previous layer type is HW, we do this to ensure we've cleaned up
+            // drawing cache created in View#draw when drawing to a SW canvas.
+            destroyDrawingCache();
+        }
+
+        mLayerType = layerType;
+        mLayerPaint = mLayerType == LAYER_TYPE_NONE ? null : paint;
+        mRenderNode.setLayerPaint(mLayerPaint);
+
+        // draw() behaves differently if we are on a layer, so we need to
+        // invalidate() here
+        invalidateParentCaches();
+        invalidate(true);
+    }
+
+    /**
+     * Configure the {@link android.graphics.RenderEffect} to apply to this View.
+     * This will apply a visual effect to the results of the View before it is drawn. For example if
+     * {@link RenderEffect#createBlurEffect(float, float, RenderEffect, Shader.TileMode)}
+     * is provided, the contents will be drawn in a separate layer, then this layer will be blurred
+     * when this View is drawn.
+     * @param renderEffect to be applied to the View. Passing null clears the previously configured
+     *                     {@link RenderEffect}
+     */
+    public void setRenderEffect(@Nullable RenderEffect renderEffect) {
+        if (mRenderNode.setRenderEffect(renderEffect)) {
+            invalidateViewProperty(true, true);
+        }
+    }
+
+    /**
+     * Updates the {@link Paint} object used with the current layer (used only if the current
+     * layer type is not set to {@link #LAYER_TYPE_NONE}). Changed properties of the Paint
+     * provided to {@link #setLayerType(int, android.graphics.Paint)} will be used the next time
+     * the View is redrawn, but {@link #setLayerPaint(android.graphics.Paint)} must be called to
+     * ensure that the view gets redrawn immediately.
+     *
+     * <p>A layer is associated with an optional {@link android.graphics.Paint}
+     * instance that controls how the layer is composed on screen. The following
+     * properties of the paint are taken into account when composing the layer:</p>
+     * <ul>
+     * <li>{@link android.graphics.Paint#getAlpha() Translucency (alpha)}</li>
+     * <li>{@link android.graphics.Paint#getXfermode() Blending mode}</li>
+     * <li>{@link android.graphics.Paint#getColorFilter() Color filter}</li>
+     * </ul>
+     *
+     * <p>If this view has an alpha value set to < 1.0 by calling {@link #setAlpha(float)}, the
+     * alpha value of the layer's paint is superseded by this view's alpha value.</p>
+     *
+     * @param paint The paint used to compose the layer. This argument is optional
+     *        and can be null. It is ignored when the layer type is
+     *        {@link #LAYER_TYPE_NONE}
+     *
+     * @see #setLayerType(int, android.graphics.Paint)
+     */
+    public void setLayerPaint(@Nullable Paint paint) {
+        int layerType = getLayerType();
+        if (layerType != LAYER_TYPE_NONE) {
+            mLayerPaint = paint;
+            if (layerType == LAYER_TYPE_HARDWARE) {
+                if (mRenderNode.setLayerPaint(paint)) {
+                    invalidateViewProperty(false, false);
+                }
+            } else {
+                invalidate();
+            }
+        }
+    }
+
+    /**
+     * Indicates what type of layer is currently associated with this view. By default
+     * a view does not have a layer, and the layer type is {@link #LAYER_TYPE_NONE}.
+     * Refer to the documentation of {@link #setLayerType(int, android.graphics.Paint)}
+     * for more information on the different types of layers.
+     *
+     * @return {@link #LAYER_TYPE_NONE}, {@link #LAYER_TYPE_SOFTWARE} or
+     *         {@link #LAYER_TYPE_HARDWARE}
+     *
+     * @see #setLayerType(int, android.graphics.Paint)
+     * @see #buildLayer()
+     * @see #LAYER_TYPE_NONE
+     * @see #LAYER_TYPE_SOFTWARE
+     * @see #LAYER_TYPE_HARDWARE
+     */
+    @InspectableProperty(enumMapping = {
+            @EnumEntry(value = LAYER_TYPE_NONE, name = "none"),
+            @EnumEntry(value = LAYER_TYPE_SOFTWARE, name = "software"),
+            @EnumEntry(value = LAYER_TYPE_HARDWARE, name = "hardware")
+    })
+    @LayerType
+    public int getLayerType() {
+        return mLayerType;
+    }
+
+    /**
+     * Forces this view's layer to be created and this view to be rendered
+     * into its layer. If this view's layer type is set to {@link #LAYER_TYPE_NONE},
+     * invoking this method will have no effect.
+     *
+     * This method can for instance be used to render a view into its layer before
+     * starting an animation. If this view is complex, rendering into the layer
+     * before starting the animation will avoid skipping frames.
+     *
+     * @throws IllegalStateException If this view is not attached to a window
+     *
+     * @see #setLayerType(int, android.graphics.Paint)
+     */
+    public void buildLayer() {
+        if (mLayerType == LAYER_TYPE_NONE) return;
+
+        final AttachInfo attachInfo = mAttachInfo;
+        if (attachInfo == null) {
+            throw new IllegalStateException("This view must be attached to a window first");
+        }
+
+        if (getWidth() == 0 || getHeight() == 0) {
+            return;
+        }
+
+        switch (mLayerType) {
+            case LAYER_TYPE_HARDWARE:
+                updateDisplayListIfDirty();
+                if (attachInfo.mThreadedRenderer != null && mRenderNode.hasDisplayList()) {
+                    attachInfo.mThreadedRenderer.buildLayer(mRenderNode);
+                }
+                break;
+            case LAYER_TYPE_SOFTWARE:
+                buildDrawingCache(true);
+                break;
+        }
+    }
+
+    /**
+     * Destroys all hardware rendering resources. This method is invoked
+     * when the system needs to reclaim resources. Upon execution of this
+     * method, you should free any OpenGL resources created by the view.
+     *
+     * Note: you <strong>must</strong> call
+     * <code>super.destroyHardwareResources()</code> when overriding
+     * this method.
+     *
+     * @hide
+     */
+    @CallSuper
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    protected void destroyHardwareResources() {
+        if (mOverlay != null) {
+            mOverlay.getOverlayView().destroyHardwareResources();
+        }
+        if (mGhostView != null) {
+            mGhostView.destroyHardwareResources();
+        }
+    }
+
+    /**
+     * <p>Enables or disables the drawing cache. When the drawing cache is enabled, the next call
+     * to {@link #getDrawingCache()} or {@link #buildDrawingCache()} will draw the view in a
+     * bitmap. Calling {@link #draw(android.graphics.Canvas)} will not draw from the cache when
+     * the cache is enabled. To benefit from the cache, you must request the drawing cache by
+     * calling {@link #getDrawingCache()} and draw it on screen if the returned bitmap is not
+     * null.</p>
+     *
+     * <p>Enabling the drawing cache is similar to
+     * {@link #setLayerType(int, android.graphics.Paint) setting a layer} when hardware
+     * acceleration is turned off. When hardware acceleration is turned on, enabling the
+     * drawing cache has no effect on rendering because the system uses a different mechanism
+     * for acceleration which ignores the flag. If you want to use a Bitmap for the view, even
+     * when hardware acceleration is enabled, see {@link #setLayerType(int, android.graphics.Paint)}
+     * for information on how to enable software and hardware layers.</p>
+     *
+     * <p>This API can be used to manually generate
+     * a bitmap copy of this view, by setting the flag to <code>true</code> and calling
+     * {@link #getDrawingCache()}.</p>
+     *
+     * @param enabled true to enable the drawing cache, false otherwise
+     *
+     * @see #isDrawingCacheEnabled()
+     * @see #getDrawingCache()
+     * @see #buildDrawingCache()
+     * @see #setLayerType(int, android.graphics.Paint)
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
+     */
+    @Deprecated
+    public void setDrawingCacheEnabled(boolean enabled) {
+        mCachingFailed = false;
+        setFlags(enabled ? DRAWING_CACHE_ENABLED : 0, DRAWING_CACHE_ENABLED);
+    }
+
+    /**
+     * <p>Indicates whether the drawing cache is enabled for this view.</p>
+     *
+     * @return true if the drawing cache is enabled
+     *
+     * @see #setDrawingCacheEnabled(boolean)
+     * @see #getDrawingCache()
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
+     */
+    @Deprecated
+    @ViewDebug.ExportedProperty(category = "drawing")
+    public boolean isDrawingCacheEnabled() {
+        return (mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED;
+    }
+
+    /**
+     * Debugging utility which recursively outputs the dirty state of a view and its
+     * descendants.
+     *
+     * @hide
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    public void outputDirtyFlags(String indent, boolean clear, int clearMask) {
+        Log.d(VIEW_LOG_TAG, indent + this + "             DIRTY("
+                + (mPrivateFlags & View.PFLAG_DIRTY_MASK)
+                + ") DRAWN(" + (mPrivateFlags & PFLAG_DRAWN) + ")" + " CACHE_VALID("
+                + (mPrivateFlags & View.PFLAG_DRAWING_CACHE_VALID)
+                + ") INVALIDATED(" + (mPrivateFlags & PFLAG_INVALIDATED) + ")");
+        if (clear) {
+            mPrivateFlags &= clearMask;
+        }
+        if (this instanceof ViewGroup) {
+            ViewGroup parent = (ViewGroup) this;
+            final int count = parent.getChildCount();
+            for (int i = 0; i < count; i++) {
+                final View child = parent.getChildAt(i);
+                child.outputDirtyFlags(indent + "  ", clear, clearMask);
+            }
+        }
+    }
+
+    /**
+     * This method is used by ViewGroup to cause its children to restore or recreate their
+     * display lists. It is called by getDisplayList() when the parent ViewGroup does not need
+     * to recreate its own display list, which would happen if it went through the normal
+     * draw/dispatchDraw mechanisms.
+     *
+     * @hide
+     */
+    protected void dispatchGetDisplayList() {}
+
+    /**
+     * A view that is not attached or hardware accelerated cannot create a display list.
+     * This method checks these conditions and returns the appropriate result.
+     *
+     * @return true if view has the ability to create a display list, false otherwise.
+     *
+     * @hide
+     */
+    public boolean canHaveDisplayList() {
+        return !(mAttachInfo == null || mAttachInfo.mThreadedRenderer == null);
+    }
+
+    /**
+     * Gets the RenderNode for the view, and updates its DisplayList (if needed and supported)
+     * @hide
+     */
+    @NonNull
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public RenderNode updateDisplayListIfDirty() {
+        final RenderNode renderNode = mRenderNode;
+        if (!canHaveDisplayList()) {
+            // can't populate RenderNode, don't try
+            return renderNode;
+        }
+
+        if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
+                || !renderNode.hasDisplayList()
+                || (mRecreateDisplayList)) {
+            // Don't need to recreate the display list, just need to tell our
+            // children to restore/recreate theirs
+            if (renderNode.hasDisplayList()
+                    && !mRecreateDisplayList) {
+                mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
+                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
+                dispatchGetDisplayList();
+
+                return renderNode; // no work needed
+            }
+
+            // If we got here, we're recreating it. Mark it as such to ensure that
+            // we copy in child display lists into ours in drawChild()
+            mRecreateDisplayList = true;
+
+            int width = mRight - mLeft;
+            int height = mBottom - mTop;
+            int layerType = getLayerType();
+
+            // Hacky hack: Reset any stretch effects as those are applied during the draw pass
+            // instead of being "stateful" like other RenderNode properties
+            renderNode.clearStretch();
+
+            final RecordingCanvas canvas = renderNode.beginRecording(width, height);
+
+            try {
+                if (layerType == LAYER_TYPE_SOFTWARE) {
+                    buildDrawingCache(true);
+                    Bitmap cache = getDrawingCache(true);
+                    if (cache != null) {
+                        canvas.drawBitmap(cache, 0, 0, mLayerPaint);
+                    }
+                } else {
+                    computeScroll();
+
+                    canvas.translate(-mScrollX, -mScrollY);
+                    mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
+                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;
+
+                    // Fast path for layouts with no backgrounds
+                    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
+                        dispatchDraw(canvas);
+                        drawAutofilledHighlight(canvas);
+                        if (mOverlay != null && !mOverlay.isEmpty()) {
+                            mOverlay.getOverlayView().draw(canvas);
+                        }
+                        if (isShowingLayoutBounds()) {
+                            debugDrawFocus(canvas);
+                        }
+                    } else {
+                        draw(canvas);
+                    }
+                }
+            } finally {
+                renderNode.endRecording();
+                setDisplayListProperties(renderNode);
+            }
+        } else {
+            mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
+            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
+        }
+        return renderNode;
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private void resetDisplayList() {
+        mRenderNode.discardDisplayList();
+        if (mBackgroundRenderNode != null) {
+            mBackgroundRenderNode.discardDisplayList();
+        }
+    }
+
+    /**
+     * <p>Calling this method is equivalent to calling <code>getDrawingCache(false)</code>.</p>
+     *
+     * @return A non-scaled bitmap representing this view or null if cache is disabled.
+     *
+     * @see #getDrawingCache(boolean)
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
+     */
+    @Deprecated
+    public Bitmap getDrawingCache() {
+        return getDrawingCache(false);
+    }
+
+    /**
+     * <p>Returns the bitmap in which this view drawing is cached. The returned bitmap
+     * is null when caching is disabled. If caching is enabled and the cache is not ready,
+     * this method will create it. Calling {@link #draw(android.graphics.Canvas)} will not
+     * draw from the cache when the cache is enabled. To benefit from the cache, you must
+     * request the drawing cache by calling this method and draw it on screen if the
+     * returned bitmap is not null.</p>
+     *
+     * <p>Note about auto scaling in compatibility mode: When auto scaling is not enabled,
+     * this method will create a bitmap of the same size as this view. Because this bitmap
+     * will be drawn scaled by the parent ViewGroup, the result on screen might show
+     * scaling artifacts. To avoid such artifacts, you should call this method by setting
+     * the auto scaling to true. Doing so, however, will generate a bitmap of a different
+     * size than the view. This implies that your application must be able to handle this
+     * size.</p>
+     *
+     * @param autoScale Indicates whether the generated bitmap should be scaled based on
+     *        the current density of the screen when the application is in compatibility
+     *        mode.
+     *
+     * @return A bitmap representing this view or null if cache is disabled.
+     *
+     * @see #setDrawingCacheEnabled(boolean)
+     * @see #isDrawingCacheEnabled()
+     * @see #buildDrawingCache(boolean)
+     * @see #destroyDrawingCache()
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
+     */
+    @Deprecated
+    public Bitmap getDrawingCache(boolean autoScale) {
+        if ((mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING) {
+            return null;
+        }
+        if ((mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED) {
+            buildDrawingCache(autoScale);
+        }
+        return autoScale ? mDrawingCache : mUnscaledDrawingCache;
+    }
+
+    /**
+     * <p>Frees the resources used by the drawing cache. If you call
+     * {@link #buildDrawingCache()} manually without calling
+     * {@link #setDrawingCacheEnabled(boolean) setDrawingCacheEnabled(true)}, you
+     * should cleanup the cache with this method afterwards.</p>
+     *
+     * @see #setDrawingCacheEnabled(boolean)
+     * @see #buildDrawingCache()
+     * @see #getDrawingCache()
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
+     */
+    @Deprecated
+    public void destroyDrawingCache() {
+        if (mDrawingCache != null) {
+            mDrawingCache.recycle();
+            mDrawingCache = null;
+        }
+        if (mUnscaledDrawingCache != null) {
+            mUnscaledDrawingCache.recycle();
+            mUnscaledDrawingCache = null;
+        }
+    }
+
+    /**
+     * Setting a solid background color for the drawing cache's bitmaps will improve
+     * performance and memory usage. Note, though that this should only be used if this
+     * view will always be drawn on top of a solid color.
+     *
+     * @param color The background color to use for the drawing cache's bitmap
+     *
+     * @see #setDrawingCacheEnabled(boolean)
+     * @see #buildDrawingCache()
+     * @see #getDrawingCache()
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
+     */
+    @Deprecated
+    public void setDrawingCacheBackgroundColor(@ColorInt int color) {
+        if (color != mDrawingCacheBackgroundColor) {
+            mDrawingCacheBackgroundColor = color;
+            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
+        }
+    }
+
+    /**
+     * @see #setDrawingCacheBackgroundColor(int)
+     *
+     * @return The background color to used for the drawing cache's bitmap
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
+     */
+    @Deprecated
+    @ColorInt
+    public int getDrawingCacheBackgroundColor() {
+        return mDrawingCacheBackgroundColor;
+    }
+
+    /**
+     * <p>Calling this method is equivalent to calling <code>buildDrawingCache(false)</code>.</p>
+     *
+     * @see #buildDrawingCache(boolean)
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
+     */
+    @Deprecated
+    public void buildDrawingCache() {
+        buildDrawingCache(false);
+    }
+
+    /**
+     * <p>Forces the drawing cache to be built if the drawing cache is invalid.</p>
+     *
+     * <p>If you call {@link #buildDrawingCache()} manually without calling
+     * {@link #setDrawingCacheEnabled(boolean) setDrawingCacheEnabled(true)}, you
+     * should cleanup the cache by calling {@link #destroyDrawingCache()} afterwards.</p>
+     *
+     * <p>Note about auto scaling in compatibility mode: When auto scaling is not enabled,
+     * this method will create a bitmap of the same size as this view. Because this bitmap
+     * will be drawn scaled by the parent ViewGroup, the result on screen might show
+     * scaling artifacts. To avoid such artifacts, you should call this method by setting
+     * the auto scaling to true. Doing so, however, will generate a bitmap of a different
+     * size than the view. This implies that your application must be able to handle this
+     * size.</p>
+     *
+     * <p>You should avoid calling this method when hardware acceleration is enabled. If
+     * you do not need the drawing cache bitmap, calling this method will increase memory
+     * usage and cause the view to be rendered in software once, thus negatively impacting
+     * performance.</p>
+     *
+     * @see #getDrawingCache()
+     * @see #destroyDrawingCache()
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
+     */
+    @Deprecated
+    public void buildDrawingCache(boolean autoScale) {
+        if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ?
+                mDrawingCache == null : mUnscaledDrawingCache == null)) {
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+                        "buildDrawingCache/SW Layer for " + getClass().getSimpleName());
+            }
+            try {
+                buildDrawingCacheImpl(autoScale);
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            }
+        }
+    }
+
+    /**
+     * private, internal implementation of buildDrawingCache, used to enable tracing
+     */
+    private void buildDrawingCacheImpl(boolean autoScale) {
+        mCachingFailed = false;
+
+        int width = mRight - mLeft;
+        int height = mBottom - mTop;
+
+        final AttachInfo attachInfo = mAttachInfo;
+        final boolean scalingRequired = attachInfo != null && attachInfo.mScalingRequired;
+
+        if (autoScale && scalingRequired) {
+            width = (int) ((width * attachInfo.mApplicationScale) + 0.5f);
+            height = (int) ((height * attachInfo.mApplicationScale) + 0.5f);
+        }
+
+        final int drawingCacheBackgroundColor = mDrawingCacheBackgroundColor;
+        final boolean opaque = drawingCacheBackgroundColor != 0 || isOpaque();
+        final boolean use32BitCache = attachInfo != null && attachInfo.mUse32BitDrawingCache;
+
+        final long projectedBitmapSize = width * height * (opaque && !use32BitCache ? 2 : 4);
+        final long drawingCacheSize =
+                ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize();
+        if (width <= 0 || height <= 0 || projectedBitmapSize > drawingCacheSize) {
+            if (width > 0 && height > 0) {
+                Log.w(VIEW_LOG_TAG, getClass().getSimpleName() + " not displayed because it is"
+                        + " too large to fit into a software layer (or drawing cache), needs "
+                        + projectedBitmapSize + " bytes, only "
+                        + drawingCacheSize + " available");
+            }
+            destroyDrawingCache();
+            mCachingFailed = true;
+            return;
+        }
+
+        boolean clear = true;
+        Bitmap bitmap = autoScale ? mDrawingCache : mUnscaledDrawingCache;
+
+        if (bitmap == null || bitmap.getWidth() != width || bitmap.getHeight() != height) {
+            Bitmap.Config quality;
+            if (!opaque) {
+                // Never pick ARGB_4444 because it looks awful
+                // Keep the DRAWING_CACHE_QUALITY_LOW flag just in case
+                switch (mViewFlags & DRAWING_CACHE_QUALITY_MASK) {
+                    case DRAWING_CACHE_QUALITY_AUTO:
+                    case DRAWING_CACHE_QUALITY_LOW:
+                    case DRAWING_CACHE_QUALITY_HIGH:
+                    default:
+                        quality = Bitmap.Config.ARGB_8888;
+                        break;
+                }
+            } else {
+                // Optimization for translucent windows
+                // If the window is translucent, use a 32 bits bitmap to benefit from memcpy()
+                quality = use32BitCache ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
+            }
+
+            // Try to cleanup memory
+            if (bitmap != null) bitmap.recycle();
+
+            try {
+                bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(),
+                        width, height, quality);
+                bitmap.setDensity(getResources().getDisplayMetrics().densityDpi);
+                if (autoScale) {
+                    mDrawingCache = bitmap;
+                } else {
+                    mUnscaledDrawingCache = bitmap;
+                }
+                if (opaque && use32BitCache) bitmap.setHasAlpha(false);
+            } catch (OutOfMemoryError e) {
+                // If there is not enough memory to create the bitmap cache, just
+                // ignore the issue as bitmap caches are not required to draw the
+                // view hierarchy
+                if (autoScale) {
+                    mDrawingCache = null;
+                } else {
+                    mUnscaledDrawingCache = null;
+                }
+                mCachingFailed = true;
+                return;
+            }
+
+            clear = drawingCacheBackgroundColor != 0;
+        }
+
+        Canvas canvas;
+        if (attachInfo != null) {
+            canvas = attachInfo.mCanvas;
+            if (canvas == null) {
+                canvas = new Canvas();
+            }
+            canvas.setBitmap(bitmap);
+            // Temporarily clobber the cached Canvas in case one of our children
+            // is also using a drawing cache. Without this, the children would
+            // steal the canvas by attaching their own bitmap to it and bad, bad
+            // thing would happen (invisible views, corrupted drawings, etc.)
+            attachInfo.mCanvas = null;
+        } else {
+            // This case should hopefully never or seldom happen
+            canvas = new Canvas(bitmap);
+        }
+
+        if (clear) {
+            bitmap.eraseColor(drawingCacheBackgroundColor);
+        }
+
+        computeScroll();
+        final int restoreCount = canvas.save();
+
+        if (autoScale && scalingRequired) {
+            final float scale = attachInfo.mApplicationScale;
+            canvas.scale(scale, scale);
+        }
+
+        canvas.translate(-mScrollX, -mScrollY);
+
+        mPrivateFlags |= PFLAG_DRAWN;
+        if (mAttachInfo == null || !mAttachInfo.mHardwareAccelerated ||
+                mLayerType != LAYER_TYPE_NONE) {
+            mPrivateFlags |= PFLAG_DRAWING_CACHE_VALID;
+        }
+
+        // Fast path for layouts with no backgrounds
+        if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
+            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
+            dispatchDraw(canvas);
+            drawAutofilledHighlight(canvas);
+            if (mOverlay != null && !mOverlay.isEmpty()) {
+                mOverlay.getOverlayView().draw(canvas);
+            }
+        } else {
+            draw(canvas);
+        }
+
+        canvas.restoreToCount(restoreCount);
+        canvas.setBitmap(null);
+
+        if (attachInfo != null) {
+            // Restore the cached Canvas for our siblings
+            attachInfo.mCanvas = canvas;
+        }
+    }
+
+    /**
+     * Create a snapshot of the view into a bitmap.  We should probably make
+     * some form of this public, but should think about the API.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public Bitmap createSnapshot(ViewDebug.CanvasProvider canvasProvider, boolean skipChildren) {
+        int width = mRight - mLeft;
+        int height = mBottom - mTop;
+
+        final AttachInfo attachInfo = mAttachInfo;
+        final float scale = attachInfo != null ? attachInfo.mApplicationScale : 1.0f;
+        width = (int) ((width * scale) + 0.5f);
+        height = (int) ((height * scale) + 0.5f);
+
+        Canvas oldCanvas = null;
+        try {
+            Canvas canvas = canvasProvider.getCanvas(this,
+                    width > 0 ? width : 1, height > 0 ? height : 1);
+
+            if (attachInfo != null) {
+                oldCanvas = attachInfo.mCanvas;
+                // Temporarily clobber the cached Canvas in case one of our children
+                // is also using a drawing cache. Without this, the children would
+                // steal the canvas by attaching their own bitmap to it and bad, bad
+                // things would happen (invisible views, corrupted drawings, etc.)
+                attachInfo.mCanvas = null;
+            }
+
+            computeScroll();
+            final int restoreCount = canvas.save();
+            canvas.scale(scale, scale);
+            canvas.translate(-mScrollX, -mScrollY);
+
+            // Temporarily remove the dirty mask
+            int flags = mPrivateFlags;
+            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
+
+            // Fast path for layouts with no backgrounds
+            if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
+                dispatchDraw(canvas);
+                drawAutofilledHighlight(canvas);
+                if (mOverlay != null && !mOverlay.isEmpty()) {
+                    mOverlay.getOverlayView().draw(canvas);
+                }
+            } else {
+                draw(canvas);
+            }
+
+            mPrivateFlags = flags;
+            canvas.restoreToCount(restoreCount);
+            return canvasProvider.createBitmap();
+        } finally {
+            if (oldCanvas != null) {
+                attachInfo.mCanvas = oldCanvas;
+            }
+        }
+    }
+
+    /**
+     * Indicates whether this View is currently in edit mode. A View is usually
+     * in edit mode when displayed within a developer tool. For instance, if
+     * this View is being drawn by a visual user interface builder, this method
+     * should return true.
+     *
+     * Subclasses should check the return value of this method to provide
+     * different behaviors if their normal behavior might interfere with the
+     * host environment. For instance: the class spawns a thread in its
+     * constructor, the drawing code relies on device-specific features, etc.
+     *
+     * This method is usually checked in the drawing code of custom widgets.
+     *
+     * @return True if this View is in edit mode, false otherwise.
+     */
+    public boolean isInEditMode() {
+        return false;
+    }
+
+    /**
+     * If the View draws content inside its padding and enables fading edges,
+     * it needs to support padding offsets. Padding offsets are added to the
+     * fading edges to extend the length of the fade so that it covers pixels
+     * drawn inside the padding.
+     *
+     * Subclasses of this class should override this method if they need
+     * to draw content inside the padding.
+     *
+     * @return True if padding offset must be applied, false otherwise.
+     *
+     * @see #getLeftPaddingOffset()
+     * @see #getRightPaddingOffset()
+     * @see #getTopPaddingOffset()
+     * @see #getBottomPaddingOffset()
+     *
+     * @since CURRENT
+     */
+    protected boolean isPaddingOffsetRequired() {
+        return false;
+    }
+
+    /**
+     * Amount by which to extend the left fading region. Called only when
+     * {@link #isPaddingOffsetRequired()} returns true.
+     *
+     * @return The left padding offset in pixels.
+     *
+     * @see #isPaddingOffsetRequired()
+     *
+     * @since CURRENT
+     */
+    protected int getLeftPaddingOffset() {
+        return 0;
+    }
+
+    /**
+     * Amount by which to extend the right fading region. Called only when
+     * {@link #isPaddingOffsetRequired()} returns true.
+     *
+     * @return The right padding offset in pixels.
+     *
+     * @see #isPaddingOffsetRequired()
+     *
+     * @since CURRENT
+     */
+    protected int getRightPaddingOffset() {
+        return 0;
+    }
+
+    /**
+     * Amount by which to extend the top fading region. Called only when
+     * {@link #isPaddingOffsetRequired()} returns true.
+     *
+     * @return The top padding offset in pixels.
+     *
+     * @see #isPaddingOffsetRequired()
+     *
+     * @since CURRENT
+     */
+    protected int getTopPaddingOffset() {
+        return 0;
+    }
+
+    /**
+     * Amount by which to extend the bottom fading region. Called only when
+     * {@link #isPaddingOffsetRequired()} returns true.
+     *
+     * @return The bottom padding offset in pixels.
+     *
+     * @see #isPaddingOffsetRequired()
+     *
+     * @since CURRENT
+     */
+    protected int getBottomPaddingOffset() {
+        return 0;
+    }
+
+    /**
+     * @hide
+     * @param offsetRequired
+     */
+    protected int getFadeTop(boolean offsetRequired) {
+        int top = mPaddingTop;
+        if (offsetRequired) top += getTopPaddingOffset();
+        return top;
+    }
+
+    /**
+     * @hide
+     * @param offsetRequired
+     */
+    protected int getFadeHeight(boolean offsetRequired) {
+        int padding = mPaddingTop;
+        if (offsetRequired) padding += getTopPaddingOffset();
+        return mBottom - mTop - mPaddingBottom - padding;
+    }
+
+    /**
+     * <p>Indicates whether this view is attached to a hardware accelerated
+     * window or not.</p>
+     *
+     * <p>Even if this method returns true, it does not mean that every call
+     * to {@link #draw(android.graphics.Canvas)} will be made with an hardware
+     * accelerated {@link android.graphics.Canvas}. For instance, if this view
+     * is drawn onto an offscreen {@link android.graphics.Bitmap} and its
+     * window is hardware accelerated,
+     * {@link android.graphics.Canvas#isHardwareAccelerated()} will likely
+     * return false, and this method will return true.</p>
+     *
+     * @return True if the view is attached to a window and the window is
+     *         hardware accelerated; false in any other case.
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    public boolean isHardwareAccelerated() {
+        return mAttachInfo != null && mAttachInfo.mHardwareAccelerated;
+    }
+
+    /**
+     * Sets a rectangular area on this view to which the view will be clipped
+     * when it is drawn. Setting the value to null will remove the clip bounds
+     * and the view will draw normally, using its full bounds.
+     *
+     * @param clipBounds The rectangular area, in the local coordinates of
+     * this view, to which future drawing operations will be clipped.
+     */
+    public void setClipBounds(Rect clipBounds) {
+        if (clipBounds == mClipBounds
+                || (clipBounds != null && clipBounds.equals(mClipBounds))) {
+            return;
+        }
+        if (clipBounds != null) {
+            if (mClipBounds == null) {
+                mClipBounds = new Rect(clipBounds);
+            } else {
+                mClipBounds.set(clipBounds);
+            }
+        } else {
+            mClipBounds = null;
+        }
+        mRenderNode.setClipRect(mClipBounds);
+        invalidateViewProperty(false, false);
+    }
+
+    /**
+     * Returns a copy of the current {@link #setClipBounds(Rect) clipBounds}.
+     *
+     * @return A copy of the current clip bounds if clip bounds are set,
+     * otherwise null.
+     */
+    public Rect getClipBounds() {
+        return (mClipBounds != null) ? new Rect(mClipBounds) : null;
+    }
+
+
+    /**
+     * Populates an output rectangle with the clip bounds of the view,
+     * returning {@code true} if successful or {@code false} if the view's
+     * clip bounds are {@code null}.
+     *
+     * @param outRect rectangle in which to place the clip bounds of the view
+     * @return {@code true} if successful or {@code false} if the view's
+     *         clip bounds are {@code null}
+     */
+    public boolean getClipBounds(Rect outRect) {
+        if (mClipBounds != null) {
+            outRect.set(mClipBounds);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Utility function, called by draw(canvas, parent, drawingTime) to handle the less common
+     * case of an active Animation being run on the view.
+     */
+    private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
+            Animation a, boolean scalingRequired) {
+        Transformation invalidationTransform;
+        final int flags = parent.mGroupFlags;
+        final boolean initialized = a.isInitialized();
+        if (!initialized) {
+            a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight());
+            a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);
+            if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);
+            onAnimationStart();
+        }
+
+        final Transformation t = parent.getChildTransformation();
+        boolean more = a.getTransformation(drawingTime, t, 1f);
+        if (scalingRequired && mAttachInfo.mApplicationScale != 1f) {
+            if (parent.mInvalidationTransformation == null) {
+                parent.mInvalidationTransformation = new Transformation();
+            }
+            invalidationTransform = parent.mInvalidationTransformation;
+            a.getTransformation(drawingTime, invalidationTransform, 1f);
+        } else {
+            invalidationTransform = t;
+        }
+
+        if (more) {
+            if (!a.willChangeBounds()) {
+                if ((flags & (ViewGroup.FLAG_OPTIMIZE_INVALIDATE | ViewGroup.FLAG_ANIMATION_DONE)) ==
+                        ViewGroup.FLAG_OPTIMIZE_INVALIDATE) {
+                    parent.mGroupFlags |= ViewGroup.FLAG_INVALIDATE_REQUIRED;
+                } else if ((flags & ViewGroup.FLAG_INVALIDATE_REQUIRED) == 0) {
+                    // The child need to draw an animation, potentially offscreen, so
+                    // make sure we do not cancel invalidate requests
+                    parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
+                    parent.invalidate(mLeft, mTop, mRight, mBottom);
+                }
+            } else {
+                if (parent.mInvalidateRegion == null) {
+                    parent.mInvalidateRegion = new RectF();
+                }
+                final RectF region = parent.mInvalidateRegion;
+                a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,
+                        invalidationTransform);
+
+                // The child need to draw an animation, potentially offscreen, so
+                // make sure we do not cancel invalidate requests
+                parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
+
+                final int left = mLeft + (int) region.left;
+                final int top = mTop + (int) region.top;
+                parent.invalidate(left, top, left + (int) (region.width() + .5f),
+                        top + (int) (region.height() + .5f));
+            }
+        }
+        return more;
+    }
+
+    /**
+     * This method is called by getDisplayList() when a display list is recorded for a View.
+     * It pushes any properties to the RenderNode that aren't managed by the RenderNode.
+     */
+    void setDisplayListProperties(RenderNode renderNode) {
+        if (renderNode != null) {
+            renderNode.setHasOverlappingRendering(getHasOverlappingRendering());
+            renderNode.setClipToBounds(mParent instanceof ViewGroup
+                    && ((ViewGroup) mParent).getClipChildren());
+
+            float alpha = 1;
+            if (mParent instanceof ViewGroup && (((ViewGroup) mParent).mGroupFlags &
+                    ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
+                ViewGroup parentVG = (ViewGroup) mParent;
+                final Transformation t = parentVG.getChildTransformation();
+                if (parentVG.getChildStaticTransformation(this, t)) {
+                    final int transformType = t.getTransformationType();
+                    if (transformType != Transformation.TYPE_IDENTITY) {
+                        if ((transformType & Transformation.TYPE_ALPHA) != 0) {
+                            alpha = t.getAlpha();
+                        }
+                        if ((transformType & Transformation.TYPE_MATRIX) != 0) {
+                            renderNode.setStaticMatrix(t.getMatrix());
+                        }
+                    }
+                }
+            }
+            if (mTransformationInfo != null) {
+                alpha *= getFinalAlpha();
+                if (alpha < 1) {
+                    final int multipliedAlpha = (int) (255 * alpha);
+                    if (onSetAlpha(multipliedAlpha)) {
+                        alpha = 1;
+                    }
+                }
+                renderNode.setAlpha(alpha);
+            } else if (alpha < 1) {
+                renderNode.setAlpha(alpha);
+            }
+        }
+    }
+
+    /**
+     * This method is called by ViewGroup.drawChild() to have each child view draw itself.
+     *
+     * This is where the View specializes rendering behavior based on layer type,
+     * and hardware acceleration.
+     */
+    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
+
+        final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
+        /* If an attached view draws to a HW canvas, it may use its RenderNode + DisplayList.
+         *
+         * If a view is dettached, its DisplayList shouldn't exist. If the canvas isn't
+         * HW accelerated, it can't handle drawing RenderNodes.
+         */
+        boolean drawingWithRenderNode = mAttachInfo != null
+                && mAttachInfo.mHardwareAccelerated
+                && hardwareAcceleratedCanvas;
+
+        boolean more = false;
+        final boolean childHasIdentityMatrix = hasIdentityMatrix();
+        final int parentFlags = parent.mGroupFlags;
+
+        if ((parentFlags & ViewGroup.FLAG_CLEAR_TRANSFORMATION) != 0) {
+            parent.getChildTransformation().clear();
+            parent.mGroupFlags &= ~ViewGroup.FLAG_CLEAR_TRANSFORMATION;
+        }
+
+        Transformation transformToApply = null;
+        boolean concatMatrix = false;
+        final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired;
+        final Animation a = getAnimation();
+        if (a != null) {
+            more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
+            concatMatrix = a.willChangeTransformationMatrix();
+            if (concatMatrix) {
+                mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
+            }
+            transformToApply = parent.getChildTransformation();
+        } else {
+            if ((mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_TRANSFORM) != 0) {
+                // No longer animating: clear out old animation matrix
+                mRenderNode.setAnimationMatrix(null);
+                mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
+            }
+            if (!drawingWithRenderNode
+                    && (parentFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
+                final Transformation t = parent.getChildTransformation();
+                final boolean hasTransform = parent.getChildStaticTransformation(this, t);
+                if (hasTransform) {
+                    final int transformType = t.getTransformationType();
+                    transformToApply = transformType != Transformation.TYPE_IDENTITY ? t : null;
+                    concatMatrix = (transformType & Transformation.TYPE_MATRIX) != 0;
+                }
+            }
+        }
+
+        concatMatrix |= !childHasIdentityMatrix;
+
+        // Sets the flag as early as possible to allow draw() implementations
+        // to call invalidate() successfully when doing animations
+        mPrivateFlags |= PFLAG_DRAWN;
+
+        if (!concatMatrix &&
+                (parentFlags & (ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS |
+                        ViewGroup.FLAG_CLIP_CHILDREN)) == ViewGroup.FLAG_CLIP_CHILDREN &&
+                canvas.quickReject(mLeft, mTop, mRight, mBottom) &&
+                (mPrivateFlags & PFLAG_DRAW_ANIMATION) == 0) {
+            mPrivateFlags2 |= PFLAG2_VIEW_QUICK_REJECTED;
+            return more;
+        }
+        mPrivateFlags2 &= ~PFLAG2_VIEW_QUICK_REJECTED;
+
+        if (hardwareAcceleratedCanvas) {
+            // Clear INVALIDATED flag to allow invalidation to occur during rendering, but
+            // retain the flag's value temporarily in the mRecreateDisplayList flag
+            mRecreateDisplayList = (mPrivateFlags & PFLAG_INVALIDATED) != 0;
+            mPrivateFlags &= ~PFLAG_INVALIDATED;
+        }
+
+        RenderNode renderNode = null;
+        Bitmap cache = null;
+        int layerType = getLayerType(); // TODO: signify cache state with just 'cache' local
+        if (layerType == LAYER_TYPE_SOFTWARE || !drawingWithRenderNode) {
+             if (layerType != LAYER_TYPE_NONE) {
+                 // If not drawing with RenderNode, treat HW layers as SW
+                 layerType = LAYER_TYPE_SOFTWARE;
+                 buildDrawingCache(true);
+            }
+            cache = getDrawingCache(true);
+        }
+
+        if (drawingWithRenderNode) {
+            // Delay getting the display list until animation-driven alpha values are
+            // set up and possibly passed on to the view
+            renderNode = updateDisplayListIfDirty();
+            if (!renderNode.hasDisplayList()) {
+                // Uncommon, but possible. If a view is removed from the hierarchy during the call
+                // to getDisplayList(), the display list will be marked invalid and we should not
+                // try to use it again.
+                renderNode = null;
+                drawingWithRenderNode = false;
+            }
+        }
+
+        int sx = 0;
+        int sy = 0;
+        if (!drawingWithRenderNode) {
+            computeScroll();
+            sx = mScrollX;
+            sy = mScrollY;
+        }
+
+        final boolean drawingWithDrawingCache = cache != null && !drawingWithRenderNode;
+        final boolean offsetForScroll = cache == null && !drawingWithRenderNode;
+
+        int restoreTo = -1;
+        if (!drawingWithRenderNode || transformToApply != null) {
+            restoreTo = canvas.save();
+        }
+        if (offsetForScroll) {
+            canvas.translate(mLeft - sx, mTop - sy);
+        } else {
+            if (!drawingWithRenderNode) {
+                canvas.translate(mLeft, mTop);
+            }
+            if (scalingRequired) {
+                if (drawingWithRenderNode) {
+                    // TODO: Might not need this if we put everything inside the DL
+                    restoreTo = canvas.save();
+                }
+                // mAttachInfo cannot be null, otherwise scalingRequired == false
+                final float scale = 1.0f / mAttachInfo.mApplicationScale;
+                canvas.scale(scale, scale);
+            }
+        }
+
+        float alpha = drawingWithRenderNode ? 1 : (getAlpha() * getTransitionAlpha());
+        if (transformToApply != null
+                || alpha < 1
+                || !hasIdentityMatrix()
+                || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {
+            if (transformToApply != null || !childHasIdentityMatrix) {
+                int transX = 0;
+                int transY = 0;
+
+                if (offsetForScroll) {
+                    transX = -sx;
+                    transY = -sy;
+                }
+
+                if (transformToApply != null) {
+                    if (concatMatrix) {
+                        if (drawingWithRenderNode) {
+                            renderNode.setAnimationMatrix(transformToApply.getMatrix());
+                        } else {
+                            // Undo the scroll translation, apply the transformation matrix,
+                            // then redo the scroll translate to get the correct result.
+                            canvas.translate(-transX, -transY);
+                            canvas.concat(transformToApply.getMatrix());
+                            canvas.translate(transX, transY);
+                        }
+                        parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
+                    }
+
+                    float transformAlpha = transformToApply.getAlpha();
+                    if (transformAlpha < 1) {
+                        alpha *= transformAlpha;
+                        parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
+                    }
+                }
+
+                if (!childHasIdentityMatrix && !drawingWithRenderNode) {
+                    canvas.translate(-transX, -transY);
+                    canvas.concat(getMatrix());
+                    canvas.translate(transX, transY);
+                }
+            }
+
+            // Deal with alpha if it is or used to be <1
+            if (alpha < 1 || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {
+                if (alpha < 1) {
+                    mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_ALPHA;
+                } else {
+                    mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_ALPHA;
+                }
+                parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
+                if (!drawingWithDrawingCache) {
+                    final int multipliedAlpha = (int) (255 * alpha);
+                    if (!onSetAlpha(multipliedAlpha)) {
+                        if (drawingWithRenderNode) {
+                            renderNode.setAlpha(alpha * getAlpha() * getTransitionAlpha());
+                        } else if (layerType == LAYER_TYPE_NONE) {
+                            canvas.saveLayerAlpha(sx, sy, sx + getWidth(), sy + getHeight(),
+                                    multipliedAlpha);
+                        }
+                    } else {
+                        // Alpha is handled by the child directly, clobber the layer's alpha
+                        mPrivateFlags |= PFLAG_ALPHA_SET;
+                    }
+                }
+            }
+        } else if ((mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) {
+            onSetAlpha(255);
+            mPrivateFlags &= ~PFLAG_ALPHA_SET;
+        }
+
+        if (!drawingWithRenderNode) {
+            // apply clips directly, since RenderNode won't do it for this draw
+            if ((parentFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0 && cache == null) {
+                if (offsetForScroll) {
+                    canvas.clipRect(sx, sy, sx + getWidth(), sy + getHeight());
+                } else {
+                    if (!scalingRequired || cache == null) {
+                        canvas.clipRect(0, 0, getWidth(), getHeight());
+                    } else {
+                        canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight());
+                    }
+                }
+            }
+
+            if (mClipBounds != null) {
+                // clip bounds ignore scroll
+                canvas.clipRect(mClipBounds);
+            }
+        }
+
+        if (!drawingWithDrawingCache) {
+            if (drawingWithRenderNode) {
+                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
+                ((RecordingCanvas) canvas).drawRenderNode(renderNode);
+            } else {
+                // Fast path for layouts with no backgrounds
+                if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
+                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;
+                    dispatchDraw(canvas);
+                } else {
+                    draw(canvas);
+                }
+            }
+        } else if (cache != null) {
+            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
+            if (layerType == LAYER_TYPE_NONE || mLayerPaint == null) {
+                // no layer paint, use temporary paint to draw bitmap
+                Paint cachePaint = parent.mCachePaint;
+                if (cachePaint == null) {
+                    cachePaint = new Paint();
+                    cachePaint.setDither(false);
+                    parent.mCachePaint = cachePaint;
+                }
+                cachePaint.setAlpha((int) (alpha * 255));
+                canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
+            } else {
+                // use layer paint to draw the bitmap, merging the two alphas, but also restore
+                int layerPaintAlpha = mLayerPaint.getAlpha();
+                if (alpha < 1) {
+                    mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
+                }
+                canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
+                if (alpha < 1) {
+                    mLayerPaint.setAlpha(layerPaintAlpha);
+                }
+            }
+        }
+
+        if (restoreTo >= 0) {
+            canvas.restoreToCount(restoreTo);
+        }
+
+        if (a != null && !more) {
+            if (!hardwareAcceleratedCanvas && !a.getFillAfter()) {
+                onSetAlpha(255);
+            }
+            parent.finishAnimatingView(this, a);
+        }
+
+        if (more && hardwareAcceleratedCanvas) {
+            if (a.hasAlpha() && (mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) {
+                // alpha animations should cause the child to recreate its display list
+                invalidate(true);
+            }
+        }
+
+        mRecreateDisplayList = false;
+
+        return more;
+    }
+
+    static Paint getDebugPaint() {
+        if (sDebugPaint == null) {
+            sDebugPaint = new Paint();
+            sDebugPaint.setAntiAlias(false);
+        }
+        return sDebugPaint;
+    }
+
+    final int dipsToPixels(int dips) {
+        float scale = getContext().getResources().getDisplayMetrics().density;
+        return (int) (dips * scale + 0.5f);
+    }
+
+    final private void debugDrawFocus(Canvas canvas) {
+        if (isFocused()) {
+            final int cornerSquareSize = dipsToPixels(DEBUG_CORNERS_SIZE_DIP);
+            final int l = mScrollX;
+            final int r = l + mRight - mLeft;
+            final int t = mScrollY;
+            final int b = t + mBottom - mTop;
+
+            final Paint paint = getDebugPaint();
+            paint.setColor(DEBUG_CORNERS_COLOR);
+
+            // Draw squares in corners.
+            paint.setStyle(Paint.Style.FILL);
+            canvas.drawRect(l, t, l + cornerSquareSize, t + cornerSquareSize, paint);
+            canvas.drawRect(r - cornerSquareSize, t, r, t + cornerSquareSize, paint);
+            canvas.drawRect(l, b - cornerSquareSize, l + cornerSquareSize, b, paint);
+            canvas.drawRect(r - cornerSquareSize, b - cornerSquareSize, r, b, paint);
+
+            // Draw big X across the view.
+            paint.setStyle(Paint.Style.STROKE);
+            canvas.drawLine(l, t, r, b, paint);
+            canvas.drawLine(l, b, r, t, paint);
+        }
+    }
+
+    /**
+     * Manually render this view (and all of its children) to the given Canvas.
+     * The view must have already done a full layout before this function is
+     * called.  When implementing a view, implement
+     * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
+     * If you do need to override this method, call the superclass version.
+     *
+     * @param canvas The Canvas to which the View is rendered.
+     */
+    @CallSuper
+    public void draw(Canvas canvas) {
+        final int privateFlags = mPrivateFlags;
+        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
+
+        /*
+         * Draw traversal performs several drawing steps which must be executed
+         * in the appropriate order:
+         *
+         *      1. Draw the background
+         *      2. If necessary, save the canvas' layers to prepare for fading
+         *      3. Draw view's content
+         *      4. Draw children
+         *      5. If necessary, draw the fading edges and restore layers
+         *      6. Draw decorations (scrollbars for instance)
+         *      7. If necessary, draw the default focus highlight
+         */
+
+        // Step 1, draw the background, if needed
+        int saveCount;
+
+        drawBackground(canvas);
+
+        // skip step 2 & 5 if possible (common case)
+        final int viewFlags = mViewFlags;
+        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
+        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
+        if (!verticalEdges && !horizontalEdges) {
+            // Step 3, draw the content
+            onDraw(canvas);
+
+            // Step 4, draw the children
+            dispatchDraw(canvas);
+
+            drawAutofilledHighlight(canvas);
+
+            // Overlay is part of the content and draws beneath Foreground
+            if (mOverlay != null && !mOverlay.isEmpty()) {
+                mOverlay.getOverlayView().dispatchDraw(canvas);
+            }
+
+            // Step 6, draw decorations (foreground, scrollbars)
+            onDrawForeground(canvas);
+
+            // Step 7, draw the default focus highlight
+            drawDefaultFocusHighlight(canvas);
+
+            if (isShowingLayoutBounds()) {
+                debugDrawFocus(canvas);
+            }
+
+            // we're done...
+            return;
+        }
+
+        /*
+         * Here we do the full fledged routine...
+         * (this is an uncommon case where speed matters less,
+         * this is why we repeat some of the tests that have been
+         * done above)
+         */
+
+        boolean drawTop = false;
+        boolean drawBottom = false;
+        boolean drawLeft = false;
+        boolean drawRight = false;
+
+        float topFadeStrength = 0.0f;
+        float bottomFadeStrength = 0.0f;
+        float leftFadeStrength = 0.0f;
+        float rightFadeStrength = 0.0f;
+
+        // Step 2, save the canvas' layers
+        int paddingLeft = mPaddingLeft;
+
+        final boolean offsetRequired = isPaddingOffsetRequired();
+        if (offsetRequired) {
+            paddingLeft += getLeftPaddingOffset();
+        }
+
+        int left = mScrollX + paddingLeft;
+        int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
+        int top = mScrollY + getFadeTop(offsetRequired);
+        int bottom = top + getFadeHeight(offsetRequired);
+
+        if (offsetRequired) {
+            right += getRightPaddingOffset();
+            bottom += getBottomPaddingOffset();
+        }
+
+        final ScrollabilityCache scrollabilityCache = mScrollCache;
+        final float fadeHeight = scrollabilityCache.fadingEdgeLength;
+        int length = (int) fadeHeight;
+
+        // clip the fade length if top and bottom fades overlap
+        // overlapping fades produce odd-looking artifacts
+        if (verticalEdges && (top + length > bottom - length)) {
+            length = (bottom - top) / 2;
+        }
+
+        // also clip horizontal fades if necessary
+        if (horizontalEdges && (left + length > right - length)) {
+            length = (right - left) / 2;
+        }
+
+        if (verticalEdges) {
+            topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
+            drawTop = topFadeStrength * fadeHeight > 1.0f;
+            bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
+            drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
+        }
+
+        if (horizontalEdges) {
+            leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
+            drawLeft = leftFadeStrength * fadeHeight > 1.0f;
+            rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
+            drawRight = rightFadeStrength * fadeHeight > 1.0f;
+        }
+
+        saveCount = canvas.getSaveCount();
+        int topSaveCount = -1;
+        int bottomSaveCount = -1;
+        int leftSaveCount = -1;
+        int rightSaveCount = -1;
+
+        int solidColor = getSolidColor();
+        if (solidColor == 0) {
+            if (drawTop) {
+                topSaveCount = canvas.saveUnclippedLayer(left, top, right, top + length);
+            }
+
+            if (drawBottom) {
+                bottomSaveCount = canvas.saveUnclippedLayer(left, bottom - length, right, bottom);
+            }
+
+            if (drawLeft) {
+                leftSaveCount = canvas.saveUnclippedLayer(left, top, left + length, bottom);
+            }
+
+            if (drawRight) {
+                rightSaveCount = canvas.saveUnclippedLayer(right - length, top, right, bottom);
+            }
+        } else {
+            scrollabilityCache.setFadeColor(solidColor);
+        }
+
+        // Step 3, draw the content
+        onDraw(canvas);
+
+        // Step 4, draw the children
+        dispatchDraw(canvas);
+
+        // Step 5, draw the fade effect and restore layers
+        final Paint p = scrollabilityCache.paint;
+        final Matrix matrix = scrollabilityCache.matrix;
+        final Shader fade = scrollabilityCache.shader;
+
+        // must be restored in the reverse order that they were saved
+        if (drawRight) {
+            matrix.setScale(1, fadeHeight * rightFadeStrength);
+            matrix.postRotate(90);
+            matrix.postTranslate(right, top);
+            fade.setLocalMatrix(matrix);
+            p.setShader(fade);
+            if (solidColor == 0) {
+                canvas.restoreUnclippedLayer(rightSaveCount, p);
+
+            } else {
+                canvas.drawRect(right - length, top, right, bottom, p);
+            }
+        }
+
+        if (drawLeft) {
+            matrix.setScale(1, fadeHeight * leftFadeStrength);
+            matrix.postRotate(-90);
+            matrix.postTranslate(left, top);
+            fade.setLocalMatrix(matrix);
+            p.setShader(fade);
+            if (solidColor == 0) {
+                canvas.restoreUnclippedLayer(leftSaveCount, p);
+            } else {
+                canvas.drawRect(left, top, left + length, bottom, p);
+            }
+        }
+
+        if (drawBottom) {
+            matrix.setScale(1, fadeHeight * bottomFadeStrength);
+            matrix.postRotate(180);
+            matrix.postTranslate(left, bottom);
+            fade.setLocalMatrix(matrix);
+            p.setShader(fade);
+            if (solidColor == 0) {
+                canvas.restoreUnclippedLayer(bottomSaveCount, p);
+            } else {
+                canvas.drawRect(left, bottom - length, right, bottom, p);
+            }
+        }
+
+        if (drawTop) {
+            matrix.setScale(1, fadeHeight * topFadeStrength);
+            matrix.postTranslate(left, top);
+            fade.setLocalMatrix(matrix);
+            p.setShader(fade);
+            if (solidColor == 0) {
+                canvas.restoreUnclippedLayer(topSaveCount, p);
+            } else {
+                canvas.drawRect(left, top, right, top + length, p);
+            }
+        }
+
+        canvas.restoreToCount(saveCount);
+
+        drawAutofilledHighlight(canvas);
+
+        // Overlay is part of the content and draws beneath Foreground
+        if (mOverlay != null && !mOverlay.isEmpty()) {
+            mOverlay.getOverlayView().dispatchDraw(canvas);
+        }
+
+        // Step 6, draw decorations (foreground, scrollbars)
+        onDrawForeground(canvas);
+
+        // Step 7, draw the default focus highlight
+        drawDefaultFocusHighlight(canvas);
+
+        if (isShowingLayoutBounds()) {
+            debugDrawFocus(canvas);
+        }
+    }
+
+    /**
+     * Draws the background onto the specified canvas.
+     *
+     * @param canvas Canvas on which to draw the background
+     */
+    @UnsupportedAppUsage
+    private void drawBackground(Canvas canvas) {
+        final Drawable background = mBackground;
+        if (background == null) {
+            return;
+        }
+
+        setBackgroundBounds();
+
+        // Attempt to use a display list if requested.
+        if (canvas.isHardwareAccelerated() && mAttachInfo != null
+                && mAttachInfo.mThreadedRenderer != null) {
+            mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);
+
+            final RenderNode renderNode = mBackgroundRenderNode;
+            if (renderNode != null && renderNode.hasDisplayList()) {
+                setBackgroundRenderNodeProperties(renderNode);
+                ((RecordingCanvas) canvas).drawRenderNode(renderNode);
+                return;
+            }
+        }
+
+        final int scrollX = mScrollX;
+        final int scrollY = mScrollY;
+        if ((scrollX | scrollY) == 0) {
+            background.draw(canvas);
+        } else {
+            canvas.translate(scrollX, scrollY);
+            background.draw(canvas);
+            canvas.translate(-scrollX, -scrollY);
+        }
+    }
+
+    /**
+     * Sets the correct background bounds and rebuilds the outline, if needed.
+     * <p/>
+     * This is called by LayoutLib.
+     */
+    void setBackgroundBounds() {
+        if (mBackgroundSizeChanged && mBackground != null) {
+            mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
+            mBackgroundSizeChanged = false;
+            rebuildOutline();
+        }
+    }
+
+    private void setBackgroundRenderNodeProperties(RenderNode renderNode) {
+        renderNode.setTranslationX(mScrollX);
+        renderNode.setTranslationY(mScrollY);
+    }
+
+    /**
+     * Creates a new display list or updates the existing display list for the
+     * specified Drawable.
+     *
+     * @param drawable Drawable for which to create a display list
+     * @param renderNode Existing RenderNode, or {@code null}
+     * @return A valid display list for the specified drawable
+     */
+    private RenderNode getDrawableRenderNode(Drawable drawable, RenderNode renderNode) {
+        if (renderNode == null) {
+            renderNode = RenderNode.create(drawable.getClass().getName(),
+                    new ViewAnimationHostBridge(this));
+            renderNode.setUsageHint(RenderNode.USAGE_BACKGROUND);
+        }
+
+        final Rect bounds = drawable.getBounds();
+        final int width = bounds.width();
+        final int height = bounds.height();
+
+        // Hacky hack: Reset any stretch effects as those are applied during the draw pass
+        // instead of being "stateful" like other RenderNode properties
+        renderNode.clearStretch();
+
+        final RecordingCanvas canvas = renderNode.beginRecording(width, height);
+
+        // Reverse left/top translation done by drawable canvas, which will
+        // instead be applied by rendernode's LTRB bounds below. This way, the
+        // drawable's bounds match with its rendernode bounds and its content
+        // will lie within those bounds in the rendernode tree.
+        canvas.translate(-bounds.left, -bounds.top);
+
+        try {
+            drawable.draw(canvas);
+        } finally {
+            renderNode.endRecording();
+        }
+
+        // Set up drawable properties that are view-independent.
+        renderNode.setLeftTopRightBottom(bounds.left, bounds.top, bounds.right, bounds.bottom);
+        renderNode.setProjectBackwards(drawable.isProjected());
+        renderNode.setProjectionReceiver(true);
+        renderNode.setClipToBounds(false);
+        return renderNode;
+    }
+
+    /**
+     * Returns the overlay for this view, creating it if it does not yet exist.
+     * Adding drawables to the overlay will cause them to be displayed whenever
+     * the view itself is redrawn. Objects in the overlay should be actively
+     * managed: remove them when they should not be displayed anymore. The
+     * overlay will always have the same size as its host view.
+     *
+     * <p>Note: Overlays do not currently work correctly with {@link
+     * SurfaceView} or {@link TextureView}; contents in overlays for these
+     * types of views may not display correctly.</p>
+     *
+     * @return The ViewOverlay object for this view.
+     * @see ViewOverlay
+     */
+    public ViewOverlay getOverlay() {
+        if (mOverlay == null) {
+            mOverlay = new ViewOverlay(mContext, this);
+        }
+        return mOverlay;
+    }
+
+    /**
+     * Override this if your view is known to always be drawn on top of a solid color background,
+     * and needs to draw fading edges. Returning a non-zero color enables the view system to
+     * optimize the drawing of the fading edges. If you do return a non-zero color, the alpha
+     * should be set to 0xFF.
+     *
+     * @see #setVerticalFadingEdgeEnabled(boolean)
+     * @see #setHorizontalFadingEdgeEnabled(boolean)
+     *
+     * @return The known solid color background for this view, or 0 if the color may vary
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    @InspectableProperty
+    @ColorInt
+    public int getSolidColor() {
+        return 0;
+    }
+
+    /**
+     * Build a human readable string representation of the specified view flags.
+     *
+     * @param flags the view flags to convert to a string
+     * @return a String representing the supplied flags
+     */
+    private static String printFlags(int flags) {
+        String output = "";
+        int numFlags = 0;
+        if ((flags & FOCUSABLE) == FOCUSABLE) {
+            output += "TAKES_FOCUS";
+            numFlags++;
+        }
+
+        switch (flags & VISIBILITY_MASK) {
+        case INVISIBLE:
+            if (numFlags > 0) {
+                output += " ";
+            }
+            output += "INVISIBLE";
+            // USELESS HERE numFlags++;
+            break;
+        case GONE:
+            if (numFlags > 0) {
+                output += " ";
+            }
+            output += "GONE";
+            // USELESS HERE numFlags++;
+            break;
+        default:
+            break;
+        }
+        return output;
+    }
+
+    /**
+     * Build a human readable string representation of the specified private
+     * view flags.
+     *
+     * @param privateFlags the private view flags to convert to a string
+     * @return a String representing the supplied flags
+     */
+    private static String printPrivateFlags(int privateFlags) {
+        String output = "";
+        int numFlags = 0;
+
+        if ((privateFlags & PFLAG_WANTS_FOCUS) == PFLAG_WANTS_FOCUS) {
+            output += "WANTS_FOCUS";
+            numFlags++;
+        }
+
+        if ((privateFlags & PFLAG_FOCUSED) == PFLAG_FOCUSED) {
+            if (numFlags > 0) {
+                output += " ";
+            }
+            output += "FOCUSED";
+            numFlags++;
+        }
+
+        if ((privateFlags & PFLAG_SELECTED) == PFLAG_SELECTED) {
+            if (numFlags > 0) {
+                output += " ";
+            }
+            output += "SELECTED";
+            numFlags++;
+        }
+
+        if ((privateFlags & PFLAG_IS_ROOT_NAMESPACE) == PFLAG_IS_ROOT_NAMESPACE) {
+            if (numFlags > 0) {
+                output += " ";
+            }
+            output += "IS_ROOT_NAMESPACE";
+            numFlags++;
+        }
+
+        if ((privateFlags & PFLAG_HAS_BOUNDS) == PFLAG_HAS_BOUNDS) {
+            if (numFlags > 0) {
+                output += " ";
+            }
+            output += "HAS_BOUNDS";
+            numFlags++;
+        }
+
+        if ((privateFlags & PFLAG_DRAWN) == PFLAG_DRAWN) {
+            if (numFlags > 0) {
+                output += " ";
+            }
+            output += "DRAWN";
+            // USELESS HERE numFlags++;
+        }
+        return output;
+    }
+
+    /**
+     * <p>Indicates whether or not this view's layout will be requested during
+     * the next hierarchy layout pass.</p>
+     *
+     * @return true if the layout will be forced during next layout pass
+     */
+    public boolean isLayoutRequested() {
+        return (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
+    }
+
+    /**
+     * Return true if o is a ViewGroup that is laying out using optical bounds.
+     * @hide
+     */
+    public static boolean isLayoutModeOptical(Object o) {
+        return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();
+    }
+
+    private boolean setOpticalFrame(int left, int top, int right, int bottom) {
+        Insets parentInsets = mParent instanceof View ?
+                ((View) mParent).getOpticalInsets() : Insets.NONE;
+        Insets childInsets = getOpticalInsets();
+        return setFrame(
+                left   + parentInsets.left - childInsets.left,
+                top    + parentInsets.top  - childInsets.top,
+                right  + parentInsets.left + childInsets.right,
+                bottom + parentInsets.top  + childInsets.bottom);
+    }
+
+    /**
+     * Assign a size and position to a view and all of its
+     * descendants
+     *
+     * <p>This is the second phase of the layout mechanism.
+     * (The first is measuring). In this phase, each parent calls
+     * layout on all of its children to position them.
+     * This is typically done using the child measurements
+     * that were stored in the measure pass().</p>
+     *
+     * <p>Derived classes should not override this method.
+     * Derived classes with children should override
+     * onLayout. In that method, they should
+     * call layout on each of their children.</p>
+     *
+     * @param l Left position, relative to parent
+     * @param t Top position, relative to parent
+     * @param r Right position, relative to parent
+     * @param b Bottom position, relative to parent
+     */
+    @SuppressWarnings({"unchecked"})
+    public void layout(int l, int t, int r, int b) {
+        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
+            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
+            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
+        }
+
+        int oldL = mLeft;
+        int oldT = mTop;
+        int oldB = mBottom;
+        int oldR = mRight;
+
+        boolean changed = isLayoutModeOptical(mParent) ?
+                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
+
+        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
+            onLayout(changed, l, t, r, b);
+
+            if (shouldDrawRoundScrollbar()) {
+                if(mRoundScrollbarRenderer == null) {
+                    mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
+                }
+            } else {
+                mRoundScrollbarRenderer = null;
+            }
+
+            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
+
+            ListenerInfo li = mListenerInfo;
+            if (li != null && li.mOnLayoutChangeListeners != null) {
+                ArrayList<OnLayoutChangeListener> listenersCopy =
+                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
+                int numListeners = listenersCopy.size();
+                for (int i = 0; i < numListeners; ++i) {
+                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
+                }
+            }
+        }
+
+        final boolean wasLayoutValid = isLayoutValid();
+
+        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
+        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
+
+        if (!wasLayoutValid && isFocused()) {
+            mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
+            if (canTakeFocus()) {
+                // We have a robust focus, so parents should no longer be wanting focus.
+                clearParentsWantFocus();
+            } else if (getViewRootImpl() == null || !getViewRootImpl().isInLayout()) {
+                // This is a weird case. Most-likely the user, rather than ViewRootImpl, called
+                // layout. In this case, there's no guarantee that parent layouts will be evaluated
+                // and thus the safest action is to clear focus here.
+                clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
+                clearParentsWantFocus();
+            } else if (!hasParentWantsFocus()) {
+                // original requestFocus was likely on this view directly, so just clear focus
+                clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
+            }
+            // otherwise, we let parents handle re-assigning focus during their layout passes.
+        } else if ((mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) {
+            mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
+            View focused = findFocus();
+            if (focused != null) {
+                // Try to restore focus as close as possible to our starting focus.
+                if (!restoreDefaultFocus() && !hasParentWantsFocus()) {
+                    // Give up and clear focus once we've reached the top-most parent which wants
+                    // focus.
+                    focused.clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
+                }
+            }
+        }
+
+        if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
+            mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
+            notifyEnterOrExitForAutoFillIfNeeded(true);
+        }
+
+        notifyAppearedOrDisappearedForContentCaptureIfNeeded(true);
+    }
+
+    private boolean hasParentWantsFocus() {
+        ViewParent parent = mParent;
+        while (parent instanceof ViewGroup) {
+            ViewGroup pv = (ViewGroup) parent;
+            if ((pv.mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) {
+                return true;
+            }
+            parent = pv.mParent;
+        }
+        return false;
+    }
+
+    /**
+     * Called from layout when this view should
+     * assign a size and position to each of its children.
+     *
+     * Derived classes with children should override
+     * this method and call layout on each of
+     * their children.
+     * @param changed This is a new size or position for this view
+     * @param left Left position, relative to parent
+     * @param top Top position, relative to parent
+     * @param right Right position, relative to parent
+     * @param bottom Bottom position, relative to parent
+     */
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+    }
+
+    /**
+     * Assign a size and position to this view.
+     *
+     * This is called from layout.
+     *
+     * @param left Left position, relative to parent
+     * @param top Top position, relative to parent
+     * @param right Right position, relative to parent
+     * @param bottom Bottom position, relative to parent
+     * @return true if the new size and position are different than the
+     *         previous ones
+     * {@hide}
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    protected boolean setFrame(int left, int top, int right, int bottom) {
+        boolean changed = false;
+
+        if (DBG) {
+            Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + ","
+                    + right + "," + bottom + ")");
+        }
+
+        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
+            changed = true;
+
+            // Remember our drawn bit
+            int drawn = mPrivateFlags & PFLAG_DRAWN;
+
+            int oldWidth = mRight - mLeft;
+            int oldHeight = mBottom - mTop;
+            int newWidth = right - left;
+            int newHeight = bottom - top;
+            boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
+
+            // Invalidate our old position
+            invalidate(sizeChanged);
+
+            mLeft = left;
+            mTop = top;
+            mRight = right;
+            mBottom = bottom;
+            mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
+
+            mPrivateFlags |= PFLAG_HAS_BOUNDS;
+
+
+            if (sizeChanged) {
+                sizeChange(newWidth, newHeight, oldWidth, oldHeight);
+            }
+
+            if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
+                // If we are visible, force the DRAWN bit to on so that
+                // this invalidate will go through (at least to our parent).
+                // This is because someone may have invalidated this view
+                // before this call to setFrame came in, thereby clearing
+                // the DRAWN bit.
+                mPrivateFlags |= PFLAG_DRAWN;
+                invalidate(sizeChanged);
+                // parent display list may need to be recreated based on a change in the bounds
+                // of any child
+                invalidateParentCaches();
+            }
+
+            // Reset drawn bit to original value (invalidate turns it off)
+            mPrivateFlags |= drawn;
+
+            mBackgroundSizeChanged = true;
+            mDefaultFocusHighlightSizeChanged = true;
+            if (mForegroundInfo != null) {
+                mForegroundInfo.mBoundsChanged = true;
+            }
+
+            notifySubtreeAccessibilityStateChangedIfNeeded();
+        }
+        return changed;
+    }
+
+    /**
+     * Assign a size and position to this view.
+     *
+     * This method is meant to be used in animations only as it applies this position and size
+     * for the view only temporary and it can be changed back at any time by the layout.
+     *
+     * @param left Left position, relative to parent
+     * @param top Top position, relative to parent
+     * @param right Right position, relative to parent
+     * @param bottom Bottom position, relative to parent
+     *
+     * @see #setLeft(int), #setRight(int), #setTop(int), #setBottom(int)
+     */
+    public final void setLeftTopRightBottom(int left, int top, int right, int bottom) {
+        setFrame(left, top, right, bottom);
+    }
+
+    private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) {
+        onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
+        if (mOverlay != null) {
+            mOverlay.getOverlayView().setRight(newWidth);
+            mOverlay.getOverlayView().setBottom(newHeight);
+        }
+        // If this isn't laid out yet, focus assignment will be handled during the "deferment/
+        // backtracking" of requestFocus during layout, so don't touch focus here.
+        if (!sCanFocusZeroSized && isLayoutValid()
+                // Don't touch focus if animating
+                && !(mParent instanceof ViewGroup && ((ViewGroup) mParent).isLayoutSuppressed())) {
+            if (newWidth <= 0 || newHeight <= 0) {
+                if (hasFocus()) {
+                    clearFocus();
+                    if (mParent instanceof ViewGroup) {
+                        ((ViewGroup) mParent).clearFocusedInCluster();
+                    }
+                }
+                clearAccessibilityFocus();
+            } else if (oldWidth <= 0 || oldHeight <= 0) {
+                if (mParent != null && canTakeFocus()) {
+                    mParent.focusableViewAvailable(this);
+                }
+            }
+        }
+        rebuildOutline();
+    }
+
+    /**
+     * Finalize inflating a view from XML.  This is called as the last phase
+     * of inflation, after all child views have been added.
+     *
+     * <p>Even if the subclass overrides onFinishInflate, they should always be
+     * sure to call the super method, so that we get called.
+     */
+    @CallSuper
+    protected void onFinishInflate() {
+    }
+
+    /**
+     * Returns the resources associated with this view.
+     *
+     * @return Resources object.
+     */
+    public Resources getResources() {
+        return mResources;
+    }
+
+    /**
+     * Invalidates the specified Drawable.
+     *
+     * @param drawable the drawable to invalidate
+     */
+    @Override
+    public void invalidateDrawable(@NonNull Drawable drawable) {
+        if (verifyDrawable(drawable)) {
+            final Rect dirty = drawable.getDirtyBounds();
+            final int scrollX = mScrollX;
+            final int scrollY = mScrollY;
+
+            invalidate(dirty.left + scrollX, dirty.top + scrollY,
+                    dirty.right + scrollX, dirty.bottom + scrollY);
+            rebuildOutline();
+        }
+    }
+
+    /**
+     * Schedules an action on a drawable to occur at a specified time.
+     *
+     * @param who the recipient of the action
+     * @param what the action to run on the drawable
+     * @param when the time at which the action must occur. Uses the
+     *        {@link SystemClock#uptimeMillis} timebase.
+     */
+    @Override
+    public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
+        if (verifyDrawable(who) && what != null) {
+            final long delay = when - SystemClock.uptimeMillis();
+            if (mAttachInfo != null) {
+                mAttachInfo.mViewRootImpl.mChoreographer.postCallbackDelayed(
+                        Choreographer.CALLBACK_ANIMATION, what, who,
+                        Choreographer.subtractFrameDelay(delay));
+            } else {
+                // Postpone the runnable until we know
+                // on which thread it needs to run.
+                getRunQueue().postDelayed(what, delay);
+            }
+        }
+    }
+
+    /**
+     * Cancels a scheduled action on a drawable.
+     *
+     * @param who the recipient of the action
+     * @param what the action to cancel
+     */
+    @Override
+    public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
+        if (verifyDrawable(who) && what != null) {
+            if (mAttachInfo != null) {
+                mAttachInfo.mViewRootImpl.mChoreographer.removeCallbacks(
+                        Choreographer.CALLBACK_ANIMATION, what, who);
+            }
+            getRunQueue().removeCallbacks(what);
+        }
+    }
+
+    /**
+     * Unschedule any events associated with the given Drawable.  This can be
+     * used when selecting a new Drawable into a view, so that the previous
+     * one is completely unscheduled.
+     *
+     * @param who The Drawable to unschedule.
+     *
+     * @see #drawableStateChanged
+     */
+    public void unscheduleDrawable(Drawable who) {
+        if (mAttachInfo != null && who != null) {
+            mAttachInfo.mViewRootImpl.mChoreographer.removeCallbacks(
+                    Choreographer.CALLBACK_ANIMATION, null, who);
+        }
+    }
+
+    /**
+     * Resolve the Drawables depending on the layout direction. This is implicitly supposing
+     * that the View directionality can and will be resolved before its Drawables.
+     *
+     * Will call {@link View#onResolveDrawables} when resolution is done.
+     *
+     * @hide
+     */
+    protected void resolveDrawables() {
+        // Drawables resolution may need to happen before resolving the layout direction (which is
+        // done only during the measure() call).
+        // If the layout direction is not resolved yet, we cannot resolve the Drawables except in
+        // one case: when the raw layout direction has not been defined as LAYOUT_DIRECTION_INHERIT.
+        // So, if the raw layout direction is LAYOUT_DIRECTION_LTR or LAYOUT_DIRECTION_RTL or
+        // LAYOUT_DIRECTION_LOCALE, we can "cheat" and we don't need to wait for the layout
+        // direction to be resolved as its resolved value will be the same as its raw value.
+        if (!isLayoutDirectionResolved() &&
+                getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT) {
+            return;
+        }
+
+        final int layoutDirection = isLayoutDirectionResolved() ?
+                getLayoutDirection() : getRawLayoutDirection();
+
+        if (mBackground != null) {
+            mBackground.setLayoutDirection(layoutDirection);
+        }
+        if (mForegroundInfo != null && mForegroundInfo.mDrawable != null) {
+            mForegroundInfo.mDrawable.setLayoutDirection(layoutDirection);
+        }
+        if (mDefaultFocusHighlight != null) {
+            mDefaultFocusHighlight.setLayoutDirection(layoutDirection);
+        }
+        mPrivateFlags2 |= PFLAG2_DRAWABLE_RESOLVED;
+        onResolveDrawables(layoutDirection);
+    }
+
+    boolean areDrawablesResolved() {
+        return (mPrivateFlags2 & PFLAG2_DRAWABLE_RESOLVED) == PFLAG2_DRAWABLE_RESOLVED;
+    }
+
+    /**
+     * Called when layout direction has been resolved.
+     *
+     * The default implementation does nothing.
+     *
+     * @param layoutDirection The resolved layout direction.
+     *
+     * @see #LAYOUT_DIRECTION_LTR
+     * @see #LAYOUT_DIRECTION_RTL
+     *
+     * @hide
+     */
+    public void onResolveDrawables(@ResolvedLayoutDir int layoutDirection) {
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    protected void resetResolvedDrawables() {
+        resetResolvedDrawablesInternal();
+    }
+
+    void resetResolvedDrawablesInternal() {
+        mPrivateFlags2 &= ~PFLAG2_DRAWABLE_RESOLVED;
+    }
+
+    /**
+     * If your view subclass is displaying its own Drawable objects, it should
+     * override this function and return true for any Drawable it is
+     * displaying.  This allows animations for those drawables to be
+     * scheduled.
+     *
+     * <p>Be sure to call through to the super class when overriding this
+     * function.
+     *
+     * @param who The Drawable to verify.  Return true if it is one you are
+     *            displaying, else return the result of calling through to the
+     *            super class.
+     *
+     * @return boolean If true then the Drawable is being displayed in the
+     *         view; else false and it is not allowed to animate.
+     *
+     * @see #unscheduleDrawable(android.graphics.drawable.Drawable)
+     * @see #drawableStateChanged()
+     */
+    @CallSuper
+    protected boolean verifyDrawable(@NonNull Drawable who) {
+        // Avoid verifying the scroll bar drawable so that we don't end up in
+        // an invalidation loop. This effectively prevents the scroll bar
+        // drawable from triggering invalidations and scheduling runnables.
+        return who == mBackground || (mForegroundInfo != null && mForegroundInfo.mDrawable == who)
+                || (mDefaultFocusHighlight == who);
+    }
+
+    /**
+     * This function is called whenever the state of the view changes in such
+     * a way that it impacts the state of drawables being shown.
+     * <p>
+     * If the View has a StateListAnimator, it will also be called to run necessary state
+     * change animations.
+     * <p>
+     * Be sure to call through to the superclass when overriding this function.
+     *
+     * @see Drawable#setState(int[])
+     */
+    @CallSuper
+    protected void drawableStateChanged() {
+        final int[] state = getDrawableState();
+        boolean changed = false;
+
+        final Drawable bg = mBackground;
+        if (bg != null && bg.isStateful()) {
+            changed |= bg.setState(state);
+        }
+
+        final Drawable hl = mDefaultFocusHighlight;
+        if (hl != null && hl.isStateful()) {
+            changed |= hl.setState(state);
+        }
+
+        final Drawable fg = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
+        if (fg != null && fg.isStateful()) {
+            changed |= fg.setState(state);
+        }
+
+        if (mScrollCache != null) {
+            final Drawable scrollBar = mScrollCache.scrollBar;
+            if (scrollBar != null && scrollBar.isStateful()) {
+                changed |= scrollBar.setState(state)
+                        && mScrollCache.state != ScrollabilityCache.OFF;
+            }
+        }
+
+        if (mStateListAnimator != null) {
+            mStateListAnimator.setState(state);
+        }
+
+        if (!isAggregatedVisible()) {
+            // If we're not visible, skip any animated changes
+            jumpDrawablesToCurrentState();
+        }
+
+        if (changed) {
+            invalidate();
+        }
+    }
+
+    /**
+     * This function is called whenever the view hotspot changes and needs to
+     * be propagated to drawables or child views managed by the view.
+     * <p>
+     * Dispatching to child views is handled by
+     * {@link #dispatchDrawableHotspotChanged(float, float)}.
+     * <p>
+     * Be sure to call through to the superclass when overriding this function.
+     *
+     * @param x hotspot x coordinate
+     * @param y hotspot y coordinate
+     */
+    @CallSuper
+    public void drawableHotspotChanged(float x, float y) {
+        if (mBackground != null) {
+            mBackground.setHotspot(x, y);
+        }
+        if (mDefaultFocusHighlight != null) {
+            mDefaultFocusHighlight.setHotspot(x, y);
+        }
+        if (mForegroundInfo != null && mForegroundInfo.mDrawable != null) {
+            mForegroundInfo.mDrawable.setHotspot(x, y);
+        }
+
+        dispatchDrawableHotspotChanged(x, y);
+    }
+
+    /**
+     * Dispatches drawableHotspotChanged to all of this View's children.
+     *
+     * @param x hotspot x coordinate
+     * @param y hotspot y coordinate
+     * @see #drawableHotspotChanged(float, float)
+     */
+    public void dispatchDrawableHotspotChanged(float x, float y) {
+    }
+
+    /**
+     * Call this to force a view to update its drawable state. This will cause
+     * drawableStateChanged to be called on this view. Views that are interested
+     * in the new state should call getDrawableState.
+     *
+     * @see #drawableStateChanged
+     * @see #getDrawableState
+     */
+    public void refreshDrawableState() {
+        mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
+        drawableStateChanged();
+
+        ViewParent parent = mParent;
+        if (parent != null) {
+            parent.childDrawableStateChanged(this);
+        }
+    }
+
+    /**
+     * Create a default focus highlight if it doesn't exist.
+     * @return a default focus highlight.
+     */
+    private Drawable getDefaultFocusHighlightDrawable() {
+        if (mDefaultFocusHighlightCache == null) {
+            if (mContext != null) {
+                final int[] attrs = new int[] { android.R.attr.selectableItemBackground };
+                final TypedArray ta = mContext.obtainStyledAttributes(attrs);
+                mDefaultFocusHighlightCache = ta.getDrawable(0);
+                ta.recycle();
+            }
+        }
+        return mDefaultFocusHighlightCache;
+    }
+
+    /**
+     * Set the current default focus highlight.
+     * @param highlight the highlight drawable, or {@code null} if it's no longer needed.
+     */
+    private void setDefaultFocusHighlight(Drawable highlight) {
+        mDefaultFocusHighlight = highlight;
+        mDefaultFocusHighlightSizeChanged = true;
+        if (highlight != null) {
+            if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
+                mPrivateFlags &= ~PFLAG_SKIP_DRAW;
+            }
+            highlight.setLayoutDirection(getLayoutDirection());
+            if (highlight.isStateful()) {
+                highlight.setState(getDrawableState());
+            }
+            if (isAttachedToWindow()) {
+                highlight.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
+            }
+            // Set callback last, since the view may still be initializing.
+            highlight.setCallback(this);
+        } else if ((mViewFlags & WILL_NOT_DRAW) != 0 && mBackground == null
+                && (mForegroundInfo == null || mForegroundInfo.mDrawable == null)) {
+            mPrivateFlags |= PFLAG_SKIP_DRAW;
+        }
+        invalidate();
+    }
+
+    /**
+     * Check whether we need to draw a default focus highlight when this view gets focused,
+     * which requires:
+     * <ul>
+     *     <li>In both background and foreground, {@link android.R.attr#state_focused}
+     *         is not defined.</li>
+     *     <li>This view is not in touch mode.</li>
+     *     <li>This view doesn't opt out for a default focus highlight, via
+     *         {@link #setDefaultFocusHighlightEnabled(boolean)}.</li>
+     *     <li>This view is attached to window.</li>
+     * </ul>
+     * @return {@code true} if a default focus highlight is needed.
+     * @hide
+     */
+    @TestApi
+    public boolean isDefaultFocusHighlightNeeded(Drawable background, Drawable foreground) {
+        final boolean lackFocusState = (background == null || !background.isStateful()
+                || !background.hasFocusStateSpecified())
+                && (foreground == null || !foreground.isStateful()
+                || !foreground.hasFocusStateSpecified());
+        return !isInTouchMode() && getDefaultFocusHighlightEnabled() && lackFocusState
+                && isAttachedToWindow() && sUseDefaultFocusHighlight;
+    }
+
+    /**
+     * When this view is focused, switches on/off the default focused highlight.
+     * <p>
+     * This always happens when this view is focused, and only at this moment the default focus
+     * highlight can be visible.
+     */
+    private void switchDefaultFocusHighlight() {
+        if (isFocused()) {
+            final boolean needed = isDefaultFocusHighlightNeeded(mBackground,
+                    mForegroundInfo == null ? null : mForegroundInfo.mDrawable);
+            final boolean active = mDefaultFocusHighlight != null;
+            if (needed && !active) {
+                setDefaultFocusHighlight(getDefaultFocusHighlightDrawable());
+            } else if (!needed && active) {
+                // The highlight is no longer needed, so tear it down.
+                setDefaultFocusHighlight(null);
+            }
+        }
+    }
+
+    /**
+     * Draw the default focus highlight onto the canvas if there is one and this view is focused.
+     * @param canvas the canvas where we're drawing the highlight.
+     */
+    private void drawDefaultFocusHighlight(Canvas canvas) {
+        if (mDefaultFocusHighlight != null && isFocused()) {
+            if (mDefaultFocusHighlightSizeChanged) {
+                mDefaultFocusHighlightSizeChanged = false;
+                final int l = mScrollX;
+                final int r = l + mRight - mLeft;
+                final int t = mScrollY;
+                final int b = t + mBottom - mTop;
+                mDefaultFocusHighlight.setBounds(l, t, r, b);
+            }
+            mDefaultFocusHighlight.draw(canvas);
+        }
+    }
+
+    /**
+     * Return an array of resource IDs of the drawable states representing the
+     * current state of the view.
+     *
+     * @return The current drawable state
+     *
+     * @see Drawable#setState(int[])
+     * @see #drawableStateChanged()
+     * @see #onCreateDrawableState(int)
+     */
+    public final int[] getDrawableState() {
+        if ((mDrawableState != null) && ((mPrivateFlags & PFLAG_DRAWABLE_STATE_DIRTY) == 0)) {
+            return mDrawableState;
+        } else {
+            mDrawableState = onCreateDrawableState(0);
+            mPrivateFlags &= ~PFLAG_DRAWABLE_STATE_DIRTY;
+            return mDrawableState;
+        }
+    }
+
+    /**
+     * Generate the new {@link android.graphics.drawable.Drawable} state for
+     * this view. This is called by the view
+     * system when the cached Drawable state is determined to be invalid.  To
+     * retrieve the current state, you should use {@link #getDrawableState}.
+     *
+     * @param extraSpace if non-zero, this is the number of extra entries you
+     * would like in the returned array in which you can place your own
+     * states.
+     *
+     * @return Returns an array holding the current {@link Drawable} state of
+     * the view.
+     *
+     * @see #mergeDrawableStates(int[], int[])
+     */
+    protected int[] onCreateDrawableState(int extraSpace) {
+        if ((mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE &&
+                mParent instanceof View) {
+            return ((View) mParent).onCreateDrawableState(extraSpace);
+        }
+
+        int[] drawableState;
+
+        int privateFlags = mPrivateFlags;
+
+        int viewStateIndex = 0;
+        if ((privateFlags & PFLAG_PRESSED) != 0) viewStateIndex |= StateSet.VIEW_STATE_PRESSED;
+        if ((mViewFlags & ENABLED_MASK) == ENABLED) viewStateIndex |= StateSet.VIEW_STATE_ENABLED;
+        if (isFocused()) viewStateIndex |= StateSet.VIEW_STATE_FOCUSED;
+        if ((privateFlags & PFLAG_SELECTED) != 0) viewStateIndex |= StateSet.VIEW_STATE_SELECTED;
+        if (hasWindowFocus()) viewStateIndex |= StateSet.VIEW_STATE_WINDOW_FOCUSED;
+        if ((privateFlags & PFLAG_ACTIVATED) != 0) viewStateIndex |= StateSet.VIEW_STATE_ACTIVATED;
+        if (mAttachInfo != null && mAttachInfo.mHardwareAccelerationRequested) {
+            // This is set if HW acceleration is requested, even if the current
+            // process doesn't allow it.  This is just to allow app preview
+            // windows to better match their app.
+            viewStateIndex |= StateSet.VIEW_STATE_ACCELERATED;
+        }
+        if ((privateFlags & PFLAG_HOVERED) != 0) viewStateIndex |= StateSet.VIEW_STATE_HOVERED;
+
+        final int privateFlags2 = mPrivateFlags2;
+        if ((privateFlags2 & PFLAG2_DRAG_CAN_ACCEPT) != 0) {
+            viewStateIndex |= StateSet.VIEW_STATE_DRAG_CAN_ACCEPT;
+        }
+        if ((privateFlags2 & PFLAG2_DRAG_HOVERED) != 0) {
+            viewStateIndex |= StateSet.VIEW_STATE_DRAG_HOVERED;
+        }
+
+        drawableState = StateSet.get(viewStateIndex);
+
+        //noinspection ConstantIfStatement
+        if (false) {
+            Log.i("View", "drawableStateIndex=" + viewStateIndex);
+            Log.i("View", toString()
+                    + " pressed=" + ((privateFlags & PFLAG_PRESSED) != 0)
+                    + " en=" + ((mViewFlags & ENABLED_MASK) == ENABLED)
+                    + " fo=" + hasFocus()
+                    + " sl=" + ((privateFlags & PFLAG_SELECTED) != 0)
+                    + " wf=" + hasWindowFocus()
+                    + ": " + Arrays.toString(drawableState));
+        }
+
+        if (extraSpace == 0) {
+            return drawableState;
+        }
+
+        final int[] fullState;
+        if (drawableState != null) {
+            fullState = new int[drawableState.length + extraSpace];
+            System.arraycopy(drawableState, 0, fullState, 0, drawableState.length);
+        } else {
+            fullState = new int[extraSpace];
+        }
+
+        return fullState;
+    }
+
+    /**
+     * Merge your own state values in <var>additionalState</var> into the base
+     * state values <var>baseState</var> that were returned by
+     * {@link #onCreateDrawableState(int)}.
+     *
+     * @param baseState The base state values returned by
+     * {@link #onCreateDrawableState(int)}, which will be modified to also hold your
+     * own additional state values.
+     *
+     * @param additionalState The additional state values you would like
+     * added to <var>baseState</var>; this array is not modified.
+     *
+     * @return As a convenience, the <var>baseState</var> array you originally
+     * passed into the function is returned.
+     *
+     * @see #onCreateDrawableState(int)
+     */
+    protected static int[] mergeDrawableStates(int[] baseState, int[] additionalState) {
+        final int N = baseState.length;
+        int i = N - 1;
+        while (i >= 0 && baseState[i] == 0) {
+            i--;
+        }
+        System.arraycopy(additionalState, 0, baseState, i + 1, additionalState.length);
+        return baseState;
+    }
+
+    /**
+     * Call {@link Drawable#jumpToCurrentState() Drawable.jumpToCurrentState()}
+     * on all Drawable objects associated with this view.
+     * <p>
+     * Also calls {@link StateListAnimator#jumpToCurrentState()} if there is a StateListAnimator
+     * attached to this view.
+     */
+    @CallSuper
+    public void jumpDrawablesToCurrentState() {
+        if (mBackground != null) {
+            mBackground.jumpToCurrentState();
+        }
+        if (mStateListAnimator != null) {
+            mStateListAnimator.jumpToCurrentState();
+        }
+        if (mDefaultFocusHighlight != null) {
+            mDefaultFocusHighlight.jumpToCurrentState();
+        }
+        if (mForegroundInfo != null && mForegroundInfo.mDrawable != null) {
+            mForegroundInfo.mDrawable.jumpToCurrentState();
+        }
+    }
+
+    /**
+     * Sets the background color for this view.
+     * @param color the color of the background
+     */
+    @RemotableViewMethod
+    public void setBackgroundColor(@ColorInt int color) {
+        if (mBackground instanceof ColorDrawable) {
+            ((ColorDrawable) mBackground.mutate()).setColor(color);
+            computeOpaqueFlags();
+            mBackgroundResource = 0;
+        } else {
+            setBackground(new ColorDrawable(color));
+        }
+    }
+
+    /**
+     * Set the background to a given resource. The resource should refer to
+     * a Drawable object or 0 to remove the background.
+     * @param resid The identifier of the resource.
+     *
+     * @attr ref android.R.styleable#View_background
+     */
+    @RemotableViewMethod
+    public void setBackgroundResource(@DrawableRes int resid) {
+        if (resid != 0 && resid == mBackgroundResource) {
+            return;
+        }
+
+        Drawable d = null;
+        if (resid != 0) {
+            d = mContext.getDrawable(resid);
+        }
+        setBackground(d);
+
+        mBackgroundResource = resid;
+    }
+
+    /**
+     * Set the background to a given Drawable, or remove the background. If the
+     * background has padding, this View's padding is set to the background's
+     * padding. However, when a background is removed, this View's padding isn't
+     * touched. If setting the padding is desired, please use
+     * {@link #setPadding(int, int, int, int)}.
+     *
+     * @param background The Drawable to use as the background, or null to remove the
+     *        background
+     */
+    public void setBackground(Drawable background) {
+        //noinspection deprecation
+        setBackgroundDrawable(background);
+    }
+
+    /**
+     * @deprecated use {@link #setBackground(Drawable)} instead
+     */
+    @Deprecated
+    public void setBackgroundDrawable(Drawable background) {
+        computeOpaqueFlags();
+
+        if (background == mBackground) {
+            return;
+        }
+
+        boolean requestLayout = false;
+
+        mBackgroundResource = 0;
+
+        /*
+         * Regardless of whether we're setting a new background or not, we want
+         * to clear the previous drawable. setVisible first while we still have the callback set.
+         */
+        if (mBackground != null) {
+            if (isAttachedToWindow()) {
+                mBackground.setVisible(false, false);
+            }
+            mBackground.setCallback(null);
+            unscheduleDrawable(mBackground);
+        }
+
+        if (background != null) {
+            Rect padding = sThreadLocal.get();
+            if (padding == null) {
+                padding = new Rect();
+                sThreadLocal.set(padding);
+            }
+            resetResolvedDrawablesInternal();
+            background.setLayoutDirection(getLayoutDirection());
+            if (background.getPadding(padding)) {
+                resetResolvedPaddingInternal();
+                switch (background.getLayoutDirection()) {
+                    case LAYOUT_DIRECTION_RTL:
+                        mUserPaddingLeftInitial = padding.right;
+                        mUserPaddingRightInitial = padding.left;
+                        internalSetPadding(padding.right, padding.top, padding.left, padding.bottom);
+                        break;
+                    case LAYOUT_DIRECTION_LTR:
+                    default:
+                        mUserPaddingLeftInitial = padding.left;
+                        mUserPaddingRightInitial = padding.right;
+                        internalSetPadding(padding.left, padding.top, padding.right, padding.bottom);
+                }
+                mLeftPaddingDefined = false;
+                mRightPaddingDefined = false;
+            }
+
+            // Compare the minimum sizes of the old Drawable and the new.  If there isn't an old or
+            // if it has a different minimum size, we should layout again
+            if (mBackground == null
+                    || mBackground.getMinimumHeight() != background.getMinimumHeight()
+                    || mBackground.getMinimumWidth() != background.getMinimumWidth()) {
+                requestLayout = true;
+            }
+
+            // Set mBackground before we set this as the callback and start making other
+            // background drawable state change calls. In particular, the setVisible call below
+            // can result in drawables attempting to start animations or otherwise invalidate,
+            // which requires the view set as the callback (us) to recognize the drawable as
+            // belonging to it as per verifyDrawable.
+            mBackground = background;
+            if (background.isStateful()) {
+                background.setState(getDrawableState());
+            }
+            if (isAttachedToWindow()) {
+                background.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
+            }
+
+            applyBackgroundTint();
+
+            // Set callback last, since the view may still be initializing.
+            background.setCallback(this);
+
+            if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
+                mPrivateFlags &= ~PFLAG_SKIP_DRAW;
+                requestLayout = true;
+            }
+        } else {
+            /* Remove the background */
+            mBackground = null;
+            if ((mViewFlags & WILL_NOT_DRAW) != 0
+                    && (mDefaultFocusHighlight == null)
+                    && (mForegroundInfo == null || mForegroundInfo.mDrawable == null)) {
+                mPrivateFlags |= PFLAG_SKIP_DRAW;
+            }
+
+            /*
+             * When the background is set, we try to apply its padding to this
+             * View. When the background is removed, we don't touch this View's
+             * padding. This is noted in the Javadocs. Hence, we don't need to
+             * requestLayout(), the invalidate() below is sufficient.
+             */
+
+            // The old background's minimum size could have affected this
+            // View's layout, so let's requestLayout
+            requestLayout = true;
+        }
+
+        computeOpaqueFlags();
+
+        if (requestLayout) {
+            requestLayout();
+        }
+
+        mBackgroundSizeChanged = true;
+        invalidate(true);
+        invalidateOutline();
+    }
+
+    /**
+     * Gets the background drawable
+     *
+     * @return The drawable used as the background for this view, if any.
+     *
+     * @see #setBackground(Drawable)
+     *
+     * @attr ref android.R.styleable#View_background
+     */
+    @InspectableProperty
+    public Drawable getBackground() {
+        return mBackground;
+    }
+
+    /**
+     * Applies a tint to the background drawable. Does not modify the current tint
+     * mode, which is {@link BlendMode#SRC_IN} by default.
+     * <p>
+     * Subsequent calls to {@link #setBackground(Drawable)} will automatically
+     * mutate the drawable and apply the specified tint and tint mode using
+     * {@link Drawable#setTintList(ColorStateList)}.
+     *
+     * @param tint the tint to apply, may be {@code null} to clear tint
+     *
+     * @attr ref android.R.styleable#View_backgroundTint
+     * @see #getBackgroundTintList()
+     * @see Drawable#setTintList(ColorStateList)
+     */
+    @RemotableViewMethod
+    public void setBackgroundTintList(@Nullable ColorStateList tint) {
+        if (mBackgroundTint == null) {
+            mBackgroundTint = new TintInfo();
+        }
+        mBackgroundTint.mTintList = tint;
+        mBackgroundTint.mHasTintList = true;
+
+        applyBackgroundTint();
+    }
+
+    /**
+     * Return the tint applied to the background drawable, if specified.
+     *
+     * @return the tint applied to the background drawable
+     * @attr ref android.R.styleable#View_backgroundTint
+     * @see #setBackgroundTintList(ColorStateList)
+     */
+    @InspectableProperty(name = "backgroundTint")
+    @Nullable
+    public ColorStateList getBackgroundTintList() {
+        return mBackgroundTint != null ? mBackgroundTint.mTintList : null;
+    }
+
+    /**
+     * Specifies the blending mode used to apply the tint specified by
+     * {@link #setBackgroundTintList(ColorStateList)}} to the background
+     * drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}.
+     *
+     * @param tintMode the blending mode used to apply the tint, may be
+     *                 {@code null} to clear tint
+     * @attr ref android.R.styleable#View_backgroundTintMode
+     * @see #getBackgroundTintMode()
+     * @see Drawable#setTintMode(PorterDuff.Mode)
+     */
+    public void setBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
+        BlendMode mode = null;
+        if (tintMode != null) {
+            mode = BlendMode.fromValue(tintMode.nativeInt);
+        }
+
+        setBackgroundTintBlendMode(mode);
+    }
+
+    /**
+     * Specifies the blending mode used to apply the tint specified by
+     * {@link #setBackgroundTintList(ColorStateList)}} to the background
+     * drawable. The default mode is {@link BlendMode#SRC_IN}.
+     *
+     * @param blendMode the blending mode used to apply the tint, may be
+     *                 {@code null} to clear tint
+     * @attr ref android.R.styleable#View_backgroundTintMode
+     * @see #getBackgroundTintMode()
+     * @see Drawable#setTintBlendMode(BlendMode)
+     */
+    @RemotableViewMethod
+    public void setBackgroundTintBlendMode(@Nullable BlendMode blendMode) {
+        if (mBackgroundTint == null) {
+            mBackgroundTint = new TintInfo();
+        }
+
+        mBackgroundTint.mBlendMode = blendMode;
+        mBackgroundTint.mHasTintMode = true;
+
+        applyBackgroundTint();
+    }
+
+    /**
+     * Return the blending mode used to apply the tint to the background
+     * drawable, if specified.
+     *
+     * @return the blending mode used to apply the tint to the background
+     *         drawable
+     * @attr ref android.R.styleable#View_backgroundTintMode
+     * @see #setBackgroundTintBlendMode(BlendMode)
+     *
+     */
+    @Nullable
+    @InspectableProperty
+    public PorterDuff.Mode getBackgroundTintMode() {
+        PorterDuff.Mode porterDuffMode;
+        if (mBackgroundTint != null && mBackgroundTint.mBlendMode != null) {
+            porterDuffMode = BlendMode.blendModeToPorterDuffMode(mBackgroundTint.mBlendMode);
+        } else {
+            porterDuffMode = null;
+        }
+        return porterDuffMode;
+    }
+
+    /**
+     * Return the blending mode used to apply the tint to the background
+     * drawable, if specified.
+     *
+     * @return the blending mode used to apply the tint to the background
+     *         drawable, null if no blend has previously been configured
+     * @attr ref android.R.styleable#View_backgroundTintMode
+     * @see #setBackgroundTintBlendMode(BlendMode)
+     */
+    public @Nullable BlendMode getBackgroundTintBlendMode() {
+        return mBackgroundTint != null ? mBackgroundTint.mBlendMode : null;
+    }
+
+    private void applyBackgroundTint() {
+        if (mBackground != null && mBackgroundTint != null) {
+            final TintInfo tintInfo = mBackgroundTint;
+            if (tintInfo.mHasTintList || tintInfo.mHasTintMode) {
+                mBackground = mBackground.mutate();
+
+                if (tintInfo.mHasTintList) {
+                    mBackground.setTintList(tintInfo.mTintList);
+                }
+
+                if (tintInfo.mHasTintMode) {
+                    mBackground.setTintBlendMode(tintInfo.mBlendMode);
+                }
+
+                // The drawable (or one of its children) may not have been
+                // stateful before applying the tint, so let's try again.
+                if (mBackground.isStateful()) {
+                    mBackground.setState(getDrawableState());
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the drawable used as the foreground of this View. The
+     * foreground drawable, if non-null, is always drawn on top of the view's content.
+     *
+     * @return a Drawable or null if no foreground was set
+     *
+     * @see #onDrawForeground(Canvas)
+     */
+    @InspectableProperty
+    public Drawable getForeground() {
+        return mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
+    }
+
+    /**
+     * Supply a Drawable that is to be rendered on top of all of the content in the view.
+     *
+     * @param foreground the Drawable to be drawn on top of the children
+     *
+     * @attr ref android.R.styleable#View_foreground
+     */
+    public void setForeground(Drawable foreground) {
+        if (mForegroundInfo == null) {
+            if (foreground == null) {
+                // Nothing to do.
+                return;
+            }
+            mForegroundInfo = new ForegroundInfo();
+        }
+
+        if (foreground == mForegroundInfo.mDrawable) {
+            // Nothing to do
+            return;
+        }
+
+        if (mForegroundInfo.mDrawable != null) {
+            if (isAttachedToWindow()) {
+                mForegroundInfo.mDrawable.setVisible(false, false);
+            }
+            mForegroundInfo.mDrawable.setCallback(null);
+            unscheduleDrawable(mForegroundInfo.mDrawable);
+        }
+
+        mForegroundInfo.mDrawable = foreground;
+        mForegroundInfo.mBoundsChanged = true;
+        if (foreground != null) {
+            if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
+                mPrivateFlags &= ~PFLAG_SKIP_DRAW;
+            }
+            foreground.setLayoutDirection(getLayoutDirection());
+            if (foreground.isStateful()) {
+                foreground.setState(getDrawableState());
+            }
+            applyForegroundTint();
+            if (isAttachedToWindow()) {
+                foreground.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
+            }
+            // Set callback last, since the view may still be initializing.
+            foreground.setCallback(this);
+        } else if ((mViewFlags & WILL_NOT_DRAW) != 0 && mBackground == null
+                && (mDefaultFocusHighlight == null)) {
+            mPrivateFlags |= PFLAG_SKIP_DRAW;
+        }
+        requestLayout();
+        invalidate();
+    }
+
+    /**
+     * Magic bit used to support features of framework-internal window decor implementation details.
+     * This used to live exclusively in FrameLayout.
+     *
+     * @return true if the foreground should draw inside the padding region or false
+     *         if it should draw inset by the view's padding
+     * @hide internal use only; only used by FrameLayout and internal screen layouts.
+     */
+    public boolean isForegroundInsidePadding() {
+        return mForegroundInfo != null ? mForegroundInfo.mInsidePadding : true;
+    }
+
+    /**
+     * Describes how the foreground is positioned.
+     *
+     * @return foreground gravity.
+     *
+     * @see #setForegroundGravity(int)
+     *
+     * @attr ref android.R.styleable#View_foregroundGravity
+     */
+    @InspectableProperty(valueType = InspectableProperty.ValueType.GRAVITY)
+    public int getForegroundGravity() {
+        return mForegroundInfo != null ? mForegroundInfo.mGravity
+                : Gravity.START | Gravity.TOP;
+    }
+
+    /**
+     * Describes how the foreground is positioned. Defaults to START and TOP.
+     *
+     * @param gravity see {@link android.view.Gravity}
+     *
+     * @see #getForegroundGravity()
+     *
+     * @attr ref android.R.styleable#View_foregroundGravity
+     */
+    public void setForegroundGravity(int gravity) {
+        if (mForegroundInfo == null) {
+            mForegroundInfo = new ForegroundInfo();
+        }
+
+        if (mForegroundInfo.mGravity != gravity) {
+            if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
+                gravity |= Gravity.START;
+            }
+
+            if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
+                gravity |= Gravity.TOP;
+            }
+
+            mForegroundInfo.mGravity = gravity;
+            requestLayout();
+        }
+    }
+
+    /**
+     * Applies a tint to the foreground drawable. Does not modify the current tint
+     * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
+     * <p>
+     * Subsequent calls to {@link #setForeground(Drawable)} will automatically
+     * mutate the drawable and apply the specified tint and tint mode using
+     * {@link Drawable#setTintList(ColorStateList)}.
+     *
+     * @param tint the tint to apply, may be {@code null} to clear tint
+     *
+     * @attr ref android.R.styleable#View_foregroundTint
+     * @see #getForegroundTintList()
+     * @see Drawable#setTintList(ColorStateList)
+     */
+    @RemotableViewMethod
+    public void setForegroundTintList(@Nullable ColorStateList tint) {
+        if (mForegroundInfo == null) {
+            mForegroundInfo = new ForegroundInfo();
+        }
+        if (mForegroundInfo.mTintInfo == null) {
+            mForegroundInfo.mTintInfo = new TintInfo();
+        }
+        mForegroundInfo.mTintInfo.mTintList = tint;
+        mForegroundInfo.mTintInfo.mHasTintList = true;
+
+        applyForegroundTint();
+    }
+
+    /**
+     * Return the tint applied to the foreground drawable, if specified.
+     *
+     * @return the tint applied to the foreground drawable
+     * @attr ref android.R.styleable#View_foregroundTint
+     * @see #setForegroundTintList(ColorStateList)
+     */
+    @InspectableProperty(name = "foregroundTint")
+    @Nullable
+    public ColorStateList getForegroundTintList() {
+        return mForegroundInfo != null && mForegroundInfo.mTintInfo != null
+                ? mForegroundInfo.mTintInfo.mTintList : null;
+    }
+
+    /**
+     * Specifies the blending mode used to apply the tint specified by
+     * {@link #setForegroundTintList(ColorStateList)}} to the background
+     * drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}.
+     *
+     * @param tintMode the blending mode used to apply the tint, may be
+     *                 {@code null} to clear tint
+     * @attr ref android.R.styleable#View_foregroundTintMode
+     * @see #getForegroundTintMode()
+     * @see Drawable#setTintMode(PorterDuff.Mode)
+     *
+     */
+    public void setForegroundTintMode(@Nullable PorterDuff.Mode tintMode) {
+        BlendMode mode = null;
+        if (tintMode != null) {
+            mode = BlendMode.fromValue(tintMode.nativeInt);
+        }
+        setForegroundTintBlendMode(mode);
+    }
+
+    /**
+     * Specifies the blending mode used to apply the tint specified by
+     * {@link #setForegroundTintList(ColorStateList)}} to the background
+     * drawable. The default mode is {@link BlendMode#SRC_IN}.
+     *
+     * @param blendMode the blending mode used to apply the tint, may be
+     *                 {@code null} to clear tint
+     * @attr ref android.R.styleable#View_foregroundTintMode
+     * @see #getForegroundTintMode()
+     * @see Drawable#setTintBlendMode(BlendMode)
+     */
+    @RemotableViewMethod
+    public void setForegroundTintBlendMode(@Nullable BlendMode blendMode) {
+        if (mForegroundInfo == null) {
+            mForegroundInfo = new ForegroundInfo();
+        }
+        if (mForegroundInfo.mTintInfo == null) {
+            mForegroundInfo.mTintInfo = new TintInfo();
+        }
+        mForegroundInfo.mTintInfo.mBlendMode = blendMode;
+        mForegroundInfo.mTintInfo.mHasTintMode = true;
+
+        applyForegroundTint();
+    }
+
+    /**
+     * Return the blending mode used to apply the tint to the foreground
+     * drawable, if specified.
+     *
+     * @return the blending mode used to apply the tint to the foreground
+     *         drawable
+     * @attr ref android.R.styleable#View_foregroundTintMode
+     * @see #setForegroundTintMode(PorterDuff.Mode)
+     */
+    @InspectableProperty
+    @Nullable
+    public PorterDuff.Mode getForegroundTintMode() {
+        BlendMode blendMode = mForegroundInfo != null && mForegroundInfo.mTintInfo != null
+                ? mForegroundInfo.mTintInfo.mBlendMode : null;
+        if (blendMode != null) {
+            return BlendMode.blendModeToPorterDuffMode(blendMode);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Return the blending mode used to apply the tint to the foreground
+     * drawable, if specified.
+     *
+     * @return the blending mode used to apply the tint to the foreground
+     *         drawable
+     * @attr ref android.R.styleable#View_foregroundTintMode
+     * @see #setForegroundTintBlendMode(BlendMode)
+     *
+     */
+    public @Nullable BlendMode getForegroundTintBlendMode() {
+        return mForegroundInfo != null && mForegroundInfo.mTintInfo != null
+                ? mForegroundInfo.mTintInfo.mBlendMode : null;
+    }
+
+    private void applyForegroundTint() {
+        if (mForegroundInfo != null && mForegroundInfo.mDrawable != null
+                && mForegroundInfo.mTintInfo != null) {
+            final TintInfo tintInfo = mForegroundInfo.mTintInfo;
+            if (tintInfo.mHasTintList || tintInfo.mHasTintMode) {
+                mForegroundInfo.mDrawable = mForegroundInfo.mDrawable.mutate();
+
+                if (tintInfo.mHasTintList) {
+                    mForegroundInfo.mDrawable.setTintList(tintInfo.mTintList);
+                }
+
+                if (tintInfo.mHasTintMode) {
+                    mForegroundInfo.mDrawable.setTintBlendMode(tintInfo.mBlendMode);
+                }
+
+                // The drawable (or one of its children) may not have been
+                // stateful before applying the tint, so let's try again.
+                if (mForegroundInfo.mDrawable.isStateful()) {
+                    mForegroundInfo.mDrawable.setState(getDrawableState());
+                }
+            }
+        }
+    }
+
+    /**
+     * Get the drawable to be overlayed when a view is autofilled
+     *
+     * @return The drawable
+     *
+     * @throws IllegalStateException if the drawable could not be found.
+     */
+    @Nullable private Drawable getAutofilledDrawable() {
+        if (mAttachInfo == null) {
+            return null;
+        }
+        // Lazily load the isAutofilled drawable.
+        if (mAttachInfo.mAutofilledDrawable == null) {
+            Context rootContext = getRootView().getContext();
+            TypedArray a = rootContext.getTheme().obtainStyledAttributes(AUTOFILL_HIGHLIGHT_ATTR);
+            int attributeResourceId = a.getResourceId(0, 0);
+            mAttachInfo.mAutofilledDrawable = rootContext.getDrawable(attributeResourceId);
+            a.recycle();
+        }
+
+        return mAttachInfo.mAutofilledDrawable;
+    }
+
+    /**
+     * Draw {@link View#isAutofilled()} highlight over view if the view is autofilled, unless
+     * {@link #PFLAG4_AUTOFILL_HIDE_HIGHLIGHT} is enabled.
+     *
+     * @param canvas The canvas to draw on
+     */
+    private void drawAutofilledHighlight(@NonNull Canvas canvas) {
+        if (isAutofilled() && !hideAutofillHighlight()) {
+            Drawable autofilledHighlight = getAutofilledDrawable();
+
+            if (autofilledHighlight != null) {
+                autofilledHighlight.setBounds(0, 0, getWidth(), getHeight());
+                autofilledHighlight.draw(canvas);
+            }
+        }
+    }
+
+    /**
+     * Draw any foreground content for this view.
+     *
+     * <p>Foreground content may consist of scroll bars, a {@link #setForeground foreground}
+     * drawable or other view-specific decorations. The foreground is drawn on top of the
+     * primary view content.</p>
+     *
+     * @param canvas canvas to draw into
+     */
+    public void onDrawForeground(Canvas canvas) {
+        onDrawScrollIndicators(canvas);
+        onDrawScrollBars(canvas);
+
+        final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
+        if (foreground != null) {
+            if (mForegroundInfo.mBoundsChanged) {
+                mForegroundInfo.mBoundsChanged = false;
+                final Rect selfBounds = mForegroundInfo.mSelfBounds;
+                final Rect overlayBounds = mForegroundInfo.mOverlayBounds;
+
+                if (mForegroundInfo.mInsidePadding) {
+                    selfBounds.set(0, 0, getWidth(), getHeight());
+                } else {
+                    selfBounds.set(getPaddingLeft(), getPaddingTop(),
+                            getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
+                }
+
+                final int ld = getLayoutDirection();
+                Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),
+                        foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);
+                foreground.setBounds(overlayBounds);
+            }
+
+            foreground.draw(canvas);
+        }
+    }
+
+    /**
+     * Sets the padding. The view may add on the space required to display
+     * the scrollbars, depending on the style and visibility of the scrollbars.
+     * So the values returned from {@link #getPaddingLeft}, {@link #getPaddingTop},
+     * {@link #getPaddingRight} and {@link #getPaddingBottom} may be different
+     * from the values set in this call.
+     *
+     * @attr ref android.R.styleable#View_padding
+     * @attr ref android.R.styleable#View_paddingBottom
+     * @attr ref android.R.styleable#View_paddingLeft
+     * @attr ref android.R.styleable#View_paddingRight
+     * @attr ref android.R.styleable#View_paddingTop
+     * @param left the left padding in pixels
+     * @param top the top padding in pixels
+     * @param right the right padding in pixels
+     * @param bottom the bottom padding in pixels
+     */
+    public void setPadding(int left, int top, int right, int bottom) {
+        resetResolvedPaddingInternal();
+
+        mUserPaddingStart = UNDEFINED_PADDING;
+        mUserPaddingEnd = UNDEFINED_PADDING;
+
+        mUserPaddingLeftInitial = left;
+        mUserPaddingRightInitial = right;
+
+        mLeftPaddingDefined = true;
+        mRightPaddingDefined = true;
+
+        internalSetPadding(left, top, right, bottom);
+    }
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768420)
+    protected void internalSetPadding(int left, int top, int right, int bottom) {
+        mUserPaddingLeft = left;
+        mUserPaddingRight = right;
+        mUserPaddingBottom = bottom;
+
+        final int viewFlags = mViewFlags;
+        boolean changed = false;
+
+        // Common case is there are no scroll bars.
+        if ((viewFlags & (SCROLLBARS_VERTICAL|SCROLLBARS_HORIZONTAL)) != 0) {
+            if ((viewFlags & SCROLLBARS_VERTICAL) != 0) {
+                final int offset = (viewFlags & SCROLLBARS_INSET_MASK) == 0
+                        ? 0 : getVerticalScrollbarWidth();
+                switch (mVerticalScrollbarPosition) {
+                    case SCROLLBAR_POSITION_DEFAULT:
+                        if (isLayoutRtl()) {
+                            left += offset;
+                        } else {
+                            right += offset;
+                        }
+                        break;
+                    case SCROLLBAR_POSITION_RIGHT:
+                        right += offset;
+                        break;
+                    case SCROLLBAR_POSITION_LEFT:
+                        left += offset;
+                        break;
+                }
+            }
+            if ((viewFlags & SCROLLBARS_HORIZONTAL) != 0) {
+                bottom += (viewFlags & SCROLLBARS_INSET_MASK) == 0
+                        ? 0 : getHorizontalScrollbarHeight();
+            }
+        }
+
+        if (mPaddingLeft != left) {
+            changed = true;
+            mPaddingLeft = left;
+        }
+        if (mPaddingTop != top) {
+            changed = true;
+            mPaddingTop = top;
+        }
+        if (mPaddingRight != right) {
+            changed = true;
+            mPaddingRight = right;
+        }
+        if (mPaddingBottom != bottom) {
+            changed = true;
+            mPaddingBottom = bottom;
+        }
+
+        if (changed) {
+            requestLayout();
+            invalidateOutline();
+        }
+    }
+
+    /**
+     * Sets the relative padding. The view may add on the space required to display
+     * the scrollbars, depending on the style and visibility of the scrollbars.
+     * So the values returned from {@link #getPaddingStart}, {@link #getPaddingTop},
+     * {@link #getPaddingEnd} and {@link #getPaddingBottom} may be different
+     * from the values set in this call.
+     *
+     * @attr ref android.R.styleable#View_padding
+     * @attr ref android.R.styleable#View_paddingBottom
+     * @attr ref android.R.styleable#View_paddingStart
+     * @attr ref android.R.styleable#View_paddingEnd
+     * @attr ref android.R.styleable#View_paddingTop
+     * @param start the start padding in pixels
+     * @param top the top padding in pixels
+     * @param end the end padding in pixels
+     * @param bottom the bottom padding in pixels
+     */
+    public void setPaddingRelative(int start, int top, int end, int bottom) {
+        resetResolvedPaddingInternal();
+
+        mUserPaddingStart = start;
+        mUserPaddingEnd = end;
+        mLeftPaddingDefined = true;
+        mRightPaddingDefined = true;
+
+        switch(getLayoutDirection()) {
+            case LAYOUT_DIRECTION_RTL:
+                mUserPaddingLeftInitial = end;
+                mUserPaddingRightInitial = start;
+                internalSetPadding(end, top, start, bottom);
+                break;
+            case LAYOUT_DIRECTION_LTR:
+            default:
+                mUserPaddingLeftInitial = start;
+                mUserPaddingRightInitial = end;
+                internalSetPadding(start, top, end, bottom);
+        }
+    }
+
+    /**
+     * A {@link View} can be inflated from an XML layout. For such Views this method returns the
+     * resource ID of the source layout.
+     *
+     * @return The layout resource id if this view was inflated from XML, otherwise
+     * {@link Resources#ID_NULL}.
+     */
+    @LayoutRes
+    public int getSourceLayoutResId() {
+        return mSourceLayoutId;
+    }
+
+    /**
+     * Returns the top padding of this view.
+     *
+     * @return the top padding in pixels
+     */
+    @InspectableProperty
+    public int getPaddingTop() {
+        return mPaddingTop;
+    }
+
+    /**
+     * Returns the bottom padding of this view. If there are inset and enabled
+     * scrollbars, this value may include the space required to display the
+     * scrollbars as well.
+     *
+     * @return the bottom padding in pixels
+     */
+    @InspectableProperty
+    public int getPaddingBottom() {
+        return mPaddingBottom;
+    }
+
+    /**
+     * Returns the left padding of this view. If there are inset and enabled
+     * scrollbars, this value may include the space required to display the
+     * scrollbars as well.
+     *
+     * @return the left padding in pixels
+     */
+    @InspectableProperty
+    public int getPaddingLeft() {
+        if (!isPaddingResolved()) {
+            resolvePadding();
+        }
+        return mPaddingLeft;
+    }
+
+    /**
+     * Returns the start padding of this view depending on its resolved layout direction.
+     * If there are inset and enabled scrollbars, this value may include the space
+     * required to display the scrollbars as well.
+     *
+     * @return the start padding in pixels
+     */
+    public int getPaddingStart() {
+        if (!isPaddingResolved()) {
+            resolvePadding();
+        }
+        return (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
+                mPaddingRight : mPaddingLeft;
+    }
+
+    /**
+     * Returns the right padding of this view. If there are inset and enabled
+     * scrollbars, this value may include the space required to display the
+     * scrollbars as well.
+     *
+     * @return the right padding in pixels
+     */
+    @InspectableProperty
+    public int getPaddingRight() {
+        if (!isPaddingResolved()) {
+            resolvePadding();
+        }
+        return mPaddingRight;
+    }
+
+    /**
+     * Returns the end padding of this view depending on its resolved layout direction.
+     * If there are inset and enabled scrollbars, this value may include the space
+     * required to display the scrollbars as well.
+     *
+     * @return the end padding in pixels
+     */
+    public int getPaddingEnd() {
+        if (!isPaddingResolved()) {
+            resolvePadding();
+        }
+        return (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
+                mPaddingLeft : mPaddingRight;
+    }
+
+    /**
+     * Return if the padding has been set through relative values
+     * {@link #setPaddingRelative(int, int, int, int)} or through
+     * @attr ref android.R.styleable#View_paddingStart or
+     * @attr ref android.R.styleable#View_paddingEnd
+     *
+     * @return true if the padding is relative or false if it is not.
+     */
+    public boolean isPaddingRelative() {
+        return (mUserPaddingStart != UNDEFINED_PADDING || mUserPaddingEnd != UNDEFINED_PADDING);
+    }
+
+    Insets computeOpticalInsets() {
+        return (mBackground == null) ? Insets.NONE : mBackground.getOpticalInsets();
+    }
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void resetPaddingToInitialValues() {
+        if (isRtlCompatibilityMode()) {
+            mPaddingLeft = mUserPaddingLeftInitial;
+            mPaddingRight = mUserPaddingRightInitial;
+            return;
+        }
+        if (isLayoutRtl()) {
+            mPaddingLeft = (mUserPaddingEnd >= 0) ? mUserPaddingEnd : mUserPaddingLeftInitial;
+            mPaddingRight = (mUserPaddingStart >= 0) ? mUserPaddingStart : mUserPaddingRightInitial;
+        } else {
+            mPaddingLeft = (mUserPaddingStart >= 0) ? mUserPaddingStart : mUserPaddingLeftInitial;
+            mPaddingRight = (mUserPaddingEnd >= 0) ? mUserPaddingEnd : mUserPaddingRightInitial;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public Insets getOpticalInsets() {
+        if (mLayoutInsets == null) {
+            mLayoutInsets = computeOpticalInsets();
+        }
+        return mLayoutInsets;
+    }
+
+    /**
+     * Set this view's optical insets.
+     *
+     * <p>This method should be treated similarly to setMeasuredDimension and not as a general
+     * property. Views that compute their own optical insets should call it as part of measurement.
+     * This method does not request layout. If you are setting optical insets outside of
+     * measure/layout itself you will want to call requestLayout() yourself.
+     * </p>
+     * @hide
+     */
+    public void setOpticalInsets(Insets insets) {
+        mLayoutInsets = insets;
+    }
+
+    /**
+     * Changes the selection state of this view. A view can be selected or not.
+     * Note that selection is not the same as focus. Views are typically
+     * selected in the context of an AdapterView like ListView or GridView;
+     * the selected view is the view that is highlighted.
+     *
+     * @param selected true if the view must be selected, false otherwise
+     */
+    public void setSelected(boolean selected) {
+        //noinspection DoubleNegation
+        if (((mPrivateFlags & PFLAG_SELECTED) != 0) != selected) {
+            mPrivateFlags = (mPrivateFlags & ~PFLAG_SELECTED) | (selected ? PFLAG_SELECTED : 0);
+            if (!selected) resetPressedState();
+            invalidate(true);
+            refreshDrawableState();
+            dispatchSetSelected(selected);
+            if (selected) {
+                sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+            } else {
+                notifyViewAccessibilityStateChangedIfNeeded(
+                        AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+            }
+        }
+    }
+
+    /**
+     * Dispatch setSelected to all of this View's children.
+     *
+     * @see #setSelected(boolean)
+     *
+     * @param selected The new selected state
+     */
+    protected void dispatchSetSelected(boolean selected) {
+    }
+
+    /**
+     * Indicates the selection state of this view.
+     *
+     * @return true if the view is selected, false otherwise
+     */
+    @ViewDebug.ExportedProperty
+    @InspectableProperty(hasAttributeId = false)
+    public boolean isSelected() {
+        return (mPrivateFlags & PFLAG_SELECTED) != 0;
+    }
+
+    /**
+     * Changes the activated state of this view. A view can be activated or not.
+     * Note that activation is not the same as selection.  Selection is
+     * a transient property, representing the view (hierarchy) the user is
+     * currently interacting with.  Activation is a longer-term state that the
+     * user can move views in and out of.  For example, in a list view with
+     * single or multiple selection enabled, the views in the current selection
+     * set are activated.  (Um, yeah, we are deeply sorry about the terminology
+     * here.)  The activated state is propagated down to children of the view it
+     * is set on.
+     *
+     * @param activated true if the view must be activated, false otherwise
+     */
+    public void setActivated(boolean activated) {
+        //noinspection DoubleNegation
+        if (((mPrivateFlags & PFLAG_ACTIVATED) != 0) != activated) {
+            mPrivateFlags = (mPrivateFlags & ~PFLAG_ACTIVATED) | (activated ? PFLAG_ACTIVATED : 0);
+            invalidate(true);
+            refreshDrawableState();
+            dispatchSetActivated(activated);
+        }
+    }
+
+    /**
+     * Dispatch setActivated to all of this View's children.
+     *
+     * @see #setActivated(boolean)
+     *
+     * @param activated The new activated state
+     */
+    protected void dispatchSetActivated(boolean activated) {
+    }
+
+    /**
+     * Indicates the activation state of this view.
+     *
+     * @return true if the view is activated, false otherwise
+     */
+    @ViewDebug.ExportedProperty
+    @InspectableProperty(hasAttributeId = false)
+    public boolean isActivated() {
+        return (mPrivateFlags & PFLAG_ACTIVATED) != 0;
+    }
+
+    /**
+     * Returns the ViewTreeObserver for this view's hierarchy. The view tree
+     * observer can be used to get notifications when global events, like
+     * layout, happen.
+     *
+     * The returned ViewTreeObserver observer is not guaranteed to remain
+     * valid for the lifetime of this View. If the caller of this method keeps
+     * a long-lived reference to ViewTreeObserver, it should always check for
+     * the return value of {@link ViewTreeObserver#isAlive()}.
+     *
+     * @return The ViewTreeObserver for this view's hierarchy.
+     */
+    public ViewTreeObserver getViewTreeObserver() {
+        if (mAttachInfo != null) {
+            return mAttachInfo.mTreeObserver;
+        }
+        if (mFloatingTreeObserver == null) {
+            mFloatingTreeObserver = new ViewTreeObserver(mContext);
+        }
+        return mFloatingTreeObserver;
+    }
+
+    /**
+     * <p>Finds the topmost view in the current view hierarchy.</p>
+     *
+     * @return the topmost view containing this view
+     */
+    public View getRootView() {
+        if (mAttachInfo != null) {
+            final View v = mAttachInfo.mRootView;
+            if (v != null) {
+                return v;
+            }
+        }
+
+        View parent = this;
+
+        while (parent.mParent != null && parent.mParent instanceof View) {
+            parent = (View) parent.mParent;
+        }
+
+        return parent;
+    }
+
+    /**
+     * Transforms a motion event from view-local coordinates to on-screen
+     * coordinates.
+     *
+     * @param ev the view-local motion event
+     * @return false if the transformation could not be applied
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public boolean toGlobalMotionEvent(MotionEvent ev) {
+        final AttachInfo info = mAttachInfo;
+        if (info == null) {
+            return false;
+        }
+
+        final Matrix m = info.mTmpMatrix;
+        m.set(Matrix.IDENTITY_MATRIX);
+        transformMatrixToGlobal(m);
+        ev.transform(m);
+        return true;
+    }
+
+    /**
+     * Transforms a motion event from on-screen coordinates to view-local
+     * coordinates.
+     *
+     * @param ev the on-screen motion event
+     * @return false if the transformation could not be applied
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public boolean toLocalMotionEvent(MotionEvent ev) {
+        final AttachInfo info = mAttachInfo;
+        if (info == null) {
+            return false;
+        }
+
+        final Matrix m = info.mTmpMatrix;
+        m.set(Matrix.IDENTITY_MATRIX);
+        transformMatrixToLocal(m);
+        ev.transform(m);
+        return true;
+    }
+
+    /**
+     * Modifies the input matrix such that it maps view-local coordinates to
+     * on-screen coordinates.
+     *
+     * @param matrix input matrix to modify
+     */
+    public void transformMatrixToGlobal(@NonNull Matrix matrix) {
+        final ViewParent parent = mParent;
+        if (parent instanceof View) {
+            final View vp = (View) parent;
+            vp.transformMatrixToGlobal(matrix);
+            matrix.preTranslate(-vp.mScrollX, -vp.mScrollY);
+        } else if (parent instanceof ViewRootImpl) {
+            final ViewRootImpl vr = (ViewRootImpl) parent;
+            vr.transformMatrixToGlobal(matrix);
+            matrix.preTranslate(0, -vr.mCurScrollY);
+        }
+
+        matrix.preTranslate(mLeft, mTop);
+
+        if (!hasIdentityMatrix()) {
+            matrix.preConcat(getMatrix());
+        }
+    }
+
+    /**
+     * Modifies the input matrix such that it maps on-screen coordinates to
+     * view-local coordinates.
+     *
+     * @param matrix input matrix to modify
+     */
+    public void transformMatrixToLocal(@NonNull Matrix matrix) {
+        final ViewParent parent = mParent;
+        if (parent instanceof View) {
+            final View vp = (View) parent;
+            vp.transformMatrixToLocal(matrix);
+            matrix.postTranslate(vp.mScrollX, vp.mScrollY);
+        } else if (parent instanceof ViewRootImpl) {
+            final ViewRootImpl vr = (ViewRootImpl) parent;
+            vr.transformMatrixToLocal(matrix);
+            matrix.postTranslate(0, vr.mCurScrollY);
+        }
+
+        matrix.postTranslate(-mLeft, -mTop);
+
+        if (!hasIdentityMatrix()) {
+            matrix.postConcat(getInverseMatrix());
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @ViewDebug.ExportedProperty(category = "layout", indexMapping = {
+            @ViewDebug.IntToString(from = 0, to = "x"),
+            @ViewDebug.IntToString(from = 1, to = "y")
+    })
+    @UnsupportedAppUsage
+    public int[] getLocationOnScreen() {
+        int[] location = new int[2];
+        getLocationOnScreen(location);
+        return location;
+    }
+
+    /**
+     * <p>Computes the coordinates of this view on the screen. The argument
+     * must be an array of two integers. After the method returns, the array
+     * contains the x and y location in that order.</p>
+     *
+     * @param outLocation an array of two integers in which to hold the coordinates
+     */
+    public void getLocationOnScreen(@Size(2) int[] outLocation) {
+        getLocationInWindow(outLocation);
+
+        final AttachInfo info = mAttachInfo;
+        if (info != null) {
+            outLocation[0] += info.mWindowLeft;
+            outLocation[1] += info.mWindowTop;
+        }
+    }
+
+    /**
+     * <p>Computes the coordinates of this view in its window. The argument
+     * must be an array of two integers. After the method returns, the array
+     * contains the x and y location in that order.</p>
+     *
+     * @param outLocation an array of two integers in which to hold the coordinates
+     */
+    public void getLocationInWindow(@Size(2) int[] outLocation) {
+        if (outLocation == null || outLocation.length < 2) {
+            throw new IllegalArgumentException("outLocation must be an array of two integers");
+        }
+
+        outLocation[0] = 0;
+        outLocation[1] = 0;
+
+        transformFromViewToWindowSpace(outLocation);
+    }
+
+    /** @hide */
+    public void transformFromViewToWindowSpace(@Size(2) int[] inOutLocation) {
+        if (inOutLocation == null || inOutLocation.length < 2) {
+            throw new IllegalArgumentException("inOutLocation must be an array of two integers");
+        }
+
+        if (mAttachInfo == null) {
+            // When the view is not attached to a window, this method does not make sense
+            inOutLocation[0] = inOutLocation[1] = 0;
+            return;
+        }
+
+        float position[] = mAttachInfo.mTmpTransformLocation;
+        position[0] = inOutLocation[0];
+        position[1] = inOutLocation[1];
+
+        if (!hasIdentityMatrix()) {
+            getMatrix().mapPoints(position);
+        }
+
+        position[0] += mLeft;
+        position[1] += mTop;
+
+        ViewParent viewParent = mParent;
+        while (viewParent instanceof View) {
+            final View view = (View) viewParent;
+
+            position[0] -= view.mScrollX;
+            position[1] -= view.mScrollY;
+
+            if (!view.hasIdentityMatrix()) {
+                view.getMatrix().mapPoints(position);
+            }
+
+            position[0] += view.mLeft;
+            position[1] += view.mTop;
+
+            viewParent = view.mParent;
+         }
+
+        if (viewParent instanceof ViewRootImpl) {
+            // *cough*
+            final ViewRootImpl vr = (ViewRootImpl) viewParent;
+            position[1] -= vr.mCurScrollY;
+        }
+
+        inOutLocation[0] = Math.round(position[0]);
+        inOutLocation[1] = Math.round(position[1]);
+    }
+
+    /**
+     * @param id the id of the view to be found
+     * @return the view of the specified id, null if cannot be found
+     * @hide
+     */
+    protected <T extends View> T findViewTraversal(@IdRes int id) {
+        if (id == mID) {
+            return (T) this;
+        }
+        return null;
+    }
+
+    /**
+     * @param tag the tag of the view to be found
+     * @return the view of specified tag, null if cannot be found
+     * @hide
+     */
+    protected <T extends View> T findViewWithTagTraversal(Object tag) {
+        if (tag != null && tag.equals(mTag)) {
+            return (T) this;
+        }
+        return null;
+    }
+
+    /**
+     * @param predicate The predicate to evaluate.
+     * @param childToSkip If not null, ignores this child during the recursive traversal.
+     * @return The first view that matches the predicate or null.
+     * @hide
+     */
+    protected <T extends View> T findViewByPredicateTraversal(Predicate<View> predicate,
+            View childToSkip) {
+        if (predicate.test(this)) {
+            return (T) this;
+        }
+        return null;
+    }
+
+    /**
+     * Finds the first descendant view with the given ID, the view itself if
+     * the ID matches {@link #getId()}, or {@code null} if the ID is invalid
+     * (< 0) or there is no matching view in the hierarchy.
+     * <p>
+     * <strong>Note:</strong> In most cases -- depending on compiler support --
+     * the resulting view is automatically cast to the target class type. If
+     * the target class type is unconstrained, an explicit cast may be
+     * necessary.
+     *
+     * @param id the ID to search for
+     * @return a view with given ID if found, or {@code null} otherwise
+     * @see View#requireViewById(int)
+     */
+    @Nullable
+    public final <T extends View> T findViewById(@IdRes int id) {
+        if (id == NO_ID) {
+            return null;
+        }
+        return findViewTraversal(id);
+    }
+
+    /**
+     * Finds the first descendant view with the given ID, the view itself if the ID matches
+     * {@link #getId()}, or throws an IllegalArgumentException if the ID is invalid or there is no
+     * matching view in the hierarchy.
+     * <p>
+     * <strong>Note:</strong> In most cases -- depending on compiler support --
+     * the resulting view is automatically cast to the target class type. If
+     * the target class type is unconstrained, an explicit cast may be
+     * necessary.
+     *
+     * @param id the ID to search for
+     * @return a view with given ID
+     * @see View#findViewById(int)
+     */
+    @NonNull
+    public final <T extends View> T requireViewById(@IdRes int id) {
+        T view = findViewById(id);
+        if (view == null) {
+            throw new IllegalArgumentException("ID does not reference a View inside this View");
+        }
+        return view;
+    }
+
+    /**
+     * Performs the traversal to find a view by its unique and stable accessibility id.
+     *
+     * <strong>Note:</strong>This method does not stop at the root namespace
+     * boundary since the user can touch the screen at an arbitrary location
+     * potentially crossing the root namespace boundary which will send an
+     * accessibility event to accessibility services and they should be able
+     * to obtain the event source. Also accessibility ids are guaranteed to be
+     * unique in the window.
+     *
+     * @param accessibilityId The accessibility id.
+     * @return The found view.
+     * @hide
+     */
+    public <T extends View> T findViewByAccessibilityIdTraversal(int accessibilityId) {
+        if (getAccessibilityViewId() == accessibilityId) {
+            return (T) this;
+        }
+        return null;
+    }
+
+    /**
+     * Performs the traversal to find a view by its autofill id.
+     *
+     * <strong>Note:</strong>This method does not stop at the root namespace
+     * boundary.
+     *
+     * @param autofillId The autofill id.
+     * @return The found view.
+     * @hide
+     */
+    public <T extends View> T findViewByAutofillIdTraversal(int autofillId) {
+        if (getAutofillViewId() == autofillId) {
+            return (T) this;
+        }
+        return null;
+    }
+
+    /**
+     * Look for a child view with the given tag.  If this view has the given
+     * tag, return this view.
+     *
+     * @param tag The tag to search for, using "tag.equals(getTag())".
+     * @return The View that has the given tag in the hierarchy or null
+     */
+    public final <T extends View> T findViewWithTag(Object tag) {
+        if (tag == null) {
+            return null;
+        }
+        return findViewWithTagTraversal(tag);
+    }
+
+    /**
+     * Look for a child view that matches the specified predicate.
+     * If this view matches the predicate, return this view.
+     *
+     * @param predicate The predicate to evaluate.
+     * @return The first view that matches the predicate or null.
+     * @hide
+     */
+    public final <T extends View> T findViewByPredicate(Predicate<View> predicate) {
+        return findViewByPredicateTraversal(predicate, null);
+    }
+
+    /**
+     * Look for a child view that matches the specified predicate,
+     * starting with the specified view and its descendents and then
+     * recusively searching the ancestors and siblings of that view
+     * until this view is reached.
+     *
+     * This method is useful in cases where the predicate does not match
+     * a single unique view (perhaps multiple views use the same id)
+     * and we are trying to find the view that is "closest" in scope to the
+     * starting view.
+     *
+     * @param start The view to start from.
+     * @param predicate The predicate to evaluate.
+     * @return The first view that matches the predicate or null.
+     * @hide
+     */
+    public final <T extends View> T findViewByPredicateInsideOut(
+            View start, Predicate<View> predicate) {
+        View childToSkip = null;
+        for (;;) {
+            T view = start.findViewByPredicateTraversal(predicate, childToSkip);
+            if (view != null || start == this) {
+                return view;
+            }
+
+            ViewParent parent = start.getParent();
+            if (parent == null || !(parent instanceof View)) {
+                return null;
+            }
+
+            childToSkip = start;
+            start = (View) parent;
+        }
+    }
+
+    /**
+     * Sets the identifier for this view. The identifier does not have to be
+     * unique in this view's hierarchy. The identifier should be a positive
+     * number.
+     *
+     * @see #NO_ID
+     * @see #getId()
+     * @see #findViewById(int)
+     *
+     * @param id a number used to identify the view
+     *
+     * @attr ref android.R.styleable#View_id
+     */
+    public void setId(@IdRes int id) {
+        mID = id;
+        if (mID == View.NO_ID && mLabelForId != View.NO_ID) {
+            mID = generateViewId();
+        }
+    }
+
+    /**
+     * {@hide}
+     *
+     * @param isRoot true if the view belongs to the root namespace, false
+     *        otherwise
+     */
+    @UnsupportedAppUsage
+    @TestApi
+    public void setIsRootNamespace(boolean isRoot) {
+        if (isRoot) {
+            mPrivateFlags |= PFLAG_IS_ROOT_NAMESPACE;
+        } else {
+            mPrivateFlags &= ~PFLAG_IS_ROOT_NAMESPACE;
+        }
+    }
+
+    /**
+     * {@hide}
+     *
+     * @return true if the view belongs to the root namespace, false otherwise
+     */
+    @UnsupportedAppUsage
+    public boolean isRootNamespace() {
+        return (mPrivateFlags&PFLAG_IS_ROOT_NAMESPACE) != 0;
+    }
+
+    /**
+     * Returns this view's identifier.
+     *
+     * @return a positive integer used to identify the view or {@link #NO_ID}
+     *         if the view has no ID
+     *
+     * @see #setId(int)
+     * @see #findViewById(int)
+     * @attr ref android.R.styleable#View_id
+     */
+    @IdRes
+    @ViewDebug.CapturedViewProperty
+    @InspectableProperty
+    public int getId() {
+        return mID;
+    }
+
+    /**
+     * Get the identifier used for this view by the drawing system.
+     *
+     * @see RenderNode#getUniqueId()
+     * @return A long that uniquely identifies this view's drawing component
+     */
+    public long getUniqueDrawingId() {
+        return mRenderNode.getUniqueId();
+    }
+
+    /**
+     * Returns this view's tag.
+     *
+     * @return the Object stored in this view as a tag, or {@code null} if not
+     *         set
+     *
+     * @see #setTag(Object)
+     * @see #getTag(int)
+     */
+    @ViewDebug.ExportedProperty
+    @InspectableProperty
+    public Object getTag() {
+        return mTag;
+    }
+
+    /**
+     * Sets the tag associated with this view. A tag can be used to mark
+     * a view in its hierarchy and does not have to be unique within the
+     * hierarchy. Tags can also be used to store data within a view without
+     * resorting to another data structure.
+     *
+     * @param tag an Object to tag the view with
+     *
+     * @see #getTag()
+     * @see #setTag(int, Object)
+     */
+    public void setTag(final Object tag) {
+        mTag = tag;
+    }
+
+    /**
+     * Returns the tag associated with this view and the specified key.
+     *
+     * @param key The key identifying the tag
+     *
+     * @return the Object stored in this view as a tag, or {@code null} if not
+     *         set
+     *
+     * @see #setTag(int, Object)
+     * @see #getTag()
+     */
+    public Object getTag(int key) {
+        if (mKeyedTags != null) return mKeyedTags.get(key);
+        return null;
+    }
+
+    /**
+     * Sets a tag associated with this view and a key. A tag can be used
+     * to mark a view in its hierarchy and does not have to be unique within
+     * the hierarchy. Tags can also be used to store data within a view
+     * without resorting to another data structure.
+     *
+     * The specified key should be an id declared in the resources of the
+     * application to ensure it is unique (see the <a
+     * href="{@docRoot}guide/topics/resources/more-resources.html#Id">ID resource type</a>).
+     * Keys identified as belonging to
+     * the Android framework or not associated with any package will cause
+     * an {@link IllegalArgumentException} to be thrown.
+     *
+     * @param key The key identifying the tag
+     * @param tag An Object to tag the view with
+     *
+     * @throws IllegalArgumentException If they specified key is not valid
+     *
+     * @see #setTag(Object)
+     * @see #getTag(int)
+     */
+    public void setTag(int key, final Object tag) {
+        // If the package id is 0x00 or 0x01, it's either an undefined package
+        // or a framework id
+        if ((key >>> 24) < 2) {
+            throw new IllegalArgumentException("The key must be an application-specific "
+                    + "resource id.");
+        }
+
+        setKeyedTag(key, tag);
+    }
+
+    /**
+     * Variation of {@link #setTag(int, Object)} that enforces the key to be a
+     * framework id.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void setTagInternal(int key, Object tag) {
+        if ((key >>> 24) != 0x1) {
+            throw new IllegalArgumentException("The key must be a framework-specific "
+                    + "resource id.");
+        }
+
+        setKeyedTag(key, tag);
+    }
+
+    private void setKeyedTag(int key, Object tag) {
+        if (mKeyedTags == null) {
+            mKeyedTags = new SparseArray<Object>(2);
+        }
+
+        mKeyedTags.put(key, tag);
+    }
+
+    /**
+     * Prints information about this view in the log output, with the tag
+     * {@link #VIEW_LOG_TAG}.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void debug() {
+        debug(0);
+    }
+
+    /**
+     * Prints information about this view in the log output, with the tag
+     * {@link #VIEW_LOG_TAG}. Each line in the output is preceded with an
+     * indentation defined by the <code>depth</code>.
+     *
+     * @param depth the indentation level
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    protected void debug(int depth) {
+        String output = debugIndent(depth - 1);
+
+        output += "+ " + this;
+        int id = getId();
+        if (id != -1) {
+            output += " (id=" + id + ")";
+        }
+        Object tag = getTag();
+        if (tag != null) {
+            output += " (tag=" + tag + ")";
+        }
+        Log.d(VIEW_LOG_TAG, output);
+
+        if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
+            output = debugIndent(depth) + " FOCUSED";
+            Log.d(VIEW_LOG_TAG, output);
+        }
+
+        output = debugIndent(depth);
+        output += "frame={" + mLeft + ", " + mTop + ", " + mRight
+                + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
+                + "} ";
+        Log.d(VIEW_LOG_TAG, output);
+
+        if (mPaddingLeft != 0 || mPaddingTop != 0 || mPaddingRight != 0
+                || mPaddingBottom != 0) {
+            output = debugIndent(depth);
+            output += "padding={" + mPaddingLeft + ", " + mPaddingTop
+                    + ", " + mPaddingRight + ", " + mPaddingBottom + "}";
+            Log.d(VIEW_LOG_TAG, output);
+        }
+
+        output = debugIndent(depth);
+        output += "mMeasureWidth=" + mMeasuredWidth +
+                " mMeasureHeight=" + mMeasuredHeight;
+        Log.d(VIEW_LOG_TAG, output);
+
+        output = debugIndent(depth);
+        if (mLayoutParams == null) {
+            output += "BAD! no layout params";
+        } else {
+            output = mLayoutParams.debug(output);
+        }
+        Log.d(VIEW_LOG_TAG, output);
+
+        output = debugIndent(depth);
+        output += "flags={";
+        output += View.printFlags(mViewFlags);
+        output += "}";
+        Log.d(VIEW_LOG_TAG, output);
+
+        output = debugIndent(depth);
+        output += "privateFlags={";
+        output += View.printPrivateFlags(mPrivateFlags);
+        output += "}";
+        Log.d(VIEW_LOG_TAG, output);
+    }
+
+    /**
+     * Creates a string of whitespaces used for indentation.
+     *
+     * @param depth the indentation level
+     * @return a String containing (depth * 2 + 3) * 2 white spaces
+     *
+     * @hide
+     */
+    protected static String debugIndent(int depth) {
+        StringBuilder spaces = new StringBuilder((depth * 2 + 3) * 2);
+        for (int i = 0; i < (depth * 2) + 3; i++) {
+            spaces.append(' ').append(' ');
+        }
+        return spaces.toString();
+    }
+
+    /**
+     * <p>Return the offset of the widget's text baseline from the widget's top
+     * boundary. If this widget does not support baseline alignment, this
+     * method returns -1. </p>
+     *
+     * @return the offset of the baseline within the widget's bounds or -1
+     *         if baseline alignment is not supported
+     */
+    @ViewDebug.ExportedProperty(category = "layout")
+    @InspectableProperty
+    public int getBaseline() {
+        return -1;
+    }
+
+    /**
+     * Returns whether the view hierarchy is currently undergoing a layout pass. This
+     * information is useful to avoid situations such as calling {@link #requestLayout()} during
+     * a layout pass.
+     *
+     * @return whether the view hierarchy is currently undergoing a layout pass
+     */
+    public boolean isInLayout() {
+        ViewRootImpl viewRoot = getViewRootImpl();
+        return (viewRoot != null && viewRoot.isInLayout());
+    }
+
+    /**
+     * Call this when something has changed which has invalidated the
+     * layout of this view. This will schedule a layout pass of the view
+     * tree. This should not be called while the view hierarchy is currently in a layout
+     * pass ({@link #isInLayout()}. If layout is happening, the request may be honored at the
+     * end of the current layout pass (and then layout will run again) or after the current
+     * frame is drawn and the next layout occurs.
+     *
+     * <p>Subclasses which override this method should call the superclass method to
+     * handle possible request-during-layout errors correctly.</p>
+     */
+    @CallSuper
+    public void requestLayout() {
+        if (mMeasureCache != null) mMeasureCache.clear();
+
+        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
+            // Only trigger request-during-layout logic if this is the view requesting it,
+            // not the views in its parent hierarchy
+            ViewRootImpl viewRoot = getViewRootImpl();
+            if (viewRoot != null && viewRoot.isInLayout()) {
+                if (!viewRoot.requestLayoutDuringLayout(this)) {
+                    return;
+                }
+            }
+            mAttachInfo.mViewRequestingLayout = this;
+        }
+
+        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
+        mPrivateFlags |= PFLAG_INVALIDATED;
+
+        if (mParent != null && !mParent.isLayoutRequested()) {
+            mParent.requestLayout();
+        }
+        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
+            mAttachInfo.mViewRequestingLayout = null;
+        }
+    }
+
+    /**
+     * Forces this view to be laid out during the next layout pass.
+     * This method does not call requestLayout() or forceLayout()
+     * on the parent.
+     */
+    public void forceLayout() {
+        if (mMeasureCache != null) mMeasureCache.clear();
+
+        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
+        mPrivateFlags |= PFLAG_INVALIDATED;
+    }
+
+    /**
+     * <p>
+     * This is called to find out how big a view should be. The parent
+     * supplies constraint information in the width and height parameters.
+     * </p>
+     *
+     * <p>
+     * The actual measurement work of a view is performed in
+     * {@link #onMeasure(int, int)}, called by this method. Therefore, only
+     * {@link #onMeasure(int, int)} can and must be overridden by subclasses.
+     * </p>
+     *
+     *
+     * @param widthMeasureSpec Horizontal space requirements as imposed by the
+     *        parent
+     * @param heightMeasureSpec Vertical space requirements as imposed by the
+     *        parent
+     *
+     * @see #onMeasure(int, int)
+     */
+    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
+        boolean optical = isLayoutModeOptical(this);
+        if (optical != isLayoutModeOptical(mParent)) {
+            Insets insets = getOpticalInsets();
+            int oWidth  = insets.left + insets.right;
+            int oHeight = insets.top  + insets.bottom;
+            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
+            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
+        }
+
+        // Suppress sign extension for the low bytes
+        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
+        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
+
+        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
+
+        // Optimize layout by avoiding an extra EXACTLY pass when the view is
+        // already measured as the correct size. In API 23 and below, this
+        // extra pass is required to make LinearLayout re-distribute weight.
+        final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
+                || heightMeasureSpec != mOldHeightMeasureSpec;
+        final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
+                && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
+        final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
+                && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
+        final boolean needsLayout = specChanged
+                && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
+
+        if (forceLayout || needsLayout) {
+            // first clears the measured dimension flag
+            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
+
+            resolveRtlPropertiesIfNeeded();
+
+            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
+            if (cacheIndex < 0 || sIgnoreMeasureCache) {
+                // measure ourselves, this should set the measured dimension flag back
+                onMeasure(widthMeasureSpec, heightMeasureSpec);
+                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
+            } else {
+                long value = mMeasureCache.valueAt(cacheIndex);
+                // Casting a long to int drops the high 32 bits, no mask needed
+                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
+                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
+            }
+
+            // flag not set, setMeasuredDimension() was not invoked, we raise
+            // an exception to warn the developer
+            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
+                throw new IllegalStateException("View with id " + getId() + ": "
+                        + getClass().getName() + "#onMeasure() did not set the"
+                        + " measured dimension by calling"
+                        + " setMeasuredDimension()");
+            }
+
+            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
+        }
+
+        mOldWidthMeasureSpec = widthMeasureSpec;
+        mOldHeightMeasureSpec = heightMeasureSpec;
+
+        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
+                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
+    }
+
+    /**
+     * <p>
+     * Measure the view and its content to determine the measured width and the
+     * measured height. This method is invoked by {@link #measure(int, int)} and
+     * should be overridden by subclasses to provide accurate and efficient
+     * measurement of their contents.
+     * </p>
+     *
+     * <p>
+     * <strong>CONTRACT:</strong> When overriding this method, you
+     * <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the
+     * measured width and height of this view. Failure to do so will trigger an
+     * <code>IllegalStateException</code>, thrown by
+     * {@link #measure(int, int)}. Calling the superclass'
+     * {@link #onMeasure(int, int)} is a valid use.
+     * </p>
+     *
+     * <p>
+     * The base class implementation of measure defaults to the background size,
+     * unless a larger size is allowed by the MeasureSpec. Subclasses should
+     * override {@link #onMeasure(int, int)} to provide better measurements of
+     * their content.
+     * </p>
+     *
+     * <p>
+     * If this method is overridden, it is the subclass's responsibility to make
+     * sure the measured height and width are at least the view's minimum height
+     * and width ({@link #getSuggestedMinimumHeight()} and
+     * {@link #getSuggestedMinimumWidth()}).
+     * </p>
+     *
+     * @param widthMeasureSpec horizontal space requirements as imposed by the parent.
+     *                         The requirements are encoded with
+     *                         {@link android.view.View.MeasureSpec}.
+     * @param heightMeasureSpec vertical space requirements as imposed by the parent.
+     *                         The requirements are encoded with
+     *                         {@link android.view.View.MeasureSpec}.
+     *
+     * @see #getMeasuredWidth()
+     * @see #getMeasuredHeight()
+     * @see #setMeasuredDimension(int, int)
+     * @see #getSuggestedMinimumHeight()
+     * @see #getSuggestedMinimumWidth()
+     * @see android.view.View.MeasureSpec#getMode(int)
+     * @see android.view.View.MeasureSpec#getSize(int)
+     */
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
+                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
+    }
+
+    /**
+     * <p>This method must be called by {@link #onMeasure(int, int)} to store the
+     * measured width and measured height. Failing to do so will trigger an
+     * exception at measurement time.</p>
+     *
+     * @param measuredWidth The measured width of this view.  May be a complex
+     * bit mask as defined by {@link #MEASURED_SIZE_MASK} and
+     * {@link #MEASURED_STATE_TOO_SMALL}.
+     * @param measuredHeight The measured height of this view.  May be a complex
+     * bit mask as defined by {@link #MEASURED_SIZE_MASK} and
+     * {@link #MEASURED_STATE_TOO_SMALL}.
+     */
+    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
+        boolean optical = isLayoutModeOptical(this);
+        if (optical != isLayoutModeOptical(mParent)) {
+            Insets insets = getOpticalInsets();
+            int opticalWidth  = insets.left + insets.right;
+            int opticalHeight = insets.top  + insets.bottom;
+
+            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
+            measuredHeight += optical ? opticalHeight : -opticalHeight;
+        }
+        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
+    }
+
+    /**
+     * Sets the measured dimension without extra processing for things like optical bounds.
+     * Useful for reapplying consistent values that have already been cooked with adjustments
+     * for optical bounds, etc. such as those from the measurement cache.
+     *
+     * @param measuredWidth The measured width of this view.  May be a complex
+     * bit mask as defined by {@link #MEASURED_SIZE_MASK} and
+     * {@link #MEASURED_STATE_TOO_SMALL}.
+     * @param measuredHeight The measured height of this view.  May be a complex
+     * bit mask as defined by {@link #MEASURED_SIZE_MASK} and
+     * {@link #MEASURED_STATE_TOO_SMALL}.
+     */
+    private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
+        mMeasuredWidth = measuredWidth;
+        mMeasuredHeight = measuredHeight;
+
+        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
+    }
+
+    /**
+     * Merge two states as returned by {@link #getMeasuredState()}.
+     * @param curState The current state as returned from a view or the result
+     * of combining multiple views.
+     * @param newState The new view state to combine.
+     * @return Returns a new integer reflecting the combination of the two
+     * states.
+     */
+    public static int combineMeasuredStates(int curState, int newState) {
+        return curState | newState;
+    }
+
+    /**
+     * Version of {@link #resolveSizeAndState(int, int, int)}
+     * returning only the {@link #MEASURED_SIZE_MASK} bits of the result.
+     */
+    public static int resolveSize(int size, int measureSpec) {
+        return resolveSizeAndState(size, measureSpec, 0) & MEASURED_SIZE_MASK;
+    }
+
+    /**
+     * Utility to reconcile a desired size and state, with constraints imposed
+     * by a MeasureSpec. Will take the desired size, unless a different size
+     * is imposed by the constraints. The returned value is a compound integer,
+     * with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and
+     * optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the
+     * resulting size is smaller than the size the view wants to be.
+     *
+     * @param size How big the view wants to be.
+     * @param measureSpec Constraints imposed by the parent.
+     * @param childMeasuredState Size information bit mask for the view's
+     *                           children.
+     * @return Size information bit mask as defined by
+     *         {@link #MEASURED_SIZE_MASK} and
+     *         {@link #MEASURED_STATE_TOO_SMALL}.
+     */
+    public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
+        final int specMode = MeasureSpec.getMode(measureSpec);
+        final int specSize = MeasureSpec.getSize(measureSpec);
+        final int result;
+        switch (specMode) {
+            case MeasureSpec.AT_MOST:
+                if (specSize < size) {
+                    result = specSize | MEASURED_STATE_TOO_SMALL;
+                } else {
+                    result = size;
+                }
+                break;
+            case MeasureSpec.EXACTLY:
+                result = specSize;
+                break;
+            case MeasureSpec.UNSPECIFIED:
+            default:
+                result = size;
+        }
+        return result | (childMeasuredState & MEASURED_STATE_MASK);
+    }
+
+    /**
+     * Utility to return a default size. Uses the supplied size if the
+     * MeasureSpec imposed no constraints. Will get larger if allowed
+     * by the MeasureSpec.
+     *
+     * @param size Default size for this view
+     * @param measureSpec Constraints imposed by the parent
+     * @return The size this view should be.
+     */
+    public static int getDefaultSize(int size, int measureSpec) {
+        int result = size;
+        int specMode = MeasureSpec.getMode(measureSpec);
+        int specSize = MeasureSpec.getSize(measureSpec);
+
+        switch (specMode) {
+        case MeasureSpec.UNSPECIFIED:
+            result = size;
+            break;
+        case MeasureSpec.AT_MOST:
+        case MeasureSpec.EXACTLY:
+            result = specSize;
+            break;
+        }
+        return result;
+    }
+
+    /**
+     * Returns the suggested minimum height that the view should use. This
+     * returns the maximum of the view's minimum height
+     * and the background's minimum height
+     * ({@link android.graphics.drawable.Drawable#getMinimumHeight()}).
+     * <p>
+     * When being used in {@link #onMeasure(int, int)}, the caller should still
+     * ensure the returned height is within the requirements of the parent.
+     *
+     * @return The suggested minimum height of the view.
+     */
+    protected int getSuggestedMinimumHeight() {
+        return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
+
+    }
+
+    /**
+     * Returns the suggested minimum width that the view should use. This
+     * returns the maximum of the view's minimum width
+     * and the background's minimum width
+     *  ({@link android.graphics.drawable.Drawable#getMinimumWidth()}).
+     * <p>
+     * When being used in {@link #onMeasure(int, int)}, the caller should still
+     * ensure the returned width is within the requirements of the parent.
+     *
+     * @return The suggested minimum width of the view.
+     */
+    protected int getSuggestedMinimumWidth() {
+        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
+    }
+
+    /**
+     * Returns the minimum height of the view.
+     *
+     * @return the minimum height the view will try to be, in pixels
+     *
+     * @see #setMinimumHeight(int)
+     *
+     * @attr ref android.R.styleable#View_minHeight
+     */
+    @InspectableProperty(name = "minHeight")
+    public int getMinimumHeight() {
+        return mMinHeight;
+    }
+
+    /**
+     * Sets the minimum height of the view. It is not guaranteed the view will
+     * be able to achieve this minimum height (for example, if its parent layout
+     * constrains it with less available height).
+     *
+     * @param minHeight The minimum height the view will try to be, in pixels
+     *
+     * @see #getMinimumHeight()
+     *
+     * @attr ref android.R.styleable#View_minHeight
+     */
+    @RemotableViewMethod
+    public void setMinimumHeight(int minHeight) {
+        mMinHeight = minHeight;
+        requestLayout();
+    }
+
+    /**
+     * Returns the minimum width of the view.
+     *
+     * @return the minimum width the view will try to be, in pixels
+     *
+     * @see #setMinimumWidth(int)
+     *
+     * @attr ref android.R.styleable#View_minWidth
+     */
+    @InspectableProperty(name = "minWidth")
+    public int getMinimumWidth() {
+        return mMinWidth;
+    }
+
+    /**
+     * Sets the minimum width of the view. It is not guaranteed the view will
+     * be able to achieve this minimum width (for example, if its parent layout
+     * constrains it with less available width).
+     *
+     * @param minWidth The minimum width the view will try to be, in pixels
+     *
+     * @see #getMinimumWidth()
+     *
+     * @attr ref android.R.styleable#View_minWidth
+     */
+    @RemotableViewMethod
+    public void setMinimumWidth(int minWidth) {
+        mMinWidth = minWidth;
+        requestLayout();
+
+    }
+
+    /**
+     * Get the animation currently associated with this view.
+     *
+     * @return The animation that is currently playing or
+     *         scheduled to play for this view.
+     */
+    public Animation getAnimation() {
+        return mCurrentAnimation;
+    }
+
+    /**
+     * Start the specified animation now.
+     *
+     * @param animation the animation to start now
+     */
+    public void startAnimation(Animation animation) {
+        animation.setStartTime(Animation.START_ON_FIRST_FRAME);
+        setAnimation(animation);
+        invalidateParentCaches();
+        invalidate(true);
+    }
+
+    /**
+     * Cancels any animations for this view.
+     */
+    public void clearAnimation() {
+        if (mCurrentAnimation != null) {
+            mCurrentAnimation.detach();
+        }
+        mCurrentAnimation = null;
+        invalidateParentIfNeeded();
+    }
+
+    /**
+     * Sets the next animation to play for this view.
+     * If you want the animation to play immediately, use
+     * {@link #startAnimation(android.view.animation.Animation)} instead.
+     * This method provides allows fine-grained
+     * control over the start time and invalidation, but you
+     * must make sure that 1) the animation has a start time set, and
+     * 2) the view's parent (which controls animations on its children)
+     * will be invalidated when the animation is supposed to
+     * start.
+     *
+     * @param animation The next animation, or null.
+     */
+    public void setAnimation(Animation animation) {
+        mCurrentAnimation = animation;
+
+        if (animation != null) {
+            // If the screen is off assume the animation start time is now instead of
+            // the next frame we draw. Keeping the START_ON_FIRST_FRAME start time
+            // would cause the animation to start when the screen turns back on
+            if (mAttachInfo != null && mAttachInfo.mDisplayState == Display.STATE_OFF
+                    && animation.getStartTime() == Animation.START_ON_FIRST_FRAME) {
+                animation.setStartTime(AnimationUtils.currentAnimationTimeMillis());
+            }
+            animation.reset();
+        }
+    }
+
+    /**
+     * Invoked by a parent ViewGroup to notify the start of the animation
+     * currently associated with this view. If you override this method,
+     * always call super.onAnimationStart();
+     *
+     * @see #setAnimation(android.view.animation.Animation)
+     * @see #getAnimation()
+     */
+    @CallSuper
+    protected void onAnimationStart() {
+        mPrivateFlags |= PFLAG_ANIMATION_STARTED;
+    }
+
+    /**
+     * Invoked by a parent ViewGroup to notify the end of the animation
+     * currently associated with this view. If you override this method,
+     * always call super.onAnimationEnd();
+     *
+     * @see #setAnimation(android.view.animation.Animation)
+     * @see #getAnimation()
+     */
+    @CallSuper
+    protected void onAnimationEnd() {
+        mPrivateFlags &= ~PFLAG_ANIMATION_STARTED;
+    }
+
+    /**
+     * Invoked if there is a Transform that involves alpha. Subclass that can
+     * draw themselves with the specified alpha should return true, and then
+     * respect that alpha when their onDraw() is called. If this returns false
+     * then the view may be redirected to draw into an offscreen buffer to
+     * fulfill the request, which will look fine, but may be slower than if the
+     * subclass handles it internally. The default implementation returns false.
+     *
+     * @param alpha The alpha (0..255) to apply to the view's drawing
+     * @return true if the view can draw with the specified alpha.
+     */
+    protected boolean onSetAlpha(int alpha) {
+        return false;
+    }
+
+    /**
+     * This is used by the ViewRoot to perform an optimization when
+     * the view hierarchy contains one or several SurfaceView.
+     * SurfaceView is always considered transparent, but its children are not,
+     * therefore all View objects remove themselves from the global transparent
+     * region (passed as a parameter to this function).
+     *
+     * @param region The transparent region for this ViewAncestor (window).
+     *
+     * @return Returns true if the effective visibility of the view at this
+     * point is opaque, regardless of the transparent region; returns false
+     * if it is possible for underlying windows to be seen behind the view.
+     *
+     */
+    public boolean gatherTransparentRegion(@Nullable Region region) {
+        final AttachInfo attachInfo = mAttachInfo;
+        if (region != null && attachInfo != null) {
+            final int pflags = mPrivateFlags;
+            if ((pflags & PFLAG_SKIP_DRAW) == 0) {
+                // The SKIP_DRAW flag IS NOT set, so this view draws. We need to
+                // remove it from the transparent region.
+                final int[] location = attachInfo.mTransparentLocation;
+                getLocationInWindow(location);
+                // When a view has Z value, then it will be better to leave some area below the view
+                // for drawing shadow. The shadow outset is proportional to the Z value. Note that
+                // the bottom part needs more offset than the left, top and right parts due to the
+                // spot light effects.
+                int shadowOffset = getZ() > 0 ? (int) getZ() : 0;
+                region.op(location[0] - shadowOffset, location[1] - shadowOffset,
+                        location[0] + mRight - mLeft + shadowOffset,
+                        location[1] + mBottom - mTop + (shadowOffset * 3), Region.Op.DIFFERENCE);
+            } else {
+                if (mBackground != null && mBackground.getOpacity() != PixelFormat.TRANSPARENT) {
+                    // The SKIP_DRAW flag IS set and the background drawable exists, we remove
+                    // the background drawable's non-transparent parts from this transparent region.
+                    applyDrawableToTransparentRegion(mBackground, region);
+                }
+                if (mForegroundInfo != null && mForegroundInfo.mDrawable != null
+                        && mForegroundInfo.mDrawable.getOpacity() != PixelFormat.TRANSPARENT) {
+                    // Similarly, we remove the foreground drawable's non-transparent parts.
+                    applyDrawableToTransparentRegion(mForegroundInfo.mDrawable, region);
+                }
+                if (mDefaultFocusHighlight != null
+                        && mDefaultFocusHighlight.getOpacity() != PixelFormat.TRANSPARENT) {
+                    // Similarly, we remove the default focus highlight's non-transparent parts.
+                    applyDrawableToTransparentRegion(mDefaultFocusHighlight, region);
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Play a sound effect for this view.
+     *
+     * <p>The framework will play sound effects for some built in actions, such as
+     * clicking, but you may wish to play these effects in your widget,
+     * for instance, for internal navigation.
+     *
+     * <p>The sound effect will only be played if sound effects are enabled by the user, and
+     * {@link #isSoundEffectsEnabled()} is true.
+     *
+     * @param soundConstant One of the constants defined in {@link SoundEffectConstants}.
+     */
+    public void playSoundEffect(@SoundEffectConstants.SoundEffect int soundConstant) {
+        if (mAttachInfo == null || mAttachInfo.mRootCallbacks == null || !isSoundEffectsEnabled()) {
+            return;
+        }
+        mAttachInfo.mRootCallbacks.playSoundEffect(soundConstant);
+    }
+
+    /**
+     * BZZZTT!!1!
+     *
+     * <p>Provide haptic feedback to the user for this view.
+     *
+     * <p>The framework will provide haptic feedback for some built in actions,
+     * such as long presses, but you may wish to provide feedback for your
+     * own widget.
+     *
+     * <p>The feedback will only be performed if
+     * {@link #isHapticFeedbackEnabled()} is true.
+     *
+     * @param feedbackConstant One of the constants defined in
+     * {@link HapticFeedbackConstants}
+     */
+    public boolean performHapticFeedback(int feedbackConstant) {
+        return performHapticFeedback(feedbackConstant, 0);
+    }
+
+    /**
+     * BZZZTT!!1!
+     *
+     * <p>Like {@link #performHapticFeedback(int)}, with additional options.
+     *
+     * @param feedbackConstant One of the constants defined in
+     * {@link HapticFeedbackConstants}
+     * @param flags Additional flags as per {@link HapticFeedbackConstants}.
+     */
+    public boolean performHapticFeedback(int feedbackConstant, int flags) {
+        if (mAttachInfo == null) {
+            return false;
+        }
+        //noinspection SimplifiableIfStatement
+        if ((flags & HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING) == 0
+                && !isHapticFeedbackEnabled()) {
+            return false;
+        }
+        return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant,
+                (flags & HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0);
+    }
+
+    /**
+     * Request that the visibility of the status bar or other screen/window
+     * decorations be changed.
+     *
+     * <p>This method is used to put the over device UI into temporary modes
+     * where the user's attention is focused more on the application content,
+     * by dimming or hiding surrounding system affordances.  This is typically
+     * used in conjunction with {@link Window#FEATURE_ACTION_BAR_OVERLAY
+     * Window.FEATURE_ACTION_BAR_OVERLAY}, allowing the applications content
+     * to be placed behind the action bar (and with these flags other system
+     * affordances) so that smooth transitions between hiding and showing them
+     * can be done.
+     *
+     * <p>Two representative examples of the use of system UI visibility is
+     * implementing a content browsing application (like a magazine reader)
+     * and a video playing application.
+     *
+     * <p>The first code shows a typical implementation of a View in a content
+     * browsing application.  In this implementation, the application goes
+     * into a content-oriented mode by hiding the status bar and action bar,
+     * and putting the navigation elements into lights out mode.  The user can
+     * then interact with content while in this mode.  Such an application should
+     * provide an easy way for the user to toggle out of the mode (such as to
+     * check information in the status bar or access notifications).  In the
+     * implementation here, this is done simply by tapping on the content.
+     *
+     * {@sample development/samples/ApiDemos/src/com/example/android/apis/view/ContentBrowserActivity.java
+     *      content}
+     *
+     * <p>This second code sample shows a typical implementation of a View
+     * in a video playing application.  In this situation, while the video is
+     * playing the application would like to go into a complete full-screen mode,
+     * to use as much of the display as possible for the video.  When in this state
+     * the user can not interact with the application; the system intercepts
+     * touching on the screen to pop the UI out of full screen mode.  See
+     * {@link #fitSystemWindows(Rect)} for a sample layout that goes with this code.
+     *
+     * {@sample development/samples/ApiDemos/src/com/example/android/apis/view/VideoPlayerActivity.java
+     *      content}
+     *
+     * @param visibility  Bitwise-or of flags {@link #SYSTEM_UI_FLAG_LOW_PROFILE},
+     * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, {@link #SYSTEM_UI_FLAG_FULLSCREEN},
+     * {@link #SYSTEM_UI_FLAG_LAYOUT_STABLE}, {@link #SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION},
+     * {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}, {@link #SYSTEM_UI_FLAG_IMMERSIVE},
+     * and {@link #SYSTEM_UI_FLAG_IMMERSIVE_STICKY}.
+     *
+     * @deprecated SystemUiVisibility flags are deprecated. Use {@link WindowInsetsController}
+     * instead.
+     */
+    @Deprecated
+    public void setSystemUiVisibility(int visibility) {
+        if (visibility != mSystemUiVisibility) {
+            mSystemUiVisibility = visibility;
+            if (mParent != null && mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) {
+                mParent.recomputeViewAttributes(this);
+            }
+        }
+    }
+
+    /**
+     * Returns the last {@link #setSystemUiVisibility(int)} that this view has requested.
+     * @return  Bitwise-or of flags {@link #SYSTEM_UI_FLAG_LOW_PROFILE},
+     * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, {@link #SYSTEM_UI_FLAG_FULLSCREEN},
+     * {@link #SYSTEM_UI_FLAG_LAYOUT_STABLE}, {@link #SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION},
+     * {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}, {@link #SYSTEM_UI_FLAG_IMMERSIVE},
+     * and {@link #SYSTEM_UI_FLAG_IMMERSIVE_STICKY}.
+     *
+     * @deprecated SystemUiVisibility flags are deprecated. Use {@link WindowInsetsController}
+     * instead.
+     */
+    @Deprecated
+    public int getSystemUiVisibility() {
+        return mSystemUiVisibility;
+    }
+
+    /**
+     * Returns the current system UI visibility that is currently set for
+     * the entire window.  This is the combination of the
+     * {@link #setSystemUiVisibility(int)} values supplied by all of the
+     * views in the window.
+     *
+     * @deprecated SystemUiVisibility flags are deprecated. Use {@link WindowInsetsController}
+     * instead.
+     */
+    @Deprecated
+    public int getWindowSystemUiVisibility() {
+        return mAttachInfo != null ? mAttachInfo.mSystemUiVisibility : 0;
+    }
+
+    /**
+     * Override to find out when the window's requested system UI visibility
+     * has changed, that is the value returned by {@link #getWindowSystemUiVisibility()}.
+     * This is different from the callbacks received through
+     * {@link #setOnSystemUiVisibilityChangeListener(OnSystemUiVisibilityChangeListener)}
+     * in that this is only telling you about the local request of the window,
+     * not the actual values applied by the system.
+     *
+     * @deprecated SystemUiVisibility flags are deprecated. Use {@link WindowInsetsController}
+     * instead.
+     */
+    @Deprecated
+    public void onWindowSystemUiVisibilityChanged(int visible) {
+    }
+
+    /**
+     * Dispatch callbacks to {@link #onWindowSystemUiVisibilityChanged(int)} down
+     * the view hierarchy.
+     *
+     * @deprecated SystemUiVisibility flags are deprecated. Use {@link WindowInsetsController}
+     * instead.
+     */
+    @Deprecated
+    public void dispatchWindowSystemUiVisiblityChanged(int visible) {
+        onWindowSystemUiVisibilityChanged(visible);
+    }
+
+    /**
+     * Set a listener to receive callbacks when the visibility of the system bar changes.
+     * @param l  The {@link OnSystemUiVisibilityChangeListener} to receive callbacks.
+     *
+     * @deprecated Use {@link WindowInsets#isVisible(int)} to find out about system bar visibilities
+     * by setting a {@link OnApplyWindowInsetsListener} on this view.
+     */
+    @Deprecated
+    public void setOnSystemUiVisibilityChangeListener(OnSystemUiVisibilityChangeListener l) {
+        getListenerInfo().mOnSystemUiVisibilityChangeListener = l;
+        if (mParent != null && mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) {
+            mParent.recomputeViewAttributes(this);
+        }
+    }
+
+    /**
+     * Dispatch callbacks to {@link #setOnSystemUiVisibilityChangeListener} down
+     * the view hierarchy.
+     *
+     * @deprecated Use {@link WindowInsets#isVisible(int)} to find out about system bar visibilities
+     * by setting a {@link OnApplyWindowInsetsListener} on this view.
+     */
+    @Deprecated
+    public void dispatchSystemUiVisibilityChanged(int visibility) {
+        ListenerInfo li = mListenerInfo;
+        if (li != null && li.mOnSystemUiVisibilityChangeListener != null) {
+            li.mOnSystemUiVisibilityChangeListener.onSystemUiVisibilityChange(
+                    visibility & PUBLIC_STATUS_BAR_VISIBILITY_MASK);
+        }
+    }
+
+    boolean updateLocalSystemUiVisibility(int localValue, int localChanges) {
+        int val = (mSystemUiVisibility&~localChanges) | (localValue&localChanges);
+        if (val != mSystemUiVisibility) {
+            setSystemUiVisibility(val);
+            return true;
+        }
+        return false;
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public void setDisabledSystemUiVisibility(int flags) {
+        if (mAttachInfo != null) {
+            if (mAttachInfo.mDisabledSystemUiVisibility != flags) {
+                mAttachInfo.mDisabledSystemUiVisibility = flags;
+                if (mParent != null) {
+                    mParent.recomputeViewAttributes(this);
+                }
+            }
+        }
+    }
+
+    /**
+     * This needs to be a better API before it is exposed. For now, only the root view will get
+     * notified.
+     * @hide
+     */
+    public void onSystemBarAppearanceChanged(@WindowInsetsController.Appearance int appearance) {
+    }
+
+    /**
+     * Creates an image that the system displays during the drag and drop
+     * operation. This is called a &quot;drag shadow&quot;. The default implementation
+     * for a DragShadowBuilder based on a View returns an image that has exactly the same
+     * appearance as the given View. The default also positions the center of the drag shadow
+     * directly under the touch point. If no View is provided (the constructor with no parameters
+     * is used), and {@link #onProvideShadowMetrics(Point,Point) onProvideShadowMetrics()} and
+     * {@link #onDrawShadow(Canvas) onDrawShadow()} are not overridden, then the
+     * default is an invisible drag shadow.
+     * <p>
+     * You are not required to use the View you provide to the constructor as the basis of the
+     * drag shadow. The {@link #onDrawShadow(Canvas) onDrawShadow()} method allows you to draw
+     * anything you want as the drag shadow.
+     * </p>
+     * <p>
+     *  You pass a DragShadowBuilder object to the system when you start the drag. The system
+     *  calls {@link #onProvideShadowMetrics(Point,Point) onProvideShadowMetrics()} to get the
+     *  size and position of the drag shadow. It uses this data to construct a
+     *  {@link android.graphics.Canvas} object, then it calls {@link #onDrawShadow(Canvas) onDrawShadow()}
+     *  so that your application can draw the shadow image in the Canvas.
+     * </p>
+     *
+     * <div class="special reference">
+     * <h3>Developer Guides</h3>
+     * <p>For a guide to implementing drag and drop features, read the
+     * <a href="{@docRoot}guide/topics/ui/drag-drop.html">Drag and Drop</a> developer guide.</p>
+     * </div>
+     */
+    public static class DragShadowBuilder {
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        private final WeakReference<View> mView;
+
+        /**
+         * Constructs a shadow image builder based on a View. By default, the resulting drag
+         * shadow will have the same appearance and dimensions as the View, with the touch point
+         * over the center of the View.
+         * @param view A View. Any View in scope can be used.
+         */
+        public DragShadowBuilder(View view) {
+            mView = new WeakReference<View>(view);
+        }
+
+        /**
+         * Construct a shadow builder object with no associated View.  This
+         * constructor variant is only useful when the {@link #onProvideShadowMetrics(Point, Point)}
+         * and {@link #onDrawShadow(Canvas)} methods are also overridden in order
+         * to supply the drag shadow's dimensions and appearance without
+         * reference to any View object.
+         */
+        public DragShadowBuilder() {
+            mView = new WeakReference<View>(null);
+        }
+
+        /**
+         * Returns the View object that had been passed to the
+         * {@link #DragShadowBuilder(View)}
+         * constructor.  If that View parameter was {@code null} or if the
+         * {@link #DragShadowBuilder()}
+         * constructor was used to instantiate the builder object, this method will return
+         * null.
+         *
+         * @return The View object associate with this builder object.
+         */
+        @SuppressWarnings({"JavadocReference"})
+        final public View getView() {
+            return mView.get();
+        }
+
+        /**
+         * Provides the metrics for the shadow image. These include the dimensions of
+         * the shadow image, and the point within that shadow that should
+         * be centered under the touch location while dragging.
+         * <p>
+         * The default implementation sets the dimensions of the shadow to be the
+         * same as the dimensions of the View itself and centers the shadow under
+         * the touch point.
+         * </p>
+         *
+         * @param outShadowSize A {@link android.graphics.Point} containing the width and height
+         * of the shadow image. Your application must set {@link android.graphics.Point#x} to the
+         * desired width and must set {@link android.graphics.Point#y} to the desired height of the
+         * image. Since Android P, the width and height must be positive values.
+         *
+         * @param outShadowTouchPoint A {@link android.graphics.Point} for the position within the
+         * shadow image that should be underneath the touch point during the drag and drop
+         * operation. Your application must set {@link android.graphics.Point#x} to the
+         * X coordinate and {@link android.graphics.Point#y} to the Y coordinate of this position.
+         */
+        public void onProvideShadowMetrics(Point outShadowSize, Point outShadowTouchPoint) {
+            final View view = mView.get();
+            if (view != null) {
+                outShadowSize.set(view.getWidth(), view.getHeight());
+                outShadowTouchPoint.set(outShadowSize.x / 2, outShadowSize.y / 2);
+            } else {
+                Log.e(View.VIEW_LOG_TAG, "Asked for drag thumb metrics but no view");
+            }
+        }
+
+        /**
+         * Draws the shadow image. The system creates the {@link android.graphics.Canvas} object
+         * based on the dimensions it received from the
+         * {@link #onProvideShadowMetrics(Point, Point)} callback.
+         *
+         * @param canvas A {@link android.graphics.Canvas} object in which to draw the shadow image.
+         */
+        public void onDrawShadow(Canvas canvas) {
+            final View view = mView.get();
+            if (view != null) {
+                view.draw(canvas);
+            } else {
+                Log.e(View.VIEW_LOG_TAG, "Asked to draw drag shadow but no view");
+            }
+        }
+    }
+
+    /**
+     * @deprecated Use {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object, int)
+     * startDragAndDrop()} for newer platform versions.
+     */
+    @Deprecated
+    public final boolean startDrag(ClipData data, DragShadowBuilder shadowBuilder,
+                                   Object myLocalState, int flags) {
+        return startDragAndDrop(data, shadowBuilder, myLocalState, flags);
+    }
+
+    /**
+     * Starts a drag and drop operation. When your application calls this method, it passes a
+     * {@link android.view.View.DragShadowBuilder} object to the system. The
+     * system calls this object's {@link DragShadowBuilder#onProvideShadowMetrics(Point, Point)}
+     * to get metrics for the drag shadow, and then calls the object's
+     * {@link DragShadowBuilder#onDrawShadow(Canvas)} to draw the drag shadow itself.
+     * <p>
+     *  Once the system has the drag shadow, it begins the drag and drop operation by sending
+     *  drag events to all the View objects in your application that are currently visible. It does
+     *  this either by calling the View object's drag listener (an implementation of
+     *  {@link android.view.View.OnDragListener#onDrag(View,DragEvent) onDrag()} or by calling the
+     *  View object's {@link android.view.View#onDragEvent(DragEvent) onDragEvent()} method.
+     *  Both are passed a {@link android.view.DragEvent} object that has a
+     *  {@link android.view.DragEvent#getAction()} value of
+     *  {@link android.view.DragEvent#ACTION_DRAG_STARTED}.
+     * </p>
+     * <p>
+     * Your application can invoke {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object,
+     * int) startDragAndDrop()} on any attached View object. The View object does not need to be
+     * the one used in {@link android.view.View.DragShadowBuilder}, nor does it need to be related
+     * to the View the user selected for dragging.
+     * </p>
+     * @param data A {@link android.content.ClipData} object pointing to the data to be
+     * transferred by the drag and drop operation.
+     * @param shadowBuilder A {@link android.view.View.DragShadowBuilder} object for building the
+     * drag shadow.
+     * @param myLocalState An {@link java.lang.Object} containing local data about the drag and
+     * drop operation. When dispatching drag events to views in the same activity this object
+     * will be available through {@link android.view.DragEvent#getLocalState()}. Views in other
+     * activities will not have access to this data ({@link android.view.DragEvent#getLocalState()}
+     * will return null).
+     * <p>
+     * myLocalState is a lightweight mechanism for the sending information from the dragged View
+     * to the target Views. For example, it can contain flags that differentiate between a
+     * a copy operation and a move operation.
+     * </p>
+     * @param flags Flags that control the drag and drop operation. This can be set to 0 for no
+     * flags, or any combination of the following:
+     *     <ul>
+     *         <li>{@link #DRAG_FLAG_GLOBAL}</li>
+     *         <li>{@link #DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION}</li>
+     *         <li>{@link #DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION}</li>
+     *         <li>{@link #DRAG_FLAG_GLOBAL_URI_READ}</li>
+     *         <li>{@link #DRAG_FLAG_GLOBAL_URI_WRITE}</li>
+     *         <li>{@link #DRAG_FLAG_OPAQUE}</li>
+     *     </ul>
+     * @return {@code true} if the method completes successfully, or
+     * {@code false} if it fails anywhere. Returning {@code false} means the system was unable to
+     * do a drag because of another ongoing operation or some other reasons.
+     */
+    public final boolean startDragAndDrop(ClipData data, DragShadowBuilder shadowBuilder,
+            Object myLocalState, int flags) {
+        if (ViewDebug.DEBUG_DRAG) {
+            Log.d(VIEW_LOG_TAG, "startDragAndDrop: data=" + data + " flags=" + flags);
+        }
+        if (mAttachInfo == null) {
+            Log.w(VIEW_LOG_TAG, "startDragAndDrop called on a detached view.");
+            return false;
+        }
+        if (!mAttachInfo.mViewRootImpl.mSurface.isValid()) {
+            Log.w(VIEW_LOG_TAG, "startDragAndDrop called with an invalid surface.");
+            return false;
+        }
+
+        if (data != null) {
+            data.prepareToLeaveProcess((flags & View.DRAG_FLAG_GLOBAL) != 0);
+        }
+
+        Point shadowSize = new Point();
+        Point shadowTouchPoint = new Point();
+        shadowBuilder.onProvideShadowMetrics(shadowSize, shadowTouchPoint);
+
+        if ((shadowSize.x < 0) || (shadowSize.y < 0)
+                || (shadowTouchPoint.x < 0) || (shadowTouchPoint.y < 0)) {
+            throw new IllegalStateException("Drag shadow dimensions must not be negative");
+        }
+
+        // Create 1x1 surface when zero surface size is specified because SurfaceControl.Builder
+        // does not accept zero size surface.
+        if (shadowSize.x == 0  || shadowSize.y == 0) {
+            if (!sAcceptZeroSizeDragShadow) {
+                throw new IllegalStateException("Drag shadow dimensions must be positive");
+            }
+            shadowSize.x = 1;
+            shadowSize.y = 1;
+        }
+
+        if (ViewDebug.DEBUG_DRAG) {
+            Log.d(VIEW_LOG_TAG, "drag shadow: width=" + shadowSize.x + " height=" + shadowSize.y
+                    + " shadowX=" + shadowTouchPoint.x + " shadowY=" + shadowTouchPoint.y);
+        }
+
+        final ViewRootImpl root = mAttachInfo.mViewRootImpl;
+        final SurfaceSession session = new SurfaceSession();
+        final SurfaceControl surfaceControl = new SurfaceControl.Builder(session)
+                .setName("drag surface")
+                .setParent(root.getSurfaceControl())
+                .setBufferSize(shadowSize.x, shadowSize.y)
+                .setFormat(PixelFormat.TRANSLUCENT)
+                .setCallsite("View.startDragAndDrop")
+                .build();
+        final Surface surface = new Surface();
+        surface.copyFrom(surfaceControl);
+        IBinder token = null;
+        try {
+            final Canvas canvas = isHardwareAccelerated()
+                    ? surface.lockHardwareCanvas()
+                    : surface.lockCanvas(null);
+            try {
+                canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+                shadowBuilder.onDrawShadow(canvas);
+            } finally {
+                surface.unlockCanvasAndPost(canvas);
+            }
+
+            // repurpose 'shadowSize' for the last touch point
+            root.getLastTouchPoint(shadowSize);
+
+            token = mAttachInfo.mSession.performDrag(
+                    mAttachInfo.mWindow, flags, surfaceControl, root.getLastTouchSource(),
+                    shadowSize.x, shadowSize.y, shadowTouchPoint.x, shadowTouchPoint.y, data);
+            if (ViewDebug.DEBUG_DRAG) {
+                Log.d(VIEW_LOG_TAG, "performDrag returned " + token);
+            }
+            if (token != null) {
+                if (mAttachInfo.mDragSurface != null) {
+                    mAttachInfo.mDragSurface.release();
+                }
+                mAttachInfo.mDragSurface = surface;
+                mAttachInfo.mDragToken = token;
+                // Cache the local state object for delivery with DragEvents
+                root.setLocalDragState(myLocalState);
+            }
+            return token != null;
+        } catch (Exception e) {
+            Log.e(VIEW_LOG_TAG, "Unable to initiate drag", e);
+            return false;
+        } finally {
+            if (token == null) {
+                surface.destroy();
+            }
+            session.kill();
+        }
+    }
+
+    /**
+     * Cancels an ongoing drag and drop operation.
+     * <p>
+     * A {@link android.view.DragEvent} object with
+     * {@link android.view.DragEvent#getAction()} value of
+     * {@link android.view.DragEvent#ACTION_DRAG_ENDED} and
+     * {@link android.view.DragEvent#getResult()} value of {@code false}
+     * will be sent to every
+     * View that received {@link android.view.DragEvent#ACTION_DRAG_STARTED}
+     * even if they are not currently visible.
+     * </p>
+     * <p>
+     * This method can be called on any View in the same window as the View on which
+     * {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object, int) startDragAndDrop}
+     * was called.
+     * </p>
+     */
+    public final void cancelDragAndDrop() {
+        if (ViewDebug.DEBUG_DRAG) {
+            Log.d(VIEW_LOG_TAG, "cancelDragAndDrop");
+        }
+        if (mAttachInfo == null) {
+            Log.w(VIEW_LOG_TAG, "cancelDragAndDrop called on a detached view.");
+            return;
+        }
+        if (mAttachInfo.mDragToken != null) {
+            try {
+                mAttachInfo.mSession.cancelDragAndDrop(mAttachInfo.mDragToken, false);
+            } catch (Exception e) {
+                Log.e(VIEW_LOG_TAG, "Unable to cancel drag", e);
+            }
+            mAttachInfo.mDragToken = null;
+        } else {
+            Log.e(VIEW_LOG_TAG, "No active drag to cancel");
+        }
+    }
+
+    /**
+     * Updates the drag shadow for the ongoing drag and drop operation.
+     *
+     * @param shadowBuilder A {@link android.view.View.DragShadowBuilder} object for building the
+     * new drag shadow.
+     */
+    public final void updateDragShadow(DragShadowBuilder shadowBuilder) {
+        if (ViewDebug.DEBUG_DRAG) {
+            Log.d(VIEW_LOG_TAG, "updateDragShadow");
+        }
+        if (mAttachInfo == null) {
+            Log.w(VIEW_LOG_TAG, "updateDragShadow called on a detached view.");
+            return;
+        }
+        if (mAttachInfo.mDragToken != null) {
+            try {
+                Canvas canvas = isHardwareAccelerated()
+                        ? mAttachInfo.mDragSurface.lockHardwareCanvas()
+                        : mAttachInfo.mDragSurface.lockCanvas(null);
+                try {
+                    canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+                    shadowBuilder.onDrawShadow(canvas);
+                } finally {
+                    mAttachInfo.mDragSurface.unlockCanvasAndPost(canvas);
+                }
+            } catch (Exception e) {
+                Log.e(VIEW_LOG_TAG, "Unable to update drag shadow", e);
+            }
+        } else {
+            Log.e(VIEW_LOG_TAG, "No active drag");
+        }
+    }
+
+    /**
+     * Starts a move from {startX, startY}, the amount of the movement will be the offset
+     * between {startX, startY} and the new cursor positon.
+     * @param startX horizontal coordinate where the move started.
+     * @param startY vertical coordinate where the move started.
+     * @return whether moving was started successfully.
+     * @hide
+     */
+    public final boolean startMovingTask(float startX, float startY) {
+        if (ViewDebug.DEBUG_POSITIONING) {
+            Log.d(VIEW_LOG_TAG, "startMovingTask: {" + startX + "," + startY + "}");
+        }
+        try {
+            return mAttachInfo.mSession.startMovingTask(mAttachInfo.mWindow, startX, startY);
+        } catch (RemoteException e) {
+            Log.e(VIEW_LOG_TAG, "Unable to start moving", e);
+        }
+        return false;
+    }
+
+    /**
+     * Finish a window move task.
+     * @hide
+     */
+    public void finishMovingTask() {
+        if (ViewDebug.DEBUG_POSITIONING) {
+            Log.d(VIEW_LOG_TAG, "finishMovingTask");
+        }
+        try {
+            mAttachInfo.mSession.finishMovingTask(mAttachInfo.mWindow);
+        } catch (RemoteException e) {
+            Log.e(VIEW_LOG_TAG, "Unable to finish moving", e);
+        }
+    }
+
+    /**
+     * Handles drag events sent by the system following a call to
+     * {@link android.view.View#startDragAndDrop(ClipData,DragShadowBuilder,Object,int)
+     * startDragAndDrop()}.
+     *<p>
+     * When the system calls this method, it passes a
+     * {@link android.view.DragEvent} object. A call to
+     * {@link android.view.DragEvent#getAction()} returns one of the action type constants defined
+     * in DragEvent. The method uses these to determine what is happening in the drag and drop
+     * operation.
+     * </p>
+     * <p>
+     * The default implementation returns false, except if an {@link OnReceiveContentListener}
+     * is {@link #setOnReceiveContentListener set} for this view. If an
+     * {@link OnReceiveContentListener} is set, the default implementation...
+     * <ul>
+     * <li>returns true for an
+     * {@link android.view.DragEvent#ACTION_DRAG_STARTED ACTION_DRAG_STARTED} event
+     * <li>calls {@link #performReceiveContent} for an
+     * {@link android.view.DragEvent#ACTION_DROP ACTION_DROP} event
+     * <li>returns true for an {@link android.view.DragEvent#ACTION_DROP ACTION_DROP} event, if
+     * the listener consumed some or all of the content
+     * </ul>
+     * </p>
+     *
+     * @param event The {@link android.view.DragEvent} sent by the system.
+     * The {@link android.view.DragEvent#getAction()} method returns an action type constant defined
+     * in DragEvent, indicating the type of drag event represented by this object.
+     * @return {@code true} if the method was successful, otherwise {@code false}.
+     * <p>
+     *  The method should return {@code true} in response to an action type of
+     *  {@link android.view.DragEvent#ACTION_DRAG_STARTED} to receive drag events for the current
+     *  operation.
+     * </p>
+     * <p>
+     *  The method should also return {@code true} in response to an action type of
+     *  {@link android.view.DragEvent#ACTION_DROP} if it consumed the drop, or
+     *  {@code false} if it didn't.
+     * </p>
+     * <p>
+     *  For all other events, the return value is ignored.
+     * </p>
+     */
+    public boolean onDragEvent(DragEvent event) {
+        if (mListenerInfo == null || mListenerInfo.mOnReceiveContentListener == null) {
+            return false;
+        }
+        // Accept drag events by default if there's an OnReceiveContentListener set.
+        if (event.getAction() == DragEvent.ACTION_DRAG_STARTED) {
+            return true;
+        }
+        if (event.getAction() == DragEvent.ACTION_DROP) {
+            final DragAndDropPermissions permissions = DragAndDropPermissions.obtain(event);
+            if (permissions != null) {
+                permissions.takeTransient();
+            }
+            final ContentInfo payload =
+                    new ContentInfo.Builder(event.getClipData(), SOURCE_DRAG_AND_DROP)
+                            .setDragAndDropPermissions(permissions)
+                            .build();
+            ContentInfo remainingPayload = performReceiveContent(payload);
+            // Return true unless none of the payload was consumed.
+            return remainingPayload != payload;
+        }
+        return false;
+    }
+
+    // Dispatches ACTION_DRAG_ENTERED and ACTION_DRAG_EXITED events for pre-Nougat apps.
+    boolean dispatchDragEnterExitInPreN(DragEvent event) {
+        return callDragEventHandler(event);
+    }
+
+    /**
+     * Detects if this View is enabled and has a drag event listener.
+     * If both are true, then it calls the drag event listener with the
+     * {@link android.view.DragEvent} it received. If the drag event listener returns
+     * {@code true}, then dispatchDragEvent() returns {@code true}.
+     * <p>
+     * For all other cases, the method calls the
+     * {@link android.view.View#onDragEvent(DragEvent) onDragEvent()} drag event handler
+     * method and returns its result.
+     * </p>
+     * <p>
+     * This ensures that a drag event is always consumed, even if the View does not have a drag
+     * event listener. However, if the View has a listener and the listener returns true, then
+     * onDragEvent() is not called.
+     * </p>
+     */
+    public boolean dispatchDragEvent(DragEvent event) {
+        event.mEventHandlerWasCalled = true;
+        if (event.mAction == DragEvent.ACTION_DRAG_LOCATION ||
+            event.mAction == DragEvent.ACTION_DROP) {
+            // About to deliver an event with coordinates to this view. Notify that now this view
+            // has drag focus. This will send exit/enter events as needed.
+            getViewRootImpl().setDragFocus(this, event);
+        }
+        return callDragEventHandler(event);
+    }
+
+    final boolean callDragEventHandler(DragEvent event) {
+        final boolean result;
+
+        ListenerInfo li = mListenerInfo;
+        //noinspection SimplifiableIfStatement
+        if (li != null && li.mOnDragListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
+                && li.mOnDragListener.onDrag(this, event)) {
+            result = true;
+        } else {
+            result = onDragEvent(event);
+        }
+
+        switch (event.mAction) {
+            case DragEvent.ACTION_DRAG_ENTERED: {
+                mPrivateFlags2 |= View.PFLAG2_DRAG_HOVERED;
+                refreshDrawableState();
+            } break;
+            case DragEvent.ACTION_DRAG_EXITED: {
+                mPrivateFlags2 &= ~View.PFLAG2_DRAG_HOVERED;
+                refreshDrawableState();
+            } break;
+            case DragEvent.ACTION_DRAG_ENDED: {
+                mPrivateFlags2 &= ~View.DRAG_MASK;
+                refreshDrawableState();
+            } break;
+        }
+
+        return result;
+    }
+
+    boolean canAcceptDrag() {
+        return (mPrivateFlags2 & PFLAG2_DRAG_CAN_ACCEPT) != 0;
+    }
+
+    /**
+     * This needs to be a better API (NOT ON VIEW) before it is exposed.  If
+     * it is ever exposed at all.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void onCloseSystemDialogs(String reason) {
+    }
+
+    /**
+     * Given a Drawable whose bounds have been set to draw into this view,
+     * update a Region being computed for
+     * {@link #gatherTransparentRegion(android.graphics.Region)} so
+     * that any non-transparent parts of the Drawable are removed from the
+     * given transparent region.
+     *
+     * @param dr The Drawable whose transparency is to be applied to the region.
+     * @param region A Region holding the current transparency information,
+     * where any parts of the region that are set are considered to be
+     * transparent.  On return, this region will be modified to have the
+     * transparency information reduced by the corresponding parts of the
+     * Drawable that are not transparent.
+     * {@hide}
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public void applyDrawableToTransparentRegion(Drawable dr, Region region) {
+        if (DBG) {
+            Log.i("View", "Getting transparent region for: " + this);
+        }
+        final Region r = dr.getTransparentRegion();
+        final Rect db = dr.getBounds();
+        final AttachInfo attachInfo = mAttachInfo;
+        if (r != null && attachInfo != null) {
+            final int w = getRight()-getLeft();
+            final int h = getBottom()-getTop();
+            if (db.left > 0) {
+                //Log.i("VIEW", "Drawable left " + db.left + " > view 0");
+                r.op(0, 0, db.left, h, Region.Op.UNION);
+            }
+            if (db.right < w) {
+                //Log.i("VIEW", "Drawable right " + db.right + " < view " + w);
+                r.op(db.right, 0, w, h, Region.Op.UNION);
+            }
+            if (db.top > 0) {
+                //Log.i("VIEW", "Drawable top " + db.top + " > view 0");
+                r.op(0, 0, w, db.top, Region.Op.UNION);
+            }
+            if (db.bottom < h) {
+                //Log.i("VIEW", "Drawable bottom " + db.bottom + " < view " + h);
+                r.op(0, db.bottom, w, h, Region.Op.UNION);
+            }
+            final int[] location = attachInfo.mTransparentLocation;
+            getLocationInWindow(location);
+            r.translate(location[0], location[1]);
+            region.op(r, Region.Op.INTERSECT);
+        } else {
+            region.op(db, Region.Op.DIFFERENCE);
+        }
+    }
+
+    private void checkForLongClick(long delay, float x, float y, int classification) {
+        if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
+            mHasPerformedLongPress = false;
+
+            if (mPendingCheckForLongPress == null) {
+                mPendingCheckForLongPress = new CheckForLongPress();
+            }
+            mPendingCheckForLongPress.setAnchor(x, y);
+            mPendingCheckForLongPress.rememberWindowAttachCount();
+            mPendingCheckForLongPress.rememberPressedState();
+            mPendingCheckForLongPress.setClassification(classification);
+            postDelayed(mPendingCheckForLongPress, delay);
+        }
+    }
+
+    /**
+     * Inflate a view from an XML resource.  This convenience method wraps the {@link
+     * LayoutInflater} class, which provides a full range of options for view inflation.
+     *
+     * @param context The Context object for your activity or application.
+     * @param resource The resource ID to inflate
+     * @param root A view group that will be the parent.  Used to properly inflate the
+     * layout_* parameters.
+     * @see LayoutInflater
+     */
+    public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
+        LayoutInflater factory = LayoutInflater.from(context);
+        return factory.inflate(resource, root);
+    }
+
+    /**
+     * Scroll the view with standard behavior for scrolling beyond the normal
+     * content boundaries. Views that call this method should override
+     * {@link #onOverScrolled(int, int, boolean, boolean)} to respond to the
+     * results of an over-scroll operation.
+     *
+     * Views can use this method to handle any touch or fling-based scrolling.
+     *
+     * @param deltaX Change in X in pixels
+     * @param deltaY Change in Y in pixels
+     * @param scrollX Current X scroll value in pixels before applying deltaX
+     * @param scrollY Current Y scroll value in pixels before applying deltaY
+     * @param scrollRangeX Maximum content scroll range along the X axis
+     * @param scrollRangeY Maximum content scroll range along the Y axis
+     * @param maxOverScrollX Number of pixels to overscroll by in either direction
+     *          along the X axis.
+     * @param maxOverScrollY Number of pixels to overscroll by in either direction
+     *          along the Y axis.
+     * @param isTouchEvent true if this scroll operation is the result of a touch event.
+     * @return true if scrolling was clamped to an over-scroll boundary along either
+     *          axis, false otherwise.
+     */
+    @SuppressWarnings({"UnusedParameters"})
+    protected boolean overScrollBy(int deltaX, int deltaY,
+            int scrollX, int scrollY,
+            int scrollRangeX, int scrollRangeY,
+            int maxOverScrollX, int maxOverScrollY,
+            boolean isTouchEvent) {
+        final int overScrollMode = mOverScrollMode;
+        final boolean canScrollHorizontal =
+                computeHorizontalScrollRange() > computeHorizontalScrollExtent();
+        final boolean canScrollVertical =
+                computeVerticalScrollRange() > computeVerticalScrollExtent();
+        final boolean overScrollHorizontal = overScrollMode == OVER_SCROLL_ALWAYS ||
+                (overScrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollHorizontal);
+        final boolean overScrollVertical = overScrollMode == OVER_SCROLL_ALWAYS ||
+                (overScrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollVertical);
+
+        int newScrollX = scrollX + deltaX;
+        if (!overScrollHorizontal) {
+            maxOverScrollX = 0;
+        }
+
+        int newScrollY = scrollY + deltaY;
+        if (!overScrollVertical) {
+            maxOverScrollY = 0;
+        }
+
+        // Clamp values if at the limits and record
+        final int left = -maxOverScrollX;
+        final int right = maxOverScrollX + scrollRangeX;
+        final int top = -maxOverScrollY;
+        final int bottom = maxOverScrollY + scrollRangeY;
+
+        boolean clampedX = false;
+        if (newScrollX > right) {
+            newScrollX = right;
+            clampedX = true;
+        } else if (newScrollX < left) {
+            newScrollX = left;
+            clampedX = true;
+        }
+
+        boolean clampedY = false;
+        if (newScrollY > bottom) {
+            newScrollY = bottom;
+            clampedY = true;
+        } else if (newScrollY < top) {
+            newScrollY = top;
+            clampedY = true;
+        }
+
+        onOverScrolled(newScrollX, newScrollY, clampedX, clampedY);
+
+        return clampedX || clampedY;
+    }
+
+    /**
+     * Called by {@link #overScrollBy(int, int, int, int, int, int, int, int, boolean)} to
+     * respond to the results of an over-scroll operation.
+     *
+     * @param scrollX New X scroll value in pixels
+     * @param scrollY New Y scroll value in pixels
+     * @param clampedX True if scrollX was clamped to an over-scroll boundary
+     * @param clampedY True if scrollY was clamped to an over-scroll boundary
+     */
+    protected void onOverScrolled(int scrollX, int scrollY,
+            boolean clampedX, boolean clampedY) {
+        // Intentionally empty.
+    }
+
+    /**
+     * Returns the over-scroll mode for this view. The result will be
+     * one of {@link #OVER_SCROLL_ALWAYS}, {@link #OVER_SCROLL_IF_CONTENT_SCROLLS}
+     * (allow over-scrolling only if the view content is larger than the container),
+     * or {@link #OVER_SCROLL_NEVER}.
+     *
+     * @return This view's over-scroll mode.
+     */
+    @InspectableProperty(enumMapping = {
+            @EnumEntry(value = OVER_SCROLL_ALWAYS, name = "always"),
+            @EnumEntry(value = OVER_SCROLL_IF_CONTENT_SCROLLS, name = "ifContentScrolls"),
+            @EnumEntry(value = OVER_SCROLL_NEVER, name = "never")
+    })
+    public int getOverScrollMode() {
+        return mOverScrollMode;
+    }
+
+    /**
+     * Set the over-scroll mode for this view. Valid over-scroll modes are
+     * {@link #OVER_SCROLL_ALWAYS}, {@link #OVER_SCROLL_IF_CONTENT_SCROLLS}
+     * (allow over-scrolling only if the view content is larger than the container),
+     * or {@link #OVER_SCROLL_NEVER}.
+     *
+     * Setting the over-scroll mode of a view will have an effect only if the
+     * view is capable of scrolling.
+     *
+     * @param overScrollMode The new over-scroll mode for this view.
+     */
+    public void setOverScrollMode(int overScrollMode) {
+        if (overScrollMode != OVER_SCROLL_ALWAYS &&
+                overScrollMode != OVER_SCROLL_IF_CONTENT_SCROLLS &&
+                overScrollMode != OVER_SCROLL_NEVER) {
+            throw new IllegalArgumentException("Invalid overscroll mode " + overScrollMode);
+        }
+        mOverScrollMode = overScrollMode;
+    }
+
+    /**
+     * Enable or disable nested scrolling for this view.
+     *
+     * <p>If this property is set to true the view will be permitted to initiate nested
+     * scrolling operations with a compatible parent view in the current hierarchy. If this
+     * view does not implement nested scrolling this will have no effect. Disabling nested scrolling
+     * while a nested scroll is in progress has the effect of {@link #stopNestedScroll() stopping}
+     * the nested scroll.</p>
+     *
+     * @param enabled true to enable nested scrolling, false to disable
+     *
+     * @see #isNestedScrollingEnabled()
+     */
+    public void setNestedScrollingEnabled(boolean enabled) {
+        if (enabled) {
+            mPrivateFlags3 |= PFLAG3_NESTED_SCROLLING_ENABLED;
+        } else {
+            stopNestedScroll();
+            mPrivateFlags3 &= ~PFLAG3_NESTED_SCROLLING_ENABLED;
+        }
+    }
+
+    /**
+     * Returns true if nested scrolling is enabled for this view.
+     *
+     * <p>If nested scrolling is enabled and this View class implementation supports it,
+     * this view will act as a nested scrolling child view when applicable, forwarding data
+     * about the scroll operation in progress to a compatible and cooperating nested scrolling
+     * parent.</p>
+     *
+     * @return true if nested scrolling is enabled
+     *
+     * @see #setNestedScrollingEnabled(boolean)
+     */
+    @InspectableProperty
+    public boolean isNestedScrollingEnabled() {
+        return (mPrivateFlags3 & PFLAG3_NESTED_SCROLLING_ENABLED) ==
+                PFLAG3_NESTED_SCROLLING_ENABLED;
+    }
+
+    /**
+     * Begin a nestable scroll operation along the given axes.
+     *
+     * <p>A view starting a nested scroll promises to abide by the following contract:</p>
+     *
+     * <p>The view will call startNestedScroll upon initiating a scroll operation. In the case
+     * of a touch scroll this corresponds to the initial {@link MotionEvent#ACTION_DOWN}.
+     * In the case of touch scrolling the nested scroll will be terminated automatically in
+     * the same manner as {@link ViewParent#requestDisallowInterceptTouchEvent(boolean)}.
+     * In the event of programmatic scrolling the caller must explicitly call
+     * {@link #stopNestedScroll()} to indicate the end of the nested scroll.</p>
+     *
+     * <p>If <code>startNestedScroll</code> returns true, a cooperative parent was found.
+     * If it returns false the caller may ignore the rest of this contract until the next scroll.
+     * Calling startNestedScroll while a nested scroll is already in progress will return true.</p>
+     *
+     * <p>At each incremental step of the scroll the caller should invoke
+     * {@link #dispatchNestedPreScroll(int, int, int[], int[]) dispatchNestedPreScroll}
+     * once it has calculated the requested scrolling delta. If it returns true the nested scrolling
+     * parent at least partially consumed the scroll and the caller should adjust the amount it
+     * scrolls by.</p>
+     *
+     * <p>After applying the remainder of the scroll delta the caller should invoke
+     * {@link #dispatchNestedScroll(int, int, int, int, int[]) dispatchNestedScroll}, passing
+     * both the delta consumed and the delta unconsumed. A nested scrolling parent may treat
+     * these values differently. See {@link ViewParent#onNestedScroll(View, int, int, int, int)}.
+     * </p>
+     *
+     * @param axes Flags consisting of a combination of {@link #SCROLL_AXIS_HORIZONTAL} and/or
+     *             {@link #SCROLL_AXIS_VERTICAL}.
+     * @return true if a cooperative parent was found and nested scrolling has been enabled for
+     *         the current gesture.
+     *
+     * @see #stopNestedScroll()
+     * @see #dispatchNestedPreScroll(int, int, int[], int[])
+     * @see #dispatchNestedScroll(int, int, int, int, int[])
+     */
+    public boolean startNestedScroll(int axes) {
+        if (hasNestedScrollingParent()) {
+            // Already in progress
+            return true;
+        }
+        if (isNestedScrollingEnabled()) {
+            ViewParent p = getParent();
+            View child = this;
+            while (p != null) {
+                try {
+                    if (p.onStartNestedScroll(child, this, axes)) {
+                        mNestedScrollingParent = p;
+                        p.onNestedScrollAccepted(child, this, axes);
+                        return true;
+                    }
+                } catch (AbstractMethodError e) {
+                    Log.e(VIEW_LOG_TAG, "ViewParent " + p + " does not implement interface " +
+                            "method onStartNestedScroll", e);
+                    // Allow the search upward to continue
+                }
+                if (p instanceof View) {
+                    child = (View) p;
+                }
+                p = p.getParent();
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Stop a nested scroll in progress.
+     *
+     * <p>Calling this method when a nested scroll is not currently in progress is harmless.</p>
+     *
+     * @see #startNestedScroll(int)
+     */
+    public void stopNestedScroll() {
+        if (mNestedScrollingParent != null) {
+            mNestedScrollingParent.onStopNestedScroll(this);
+            mNestedScrollingParent = null;
+        }
+    }
+
+    /**
+     * Returns true if this view has a nested scrolling parent.
+     *
+     * <p>The presence of a nested scrolling parent indicates that this view has initiated
+     * a nested scroll and it was accepted by an ancestor view further up the view hierarchy.</p>
+     *
+     * @return whether this view has a nested scrolling parent
+     */
+    public boolean hasNestedScrollingParent() {
+        return mNestedScrollingParent != null;
+    }
+
+    /**
+     * Dispatch one step of a nested scroll in progress.
+     *
+     * <p>Implementations of views that support nested scrolling should call this to report
+     * info about a scroll in progress to the current nested scrolling parent. If a nested scroll
+     * is not currently in progress or nested scrolling is not
+     * {@link #isNestedScrollingEnabled() enabled} for this view this method does nothing.</p>
+     *
+     * <p>Compatible View implementations should also call
+     * {@link #dispatchNestedPreScroll(int, int, int[], int[]) dispatchNestedPreScroll} before
+     * consuming a component of the scroll event themselves.</p>
+     *
+     * @param dxConsumed Horizontal distance in pixels consumed by this view during this scroll step
+     * @param dyConsumed Vertical distance in pixels consumed by this view during this scroll step
+     * @param dxUnconsumed Horizontal scroll distance in pixels not consumed by this view
+     * @param dyUnconsumed Horizontal scroll distance in pixels not consumed by this view
+     * @param offsetInWindow Optional. If not null, on return this will contain the offset
+     *                       in local view coordinates of this view from before this operation
+     *                       to after it completes. View implementations may use this to adjust
+     *                       expected input coordinate tracking.
+     * @return true if the event was dispatched, false if it could not be dispatched.
+     * @see #dispatchNestedPreScroll(int, int, int[], int[])
+     */
+    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
+            int dxUnconsumed, int dyUnconsumed, @Nullable @Size(2) int[] offsetInWindow) {
+        if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
+            if (dxConsumed != 0 || dyConsumed != 0 || dxUnconsumed != 0 || dyUnconsumed != 0) {
+                int startX = 0;
+                int startY = 0;
+                if (offsetInWindow != null) {
+                    getLocationInWindow(offsetInWindow);
+                    startX = offsetInWindow[0];
+                    startY = offsetInWindow[1];
+                }
+
+                mNestedScrollingParent.onNestedScroll(this, dxConsumed, dyConsumed,
+                        dxUnconsumed, dyUnconsumed);
+
+                if (offsetInWindow != null) {
+                    getLocationInWindow(offsetInWindow);
+                    offsetInWindow[0] -= startX;
+                    offsetInWindow[1] -= startY;
+                }
+                return true;
+            } else if (offsetInWindow != null) {
+                // No motion, no dispatch. Keep offsetInWindow up to date.
+                offsetInWindow[0] = 0;
+                offsetInWindow[1] = 0;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Dispatch one step of a nested scroll in progress before this view consumes any portion of it.
+     *
+     * <p>Nested pre-scroll events are to nested scroll events what touch intercept is to touch.
+     * <code>dispatchNestedPreScroll</code> offers an opportunity for the parent view in a nested
+     * scrolling operation to consume some or all of the scroll operation before the child view
+     * consumes it.</p>
+     *
+     * @param dx Horizontal scroll distance in pixels
+     * @param dy Vertical scroll distance in pixels
+     * @param consumed Output. If not null, consumed[0] will contain the consumed component of dx
+     *                 and consumed[1] the consumed dy.
+     * @param offsetInWindow Optional. If not null, on return this will contain the offset
+     *                       in local view coordinates of this view from before this operation
+     *                       to after it completes. View implementations may use this to adjust
+     *                       expected input coordinate tracking.
+     * @return true if the parent consumed some or all of the scroll delta
+     * @see #dispatchNestedScroll(int, int, int, int, int[])
+     */
+    public boolean dispatchNestedPreScroll(int dx, int dy,
+            @Nullable @Size(2) int[] consumed, @Nullable @Size(2) int[] offsetInWindow) {
+        if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
+            if (dx != 0 || dy != 0) {
+                int startX = 0;
+                int startY = 0;
+                if (offsetInWindow != null) {
+                    getLocationInWindow(offsetInWindow);
+                    startX = offsetInWindow[0];
+                    startY = offsetInWindow[1];
+                }
+
+                if (consumed == null) {
+                    if (mTempNestedScrollConsumed == null) {
+                        mTempNestedScrollConsumed = new int[2];
+                    }
+                    consumed = mTempNestedScrollConsumed;
+                }
+                consumed[0] = 0;
+                consumed[1] = 0;
+                mNestedScrollingParent.onNestedPreScroll(this, dx, dy, consumed);
+
+                if (offsetInWindow != null) {
+                    getLocationInWindow(offsetInWindow);
+                    offsetInWindow[0] -= startX;
+                    offsetInWindow[1] -= startY;
+                }
+                return consumed[0] != 0 || consumed[1] != 0;
+            } else if (offsetInWindow != null) {
+                offsetInWindow[0] = 0;
+                offsetInWindow[1] = 0;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Dispatch a fling to a nested scrolling parent.
+     *
+     * <p>This method should be used to indicate that a nested scrolling child has detected
+     * suitable conditions for a fling. Generally this means that a touch scroll has ended with a
+     * {@link VelocityTracker velocity} in the direction of scrolling that meets or exceeds
+     * the {@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity}
+     * along a scrollable axis.</p>
+     *
+     * <p>If a nested scrolling child view would normally fling but it is at the edge of
+     * its own content, it can use this method to delegate the fling to its nested scrolling
+     * parent instead. The parent may optionally consume the fling or observe a child fling.</p>
+     *
+     * @param velocityX Horizontal fling velocity in pixels per second
+     * @param velocityY Vertical fling velocity in pixels per second
+     * @param consumed true if the child consumed the fling, false otherwise
+     * @return true if the nested scrolling parent consumed or otherwise reacted to the fling
+     */
+    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
+        if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
+            return mNestedScrollingParent.onNestedFling(this, velocityX, velocityY, consumed);
+        }
+        return false;
+    }
+
+    /**
+     * Dispatch a fling to a nested scrolling parent before it is processed by this view.
+     *
+     * <p>Nested pre-fling events are to nested fling events what touch intercept is to touch
+     * and what nested pre-scroll is to nested scroll. <code>dispatchNestedPreFling</code>
+     * offsets an opportunity for the parent view in a nested fling to fully consume the fling
+     * before the child view consumes it. If this method returns <code>true</code>, a nested
+     * parent view consumed the fling and this view should not scroll as a result.</p>
+     *
+     * <p>For a better user experience, only one view in a nested scrolling chain should consume
+     * the fling at a time. If a parent view consumed the fling this method will return false.
+     * Custom view implementations should account for this in two ways:</p>
+     *
+     * <ul>
+     *     <li>If a custom view is paged and needs to settle to a fixed page-point, do not
+     *     call <code>dispatchNestedPreFling</code>; consume the fling and settle to a valid
+     *     position regardless.</li>
+     *     <li>If a nested parent does consume the fling, this view should not scroll at all,
+     *     even to settle back to a valid idle position.</li>
+     * </ul>
+     *
+     * <p>Views should also not offer fling velocities to nested parent views along an axis
+     * where scrolling is not currently supported; a {@link android.widget.ScrollView ScrollView}
+     * should not offer a horizontal fling velocity to its parents since scrolling along that
+     * axis is not permitted and carrying velocity along that motion does not make sense.</p>
+     *
+     * @param velocityX Horizontal fling velocity in pixels per second
+     * @param velocityY Vertical fling velocity in pixels per second
+     * @return true if a nested scrolling parent consumed the fling
+     */
+    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
+        if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
+            return mNestedScrollingParent.onNestedPreFling(this, velocityX, velocityY);
+        }
+        return false;
+    }
+
+    /**
+     * Gets a scale factor that determines the distance the view should scroll
+     * vertically in response to {@link MotionEvent#ACTION_SCROLL}.
+     * @return The vertical scroll scale factor.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    protected float getVerticalScrollFactor() {
+        if (mVerticalScrollFactor == 0) {
+            TypedValue outValue = new TypedValue();
+            if (!mContext.getTheme().resolveAttribute(
+                    com.android.internal.R.attr.listPreferredItemHeight, outValue, true)) {
+                throw new IllegalStateException(
+                        "Expected theme to define listPreferredItemHeight.");
+            }
+            mVerticalScrollFactor = outValue.getDimension(
+                    mContext.getResources().getDisplayMetrics());
+        }
+        return mVerticalScrollFactor;
+    }
+
+    /**
+     * Gets a scale factor that determines the distance the view should scroll
+     * horizontally in response to {@link MotionEvent#ACTION_SCROLL}.
+     * @return The horizontal scroll scale factor.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    protected float getHorizontalScrollFactor() {
+        // TODO: Should use something else.
+        return getVerticalScrollFactor();
+    }
+
+    /**
+     * Return the value specifying the text direction or policy that was set with
+     * {@link #setTextDirection(int)}.
+     *
+     * @return the defined text direction. It can be one of:
+     *
+     * {@link #TEXT_DIRECTION_INHERIT},
+     * {@link #TEXT_DIRECTION_FIRST_STRONG},
+     * {@link #TEXT_DIRECTION_ANY_RTL},
+     * {@link #TEXT_DIRECTION_LTR},
+     * {@link #TEXT_DIRECTION_RTL},
+     * {@link #TEXT_DIRECTION_LOCALE},
+     * {@link #TEXT_DIRECTION_FIRST_STRONG_LTR},
+     * {@link #TEXT_DIRECTION_FIRST_STRONG_RTL}
+     *
+     * @attr ref android.R.styleable#View_textDirection
+     *
+     * @hide
+     */
+    @ViewDebug.ExportedProperty(category = "text", mapping = {
+            @ViewDebug.IntToString(from = TEXT_DIRECTION_INHERIT, to = "INHERIT"),
+            @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG, to = "FIRST_STRONG"),
+            @ViewDebug.IntToString(from = TEXT_DIRECTION_ANY_RTL, to = "ANY_RTL"),
+            @ViewDebug.IntToString(from = TEXT_DIRECTION_LTR, to = "LTR"),
+            @ViewDebug.IntToString(from = TEXT_DIRECTION_RTL, to = "RTL"),
+            @ViewDebug.IntToString(from = TEXT_DIRECTION_LOCALE, to = "LOCALE"),
+            @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG_LTR, to = "FIRST_STRONG_LTR"),
+            @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG_RTL, to = "FIRST_STRONG_RTL")
+    })
+    @InspectableProperty(hasAttributeId = false, enumMapping = {
+            @EnumEntry(value = TEXT_DIRECTION_INHERIT, name = "inherit"),
+            @EnumEntry(value = TEXT_DIRECTION_LOCALE, name = "locale"),
+            @EnumEntry(value = TEXT_DIRECTION_ANY_RTL, name = "anyRtl"),
+            @EnumEntry(value = TEXT_DIRECTION_LTR, name = "ltr"),
+            @EnumEntry(value = TEXT_DIRECTION_RTL, name = "rtl"),
+            @EnumEntry(value = TEXT_DIRECTION_FIRST_STRONG, name = "firstStrong"),
+            @EnumEntry(value = TEXT_DIRECTION_FIRST_STRONG_LTR, name = "firstStrongLtr"),
+            @EnumEntry(value = TEXT_DIRECTION_FIRST_STRONG_RTL, name = "firstStrongRtl"),
+    })
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public int getRawTextDirection() {
+        return (mPrivateFlags2 & PFLAG2_TEXT_DIRECTION_MASK) >> PFLAG2_TEXT_DIRECTION_MASK_SHIFT;
+    }
+
+    /**
+     * Set the text direction.
+     *
+     * @param textDirection the direction to set. Should be one of:
+     *
+     * {@link #TEXT_DIRECTION_INHERIT},
+     * {@link #TEXT_DIRECTION_FIRST_STRONG},
+     * {@link #TEXT_DIRECTION_ANY_RTL},
+     * {@link #TEXT_DIRECTION_LTR},
+     * {@link #TEXT_DIRECTION_RTL},
+     * {@link #TEXT_DIRECTION_LOCALE}
+     * {@link #TEXT_DIRECTION_FIRST_STRONG_LTR},
+     * {@link #TEXT_DIRECTION_FIRST_STRONG_RTL},
+     *
+     * Resolution will be done if the value is set to TEXT_DIRECTION_INHERIT. The resolution
+     * proceeds up the parent chain of the view to get the value. If there is no parent, then it will
+     * return the default {@link #TEXT_DIRECTION_FIRST_STRONG}.
+     *
+     * @attr ref android.R.styleable#View_textDirection
+     */
+    public void setTextDirection(int textDirection) {
+        if (getRawTextDirection() != textDirection) {
+            // Reset the current text direction and the resolved one
+            mPrivateFlags2 &= ~PFLAG2_TEXT_DIRECTION_MASK;
+            resetResolvedTextDirection();
+            // Set the new text direction
+            mPrivateFlags2 |= ((textDirection << PFLAG2_TEXT_DIRECTION_MASK_SHIFT) & PFLAG2_TEXT_DIRECTION_MASK);
+            // Do resolution
+            resolveTextDirection();
+            // Notify change
+            onRtlPropertiesChanged(getLayoutDirection());
+            // Refresh
+            requestLayout();
+            invalidate(true);
+        }
+    }
+
+    /**
+     * Return the resolved text direction.
+     *
+     * @return the resolved text direction. Returns one of:
+     *
+     * {@link #TEXT_DIRECTION_FIRST_STRONG},
+     * {@link #TEXT_DIRECTION_ANY_RTL},
+     * {@link #TEXT_DIRECTION_LTR},
+     * {@link #TEXT_DIRECTION_RTL},
+     * {@link #TEXT_DIRECTION_LOCALE},
+     * {@link #TEXT_DIRECTION_FIRST_STRONG_LTR},
+     * {@link #TEXT_DIRECTION_FIRST_STRONG_RTL}
+     *
+     * @attr ref android.R.styleable#View_textDirection
+     */
+    @ViewDebug.ExportedProperty(category = "text", mapping = {
+            @ViewDebug.IntToString(from = TEXT_DIRECTION_INHERIT, to = "INHERIT"),
+            @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG, to = "FIRST_STRONG"),
+            @ViewDebug.IntToString(from = TEXT_DIRECTION_ANY_RTL, to = "ANY_RTL"),
+            @ViewDebug.IntToString(from = TEXT_DIRECTION_LTR, to = "LTR"),
+            @ViewDebug.IntToString(from = TEXT_DIRECTION_RTL, to = "RTL"),
+            @ViewDebug.IntToString(from = TEXT_DIRECTION_LOCALE, to = "LOCALE"),
+            @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG_LTR, to = "FIRST_STRONG_LTR"),
+            @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG_RTL, to = "FIRST_STRONG_RTL")
+    })
+    @InspectableProperty(hasAttributeId = false, enumMapping = {
+            @EnumEntry(value = TEXT_DIRECTION_LOCALE, name = "locale"),
+            @EnumEntry(value = TEXT_DIRECTION_ANY_RTL, name = "anyRtl"),
+            @EnumEntry(value = TEXT_DIRECTION_LTR, name = "ltr"),
+            @EnumEntry(value = TEXT_DIRECTION_RTL, name = "rtl"),
+            @EnumEntry(value = TEXT_DIRECTION_FIRST_STRONG, name = "firstStrong"),
+            @EnumEntry(value = TEXT_DIRECTION_FIRST_STRONG_LTR, name = "firstStrongLtr"),
+            @EnumEntry(value = TEXT_DIRECTION_FIRST_STRONG_RTL, name = "firstStrongRtl"),
+    })
+    public int getTextDirection() {
+        return (mPrivateFlags2 & PFLAG2_TEXT_DIRECTION_RESOLVED_MASK) >> PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT;
+    }
+
+    /**
+     * Resolve the text direction.
+     *
+     * @return true if resolution has been done, false otherwise.
+     *
+     * @hide
+     */
+    public boolean resolveTextDirection() {
+        // Reset any previous text direction resolution
+        mPrivateFlags2 &= ~(PFLAG2_TEXT_DIRECTION_RESOLVED | PFLAG2_TEXT_DIRECTION_RESOLVED_MASK);
+
+        if (hasRtlSupport()) {
+            // Set resolved text direction flag depending on text direction flag
+            final int textDirection = getRawTextDirection();
+            switch(textDirection) {
+                case TEXT_DIRECTION_INHERIT:
+                    if (!canResolveTextDirection()) {
+                        // We cannot do the resolution if there is no parent, so use the default one
+                        mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
+                        // Resolution will need to happen again later
+                        return false;
+                    }
+
+                    // Parent has not yet resolved, so we still return the default
+                    try {
+                        if (!mParent.isTextDirectionResolved()) {
+                            mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
+                            // Resolution will need to happen again later
+                            return false;
+                        }
+                    } catch (AbstractMethodError e) {
+                        Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
+                                " does not fully implement ViewParent", e);
+                        mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED |
+                                PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
+                        return true;
+                    }
+
+                    // Set current resolved direction to the same value as the parent's one
+                    int parentResolvedDirection;
+                    try {
+                        parentResolvedDirection = mParent.getTextDirection();
+                    } catch (AbstractMethodError e) {
+                        Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
+                                " does not fully implement ViewParent", e);
+                        parentResolvedDirection = TEXT_DIRECTION_LTR;
+                    }
+                    switch (parentResolvedDirection) {
+                        case TEXT_DIRECTION_FIRST_STRONG:
+                        case TEXT_DIRECTION_ANY_RTL:
+                        case TEXT_DIRECTION_LTR:
+                        case TEXT_DIRECTION_RTL:
+                        case TEXT_DIRECTION_LOCALE:
+                        case TEXT_DIRECTION_FIRST_STRONG_LTR:
+                        case TEXT_DIRECTION_FIRST_STRONG_RTL:
+                            mPrivateFlags2 |=
+                                    (parentResolvedDirection << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT);
+                            break;
+                        default:
+                            // Default resolved direction is "first strong" heuristic
+                            mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
+                    }
+                    break;
+                case TEXT_DIRECTION_FIRST_STRONG:
+                case TEXT_DIRECTION_ANY_RTL:
+                case TEXT_DIRECTION_LTR:
+                case TEXT_DIRECTION_RTL:
+                case TEXT_DIRECTION_LOCALE:
+                case TEXT_DIRECTION_FIRST_STRONG_LTR:
+                case TEXT_DIRECTION_FIRST_STRONG_RTL:
+                    // Resolved direction is the same as text direction
+                    mPrivateFlags2 |= (textDirection << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT);
+                    break;
+                default:
+                    // Default resolved direction is "first strong" heuristic
+                    mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
+            }
+        } else {
+            // Default resolved direction is "first strong" heuristic
+            mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
+        }
+
+        // Set to resolved
+        mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED;
+        return true;
+    }
+
+    /**
+     * Check if text direction resolution can be done.
+     *
+     * @return true if text direction resolution can be done otherwise return false.
+     */
+    public boolean canResolveTextDirection() {
+        switch (getRawTextDirection()) {
+            case TEXT_DIRECTION_INHERIT:
+                if (mParent != null) {
+                    try {
+                        return mParent.canResolveTextDirection();
+                    } catch (AbstractMethodError e) {
+                        Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
+                                " does not fully implement ViewParent", e);
+                    }
+                }
+                return false;
+
+            default:
+                return true;
+        }
+    }
+
+    /**
+     * Reset resolved text direction. Text direction will be resolved during a call to
+     * {@link #onMeasure(int, int)}.
+     *
+     * @hide
+     */
+    @TestApi
+    public void resetResolvedTextDirection() {
+        // Reset any previous text direction resolution
+        mPrivateFlags2 &= ~(PFLAG2_TEXT_DIRECTION_RESOLVED | PFLAG2_TEXT_DIRECTION_RESOLVED_MASK);
+        // Set to default value
+        mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
+    }
+
+    /**
+     * @return true if text direction is inherited.
+     *
+     * @hide
+     */
+    public boolean isTextDirectionInherited() {
+        return (getRawTextDirection() == TEXT_DIRECTION_INHERIT);
+    }
+
+    /**
+     * @return true if text direction is resolved.
+     */
+    public boolean isTextDirectionResolved() {
+        return (mPrivateFlags2 & PFLAG2_TEXT_DIRECTION_RESOLVED) == PFLAG2_TEXT_DIRECTION_RESOLVED;
+    }
+
+    /**
+     * Return the value specifying the text alignment or policy that was set with
+     * {@link #setTextAlignment(int)}.
+     *
+     * @return the defined text alignment. It can be one of:
+     *
+     * {@link #TEXT_ALIGNMENT_INHERIT},
+     * {@link #TEXT_ALIGNMENT_GRAVITY},
+     * {@link #TEXT_ALIGNMENT_CENTER},
+     * {@link #TEXT_ALIGNMENT_TEXT_START},
+     * {@link #TEXT_ALIGNMENT_TEXT_END},
+     * {@link #TEXT_ALIGNMENT_VIEW_START},
+     * {@link #TEXT_ALIGNMENT_VIEW_END}
+     *
+     * @attr ref android.R.styleable#View_textAlignment
+     *
+     * @hide
+     */
+    @ViewDebug.ExportedProperty(category = "text", mapping = {
+            @ViewDebug.IntToString(from = TEXT_ALIGNMENT_INHERIT, to = "INHERIT"),
+            @ViewDebug.IntToString(from = TEXT_ALIGNMENT_GRAVITY, to = "GRAVITY"),
+            @ViewDebug.IntToString(from = TEXT_ALIGNMENT_TEXT_START, to = "TEXT_START"),
+            @ViewDebug.IntToString(from = TEXT_ALIGNMENT_TEXT_END, to = "TEXT_END"),
+            @ViewDebug.IntToString(from = TEXT_ALIGNMENT_CENTER, to = "CENTER"),
+            @ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_START, to = "VIEW_START"),
+            @ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_END, to = "VIEW_END")
+    })
+    @InspectableProperty(hasAttributeId = false, enumMapping = {
+            @EnumEntry(value = TEXT_ALIGNMENT_INHERIT, name = "inherit"),
+            @EnumEntry(value = TEXT_ALIGNMENT_GRAVITY, name = "gravity"),
+            @EnumEntry(value = TEXT_ALIGNMENT_TEXT_START, name = "textStart"),
+            @EnumEntry(value = TEXT_ALIGNMENT_TEXT_END, name = "textEnd"),
+            @EnumEntry(value = TEXT_ALIGNMENT_CENTER, name = "center"),
+            @EnumEntry(value = TEXT_ALIGNMENT_VIEW_START, name = "viewStart"),
+            @EnumEntry(value = TEXT_ALIGNMENT_VIEW_END, name = "viewEnd")
+    })
+    @TextAlignment
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public int getRawTextAlignment() {
+        return (mPrivateFlags2 & PFLAG2_TEXT_ALIGNMENT_MASK) >> PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT;
+    }
+
+    /**
+     * Set the text alignment.
+     *
+     * @param textAlignment The text alignment to set. Should be one of
+     *
+     * {@link #TEXT_ALIGNMENT_INHERIT},
+     * {@link #TEXT_ALIGNMENT_GRAVITY},
+     * {@link #TEXT_ALIGNMENT_CENTER},
+     * {@link #TEXT_ALIGNMENT_TEXT_START},
+     * {@link #TEXT_ALIGNMENT_TEXT_END},
+     * {@link #TEXT_ALIGNMENT_VIEW_START},
+     * {@link #TEXT_ALIGNMENT_VIEW_END}
+     *
+     * Resolution will be done if the value is set to TEXT_ALIGNMENT_INHERIT. The resolution
+     * proceeds up the parent chain of the view to get the value. If there is no parent, then it
+     * will return the default {@link #TEXT_ALIGNMENT_GRAVITY}.
+     *
+     * @attr ref android.R.styleable#View_textAlignment
+     */
+    public void setTextAlignment(@TextAlignment int textAlignment) {
+        if (textAlignment != getRawTextAlignment()) {
+            // Reset the current and resolved text alignment
+            mPrivateFlags2 &= ~PFLAG2_TEXT_ALIGNMENT_MASK;
+            resetResolvedTextAlignment();
+            // Set the new text alignment
+            mPrivateFlags2 |=
+                    ((textAlignment << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT) & PFLAG2_TEXT_ALIGNMENT_MASK);
+            // Do resolution
+            resolveTextAlignment();
+            // Notify change
+            onRtlPropertiesChanged(getLayoutDirection());
+            // Refresh
+            requestLayout();
+            invalidate(true);
+        }
+    }
+
+    /**
+     * Return the resolved text alignment.
+     *
+     * @return the resolved text alignment. Returns one of:
+     *
+     * {@link #TEXT_ALIGNMENT_GRAVITY},
+     * {@link #TEXT_ALIGNMENT_CENTER},
+     * {@link #TEXT_ALIGNMENT_TEXT_START},
+     * {@link #TEXT_ALIGNMENT_TEXT_END},
+     * {@link #TEXT_ALIGNMENT_VIEW_START},
+     * {@link #TEXT_ALIGNMENT_VIEW_END}
+     *
+     * @attr ref android.R.styleable#View_textAlignment
+     */
+    @ViewDebug.ExportedProperty(category = "text", mapping = {
+            @ViewDebug.IntToString(from = TEXT_ALIGNMENT_INHERIT, to = "INHERIT"),
+            @ViewDebug.IntToString(from = TEXT_ALIGNMENT_GRAVITY, to = "GRAVITY"),
+            @ViewDebug.IntToString(from = TEXT_ALIGNMENT_TEXT_START, to = "TEXT_START"),
+            @ViewDebug.IntToString(from = TEXT_ALIGNMENT_TEXT_END, to = "TEXT_END"),
+            @ViewDebug.IntToString(from = TEXT_ALIGNMENT_CENTER, to = "CENTER"),
+            @ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_START, to = "VIEW_START"),
+            @ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_END, to = "VIEW_END")
+    })
+    @InspectableProperty(enumMapping = {
+            @EnumEntry(value = TEXT_ALIGNMENT_GRAVITY, name = "gravity"),
+            @EnumEntry(value = TEXT_ALIGNMENT_TEXT_START, name = "textStart"),
+            @EnumEntry(value = TEXT_ALIGNMENT_TEXT_END, name = "textEnd"),
+            @EnumEntry(value = TEXT_ALIGNMENT_CENTER, name = "center"),
+            @EnumEntry(value = TEXT_ALIGNMENT_VIEW_START, name = "viewStart"),
+            @EnumEntry(value = TEXT_ALIGNMENT_VIEW_END, name = "viewEnd")
+    })
+    @TextAlignment
+    public int getTextAlignment() {
+        return (mPrivateFlags2 & PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK) >>
+                PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT;
+    }
+
+    /**
+     * Resolve the text alignment.
+     *
+     * @return true if resolution has been done, false otherwise.
+     *
+     * @hide
+     */
+    public boolean resolveTextAlignment() {
+        // Reset any previous text alignment resolution
+        mPrivateFlags2 &= ~(PFLAG2_TEXT_ALIGNMENT_RESOLVED | PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK);
+
+        if (hasRtlSupport()) {
+            // Set resolved text alignment flag depending on text alignment flag
+            final int textAlignment = getRawTextAlignment();
+            switch (textAlignment) {
+                case TEXT_ALIGNMENT_INHERIT:
+                    // Check if we can resolve the text alignment
+                    if (!canResolveTextAlignment()) {
+                        // We cannot do the resolution if there is no parent so use the default
+                        mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
+                        // Resolution will need to happen again later
+                        return false;
+                    }
+
+                    // Parent has not yet resolved, so we still return the default
+                    try {
+                        if (!mParent.isTextAlignmentResolved()) {
+                            mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
+                            // Resolution will need to happen again later
+                            return false;
+                        }
+                    } catch (AbstractMethodError e) {
+                        Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
+                                " does not fully implement ViewParent", e);
+                        mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED |
+                                PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
+                        return true;
+                    }
+
+                    int parentResolvedTextAlignment;
+                    try {
+                        parentResolvedTextAlignment = mParent.getTextAlignment();
+                    } catch (AbstractMethodError e) {
+                        Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
+                                " does not fully implement ViewParent", e);
+                        parentResolvedTextAlignment = TEXT_ALIGNMENT_GRAVITY;
+                    }
+                    switch (parentResolvedTextAlignment) {
+                        case TEXT_ALIGNMENT_GRAVITY:
+                        case TEXT_ALIGNMENT_TEXT_START:
+                        case TEXT_ALIGNMENT_TEXT_END:
+                        case TEXT_ALIGNMENT_CENTER:
+                        case TEXT_ALIGNMENT_VIEW_START:
+                        case TEXT_ALIGNMENT_VIEW_END:
+                            // Resolved text alignment is the same as the parent resolved
+                            // text alignment
+                            mPrivateFlags2 |=
+                                    (parentResolvedTextAlignment << PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT);
+                            break;
+                        default:
+                            // Use default resolved text alignment
+                            mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
+                    }
+                    break;
+                case TEXT_ALIGNMENT_GRAVITY:
+                case TEXT_ALIGNMENT_TEXT_START:
+                case TEXT_ALIGNMENT_TEXT_END:
+                case TEXT_ALIGNMENT_CENTER:
+                case TEXT_ALIGNMENT_VIEW_START:
+                case TEXT_ALIGNMENT_VIEW_END:
+                    // Resolved text alignment is the same as text alignment
+                    mPrivateFlags2 |= (textAlignment << PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT);
+                    break;
+                default:
+                    // Use default resolved text alignment
+                    mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
+            }
+        } else {
+            // Use default resolved text alignment
+            mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
+        }
+
+        // Set the resolved
+        mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED;
+        return true;
+    }
+
+    /**
+     * Check if text alignment resolution can be done.
+     *
+     * @return true if text alignment resolution can be done otherwise return false.
+     */
+    public boolean canResolveTextAlignment() {
+        switch (getRawTextAlignment()) {
+            case TEXT_DIRECTION_INHERIT:
+                if (mParent != null) {
+                    try {
+                        return mParent.canResolveTextAlignment();
+                    } catch (AbstractMethodError e) {
+                        Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
+                                " does not fully implement ViewParent", e);
+                    }
+                }
+                return false;
+
+            default:
+                return true;
+        }
+    }
+
+    /**
+     * Reset resolved text alignment. Text alignment will be resolved during a call to
+     * {@link #onMeasure(int, int)}.
+     *
+     * @hide
+     */
+    @TestApi
+    public void resetResolvedTextAlignment() {
+        // Reset any previous text alignment resolution
+        mPrivateFlags2 &= ~(PFLAG2_TEXT_ALIGNMENT_RESOLVED | PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK);
+        // Set to default
+        mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
+    }
+
+    /**
+     * @return true if text alignment is inherited.
+     *
+     * @hide
+     */
+    public boolean isTextAlignmentInherited() {
+        return (getRawTextAlignment() == TEXT_ALIGNMENT_INHERIT);
+    }
+
+    /**
+     * @return true if text alignment is resolved.
+     */
+    public boolean isTextAlignmentResolved() {
+        return (mPrivateFlags2 & PFLAG2_TEXT_ALIGNMENT_RESOLVED) == PFLAG2_TEXT_ALIGNMENT_RESOLVED;
+    }
+
+    /**
+     * Generate a value suitable for use in {@link #setId(int)}.
+     * This value will not collide with ID values generated at build time by aapt for R.id.
+     *
+     * @return a generated ID value
+     */
+    public static int generateViewId() {
+        for (;;) {
+            final int result = sNextGeneratedId.get();
+            // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
+            int newValue = result + 1;
+            if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
+            if (sNextGeneratedId.compareAndSet(result, newValue)) {
+                return result;
+            }
+        }
+    }
+
+    private static boolean isViewIdGenerated(int id) {
+        return (id & 0xFF000000) == 0 && (id & 0x00FFFFFF) != 0;
+    }
+
+    /**
+     * Gets the Views in the hierarchy affected by entering and exiting Activity Scene transitions.
+     * @param transitioningViews This View will be added to transitioningViews if it is VISIBLE and
+     *                           a normal View or a ViewGroup with
+     *                           {@link android.view.ViewGroup#isTransitionGroup()} true.
+     * @hide
+     */
+    public void captureTransitioningViews(List<View> transitioningViews) {
+        if (getVisibility() == View.VISIBLE) {
+            transitioningViews.add(this);
+        }
+    }
+
+    /**
+     * Adds all Views that have {@link #getTransitionName()} non-null to namedElements.
+     * @param namedElements Will contain all Views in the hierarchy having a transitionName.
+     * @hide
+     */
+    public void findNamedViews(Map<String, View> namedElements) {
+        if (getVisibility() == VISIBLE || mGhostView != null) {
+            String transitionName = getTransitionName();
+            if (transitionName != null) {
+                namedElements.put(transitionName, this);
+            }
+        }
+    }
+
+    /**
+     * Returns the pointer icon for the motion event, or null if it doesn't specify the icon.
+     * The default implementation does not care the location or event types, but some subclasses
+     * may use it (such as WebViews).
+     * @param event The MotionEvent from a mouse
+     * @param pointerIndex The index of the pointer for which to retrieve the {@link PointerIcon}.
+     *                     This will be between 0 and {@link MotionEvent#getPointerCount()}.
+     * @see PointerIcon
+     */
+    public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
+        final float x = event.getX(pointerIndex);
+        final float y = event.getY(pointerIndex);
+        if (isDraggingScrollBar() || isOnScrollbarThumb(x, y)) {
+            return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_ARROW);
+        }
+        return mPointerIcon;
+    }
+
+    /**
+     * Set the pointer icon for the current view.
+     * Passing {@code null} will restore the pointer icon to its default value.
+     * @param pointerIcon A PointerIcon instance which will be shown when the mouse hovers.
+     */
+    public void setPointerIcon(PointerIcon pointerIcon) {
+        mPointerIcon = pointerIcon;
+        if (mAttachInfo == null || mAttachInfo.mHandlingPointerEvent) {
+            return;
+        }
+        try {
+            mAttachInfo.mSession.updatePointerIcon(mAttachInfo.mWindow);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Gets the pointer icon for the current view.
+     */
+    @InspectableProperty
+    public PointerIcon getPointerIcon() {
+        return mPointerIcon;
+    }
+
+    /**
+     * Checks pointer capture status.
+     *
+     * @return true if the view has pointer capture.
+     * @see #requestPointerCapture()
+     * @see #hasPointerCapture()
+     */
+    public boolean hasPointerCapture() {
+        final ViewRootImpl viewRootImpl = getViewRootImpl();
+        if (viewRootImpl == null) {
+            return false;
+        }
+        return viewRootImpl.hasPointerCapture();
+    }
+
+    /**
+     * Requests pointer capture mode.
+     * <p>
+     * When the window has pointer capture, the mouse pointer icon will disappear and will not
+     * change its position. Enabling pointer capture will change the behavior of input devices in
+     * the following ways:
+     * <ul>
+     *     <li>Events from a mouse will be delivered with the source
+     *     {@link InputDevice#SOURCE_MOUSE_RELATIVE}, and relative position changes will be
+     *     available through {@link MotionEvent#getX} and {@link MotionEvent#getY}.</li>
+     *
+     *     <li>Events from a touchpad will be delivered with the source
+     *     {@link InputDevice#SOURCE_TOUCHPAD}, where the absolute position of each of the pointers
+     *     on the touchpad will be available through {@link MotionEvent#getX(int)} and
+     *     {@link MotionEvent#getY(int)}, and their relative movements are stored in
+     *     {@link MotionEvent#AXIS_RELATIVE_X} and {@link MotionEvent#AXIS_RELATIVE_Y}.</li>
+     *
+     *     <li>Events from other types of devices, such as touchscreens, will not be affected.</li>
+     * </ul>
+     * <p>
+     * Events captured through pointer capture will be dispatched to
+     * {@link OnCapturedPointerListener#onCapturedPointer(View, MotionEvent)} if an
+     * {@link OnCapturedPointerListener} is set, and otherwise to
+     * {@link #onCapturedPointerEvent(MotionEvent)}.
+     * <p>
+     * If the window already has pointer capture, this call does nothing.
+     * <p>
+     * The capture may be released through {@link #releasePointerCapture()}, or will be lost
+     * automatically when the window loses focus.
+     *
+     * @see #releasePointerCapture()
+     * @see #hasPointerCapture()
+     * @see #onPointerCaptureChange(boolean)
+     */
+    public void requestPointerCapture() {
+        final ViewRootImpl viewRootImpl = getViewRootImpl();
+        if (viewRootImpl != null) {
+            viewRootImpl.requestPointerCapture(true);
+        }
+    }
+
+
+    /**
+     * Releases the pointer capture.
+     * <p>
+     * If the window does not have pointer capture, this call will do nothing.
+     * @see #requestPointerCapture()
+     * @see #hasPointerCapture()
+     * @see #onPointerCaptureChange(boolean)
+     */
+    public void releasePointerCapture() {
+        final ViewRootImpl viewRootImpl = getViewRootImpl();
+        if (viewRootImpl != null) {
+            viewRootImpl.requestPointerCapture(false);
+        }
+    }
+
+    /**
+     * Called when the window has just acquired or lost pointer capture.
+     *
+     * @param hasCapture True if the view now has pointerCapture, false otherwise.
+     */
+    @CallSuper
+    public void onPointerCaptureChange(boolean hasCapture) {
+    }
+
+    /**
+     * @see #onPointerCaptureChange
+     */
+    public void dispatchPointerCaptureChanged(boolean hasCapture) {
+        onPointerCaptureChange(hasCapture);
+    }
+
+    /**
+     * Implement this method to handle captured pointer events
+     *
+     * @param event The captured pointer event.
+     * @return True if the event was handled, false otherwise.
+     * @see #requestPointerCapture()
+     */
+    public boolean onCapturedPointerEvent(MotionEvent event) {
+        return false;
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when a captured pointer event
+     * is being dispatched this view. The callback will be invoked before the event is
+     * given to the view.
+     */
+    public interface OnCapturedPointerListener {
+        /**
+         * Called when a captured pointer event is dispatched to a view.
+         * @param view The view this event has been dispatched to.
+         * @param event The captured event.
+         * @return True if the listener has consumed the event, false otherwise.
+         */
+        boolean onCapturedPointer(View view, MotionEvent event);
+    }
+
+    /**
+     * Set a listener to receive callbacks when the pointer capture state of a view changes.
+     * @param l  The {@link OnCapturedPointerListener} to receive callbacks.
+     */
+    public void setOnCapturedPointerListener(OnCapturedPointerListener l) {
+        getListenerInfo().mOnCapturedPointerListener = l;
+    }
+
+    // Properties
+    //
+    /**
+     * A Property wrapper around the <code>alpha</code> functionality handled by the
+     * {@link View#setAlpha(float)} and {@link View#getAlpha()} methods.
+     */
+    public static final Property<View, Float> ALPHA = new FloatProperty<View>("alpha") {
+        @Override
+        public void setValue(View object, float value) {
+            object.setAlpha(value);
+        }
+
+        @Override
+        public Float get(View object) {
+            return object.getAlpha();
+        }
+    };
+
+    /**
+     * A Property wrapper around the <code>translationX</code> functionality handled by the
+     * {@link View#setTranslationX(float)} and {@link View#getTranslationX()} methods.
+     */
+    public static final Property<View, Float> TRANSLATION_X = new FloatProperty<View>("translationX") {
+        @Override
+        public void setValue(View object, float value) {
+            object.setTranslationX(value);
+        }
+
+                @Override
+        public Float get(View object) {
+            return object.getTranslationX();
+        }
+    };
+
+    /**
+     * A Property wrapper around the <code>translationY</code> functionality handled by the
+     * {@link View#setTranslationY(float)} and {@link View#getTranslationY()} methods.
+     */
+    public static final Property<View, Float> TRANSLATION_Y = new FloatProperty<View>("translationY") {
+        @Override
+        public void setValue(View object, float value) {
+            object.setTranslationY(value);
+        }
+
+        @Override
+        public Float get(View object) {
+            return object.getTranslationY();
+        }
+    };
+
+    /**
+     * A Property wrapper around the <code>translationZ</code> functionality handled by the
+     * {@link View#setTranslationZ(float)} and {@link View#getTranslationZ()} methods.
+     */
+    public static final Property<View, Float> TRANSLATION_Z = new FloatProperty<View>("translationZ") {
+        @Override
+        public void setValue(View object, float value) {
+            object.setTranslationZ(value);
+        }
+
+        @Override
+        public Float get(View object) {
+            return object.getTranslationZ();
+        }
+    };
+
+    /**
+     * A Property wrapper around the <code>x</code> functionality handled by the
+     * {@link View#setX(float)} and {@link View#getX()} methods.
+     */
+    public static final Property<View, Float> X = new FloatProperty<View>("x") {
+        @Override
+        public void setValue(View object, float value) {
+            object.setX(value);
+        }
+
+        @Override
+        public Float get(View object) {
+            return object.getX();
+        }
+    };
+
+    /**
+     * A Property wrapper around the <code>y</code> functionality handled by the
+     * {@link View#setY(float)} and {@link View#getY()} methods.
+     */
+    public static final Property<View, Float> Y = new FloatProperty<View>("y") {
+        @Override
+        public void setValue(View object, float value) {
+            object.setY(value);
+        }
+
+        @Override
+        public Float get(View object) {
+            return object.getY();
+        }
+    };
+
+    /**
+     * A Property wrapper around the <code>z</code> functionality handled by the
+     * {@link View#setZ(float)} and {@link View#getZ()} methods.
+     */
+    public static final Property<View, Float> Z = new FloatProperty<View>("z") {
+        @Override
+        public void setValue(View object, float value) {
+            object.setZ(value);
+        }
+
+        @Override
+        public Float get(View object) {
+            return object.getZ();
+        }
+    };
+
+    /**
+     * A Property wrapper around the <code>rotation</code> functionality handled by the
+     * {@link View#setRotation(float)} and {@link View#getRotation()} methods.
+     */
+    public static final Property<View, Float> ROTATION = new FloatProperty<View>("rotation") {
+        @Override
+        public void setValue(View object, float value) {
+            object.setRotation(value);
+        }
+
+        @Override
+        public Float get(View object) {
+            return object.getRotation();
+        }
+    };
+
+    /**
+     * A Property wrapper around the <code>rotationX</code> functionality handled by the
+     * {@link View#setRotationX(float)} and {@link View#getRotationX()} methods.
+     */
+    public static final Property<View, Float> ROTATION_X = new FloatProperty<View>("rotationX") {
+        @Override
+        public void setValue(View object, float value) {
+            object.setRotationX(value);
+        }
+
+        @Override
+        public Float get(View object) {
+            return object.getRotationX();
+        }
+    };
+
+    /**
+     * A Property wrapper around the <code>rotationY</code> functionality handled by the
+     * {@link View#setRotationY(float)} and {@link View#getRotationY()} methods.
+     */
+    public static final Property<View, Float> ROTATION_Y = new FloatProperty<View>("rotationY") {
+        @Override
+        public void setValue(View object, float value) {
+            object.setRotationY(value);
+        }
+
+        @Override
+        public Float get(View object) {
+            return object.getRotationY();
+        }
+    };
+
+    /**
+     * A Property wrapper around the <code>scaleX</code> functionality handled by the
+     * {@link View#setScaleX(float)} and {@link View#getScaleX()} methods.
+     */
+    public static final Property<View, Float> SCALE_X = new FloatProperty<View>("scaleX") {
+        @Override
+        public void setValue(View object, float value) {
+            object.setScaleX(value);
+        }
+
+        @Override
+        public Float get(View object) {
+            return object.getScaleX();
+        }
+    };
+
+    /**
+     * A Property wrapper around the <code>scaleY</code> functionality handled by the
+     * {@link View#setScaleY(float)} and {@link View#getScaleY()} methods.
+     */
+    public static final Property<View, Float> SCALE_Y = new FloatProperty<View>("scaleY") {
+        @Override
+        public void setValue(View object, float value) {
+            object.setScaleY(value);
+        }
+
+        @Override
+        public Float get(View object) {
+            return object.getScaleY();
+        }
+    };
+
+    /**
+     * A MeasureSpec encapsulates the layout requirements passed from parent to child.
+     * Each MeasureSpec represents a requirement for either the width or the height.
+     * A MeasureSpec is comprised of a size and a mode. There are three possible
+     * modes:
+     * <dl>
+     * <dt>UNSPECIFIED</dt>
+     * <dd>
+     * The parent has not imposed any constraint on the child. It can be whatever size
+     * it wants.
+     * </dd>
+     *
+     * <dt>EXACTLY</dt>
+     * <dd>
+     * The parent has determined an exact size for the child. The child is going to be
+     * given those bounds regardless of how big it wants to be.
+     * </dd>
+     *
+     * <dt>AT_MOST</dt>
+     * <dd>
+     * The child can be as large as it wants up to the specified size.
+     * </dd>
+     * </dl>
+     *
+     * MeasureSpecs are implemented as ints to reduce object allocation. This class
+     * is provided to pack and unpack the &lt;size, mode&gt; tuple into the int.
+     */
+    public static class MeasureSpec {
+        private static final int MODE_SHIFT = 30;
+        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
+
+        /** @hide */
+        @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface MeasureSpecMode {}
+
+        /**
+         * Measure specification mode: The parent has not imposed any constraint
+         * on the child. It can be whatever size it wants.
+         */
+        public static final int UNSPECIFIED = 0 << MODE_SHIFT;
+
+        /**
+         * Measure specification mode: The parent has determined an exact size
+         * for the child. The child is going to be given those bounds regardless
+         * of how big it wants to be.
+         */
+        public static final int EXACTLY     = 1 << MODE_SHIFT;
+
+        /**
+         * Measure specification mode: The child can be as large as it wants up
+         * to the specified size.
+         */
+        public static final int AT_MOST     = 2 << MODE_SHIFT;
+
+        /**
+         * Creates a measure specification based on the supplied size and mode.
+         *
+         * The mode must always be one of the following:
+         * <ul>
+         *  <li>{@link android.view.View.MeasureSpec#UNSPECIFIED}</li>
+         *  <li>{@link android.view.View.MeasureSpec#EXACTLY}</li>
+         *  <li>{@link android.view.View.MeasureSpec#AT_MOST}</li>
+         * </ul>
+         *
+         * <p><strong>Note:</strong> On API level 17 and lower, makeMeasureSpec's
+         * implementation was such that the order of arguments did not matter
+         * and overflow in either value could impact the resulting MeasureSpec.
+         * {@link android.widget.RelativeLayout} was affected by this bug.
+         * Apps targeting API levels greater than 17 will get the fixed, more strict
+         * behavior.</p>
+         *
+         * @param size the size of the measure specification
+         * @param mode the mode of the measure specification
+         * @return the measure specification based on size and mode
+         */
+        public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
+                                          @MeasureSpecMode int mode) {
+            if (sUseBrokenMakeMeasureSpec) {
+                return size + mode;
+            } else {
+                return (size & ~MODE_MASK) | (mode & MODE_MASK);
+            }
+        }
+
+        /**
+         * Like {@link #makeMeasureSpec(int, int)}, but any spec with a mode of UNSPECIFIED
+         * will automatically get a size of 0. Older apps expect this.
+         *
+         * @hide internal use only for compatibility with system widgets and older apps
+         */
+        @UnsupportedAppUsage
+        public static int makeSafeMeasureSpec(int size, int mode) {
+            if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
+                return 0;
+            }
+            return makeMeasureSpec(size, mode);
+        }
+
+        /**
+         * Extracts the mode from the supplied measure specification.
+         *
+         * @param measureSpec the measure specification to extract the mode from
+         * @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
+         *         {@link android.view.View.MeasureSpec#AT_MOST} or
+         *         {@link android.view.View.MeasureSpec#EXACTLY}
+         */
+        @MeasureSpecMode
+        public static int getMode(int measureSpec) {
+            //noinspection ResourceType
+            return (measureSpec & MODE_MASK);
+        }
+
+        /**
+         * Extracts the size from the supplied measure specification.
+         *
+         * @param measureSpec the measure specification to extract the size from
+         * @return the size in pixels defined in the supplied measure specification
+         */
+        public static int getSize(int measureSpec) {
+            return (measureSpec & ~MODE_MASK);
+        }
+
+        static int adjust(int measureSpec, int delta) {
+            final int mode = getMode(measureSpec);
+            int size = getSize(measureSpec);
+            if (mode == UNSPECIFIED) {
+                // No need to adjust size for UNSPECIFIED mode.
+                return makeMeasureSpec(size, UNSPECIFIED);
+            }
+            size += delta;
+            if (size < 0) {
+                Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
+                        ") spec: " + toString(measureSpec) + " delta: " + delta);
+                size = 0;
+            }
+            return makeMeasureSpec(size, mode);
+        }
+
+        /**
+         * Returns a String representation of the specified measure
+         * specification.
+         *
+         * @param measureSpec the measure specification to convert to a String
+         * @return a String with the following format: "MeasureSpec: MODE SIZE"
+         */
+        public static String toString(int measureSpec) {
+            int mode = getMode(measureSpec);
+            int size = getSize(measureSpec);
+
+            StringBuilder sb = new StringBuilder("MeasureSpec: ");
+
+            if (mode == UNSPECIFIED)
+                sb.append("UNSPECIFIED ");
+            else if (mode == EXACTLY)
+                sb.append("EXACTLY ");
+            else if (mode == AT_MOST)
+                sb.append("AT_MOST ");
+            else
+                sb.append(mode).append(" ");
+
+            sb.append(size);
+            return sb.toString();
+        }
+    }
+
+    private final class CheckForLongPress implements Runnable {
+        private int mOriginalWindowAttachCount;
+        private float mX;
+        private float mY;
+        private boolean mOriginalPressedState;
+        /**
+         * The classification of the long click being checked: one of the
+         * FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__* constants.
+         */
+        private int mClassification;
+
+        @UnsupportedAppUsage
+        private CheckForLongPress() {
+        }
+
+        @Override
+        public void run() {
+            if ((mOriginalPressedState == isPressed()) && (mParent != null)
+                    && mOriginalWindowAttachCount == mWindowAttachCount) {
+                recordGestureClassification(mClassification);
+                if (performLongClick(mX, mY)) {
+                    mHasPerformedLongPress = true;
+                }
+            }
+        }
+
+        public void setAnchor(float x, float y) {
+            mX = x;
+            mY = y;
+        }
+
+        public void rememberWindowAttachCount() {
+            mOriginalWindowAttachCount = mWindowAttachCount;
+        }
+
+        public void rememberPressedState() {
+            mOriginalPressedState = isPressed();
+        }
+
+        public void setClassification(int classification) {
+            mClassification = classification;
+        }
+    }
+
+    private final class CheckForTap implements Runnable {
+        public float x;
+        public float y;
+
+        @Override
+        public void run() {
+            mPrivateFlags &= ~PFLAG_PREPRESSED;
+            setPressed(true, x, y);
+            final long delay =
+                    ViewConfiguration.getLongPressTimeout() - ViewConfiguration.getTapTimeout();
+            checkForLongClick(delay, x, y, TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
+        }
+    }
+
+    private final class PerformClick implements Runnable {
+        @Override
+        public void run() {
+            recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP);
+            performClickInternal();
+        }
+    }
+
+    /** Records a classification for the current event stream. */
+    private void recordGestureClassification(int classification) {
+        if (classification == TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION) {
+            return;
+        }
+        // To avoid negatively impacting View performance, the latency and displacement metrics
+        // are omitted.
+        FrameworkStatsLog.write(FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED, getClass().getName(),
+                classification);
+    }
+
+    /**
+     * This method returns a ViewPropertyAnimator object, which can be used to animate
+     * specific properties on this View.
+     *
+     * @return ViewPropertyAnimator The ViewPropertyAnimator associated with this View.
+     */
+    public ViewPropertyAnimator animate() {
+        if (mAnimator == null) {
+            mAnimator = new ViewPropertyAnimator(this);
+        }
+        return mAnimator;
+    }
+
+    /**
+     * Sets the name of the View to be used to identify Views in Transitions.
+     * Names should be unique in the View hierarchy.
+     *
+     * @param transitionName The name of the View to uniquely identify it for Transitions.
+     */
+    public final void setTransitionName(String transitionName) {
+        mTransitionName = transitionName;
+    }
+
+    /**
+     * Returns the name of the View to be used to identify Views in Transitions.
+     * Names should be unique in the View hierarchy.
+     *
+     * <p>This returns null if the View has not been given a name.</p>
+     *
+     * @return The name used of the View to be used to identify Views in Transitions or null
+     * if no name has been given.
+     */
+    @ViewDebug.ExportedProperty
+    @InspectableProperty
+    public String getTransitionName() {
+        return mTransitionName;
+    }
+
+    /**
+     * @hide
+     */
+    public void requestKeyboardShortcuts(List<KeyboardShortcutGroup> data, int deviceId) {
+        // Do nothing.
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when a hardware key event is
+     * dispatched to this view. The callback will be invoked before the key event is
+     * given to the view. This is only useful for hardware keyboards; a software input
+     * method has no obligation to trigger this listener.
+     */
+    public interface OnKeyListener {
+        /**
+         * Called when a hardware key is dispatched to a view. This allows listeners to
+         * get a chance to respond before the target view.
+         * <p>Key presses in software keyboards will generally NOT trigger this method,
+         * although some may elect to do so in some situations. Do not assume a
+         * software input method has to be key-based; even if it is, it may use key presses
+         * in a different way than you expect, so there is no way to reliably catch soft
+         * input key presses.
+         *
+         * @param v The view the key has been dispatched to.
+         * @param keyCode The code for the physical key that was pressed
+         * @param event The KeyEvent object containing full information about
+         *        the event.
+         * @return True if the listener has consumed the event, false otherwise.
+         */
+        boolean onKey(View v, int keyCode, KeyEvent event);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when a hardware key event hasn't
+     * been handled by the view hierarchy.
+     */
+    public interface OnUnhandledKeyEventListener {
+        /**
+         * Called when a hardware key is dispatched to a view after being unhandled during normal
+         * {@link KeyEvent} dispatch.
+         *
+         * @param v The view the key has been dispatched to.
+         * @param event The KeyEvent object containing information about the event.
+         * @return {@code true} if the listener has consumed the event, {@code false} otherwise.
+         */
+        boolean onUnhandledKeyEvent(View v, KeyEvent event);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when a touch event is
+     * dispatched to this view. The callback will be invoked before the touch
+     * event is given to the view.
+     */
+    public interface OnTouchListener {
+        /**
+         * Called when a touch event is dispatched to a view. This allows listeners to
+         * get a chance to respond before the target view.
+         *
+         * @param v The view the touch event has been dispatched to.
+         * @param event The MotionEvent object containing full information about
+         *        the event.
+         * @return True if the listener has consumed the event, false otherwise.
+         */
+        boolean onTouch(View v, MotionEvent event);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when a hover event is
+     * dispatched to this view. The callback will be invoked before the hover
+     * event is given to the view.
+     */
+    public interface OnHoverListener {
+        /**
+         * Called when a hover event is dispatched to a view. This allows listeners to
+         * get a chance to respond before the target view.
+         *
+         * @param v The view the hover event has been dispatched to.
+         * @param event The MotionEvent object containing full information about
+         *        the event.
+         * @return True if the listener has consumed the event, false otherwise.
+         */
+        boolean onHover(View v, MotionEvent event);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when a generic motion event is
+     * dispatched to this view. The callback will be invoked before the generic motion
+     * event is given to the view.
+     */
+    public interface OnGenericMotionListener {
+        /**
+         * Called when a generic motion event is dispatched to a view. This allows listeners to
+         * get a chance to respond before the target view.
+         *
+         * @param v The view the generic motion event has been dispatched to.
+         * @param event The MotionEvent object containing full information about
+         *        the event.
+         * @return True if the listener has consumed the event, false otherwise.
+         */
+        boolean onGenericMotion(View v, MotionEvent event);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when a view has been clicked and held.
+     */
+    public interface OnLongClickListener {
+        /**
+         * Called when a view has been clicked and held.
+         *
+         * @param v The view that was clicked and held.
+         *
+         * @return true if the callback consumed the long click, false otherwise.
+         */
+        boolean onLongClick(View v);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when a drag is being dispatched
+     * to this view.  The callback will be invoked before the hosting view's own
+     * onDrag(event) method.  If the listener wants to fall back to the hosting view's
+     * onDrag(event) behavior, it should return 'false' from this callback.
+     *
+     * <div class="special reference">
+     * <h3>Developer Guides</h3>
+     * <p>For a guide to implementing drag and drop features, read the
+     * <a href="{@docRoot}guide/topics/ui/drag-drop.html">Drag and Drop</a> developer guide.</p>
+     * </div>
+     */
+    public interface OnDragListener {
+        /**
+         * Called when a drag event is dispatched to a view. This allows listeners
+         * to get a chance to override base View behavior.
+         *
+         * @param v The View that received the drag event.
+         * @param event The {@link android.view.DragEvent} object for the drag event.
+         * @return {@code true} if the drag event was handled successfully, or {@code false}
+         * if the drag event was not handled. Note that {@code false} will trigger the View
+         * to call its {@link #onDragEvent(DragEvent) onDragEvent()} handler.
+         */
+        boolean onDrag(View v, DragEvent event);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when the focus state of
+     * a view changed.
+     */
+    public interface OnFocusChangeListener {
+        /**
+         * Called when the focus state of a view has changed.
+         *
+         * @param v The view whose state has changed.
+         * @param hasFocus The new focus state of v.
+         */
+        void onFocusChange(View v, boolean hasFocus);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when a view is clicked.
+     */
+    public interface OnClickListener {
+        /**
+         * Called when a view has been clicked.
+         *
+         * @param v The view that was clicked.
+         */
+        void onClick(View v);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when a view is context clicked.
+     */
+    public interface OnContextClickListener {
+        /**
+         * Called when a view is context clicked.
+         *
+         * @param v The view that has been context clicked.
+         * @return true if the callback consumed the context click, false otherwise.
+         */
+        boolean onContextClick(View v);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when the context menu
+     * for this view is being built.
+     */
+    public interface OnCreateContextMenuListener {
+        /**
+         * Called when the context menu for this view is being built. It is not
+         * safe to hold onto the menu after this method returns.
+         *
+         * @param menu The context menu that is being built
+         * @param v The view for which the context menu is being built
+         * @param menuInfo Extra information about the item for which the
+         *            context menu should be shown. This information will vary
+         *            depending on the class of v.
+         */
+        void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when the status bar changes
+     * visibility.  This reports <strong>global</strong> changes to the system UI
+     * state, not what the application is requesting.
+     *
+     * @see View#setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener)
+     *
+     * @deprecated Use {@link WindowInsets#isVisible(int)} to find out about system bar visibilities
+     * by setting a {@link OnApplyWindowInsetsListener} on this view.
+     */
+    @Deprecated
+    public interface OnSystemUiVisibilityChangeListener {
+        /**
+         * Called when the status bar changes visibility because of a call to
+         * {@link View#setSystemUiVisibility(int)}.
+         *
+         * @param visibility  Bitwise-or of flags {@link #SYSTEM_UI_FLAG_LOW_PROFILE},
+         * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, and {@link #SYSTEM_UI_FLAG_FULLSCREEN}.
+         * This tells you the <strong>global</strong> state of these UI visibility
+         * flags, not what your app is currently applying.
+         */
+        public void onSystemUiVisibilityChange(int visibility);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when this view is attached
+     * or detached from its window.
+     */
+    public interface OnAttachStateChangeListener {
+        /**
+         * Called when the view is attached to a window.
+         * @param v The view that was attached
+         */
+        public void onViewAttachedToWindow(View v);
+        /**
+         * Called when the view is detached from a window.
+         * @param v The view that was detached
+         */
+        public void onViewDetachedFromWindow(View v);
+    }
+
+    /**
+     * Listener for applying window insets on a view in a custom way.
+     *
+     * <p>Apps may choose to implement this interface if they want to apply custom policy
+     * to the way that window insets are treated for a view. If an OnApplyWindowInsetsListener
+     * is set, its
+     * {@link OnApplyWindowInsetsListener#onApplyWindowInsets(View, WindowInsets) onApplyWindowInsets}
+     * method will be called instead of the View's own
+     * {@link #onApplyWindowInsets(WindowInsets) onApplyWindowInsets} method. The listener
+     * may optionally call the parameter View's <code>onApplyWindowInsets</code> method to apply
+     * the View's normal behavior as part of its own.</p>
+     */
+    public interface OnApplyWindowInsetsListener {
+        /**
+         * When {@link View#setOnApplyWindowInsetsListener(View.OnApplyWindowInsetsListener) set}
+         * on a View, this listener method will be called instead of the view's own
+         * {@link View#onApplyWindowInsets(WindowInsets) onApplyWindowInsets} method.
+         *
+         * @param v The view applying window insets
+         * @param insets The insets to apply
+         * @return The insets supplied, minus any insets that were consumed
+         */
+        public WindowInsets onApplyWindowInsets(View v, WindowInsets insets);
+    }
+
+    private final class UnsetPressedState implements Runnable {
+        @Override
+        public void run() {
+            setPressed(false);
+        }
+    }
+
+    /**
+     * When a view becomes invisible checks if autofill considers the view invisible too. This
+     * happens after the regular removal operation to make sure the operation is finished by the
+     * time this is called.
+     */
+    private static class VisibilityChangeForAutofillHandler extends Handler {
+        private final AutofillManager mAfm;
+        private final View mView;
+
+        private VisibilityChangeForAutofillHandler(@NonNull AutofillManager afm,
+                @NonNull View view) {
+            mAfm = afm;
+            mView = view;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            mAfm.notifyViewVisibilityChanged(mView, mView.isShown());
+        }
+    }
+
+    /**
+     * Base class for derived classes that want to save and restore their own
+     * state in {@link android.view.View#onSaveInstanceState()}.
+     */
+    public static class BaseSavedState extends AbsSavedState {
+        static final int START_ACTIVITY_REQUESTED_WHO_SAVED = 0b1;
+        static final int IS_AUTOFILLED = 0b10;
+        static final int AUTOFILL_ID = 0b100;
+
+        // Flags that describe what data in this state is valid
+        int mSavedData;
+        String mStartActivityRequestWhoSaved;
+        boolean mIsAutofilled;
+        boolean mHideHighlight;
+        int mAutofillViewId;
+
+        /**
+         * Constructor used when reading from a parcel. Reads the state of the superclass.
+         *
+         * @param source parcel to read from
+         */
+        public BaseSavedState(Parcel source) {
+            this(source, null);
+        }
+
+        /**
+         * Constructor used when reading from a parcel using a given class loader.
+         * Reads the state of the superclass.
+         *
+         * @param source parcel to read from
+         * @param loader ClassLoader to use for reading
+         */
+        public BaseSavedState(Parcel source, ClassLoader loader) {
+            super(source, loader);
+            mSavedData = source.readInt();
+            mStartActivityRequestWhoSaved = source.readString();
+            mIsAutofilled = source.readBoolean();
+            mHideHighlight = source.readBoolean();
+            mAutofillViewId = source.readInt();
+        }
+
+        /**
+         * Constructor called by derived classes when creating their SavedState objects
+         *
+         * @param superState The state of the superclass of this view
+         */
+        public BaseSavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+
+            out.writeInt(mSavedData);
+            out.writeString(mStartActivityRequestWhoSaved);
+            out.writeBoolean(mIsAutofilled);
+            out.writeBoolean(mHideHighlight);
+            out.writeInt(mAutofillViewId);
+        }
+
+        public static final @android.annotation.NonNull Parcelable.Creator<BaseSavedState> CREATOR
+                = new Parcelable.ClassLoaderCreator<BaseSavedState>() {
+            @Override
+            public BaseSavedState createFromParcel(Parcel in) {
+                return new BaseSavedState(in);
+            }
+
+            @Override
+            public BaseSavedState createFromParcel(Parcel in, ClassLoader loader) {
+                return new BaseSavedState(in, loader);
+            }
+
+            @Override
+            public BaseSavedState[] newArray(int size) {
+                return new BaseSavedState[size];
+            }
+        };
+    }
+
+    /**
+     * A set of information given to a view when it is attached to its parent
+     * window.
+     */
+    final static class AttachInfo {
+
+        interface Callbacks {
+            void playSoundEffect(int effectId);
+            boolean performHapticFeedback(int effectId, boolean always);
+        }
+
+        /**
+         * InvalidateInfo is used to post invalidate(int, int, int, int) messages
+         * to a Handler. This class contains the target (View) to invalidate and
+         * the coordinates of the dirty rectangle.
+         *
+         * For performance purposes, this class also implements a pool of up to
+         * POOL_LIMIT objects that get reused. This reduces memory allocations
+         * whenever possible.
+         */
+        static class InvalidateInfo {
+
+            @UnsupportedAppUsage
+            InvalidateInfo() {
+            }
+
+            private static final int POOL_LIMIT = 10;
+
+            private static final SynchronizedPool<InvalidateInfo> sPool =
+                    new SynchronizedPool<InvalidateInfo>(POOL_LIMIT);
+
+            @UnsupportedAppUsage
+            View target;
+
+            @UnsupportedAppUsage
+            int left;
+            @UnsupportedAppUsage
+            int top;
+            @UnsupportedAppUsage
+            int right;
+            @UnsupportedAppUsage
+            int bottom;
+
+            public static InvalidateInfo obtain() {
+                InvalidateInfo instance = sPool.acquire();
+                return (instance != null) ? instance : new InvalidateInfo();
+            }
+
+            public void recycle() {
+                target = null;
+                sPool.release(this);
+            }
+        }
+
+        @UnsupportedAppUsage
+        final IWindowSession mSession;
+
+        @UnsupportedAppUsage
+        final IWindow mWindow;
+
+        final IBinder mWindowToken;
+
+        Display mDisplay;
+
+        final Callbacks mRootCallbacks;
+
+        IWindowId mIWindowId;
+        WindowId mWindowId;
+
+        /**
+         * The top view of the hierarchy.
+         */
+        View mRootView;
+
+        IBinder mPanelParentWindowToken;
+
+        boolean mHardwareAccelerated;
+        boolean mHardwareAccelerationRequested;
+        ThreadedRenderer mThreadedRenderer;
+        List<RenderNode> mPendingAnimatingRenderNodes;
+
+        /**
+         * The state of the display to which the window is attached, as reported
+         * by {@link Display#getState()}.  Note that the display state constants
+         * declared by {@link Display} do not exactly line up with the screen state
+         * constants declared by {@link View} (there are more display states than
+         * screen states).
+         */
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        int mDisplayState = Display.STATE_UNKNOWN;
+
+        /**
+         * Scale factor used by the compatibility mode
+         */
+        @UnsupportedAppUsage
+        float mApplicationScale;
+
+        /**
+         * Indicates whether the application is in compatibility mode
+         */
+        @UnsupportedAppUsage
+        boolean mScalingRequired;
+
+        /**
+         * Left position of this view's window
+         */
+        int mWindowLeft;
+
+        /**
+         * Top position of this view's window
+         */
+        int mWindowTop;
+
+        /**
+         * Indicates whether views need to use 32-bit drawing caches
+         */
+        boolean mUse32BitDrawingCache;
+
+        /**
+         * For windows that are full-screen but using insets to layout inside
+         * of the screen decorations, these are the current insets for the
+         * content of the window.
+         */
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+                publicAlternatives = "Use {@link WindowInsets#getInsets(int)}")
+        final Rect mContentInsets = new Rect();
+
+        /**
+         * For windows that are full-screen but using insets to layout inside
+         * of the screen decorations, these are the current insets for the
+         * actual visible parts of the window.
+         */
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+                publicAlternatives = "Use {@link WindowInsets#getInsets(int)}")
+        final Rect mVisibleInsets = new Rect();
+
+        /**
+         * For windows that are full-screen but using insets to layout inside
+         * of the screen decorations, these are the current insets for the
+         * stable system windows.
+         */
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+                publicAlternatives = "Use {@link WindowInsets#getInsets(int)}")
+        final Rect mStableInsets = new Rect();
+
+        /**
+         * Current caption insets to the display coordinate.
+         */
+        final Rect mCaptionInsets = new Rect();
+
+        /**
+         * In multi-window we force show the system bars. Because we don't want that the surface
+         * size changes in this mode, we instead have a flag whether the system bars sizes should
+         * always be consumed, so the app is treated like there are no virtual system bars at all.
+         */
+        boolean mAlwaysConsumeSystemBars;
+
+        /**
+         * The internal insets given by this window.  This value is
+         * supplied by the client (through
+         * {@link ViewTreeObserver.OnComputeInternalInsetsListener}) and will
+         * be given to the window manager when changed to be used in laying
+         * out windows behind it.
+         */
+        @UnsupportedAppUsage
+        final ViewTreeObserver.InternalInsetsInfo mGivenInternalInsets
+                = new ViewTreeObserver.InternalInsetsInfo();
+
+        /**
+         * Set to true when mGivenInternalInsets is non-empty.
+         */
+        boolean mHasNonEmptyGivenInternalInsets;
+
+        /**
+         * All views in the window's hierarchy that serve as scroll containers,
+         * used to determine if the window can be resized or must be panned
+         * to adjust for a soft input area.
+         */
+        @UnsupportedAppUsage
+        final ArrayList<View> mScrollContainers = new ArrayList<View>();
+
+        @UnsupportedAppUsage
+        final KeyEvent.DispatcherState mKeyDispatchState
+                = new KeyEvent.DispatcherState();
+
+        /**
+         * Indicates whether the view's window currently has the focus.
+         */
+        @UnsupportedAppUsage
+        boolean mHasWindowFocus;
+
+        /**
+         * The current visibility of the window.
+         */
+        int mWindowVisibility;
+
+        /**
+         * Indicates the time at which drawing started to occur.
+         */
+        @UnsupportedAppUsage
+        long mDrawingTime;
+
+        /**
+         * Indicates whether the view's window is currently in touch mode.
+         */
+        @UnsupportedAppUsage
+        boolean mInTouchMode;
+
+        /**
+         * Indicates whether the view has requested unbuffered input dispatching for the current
+         * event stream.
+         */
+        boolean mUnbufferedDispatchRequested;
+
+        /**
+         * Indicates that ViewAncestor should trigger a global layout change
+         * the next time it performs a traversal
+         */
+        @UnsupportedAppUsage
+        boolean mRecomputeGlobalAttributes;
+
+        /**
+         * Always report new attributes at next traversal.
+         */
+        boolean mForceReportNewAttributes;
+
+        /**
+         * Set during a traveral if any views want to keep the screen on.
+         */
+        @UnsupportedAppUsage
+        boolean mKeepScreenOn;
+
+        /**
+         * Set during a traveral if the light center needs to be updated.
+         */
+        boolean mNeedsUpdateLightCenter;
+
+        /**
+         * Bitwise-or of all of the values that views have passed to setSystemUiVisibility().
+         */
+        int mSystemUiVisibility;
+
+        /**
+         * Hack to force certain system UI visibility flags to be cleared.
+         */
+        int mDisabledSystemUiVisibility;
+
+        /**
+         * True if a view in this hierarchy has an OnSystemUiVisibilityChangeListener
+         * attached.
+         */
+        boolean mHasSystemUiListeners;
+
+        /**
+         * Set if the visibility of any views has changed.
+         */
+        @UnsupportedAppUsage
+        boolean mViewVisibilityChanged;
+
+        /**
+         * Set to true if a view has been scrolled.
+         */
+        @UnsupportedAppUsage
+        boolean mViewScrollChanged;
+
+        /**
+         * Set to true if a pointer event is currently being handled.
+         */
+        boolean mHandlingPointerEvent;
+
+        /**
+         * The offset of this view's window when it's on an embedded display that is re-parented
+         * to another window.
+         */
+        final Point mLocationInParentDisplay = new Point();
+
+        /**
+         * The screen matrix of this view when it's on a {@link SurfaceControlViewHost} that is
+         * embedded within a SurfaceView.
+         */
+        Matrix mScreenMatrixInEmbeddedHierarchy;
+
+        /**
+         * Global to the view hierarchy used as a temporary for dealing with
+         * x/y points in the transparent region computations.
+         */
+        final int[] mTransparentLocation = new int[2];
+
+        /**
+         * Global to the view hierarchy used as a temporary for dealing with
+         * x/y points in the ViewGroup.invalidateChild implementation.
+         */
+        final int[] mInvalidateChildLocation = new int[2];
+
+        /**
+         * Global to the view hierarchy used as a temporary for dealing with
+         * computing absolute on-screen location.
+         */
+        final int[] mTmpLocation = new int[2];
+
+        /**
+         * Global to the view hierarchy used as a temporary for dealing with
+         * x/y location when view is transformed.
+         */
+        final float[] mTmpTransformLocation = new float[2];
+
+        /**
+         * The view tree observer used to dispatch global events like
+         * layout, pre-draw, touch mode change, etc.
+         */
+        @UnsupportedAppUsage
+        final ViewTreeObserver mTreeObserver;
+
+        /**
+         * A Canvas used by the view hierarchy to perform bitmap caching.
+         */
+        Canvas mCanvas;
+
+        /**
+         * The view root impl.
+         */
+        final ViewRootImpl mViewRootImpl;
+
+        /**
+         * A Handler supplied by a view's {@link android.view.ViewRootImpl}. This
+         * handler can be used to pump events in the UI events queue.
+         */
+        @UnsupportedAppUsage
+        final Handler mHandler;
+
+        /**
+         * Temporary for use in computing invalidate rectangles while
+         * calling up the hierarchy.
+         */
+        final Rect mTmpInvalRect = new Rect();
+
+        /**
+         * Temporary for use in computing hit areas with transformed views
+         */
+        final RectF mTmpTransformRect = new RectF();
+
+        /**
+         * Temporary for use in computing hit areas with transformed views
+         */
+        final RectF mTmpTransformRect1 = new RectF();
+
+        /**
+         * Temporary list of rectanges.
+         */
+        final List<RectF> mTmpRectList = new ArrayList<>();
+
+        /**
+         * Temporary for use in transforming invalidation rect
+         */
+        final Matrix mTmpMatrix = new Matrix();
+
+        /**
+         * Temporary for use in transforming invalidation rect
+         */
+        final Transformation mTmpTransformation = new Transformation();
+
+        /**
+         * Temporary for use in querying outlines from OutlineProviders
+         */
+        final Outline mTmpOutline = new Outline();
+
+        /**
+         * Temporary list for use in collecting focusable descendents of a view.
+         */
+        final ArrayList<View> mTempArrayList = new ArrayList<View>(24);
+
+        /**
+         * The id of the window for accessibility purposes.
+         */
+        int mAccessibilityWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+
+        /**
+         * Flags related to accessibility processing.
+         *
+         * @see AccessibilityNodeInfo#FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+         * @see AccessibilityNodeInfo#FLAG_REPORT_VIEW_IDS
+         */
+        int mAccessibilityFetchFlags;
+
+        /**
+         * The drawable for highlighting accessibility focus.
+         */
+        Drawable mAccessibilityFocusDrawable;
+
+        /**
+         * The drawable for highlighting autofilled views.
+         *
+         * @see #isAutofilled()
+         */
+        Drawable mAutofilledDrawable;
+
+        /**
+         * Show where the margins, bounds and layout bounds are for each view.
+         */
+        boolean mDebugLayout = DisplayProperties.debug_layout().orElse(false);
+
+        /**
+         * Point used to compute visible regions.
+         */
+        final Point mPoint = new Point();
+
+        /**
+         * Used to track which View originated a requestLayout() call, used when
+         * requestLayout() is called during layout.
+         */
+        View mViewRequestingLayout;
+
+        /**
+         * Used to track the identity of the current drag operation.
+         */
+        IBinder mDragToken;
+
+        /**
+         * The drag shadow surface for the current drag operation.
+         */
+        public Surface mDragSurface;
+
+
+        /**
+         * The view that currently has a tooltip displayed.
+         */
+        View mTooltipHost;
+
+        /**
+         * The initial structure has been reported so the view is ready to report updates.
+         */
+        boolean mReadyForContentCaptureUpdates;
+
+        /**
+         * Map(keyed by session) of content capture events that need to be notified after the view
+         * hierarchy is traversed: value is either the view itself for appearead events, or its
+         * autofill id for disappeared.
+         */
+        SparseArray<ArrayList<Object>> mContentCaptureEvents;
+
+        /**
+         * Cached reference to the {@link ContentCaptureManager}.
+         */
+        ContentCaptureManager mContentCaptureManager;
+
+        /**
+         * Listener used to fit content on window level.
+         */
+        OnContentApplyWindowInsetsListener mContentOnApplyWindowInsetsListener;
+
+        /**
+         * The leash token of this view's parent when it's in an embedded hierarchy that is
+         * re-parented to another window.
+         */
+        IBinder mLeashedParentToken;
+
+        /**
+         * The accessibility view id of this view's parent when it's in an embedded
+         * hierarchy that is re-parented to another window.
+         */
+        int mLeashedParentAccessibilityViewId;
+
+        /**
+         *
+         */
+        ScrollCaptureInternal mScrollCaptureInternal;
+
+        /**
+         * Creates a new set of attachment information with the specified
+         * events handler and thread.
+         *
+         * @param handler the events handler the view must use
+         */
+        AttachInfo(IWindowSession session, IWindow window, Display display,
+                ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer,
+                Context context) {
+            mSession = session;
+            mWindow = window;
+            mWindowToken = window.asBinder();
+            mDisplay = display;
+            mViewRootImpl = viewRootImpl;
+            mHandler = handler;
+            mRootCallbacks = effectPlayer;
+            mTreeObserver = new ViewTreeObserver(context);
+        }
+
+        @Nullable
+        ContentCaptureManager getContentCaptureManager(@NonNull Context context) {
+            if (mContentCaptureManager != null) {
+                return mContentCaptureManager;
+            }
+            mContentCaptureManager = context.getSystemService(ContentCaptureManager.class);
+            return mContentCaptureManager;
+        }
+
+        void delayNotifyContentCaptureInsetsEvent(@NonNull Insets insets) {
+            if (mContentCaptureManager == null) {
+                return;
+            }
+
+            ArrayList<Object> events = ensureEvents(
+                        mContentCaptureManager.getMainContentCaptureSession());
+            events.add(insets);
+        }
+
+        private void delayNotifyContentCaptureEvent(@NonNull ContentCaptureSession session,
+                @NonNull View view, boolean appeared) {
+            ArrayList<Object> events = ensureEvents(session);
+            events.add(appeared ? view : view.getAutofillId());
+        }
+
+        @NonNull
+        private ArrayList<Object> ensureEvents(@NonNull ContentCaptureSession session) {
+            if (mContentCaptureEvents == null) {
+                // Most of the time there will be just one session, so intial capacity is 1
+                mContentCaptureEvents = new SparseArray<>(1);
+            }
+            int sessionId = session.getId();
+            // TODO: life would be much easier if we provided a MultiMap implementation somwhere...
+            ArrayList<Object> events = mContentCaptureEvents.get(sessionId);
+            if (events == null) {
+                events = new ArrayList<>();
+                mContentCaptureEvents.put(sessionId, events);
+            }
+
+            return events;
+        }
+
+        @Nullable
+        ScrollCaptureInternal getScrollCaptureInternal() {
+            if (mScrollCaptureInternal != null) {
+                mScrollCaptureInternal = new ScrollCaptureInternal();
+            }
+            return mScrollCaptureInternal;
+        }
+
+        AttachedSurfaceControl getRootSurfaceControl() {
+            return mViewRootImpl;
+        }
+
+        public void dump(String prefix, PrintWriter writer) {
+            String innerPrefix = prefix + "  ";
+            writer.println(prefix + "AttachInfo:");
+            writer.println(innerPrefix + "mHasWindowFocus=" + mHasWindowFocus);
+            writer.println(innerPrefix + "mWindowVisibility=" + mWindowVisibility);
+            writer.println(innerPrefix + "mInTouchMode=" + mInTouchMode);
+            writer.println(innerPrefix + "mUnbufferedDispatchRequested="
+                    + mUnbufferedDispatchRequested);
+        }
+    }
+
+    /**
+     * <p>ScrollabilityCache holds various fields used by a View when scrolling
+     * is supported. This avoids keeping too many unused fields in most
+     * instances of View.</p>
+     */
+    private static class ScrollabilityCache implements Runnable {
+
+        /**
+         * Scrollbars are not visible
+         */
+        public static final int OFF = 0;
+
+        /**
+         * Scrollbars are visible
+         */
+        public static final int ON = 1;
+
+        /**
+         * Scrollbars are fading away
+         */
+        public static final int FADING = 2;
+
+        public boolean fadeScrollBars;
+
+        public int fadingEdgeLength;
+        public int scrollBarDefaultDelayBeforeFade;
+        public int scrollBarFadeDuration;
+
+        public int scrollBarSize;
+        public int scrollBarMinTouchTarget;
+        @UnsupportedAppUsage
+        public ScrollBarDrawable scrollBar;
+        public float[] interpolatorValues;
+        @UnsupportedAppUsage
+        public View host;
+
+        public final Paint paint;
+        public final Matrix matrix;
+        public Shader shader;
+
+        public final Interpolator scrollBarInterpolator = new Interpolator(1, 2);
+
+        private static final float[] OPAQUE = { 255 };
+        private static final float[] TRANSPARENT = { 0.0f };
+
+        /**
+         * When fading should start. This time moves into the future every time
+         * a new scroll happens. Measured based on SystemClock.uptimeMillis()
+         */
+        public long fadeStartTime;
+
+
+        /**
+         * The current state of the scrollbars: ON, OFF, or FADING
+         */
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        public int state = OFF;
+
+        private int mLastColor;
+
+        public final Rect mScrollBarBounds = new Rect();
+        public final Rect mScrollBarTouchBounds = new Rect();
+
+        public static final int NOT_DRAGGING = 0;
+        public static final int DRAGGING_VERTICAL_SCROLL_BAR = 1;
+        public static final int DRAGGING_HORIZONTAL_SCROLL_BAR = 2;
+        public int mScrollBarDraggingState = NOT_DRAGGING;
+
+        public float mScrollBarDraggingPos = 0;
+
+        public ScrollabilityCache(ViewConfiguration configuration, View host) {
+            fadingEdgeLength = configuration.getScaledFadingEdgeLength();
+            scrollBarSize = configuration.getScaledScrollBarSize();
+            scrollBarMinTouchTarget = configuration.getScaledMinScrollbarTouchTarget();
+            scrollBarDefaultDelayBeforeFade = ViewConfiguration.getScrollDefaultDelay();
+            scrollBarFadeDuration = ViewConfiguration.getScrollBarFadeDuration();
+
+            paint = new Paint();
+            matrix = new Matrix();
+            // use use a height of 1, and then wack the matrix each time we
+            // actually use it.
+            shader = new LinearGradient(0, 0, 0, 1, 0xFF000000, 0, Shader.TileMode.CLAMP);
+            paint.setShader(shader);
+            paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
+
+            this.host = host;
+        }
+
+        public void setFadeColor(int color) {
+            if (color != mLastColor) {
+                mLastColor = color;
+
+                if (color != 0) {
+                    shader = new LinearGradient(0, 0, 0, 1, color | 0xFF000000,
+                            color & 0x00FFFFFF, Shader.TileMode.CLAMP);
+                    paint.setShader(shader);
+                    // Restore the default transfer mode (src_over)
+                    paint.setXfermode(null);
+                } else {
+                    shader = new LinearGradient(0, 0, 0, 1, 0xFF000000, 0, Shader.TileMode.CLAMP);
+                    paint.setShader(shader);
+                    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
+                }
+            }
+        }
+
+        public void run() {
+            long now = AnimationUtils.currentAnimationTimeMillis();
+            if (now >= fadeStartTime) {
+
+                // the animation fades the scrollbars out by changing
+                // the opacity (alpha) from fully opaque to fully
+                // transparent
+                int nextFrame = (int) now;
+                int framesCount = 0;
+
+                Interpolator interpolator = scrollBarInterpolator;
+
+                // Start opaque
+                interpolator.setKeyFrame(framesCount++, nextFrame, OPAQUE);
+
+                // End transparent
+                nextFrame += scrollBarFadeDuration;
+                interpolator.setKeyFrame(framesCount, nextFrame, TRANSPARENT);
+
+                state = FADING;
+
+                // Kick off the fade animation
+                host.invalidate(true);
+            }
+        }
+    }
+
+    private class SendAccessibilityEventThrottle implements Runnable {
+        public volatile boolean mIsPending;
+        private AccessibilityEvent mAccessibilityEvent;
+
+        public void post(AccessibilityEvent accessibilityEvent) {
+            updateWithAccessibilityEvent(accessibilityEvent);
+            if (!mIsPending) {
+                mIsPending = true;
+                postDelayed(this,
+                        ViewConfiguration.getSendRecurringAccessibilityEventsInterval());
+            }
+        }
+
+        @Override
+        public void run() {
+            if (AccessibilityManager.getInstance(mContext).isEnabled() && isShown()) {
+                requestParentSendAccessibilityEvent(mAccessibilityEvent);
+            }
+            reset();
+        }
+
+        public void updateWithAccessibilityEvent(AccessibilityEvent accessibilityEvent) {
+            mAccessibilityEvent = accessibilityEvent;
+        }
+
+        public void reset() {
+            mIsPending = false;
+            mAccessibilityEvent = null;
+        }
+
+    }
+
+    /**
+     * Resuable callback for sending
+     * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event.
+     */
+    private class SendViewScrolledAccessibilityEvent extends SendAccessibilityEventThrottle {
+        public int mDeltaX;
+        public int mDeltaY;
+
+        @Override
+        public void updateWithAccessibilityEvent(AccessibilityEvent accessibilityEvent) {
+            super.updateWithAccessibilityEvent(accessibilityEvent);
+            mDeltaX += accessibilityEvent.getScrollDeltaX();
+            mDeltaY += accessibilityEvent.getScrollDeltaY();
+            accessibilityEvent.setScrollDeltaX(mDeltaX);
+            accessibilityEvent.setScrollDeltaY(mDeltaY);
+        }
+
+        @Override
+        public void reset() {
+            super.reset();
+            mDeltaX = 0;
+            mDeltaY = 0;
+        }
+    }
+    /**
+     * Remove the pending callback for sending a throttled accessibility event.
+     */
+    @UnsupportedAppUsage
+    private void cancel(@Nullable SendAccessibilityEventThrottle callback) {
+        if (callback == null || !callback.mIsPending) return;
+        removeCallbacks(callback);
+        callback.reset();
+    }
+
+    /**
+     * <p>
+     * This class represents a delegate that can be registered in a {@link View}
+     * to enhance accessibility support via composition rather via inheritance.
+     * It is specifically targeted to widget developers that extend basic View
+     * classes i.e. classes in package android.view, that would like their
+     * applications to be backwards compatible.
+     * </p>
+     * <div class="special reference">
+     * <h3>Developer Guides</h3>
+     * <p>For more information about making applications accessible, read the
+     * <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a>
+     * developer guide.</p>
+     * </div>
+     * <p>
+     * A scenario in which a developer would like to use an accessibility delegate
+     * is overriding a method introduced in a later API version than the minimal API
+     * version supported by the application. For example, the method
+     * {@link View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)} is not available
+     * in API version 4 when the accessibility APIs were first introduced. If a
+     * developer would like their application to run on API version 4 devices (assuming
+     * all other APIs used by the application are version 4 or lower) and take advantage
+     * of this method, instead of overriding the method which would break the application's
+     * backwards compatibility, they can override the corresponding method in this
+     * delegate and register the delegate in the target View if the API version of
+     * the system is high enough, i.e. the API version is the same as or higher than the API
+     * version that introduced
+     * {@link View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)}.
+     * </p>
+     * <p>
+     * Here is an example implementation:
+     * </p>
+     * <code><pre><p>
+     * if (Build.VERSION.SDK_INT >= 14) {
+     *     // If the API version is equal of higher than the version in
+     *     // which onInitializeAccessibilityNodeInfo was introduced we
+     *     // register a delegate with a customized implementation.
+     *     View view = findViewById(R.id.view_id);
+     *     view.setAccessibilityDelegate(new AccessibilityDelegate() {
+     *         public void onInitializeAccessibilityNodeInfo(View host,
+     *                 AccessibilityNodeInfo info) {
+     *             // Let the default implementation populate the info.
+     *             super.onInitializeAccessibilityNodeInfo(host, info);
+     *             // Set some other information.
+     *             info.setEnabled(host.isEnabled());
+     *         }
+     *     });
+     * }
+     * </code></pre></p>
+     * <p>
+     * This delegate contains methods that correspond to the accessibility methods
+     * in View. If a delegate has been specified the implementation in View hands
+     * off handling to the corresponding method in this delegate. The default
+     * implementation the delegate methods behaves exactly as the corresponding
+     * method in View for the case of no accessibility delegate been set. Hence,
+     * to customize the behavior of a View method, clients can override only the
+     * corresponding delegate method without altering the behavior of the rest
+     * accessibility related methods of the host view.
+     * </p>
+     * <p>
+     * <strong>Note:</strong> On platform versions prior to
+     * {@link android.os.Build.VERSION_CODES#M API 23}, delegate methods on
+     * views in the {@code android.widget.*} package are called <i>before</i>
+     * host methods. This prevents certain properties such as class name from
+     * being modified by overriding
+     * {@link AccessibilityDelegate#onInitializeAccessibilityNodeInfo(View, AccessibilityNodeInfo)},
+     * as any changes will be overwritten by the host class.
+     * <p>
+     * Starting in {@link android.os.Build.VERSION_CODES#M API 23}, delegate
+     * methods are called <i>after</i> host methods, which all properties to be
+     * modified without being overwritten by the host class.
+     */
+    public static class AccessibilityDelegate {
+
+        /**
+         * Sends an accessibility event of the given type. If accessibility is not
+         * enabled this method has no effect.
+         * <p>
+         * The default implementation behaves as {@link View#sendAccessibilityEvent(int)
+         *  View#sendAccessibilityEvent(int)} for the case of no accessibility delegate
+         * been set.
+         * </p>
+         *
+         * @param host The View hosting the delegate.
+         * @param eventType The type of the event to send.
+         *
+         * @see View#sendAccessibilityEvent(int) View#sendAccessibilityEvent(int)
+         */
+        public void sendAccessibilityEvent(View host, int eventType) {
+            host.sendAccessibilityEventInternal(eventType);
+        }
+
+        /**
+         * Performs the specified accessibility action on the view. For
+         * possible accessibility actions look at {@link AccessibilityNodeInfo}.
+         * <p>
+         * The default implementation behaves as
+         * {@link View#performAccessibilityAction(int, Bundle)
+         *  View#performAccessibilityAction(int, Bundle)} for the case of
+         *  no accessibility delegate been set.
+         * </p>
+         *
+         * @param action The action to perform.
+         * @return Whether the action was performed.
+         *
+         * @see View#performAccessibilityAction(int, Bundle)
+         *      View#performAccessibilityAction(int, Bundle)
+         */
+        public boolean performAccessibilityAction(View host, int action, Bundle args) {
+            return host.performAccessibilityActionInternal(action, args);
+        }
+
+        /**
+         * Sends an accessibility event. This method behaves exactly as
+         * {@link #sendAccessibilityEvent(View, int)} but takes as an argument an
+         * empty {@link AccessibilityEvent} and does not perform a check whether
+         * accessibility is enabled.
+         * <p>
+         * The default implementation behaves as
+         * {@link View#sendAccessibilityEventUnchecked(AccessibilityEvent)
+         *  View#sendAccessibilityEventUnchecked(AccessibilityEvent)} for
+         * the case of no accessibility delegate been set.
+         * </p>
+         *
+         * @param host The View hosting the delegate.
+         * @param event The event to send.
+         *
+         * @see View#sendAccessibilityEventUnchecked(AccessibilityEvent)
+         *      View#sendAccessibilityEventUnchecked(AccessibilityEvent)
+         */
+        public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) {
+            host.sendAccessibilityEventUncheckedInternal(event);
+        }
+
+        /**
+         * Dispatches an {@link AccessibilityEvent} to the host {@link View} first and then
+         * to its children for adding their text content to the event.
+         * <p>
+         * The default implementation behaves as
+         * {@link View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)
+         *  View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)} for
+         * the case of no accessibility delegate been set.
+         * </p>
+         *
+         * @param host The View hosting the delegate.
+         * @param event The event.
+         * @return True if the event population was completed.
+         *
+         * @see View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)
+         *      View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)
+         */
+        public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
+            return host.dispatchPopulateAccessibilityEventInternal(event);
+        }
+
+        /**
+         * Gives a chance to the host View to populate the accessibility event with its
+         * text content.
+         * <p>
+         * The default implementation behaves as
+         * {@link View#onPopulateAccessibilityEvent(AccessibilityEvent)
+         *  View#onPopulateAccessibilityEvent(AccessibilityEvent)} for
+         * the case of no accessibility delegate been set.
+         * </p>
+         *
+         * @param host The View hosting the delegate.
+         * @param event The accessibility event which to populate.
+         *
+         * @see View#onPopulateAccessibilityEvent(AccessibilityEvent)
+         *      View#onPopulateAccessibilityEvent(AccessibilityEvent)
+         */
+        public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
+            host.onPopulateAccessibilityEventInternal(event);
+        }
+
+        /**
+         * Initializes an {@link AccessibilityEvent} with information about the
+         * the host View which is the event source.
+         * <p>
+         * The default implementation behaves as
+         * {@link View#onInitializeAccessibilityEvent(AccessibilityEvent)
+         *  View#onInitializeAccessibilityEvent(AccessibilityEvent)} for
+         * the case of no accessibility delegate been set.
+         * </p>
+         *
+         * @param host The View hosting the delegate.
+         * @param event The event to initialize.
+         *
+         * @see View#onInitializeAccessibilityEvent(AccessibilityEvent)
+         *      View#onInitializeAccessibilityEvent(AccessibilityEvent)
+         */
+        public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
+            host.onInitializeAccessibilityEventInternal(event);
+        }
+
+        /**
+         * Initializes an {@link AccessibilityNodeInfo} with information about the host view.
+         * <p>
+         * The default implementation behaves as
+         * {@link View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
+         *  View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)} for
+         * the case of no accessibility delegate been set.
+         * </p>
+         *
+         * @param host The View hosting the delegate.
+         * @param info The instance to initialize.
+         *
+         * @see View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
+         *      View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
+         */
+        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+            host.onInitializeAccessibilityNodeInfoInternal(info);
+        }
+
+        /**
+         * Adds extra data to an {@link AccessibilityNodeInfo} based on an explicit request for the
+         * additional data.
+         * <p>
+         * This method only needs to be implemented if the View offers to provide additional data.
+         * </p>
+         * <p>
+         * The default implementation behaves as
+         * {@link View#addExtraDataToAccessibilityNodeInfo(AccessibilityNodeInfo, String, Bundle)
+         * for the case where no accessibility delegate is set.
+         * </p>
+         *
+         * @param host The View hosting the delegate. Never {@code null}.
+         * @param info The info to which to add the extra data. Never {@code null}.
+         * @param extraDataKey A key specifying the type of extra data to add to the info. The
+         *                     extra data should be added to the {@link Bundle} returned by
+         *                     the info's {@link AccessibilityNodeInfo#getExtras} method.  Never
+         *                     {@code null}.
+         * @param arguments A {@link Bundle} holding any arguments relevant for this request.
+         *                  May be {@code null} if the if the service provided no arguments.
+         *
+         * @see AccessibilityNodeInfo#setAvailableExtraData(List)
+         */
+        public void addExtraDataToAccessibilityNodeInfo(@NonNull View host,
+                @NonNull AccessibilityNodeInfo info, @NonNull String extraDataKey,
+                @Nullable Bundle arguments) {
+            host.addExtraDataToAccessibilityNodeInfo(info, extraDataKey, arguments);
+        }
+
+        /**
+         * Called when a child of the host View has requested sending an
+         * {@link AccessibilityEvent} and gives an opportunity to the parent (the host)
+         * to augment the event.
+         * <p>
+         * The default implementation behaves as
+         * {@link ViewGroup#onRequestSendAccessibilityEvent(View, AccessibilityEvent)
+         *  ViewGroup#onRequestSendAccessibilityEvent(View, AccessibilityEvent)} for
+         * the case of no accessibility delegate been set.
+         * </p>
+         *
+         * @param host The View hosting the delegate.
+         * @param child The child which requests sending the event.
+         * @param event The event to be sent.
+         * @return True if the event should be sent
+         *
+         * @see ViewGroup#onRequestSendAccessibilityEvent(View, AccessibilityEvent)
+         *      ViewGroup#onRequestSendAccessibilityEvent(View, AccessibilityEvent)
+         */
+        public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
+                AccessibilityEvent event) {
+            return host.onRequestSendAccessibilityEventInternal(child, event);
+        }
+
+        /**
+         * Gets the provider for managing a virtual view hierarchy rooted at this View
+         * and reported to {@link android.accessibilityservice.AccessibilityService}s
+         * that explore the window content.
+         * <p>
+         * The default implementation behaves as
+         * {@link View#getAccessibilityNodeProvider() View#getAccessibilityNodeProvider()} for
+         * the case of no accessibility delegate been set.
+         * </p>
+         *
+         * @return The provider.
+         *
+         * @see AccessibilityNodeProvider
+         */
+        public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) {
+            return null;
+        }
+
+        /**
+         * Returns an {@link AccessibilityNodeInfo} representing the host view from the
+         * point of view of an {@link android.accessibilityservice.AccessibilityService}.
+         * This method is responsible for obtaining an accessibility node info from a
+         * pool of reusable instances and calling
+         * {@link #onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)} on the host
+         * view to initialize the former.
+         * <p>
+         * <strong>Note:</strong> The client is responsible for recycling the obtained
+         * instance by calling {@link AccessibilityNodeInfo#recycle()} to minimize object
+         * creation.
+         * </p>
+         * <p>
+         * The default implementation behaves as
+         * {@link View#createAccessibilityNodeInfo() View#createAccessibilityNodeInfo()} for
+         * the case of no accessibility delegate been set.
+         * </p>
+         * @return A populated {@link AccessibilityNodeInfo}.
+         *
+         * @see AccessibilityNodeInfo
+         *
+         * @hide
+         */
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        public AccessibilityNodeInfo createAccessibilityNodeInfo(View host) {
+            return host.createAccessibilityNodeInfoInternal();
+        }
+    }
+
+    private static class MatchIdPredicate implements Predicate<View> {
+        public int mId;
+
+        @Override
+        public boolean test(View view) {
+            return (view.mID == mId);
+        }
+    }
+
+    private static class MatchLabelForPredicate implements Predicate<View> {
+        private int mLabeledId;
+
+        @Override
+        public boolean test(View view) {
+            return (view.mLabelForId == mLabeledId);
+        }
+    }
+
+
+    /**
+     * Returns the current scroll capture hint for this view.
+     *
+     * @return the current scroll capture hint
+     */
+    @ScrollCaptureHint
+    public int getScrollCaptureHint() {
+        return (mPrivateFlags4 & PFLAG4_SCROLL_CAPTURE_HINT_MASK)
+                >> PFLAG4_SCROLL_CAPTURE_HINT_SHIFT;
+    }
+
+    /**
+     * Sets the scroll capture hint for this View. These flags affect the search for a potential
+     * scroll capture targets.
+     *
+     * @param hint the scrollCaptureHint flags value to set
+     */
+    public void setScrollCaptureHint(@ScrollCaptureHint int hint) {
+        mPrivateFlags4 &= ~PFLAG4_SCROLL_CAPTURE_HINT_MASK;
+        // Since include/exclude are mutually exclusive, exclude takes precedence.
+        if ((hint & SCROLL_CAPTURE_HINT_EXCLUDE) != 0) {
+            hint &= ~SCROLL_CAPTURE_HINT_INCLUDE;
+        }
+        mPrivateFlags4 |= ((hint << PFLAG4_SCROLL_CAPTURE_HINT_SHIFT)
+                & PFLAG4_SCROLL_CAPTURE_HINT_MASK);
+    }
+
+    /**
+     * Sets the callback to receive scroll capture requests. This component is the adapter between
+     * the scroll capture API and application UI code. If no callback is set, the system may provide
+     * an implementation. Any value provided here will take precedence over a system version.
+     * <p>
+     * This view will be ignored when {@link #SCROLL_CAPTURE_HINT_EXCLUDE} is set in its {@link
+     * #setScrollCaptureHint(int) scrollCaptureHint}, regardless whether a callback has been set.
+     * <p>
+     * It is recommended to set the scroll capture hint {@link #SCROLL_CAPTURE_HINT_INCLUDE} when
+     * setting a custom callback to help ensure it is selected as the target.
+     *
+     * @param callback the new callback to assign
+     */
+    public final void setScrollCaptureCallback(@Nullable ScrollCaptureCallback callback) {
+        getListenerInfo().mScrollCaptureCallback = callback;
+    }
+
+    /** {@hide} */
+    @Nullable
+    public ScrollCaptureCallback createScrollCaptureCallbackInternal(@NonNull Rect localVisibleRect,
+            @NonNull Point windowOffset) {
+        if (mAttachInfo == null) {
+            return null;
+        }
+        if (mAttachInfo.mScrollCaptureInternal == null) {
+            mAttachInfo.mScrollCaptureInternal = new ScrollCaptureInternal();
+        }
+        return mAttachInfo.mScrollCaptureInternal.requestCallback(this, localVisibleRect,
+                windowOffset);
+    }
+
+    /**
+     * Dispatch a scroll capture search request down the view hierarchy.
+     *
+     * @param localVisibleRect the visible area of this ViewGroup in local coordinates, according to
+     *                         the parent
+     * @param windowOffset     the offset of this view within the window
+     * @param targets          accepts potential scroll capture targets; {@link Consumer#accept
+     *                         results.accept} may be called zero or more times on the calling
+     *                         thread before onScrollCaptureSearch returns
+     */
+    public void dispatchScrollCaptureSearch(
+            @NonNull Rect localVisibleRect, @NonNull Point windowOffset,
+            @NonNull Consumer<ScrollCaptureTarget> targets) {
+        onScrollCaptureSearch(localVisibleRect, windowOffset, targets);
+    }
+
+    /**
+     * Called when scroll capture is requested, to search for appropriate content to scroll. If
+     * applicable, this view adds itself to the provided list for consideration, subject to the
+     * flags set by {@link #setScrollCaptureHint}.
+     *
+     * @param localVisibleRect the local visible rect of this view
+     * @param windowOffset     the offset of localVisibleRect within the window
+     * @param targets          accepts potential scroll capture targets; {@link Consumer#accept
+     *                         results.accept} may be called zero or more times on the calling
+     *                         thread before onScrollCaptureSearch returns
+     * @throws IllegalStateException if this view is not attached to a window
+     */
+    public void onScrollCaptureSearch(@NonNull Rect localVisibleRect,
+            @NonNull Point windowOffset, @NonNull Consumer<ScrollCaptureTarget> targets) {
+        int hint = getScrollCaptureHint();
+        if ((hint & SCROLL_CAPTURE_HINT_EXCLUDE) != 0) {
+            return;
+        }
+        boolean rectIsVisible = true;
+
+        // Apply clipBounds if present.
+        if (mClipBounds != null) {
+            rectIsVisible = localVisibleRect.intersect(mClipBounds);
+        }
+        if (!rectIsVisible) {
+            return;
+        }
+
+        // Get a callback provided by the framework, library or application.
+        ScrollCaptureCallback callback =
+                (mListenerInfo == null) ? null : mListenerInfo.mScrollCaptureCallback;
+
+        // Try framework support for standard scrolling containers.
+        if (callback == null) {
+            callback = createScrollCaptureCallbackInternal(localVisibleRect, windowOffset);
+        }
+
+        // If found, then add it to the list.
+        if (callback != null) {
+            // Add to the list for consideration
+            Point offset = new Point(windowOffset.x, windowOffset.y);
+            Rect rect = new Rect(localVisibleRect);
+            targets.accept(new ScrollCaptureTarget(this, rect, offset, callback));
+        }
+    }
+
+    /**
+     * Dump all private flags in readable format, useful for documentation and
+     * consistency checking.
+     */
+    private static void dumpFlags() {
+        final HashMap<String, String> found = Maps.newHashMap();
+        try {
+            for (Field field : View.class.getDeclaredFields()) {
+                final int modifiers = field.getModifiers();
+                if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)) {
+                    if (field.getType().equals(int.class)) {
+                        final int value = field.getInt(null);
+                        dumpFlag(found, field.getName(), value);
+                    } else if (field.getType().equals(int[].class)) {
+                        final int[] values = (int[]) field.get(null);
+                        for (int i = 0; i < values.length; i++) {
+                            dumpFlag(found, field.getName() + "[" + i + "]", values[i]);
+                        }
+                    }
+                }
+            }
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+
+        final ArrayList<String> keys = Lists.newArrayList();
+        keys.addAll(found.keySet());
+        Collections.sort(keys);
+        for (String key : keys) {
+            Log.d(VIEW_LOG_TAG, found.get(key));
+        }
+    }
+
+    private static void dumpFlag(HashMap<String, String> found, String name, int value) {
+        // Sort flags by prefix, then by bits, always keeping unique keys
+        final String bits = String.format("%32s", Integer.toBinaryString(value)).replace('0', ' ');
+        final int prefix = name.indexOf('_');
+        final String key = (prefix > 0 ? name.substring(0, prefix) : name) + bits + name;
+        final String output = bits + " " + name;
+        found.put(key, output);
+    }
+
+    /** {@hide} */
+    public void encode(@NonNull ViewHierarchyEncoder stream) {
+        stream.beginObject(this);
+        encodeProperties(stream);
+        stream.endObject();
+    }
+
+    /** {@hide} */
+    @CallSuper
+    protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
+        Object resolveId = ViewDebug.resolveId(getContext(), mID);
+        if (resolveId instanceof String) {
+            stream.addProperty("id", (String) resolveId);
+        } else {
+            stream.addProperty("id", mID);
+        }
+
+        stream.addProperty("misc:transformation.alpha",
+                mTransformationInfo != null ? mTransformationInfo.mAlpha : 0);
+        stream.addProperty("misc:transitionName", getTransitionName());
+
+        // layout
+        stream.addProperty("layout:left", mLeft);
+        stream.addProperty("layout:right", mRight);
+        stream.addProperty("layout:top", mTop);
+        stream.addProperty("layout:bottom", mBottom);
+        stream.addProperty("layout:width", getWidth());
+        stream.addProperty("layout:height", getHeight());
+        stream.addProperty("layout:layoutDirection", getLayoutDirection());
+        stream.addProperty("layout:layoutRtl", isLayoutRtl());
+        stream.addProperty("layout:hasTransientState", hasTransientState());
+        stream.addProperty("layout:baseline", getBaseline());
+
+        // layout params
+        ViewGroup.LayoutParams layoutParams = getLayoutParams();
+        if (layoutParams != null) {
+            stream.addPropertyKey("layoutParams");
+            layoutParams.encode(stream);
+        }
+
+        // scrolling
+        stream.addProperty("scrolling:scrollX", mScrollX);
+        stream.addProperty("scrolling:scrollY", mScrollY);
+
+        // padding
+        stream.addProperty("padding:paddingLeft", mPaddingLeft);
+        stream.addProperty("padding:paddingRight", mPaddingRight);
+        stream.addProperty("padding:paddingTop", mPaddingTop);
+        stream.addProperty("padding:paddingBottom", mPaddingBottom);
+        stream.addProperty("padding:userPaddingRight", mUserPaddingRight);
+        stream.addProperty("padding:userPaddingLeft", mUserPaddingLeft);
+        stream.addProperty("padding:userPaddingBottom", mUserPaddingBottom);
+        stream.addProperty("padding:userPaddingStart", mUserPaddingStart);
+        stream.addProperty("padding:userPaddingEnd", mUserPaddingEnd);
+
+        // measurement
+        stream.addProperty("measurement:minHeight", mMinHeight);
+        stream.addProperty("measurement:minWidth", mMinWidth);
+        stream.addProperty("measurement:measuredWidth", mMeasuredWidth);
+        stream.addProperty("measurement:measuredHeight", mMeasuredHeight);
+
+        // drawing
+        stream.addProperty("drawing:elevation", getElevation());
+        stream.addProperty("drawing:translationX", getTranslationX());
+        stream.addProperty("drawing:translationY", getTranslationY());
+        stream.addProperty("drawing:translationZ", getTranslationZ());
+        stream.addProperty("drawing:rotation", getRotation());
+        stream.addProperty("drawing:rotationX", getRotationX());
+        stream.addProperty("drawing:rotationY", getRotationY());
+        stream.addProperty("drawing:scaleX", getScaleX());
+        stream.addProperty("drawing:scaleY", getScaleY());
+        stream.addProperty("drawing:pivotX", getPivotX());
+        stream.addProperty("drawing:pivotY", getPivotY());
+        stream.addProperty("drawing:clipBounds",
+                mClipBounds == null ? null : mClipBounds.toString());
+        stream.addProperty("drawing:opaque", isOpaque());
+        stream.addProperty("drawing:alpha", getAlpha());
+        stream.addProperty("drawing:transitionAlpha", getTransitionAlpha());
+        stream.addProperty("drawing:shadow", hasShadow());
+        stream.addProperty("drawing:solidColor", getSolidColor());
+        stream.addProperty("drawing:layerType", mLayerType);
+        stream.addProperty("drawing:willNotDraw", willNotDraw());
+        stream.addProperty("drawing:hardwareAccelerated", isHardwareAccelerated());
+        stream.addProperty("drawing:willNotCacheDrawing", willNotCacheDrawing());
+        stream.addProperty("drawing:drawingCacheEnabled", isDrawingCacheEnabled());
+        stream.addProperty("drawing:overlappingRendering", hasOverlappingRendering());
+        stream.addProperty("drawing:outlineAmbientShadowColor", getOutlineAmbientShadowColor());
+        stream.addProperty("drawing:outlineSpotShadowColor", getOutlineSpotShadowColor());
+
+        // focus
+        stream.addProperty("focus:hasFocus", hasFocus());
+        stream.addProperty("focus:isFocused", isFocused());
+        stream.addProperty("focus:focusable", getFocusable());
+        stream.addProperty("focus:isFocusable", isFocusable());
+        stream.addProperty("focus:isFocusableInTouchMode", isFocusableInTouchMode());
+
+        stream.addProperty("misc:clickable", isClickable());
+        stream.addProperty("misc:pressed", isPressed());
+        stream.addProperty("misc:selected", isSelected());
+        stream.addProperty("misc:touchMode", isInTouchMode());
+        stream.addProperty("misc:hovered", isHovered());
+        stream.addProperty("misc:activated", isActivated());
+
+        stream.addProperty("misc:visibility", getVisibility());
+        stream.addProperty("misc:fitsSystemWindows", getFitsSystemWindows());
+        stream.addProperty("misc:filterTouchesWhenObscured", getFilterTouchesWhenObscured());
+
+        stream.addProperty("misc:enabled", isEnabled());
+        stream.addProperty("misc:soundEffectsEnabled", isSoundEffectsEnabled());
+        stream.addProperty("misc:hapticFeedbackEnabled", isHapticFeedbackEnabled());
+
+        // theme attributes
+        Resources.Theme theme = getContext().getTheme();
+        if (theme != null) {
+            stream.addPropertyKey("theme");
+            theme.encode(stream);
+        }
+
+        // view attribute information
+        int n = mAttributes != null ? mAttributes.length : 0;
+        stream.addProperty("meta:__attrCount__", n/2);
+        for (int i = 0; i < n; i += 2) {
+            stream.addProperty("meta:__attr__" + mAttributes[i], mAttributes[i+1]);
+        }
+
+        stream.addProperty("misc:scrollBarStyle", getScrollBarStyle());
+
+        // text
+        stream.addProperty("text:textDirection", getTextDirection());
+        stream.addProperty("text:textAlignment", getTextAlignment());
+
+        // accessibility
+        CharSequence contentDescription = getContentDescription();
+        stream.addUserProperty("accessibility:contentDescription",
+                contentDescription == null ? "" : contentDescription.toString());
+        stream.addProperty("accessibility:labelFor", getLabelFor());
+        stream.addProperty("accessibility:importantForAccessibility", getImportantForAccessibility());
+    }
+
+    /**
+     * Determine if this view is rendered on a round wearable device and is the main view
+     * on the screen.
+     */
+    boolean shouldDrawRoundScrollbar() {
+        if (!mResources.getConfiguration().isScreenRound() || mAttachInfo == null) {
+            return false;
+        }
+
+        final View rootView = getRootView();
+        final WindowInsets insets = getRootWindowInsets();
+
+        int height = getHeight();
+        int width = getWidth();
+        int displayHeight = rootView.getHeight();
+        int displayWidth = rootView.getWidth();
+
+        if (height != displayHeight || width != displayWidth) {
+            return false;
+        }
+
+        getLocationInWindow(mAttachInfo.mTmpLocation);
+        return mAttachInfo.mTmpLocation[0] == insets.getStableInsetLeft()
+                && mAttachInfo.mTmpLocation[1] == insets.getStableInsetTop();
+    }
+
+    /**
+     * Sets the tooltip text which will be displayed in a small popup next to the view.
+     * <p>
+     * The tooltip will be displayed:
+     * <ul>
+     * <li>On long click, unless it is handled otherwise (by OnLongClickListener or a context
+     * menu). </li>
+     * <li>On hover, after a brief delay since the pointer has stopped moving </li>
+     * </ul>
+     * <p>
+     * <strong>Note:</strong> Do not override this method, as it will have no
+     * effect on the text displayed in the tooltip.
+     *
+     * @param tooltipText the tooltip text, or null if no tooltip is required
+     * @see #getTooltipText()
+     * @attr ref android.R.styleable#View_tooltipText
+     */
+    public void setTooltipText(@Nullable CharSequence tooltipText) {
+        if (TextUtils.isEmpty(tooltipText)) {
+            setFlags(0, TOOLTIP);
+            hideTooltip();
+            mTooltipInfo = null;
+        } else {
+            setFlags(TOOLTIP, TOOLTIP);
+            if (mTooltipInfo == null) {
+                mTooltipInfo = new TooltipInfo();
+                mTooltipInfo.mShowTooltipRunnable = this::showHoverTooltip;
+                mTooltipInfo.mHideTooltipRunnable = this::hideTooltip;
+                mTooltipInfo.mHoverSlop = ViewConfiguration.get(mContext).getScaledHoverSlop();
+                mTooltipInfo.clearAnchorPos();
+            }
+            mTooltipInfo.mTooltipText = tooltipText;
+        }
+    }
+
+    /**
+     * @hide Binary compatibility stub. To be removed when we finalize O APIs.
+     */
+    @UnsupportedAppUsage
+    public void setTooltip(@Nullable CharSequence tooltipText) {
+        setTooltipText(tooltipText);
+    }
+
+    /**
+     * Returns the view's tooltip text.
+     *
+     * <strong>Note:</strong> Do not override this method, as it will have no
+     * effect on the text displayed in the tooltip. You must call
+     * {@link #setTooltipText(CharSequence)} to modify the tooltip text.
+     *
+     * @return the tooltip text
+     * @see #setTooltipText(CharSequence)
+     * @attr ref android.R.styleable#View_tooltipText
+     */
+    @InspectableProperty
+    @Nullable
+    public CharSequence getTooltipText() {
+        return mTooltipInfo != null ? mTooltipInfo.mTooltipText : null;
+    }
+
+    /**
+     * @hide Binary compatibility stub. To be removed when we finalize O APIs.
+     */
+    @Nullable
+    public CharSequence getTooltip() {
+        return getTooltipText();
+    }
+
+    private boolean showTooltip(int x, int y, boolean fromLongClick) {
+        if (mAttachInfo == null || mTooltipInfo == null) {
+            return false;
+        }
+        if (fromLongClick && (mViewFlags & ENABLED_MASK) != ENABLED) {
+            return false;
+        }
+        if (TextUtils.isEmpty(mTooltipInfo.mTooltipText)) {
+            return false;
+        }
+        hideTooltip();
+        mTooltipInfo.mTooltipFromLongClick = fromLongClick;
+        mTooltipInfo.mTooltipPopup = new TooltipPopup(getContext());
+        final boolean fromTouch = (mPrivateFlags3 & PFLAG3_FINGER_DOWN) == PFLAG3_FINGER_DOWN;
+        mTooltipInfo.mTooltipPopup.show(this, x, y, fromTouch, mTooltipInfo.mTooltipText);
+        mAttachInfo.mTooltipHost = this;
+        // The available accessibility actions have changed
+        notifyViewAccessibilityStateChangedIfNeeded(CONTENT_CHANGE_TYPE_UNDEFINED);
+        return true;
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    void hideTooltip() {
+        if (mTooltipInfo == null) {
+            return;
+        }
+        removeCallbacks(mTooltipInfo.mShowTooltipRunnable);
+        if (mTooltipInfo.mTooltipPopup == null) {
+            return;
+        }
+        mTooltipInfo.mTooltipPopup.hide();
+        mTooltipInfo.mTooltipPopup = null;
+        mTooltipInfo.mTooltipFromLongClick = false;
+        mTooltipInfo.clearAnchorPos();
+        if (mAttachInfo != null) {
+            mAttachInfo.mTooltipHost = null;
+        }
+        // The available accessibility actions have changed
+        notifyViewAccessibilityStateChangedIfNeeded(CONTENT_CHANGE_TYPE_UNDEFINED);
+    }
+
+    private boolean showLongClickTooltip(int x, int y) {
+        removeCallbacks(mTooltipInfo.mShowTooltipRunnable);
+        removeCallbacks(mTooltipInfo.mHideTooltipRunnable);
+        return showTooltip(x, y, true);
+    }
+
+    private boolean showHoverTooltip() {
+        return showTooltip(mTooltipInfo.mAnchorX, mTooltipInfo.mAnchorY, false);
+    }
+
+    boolean dispatchTooltipHoverEvent(MotionEvent event) {
+        if (mTooltipInfo == null) {
+            return false;
+        }
+        switch(event.getAction()) {
+            case MotionEvent.ACTION_HOVER_MOVE:
+                if ((mViewFlags & TOOLTIP) != TOOLTIP) {
+                    break;
+                }
+                if (!mTooltipInfo.mTooltipFromLongClick && mTooltipInfo.updateAnchorPos(event)) {
+                    if (mTooltipInfo.mTooltipPopup == null) {
+                        // Schedule showing the tooltip after a timeout.
+                        removeCallbacks(mTooltipInfo.mShowTooltipRunnable);
+                        postDelayed(mTooltipInfo.mShowTooltipRunnable,
+                                ViewConfiguration.getHoverTooltipShowTimeout());
+                    }
+
+                    // Hide hover-triggered tooltip after a period of inactivity.
+                    // Match the timeout used by NativeInputManager to hide the mouse pointer
+                    // (depends on SYSTEM_UI_FLAG_LOW_PROFILE being set).
+                    final int timeout;
+                    if ((getWindowSystemUiVisibility() & SYSTEM_UI_FLAG_LOW_PROFILE)
+                            == SYSTEM_UI_FLAG_LOW_PROFILE) {
+                        timeout = ViewConfiguration.getHoverTooltipHideShortTimeout();
+                    } else {
+                        timeout = ViewConfiguration.getHoverTooltipHideTimeout();
+                    }
+                    removeCallbacks(mTooltipInfo.mHideTooltipRunnable);
+                    postDelayed(mTooltipInfo.mHideTooltipRunnable, timeout);
+                }
+                return true;
+
+            case MotionEvent.ACTION_HOVER_EXIT:
+                mTooltipInfo.clearAnchorPos();
+                if (!mTooltipInfo.mTooltipFromLongClick) {
+                    hideTooltip();
+                }
+                break;
+        }
+        return false;
+    }
+
+    void handleTooltipKey(KeyEvent event) {
+        switch (event.getAction()) {
+            case KeyEvent.ACTION_DOWN:
+                if (event.getRepeatCount() == 0) {
+                    hideTooltip();
+                }
+                break;
+
+            case KeyEvent.ACTION_UP:
+                handleTooltipUp();
+                break;
+        }
+    }
+
+    private void handleTooltipUp() {
+        if (mTooltipInfo == null || mTooltipInfo.mTooltipPopup == null) {
+            return;
+        }
+        removeCallbacks(mTooltipInfo.mHideTooltipRunnable);
+        postDelayed(mTooltipInfo.mHideTooltipRunnable,
+                ViewConfiguration.getLongPressTooltipHideTimeout());
+    }
+
+    private int getFocusableAttribute(TypedArray attributes) {
+        TypedValue val = new TypedValue();
+        if (attributes.getValue(com.android.internal.R.styleable.View_focusable, val)) {
+            if (val.type == TypedValue.TYPE_INT_BOOLEAN) {
+                return (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE);
+            } else {
+                return val.data;
+            }
+        } else {
+            return FOCUSABLE_AUTO;
+        }
+    }
+
+    /**
+     * @return The content view of the tooltip popup currently being shown, or null if the tooltip
+     * is not showing.
+     * @hide
+     */
+    @TestApi
+    public View getTooltipView() {
+        if (mTooltipInfo == null || mTooltipInfo.mTooltipPopup == null) {
+            return null;
+        }
+        return mTooltipInfo.mTooltipPopup.getContentView();
+    }
+
+    /**
+     * @return {@code true} if the default focus highlight is enabled, {@code false} otherwies.
+     * @hide
+     */
+    @TestApi
+    public static boolean isDefaultFocusHighlightEnabled() {
+        return sUseDefaultFocusHighlight;
+    }
+
+    /**
+     * Dispatch a previously unhandled {@link KeyEvent} to this view. Unlike normal key dispatch,
+     * this dispatches to ALL child views until it is consumed. The dispatch order is z-order
+     * (visually on-top views first).
+     *
+     * @param evt the previously unhandled {@link KeyEvent}.
+     * @return the {@link View} which consumed the event or {@code null} if not consumed.
+     */
+    View dispatchUnhandledKeyEvent(KeyEvent evt) {
+        if (onUnhandledKeyEvent(evt)) {
+            return this;
+        }
+        return null;
+    }
+
+    /**
+     * Allows this view to handle {@link KeyEvent}s which weren't handled by normal dispatch. This
+     * occurs after the normal view hierarchy dispatch, but before the window callback. By default,
+     * this will dispatch into all the listeners registered via
+     * {@link #addOnUnhandledKeyEventListener(OnUnhandledKeyEventListener)} in last-in-first-out
+     * order (most recently added will receive events first).
+     *
+     * @param event An unhandled event.
+     * @return {@code true} if the event was handled, {@code false} otherwise.
+     * @see #addOnUnhandledKeyEventListener
+     */
+    boolean onUnhandledKeyEvent(@NonNull KeyEvent event) {
+        if (mListenerInfo != null && mListenerInfo.mUnhandledKeyListeners != null) {
+            for (int i = mListenerInfo.mUnhandledKeyListeners.size() - 1; i >= 0; --i) {
+                if (mListenerInfo.mUnhandledKeyListeners.get(i).onUnhandledKeyEvent(this, event)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    boolean hasUnhandledKeyListener() {
+        return (mListenerInfo != null && mListenerInfo.mUnhandledKeyListeners != null
+                && !mListenerInfo.mUnhandledKeyListeners.isEmpty());
+    }
+
+    /**
+     * Adds a listener which will receive unhandled {@link KeyEvent}s. This must be called on the
+     * UI thread.
+     *
+     * @param listener a receiver of unhandled {@link KeyEvent}s.
+     * @see #removeOnUnhandledKeyEventListener
+     */
+    public void addOnUnhandledKeyEventListener(OnUnhandledKeyEventListener listener) {
+        ArrayList<OnUnhandledKeyEventListener> listeners = getListenerInfo().mUnhandledKeyListeners;
+        if (listeners == null) {
+            listeners = new ArrayList<>();
+            getListenerInfo().mUnhandledKeyListeners = listeners;
+        }
+        listeners.add(listener);
+        if (listeners.size() == 1 && mParent instanceof ViewGroup) {
+            ((ViewGroup) mParent).incrementChildUnhandledKeyListeners();
+        }
+    }
+
+    /**
+     * Removes a listener which will receive unhandled {@link KeyEvent}s. This must be called on the
+     * UI thread.
+     *
+     * @param listener a receiver of unhandled {@link KeyEvent}s.
+     * @see #addOnUnhandledKeyEventListener
+     */
+    public void removeOnUnhandledKeyEventListener(OnUnhandledKeyEventListener listener) {
+        if (mListenerInfo != null) {
+            if (mListenerInfo.mUnhandledKeyListeners != null
+                    && !mListenerInfo.mUnhandledKeyListeners.isEmpty()) {
+                mListenerInfo.mUnhandledKeyListeners.remove(listener);
+                if (mListenerInfo.mUnhandledKeyListeners.isEmpty()) {
+                    mListenerInfo.mUnhandledKeyListeners = null;
+                    if (mParent instanceof ViewGroup) {
+                        ((ViewGroup) mParent).decrementChildUnhandledKeyListeners();
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Set the view to be detached or not detached.
+     *
+     * @param detached Whether the view is detached.
+     *
+     * @hide
+     */
+    protected void setDetached(boolean detached) {
+        if (detached) {
+            mPrivateFlags4 |= PFLAG4_DETACHED;
+        } else {
+            mPrivateFlags4 &= ~PFLAG4_DETACHED;
+        }
+    }
+
+    /**
+     * Collects a {@link ViewTranslationRequest} which represents the content to be translated in
+     * the view.
+     *
+     * <p>The default implementation does nothing.</p>
+     *
+     * @param supportedFormats the supported translation formats. For now, the only possible value
+     * is the {@link android.view.translation.TranslationSpec#DATA_FORMAT_TEXT}.
+     * @param requestsCollector a {@link ViewTranslationRequest} collector that can be used to
+     * collect the information to be translated in the view. The {@code requestsCollector} only
+     * accepts one request; an IllegalStateException is thrown if more than one
+     * {@link ViewTranslationRequest} is submitted to it. The {@link AutofillId} must be set on the
+     * {@link ViewTranslationRequest}.
+     */
+    public void onCreateViewTranslationRequest(@NonNull @DataFormat int[] supportedFormats,
+            @NonNull Consumer<ViewTranslationRequest> requestsCollector) {
+    }
+
+    /**
+     * Collects {@link ViewTranslationRequest}s which represents the content to be translated
+     * for the virtual views in the host view. This is called if this view returned a virtual
+     * view structure from {@link #onProvideContentCaptureStructure} and the system determined that
+     * those virtual views were relevant for translation.
+     *
+     * <p>The default implementation does nothing.</p>
+     *
+     * @param virtualIds the virtual view ids which represents the virtual views in the host
+     * view.
+     * @param supportedFormats the supported translation formats. For now, the only possible value
+     * is the {@link android.view.translation.TranslationSpec#DATA_FORMAT_TEXT}.
+     * @param requestsCollector a {@link ViewTranslationRequest} collector that can be called
+     * multiple times to collect the information to be translated in the host view. One
+     * {@link ViewTranslationRequest} per virtual child. The {@link ViewTranslationRequest} must
+     * contains the {@link AutofillId} corresponding to the virtualChildIds. Do not keep this
+     * Consumer after the method returns.
+     */
+    @SuppressLint("NullableCollection")
+    public void onCreateVirtualViewTranslationRequests(@NonNull long[] virtualIds,
+            @NonNull @DataFormat int[] supportedFormats,
+            @NonNull Consumer<ViewTranslationRequest> requestsCollector) {
+        // no-op
+    }
+
+    /**
+     * Returns a {@link ViewTranslationCallback} that is used to display the translated information
+     * or {@code null} if this View doesn't support translation.
+     *
+     * @hide
+     */
+    @Nullable
+    public ViewTranslationCallback getViewTranslationCallback() {
+        return mViewTranslationCallback;
+    }
+
+    /**
+     * Sets a {@link ViewTranslationCallback} that is used to display/hide the translated
+     * information. Developers can provide the customized implementation for show/hide translated
+     * information.
+     *
+     * @param callback a {@link ViewTranslationCallback} that is used to control how to display the
+     * translated information
+     */
+    public void setViewTranslationCallback(@NonNull ViewTranslationCallback callback) {
+        mViewTranslationCallback = callback;
+    }
+
+    /**
+     * Clear the {@link ViewTranslationCallback} from this view.
+     */
+    public void clearViewTranslationCallback() {
+        mViewTranslationCallback = null;
+    }
+
+    /**
+     * Returns the {@link ViewTranslationResponse} associated with this view. The response will be
+     * set when the translation is done then {@link #onViewTranslationResponse} is called. The
+     * {@link ViewTranslationCallback} can use to get {@link ViewTranslationResponse} to display the
+     * translated information.
+     *
+     * @return a {@link ViewTranslationResponse} that contains the translated information associated
+     * with this view or {@code null} if this View doesn't have the translation.
+     */
+    @Nullable
+    public ViewTranslationResponse getViewTranslationResponse() {
+        return mViewTranslationResponse;
+    }
+
+    /**
+     * Called when the content from {@link View#onCreateViewTranslationRequest} had been translated
+     * by the TranslationService. The {@link ViewTranslationResponse} should be saved here so that
+     * the {@link ViewTranslationResponse} can be used to display the translation when the system
+     * calls {@link ViewTranslationCallback#onShowTranslation}.
+     *
+     * <p> The default implementation will set the ViewTranslationResponse that can be get from
+     * {@link View#getViewTranslationResponse}. </p>
+     *
+     * @param response a {@link ViewTranslationResponse} that contains the translated information
+     * which can be shown in the view.
+     */
+    public void onViewTranslationResponse(@NonNull ViewTranslationResponse response) {
+        mViewTranslationResponse = response;
+    }
+
+    /**
+     * Clears the ViewTranslationResponse stored by the default implementation of {@link
+     * #onViewTranslationResponse}.
+     *
+     * @hide
+     */
+    public void clearViewTranslationResponse() {
+        mViewTranslationResponse = null;
+    }
+
+    /**
+     * Called when the content from {@link View#onCreateVirtualViewTranslationRequests} had been
+     * translated by the TranslationService.
+     *
+     * <p> The default implementation does nothing.</p>
+     *
+     * @param response a {@link ViewTranslationResponse} SparseArray for the request that send by
+     * {@link View#onCreateVirtualViewTranslationRequests} that contains the translated information
+     * which can be shown in the view. The key of SparseArray is the virtual child ids.
+     */
+    public void onVirtualViewTranslationResponses(
+            @NonNull LongSparseArray<ViewTranslationResponse> response) {
+        // no-op
+    }
+
+    /**
+     * Dispatch to collect the {@link ViewTranslationRequest}s for translation purpose by traversing
+     * the hierarchy when the app requests ui translation. Typically, this method should only be
+     * overridden by subclasses that provide a view hierarchy (such as {@link ViewGroup}). Other
+     * classes should override {@link View#onCreateViewTranslationRequest} for normal view or
+     * override {@link View#onVirtualViewTranslationResponses} for view contains virtual children.
+     * When requested to start the ui translation, the system will call this method to traverse the
+     * view hierarchy to collect {@link ViewTranslationRequest}s and create a
+     * {@link android.view.translation.Translator} to translate the requests. All the
+     * {@link ViewTranslationRequest}s must be added when the traversal is done.
+     *
+     * <p> The default implementation calls {@link View#onCreateViewTranslationRequest} for normal
+     * view or calls {@link View#onVirtualViewTranslationResponses} for view contains virtual
+     * children to build {@link ViewTranslationRequest} if the view should be translated.
+     * The view is marked as having {@link #setHasTransientState(boolean) transient state} so that
+     * recycling of views doesn't prevent the system from attaching the response to it. Therefore,
+     * if overriding this method, you should set or reset the transient state. </p>
+     *
+     * @param viewIds a map for the view's {@link AutofillId} and its virtual child ids or
+     * {@code null} if the view doesn't have virtual child that should be translated. The virtual
+     * child ids are the same virtual ids provided by ContentCapture.
+     * @param supportedFormats the supported translation formats. For now, the only possible value
+     * is the {@link android.view.translation.TranslationSpec#DATA_FORMAT_TEXT}.
+     * @param capability a {@link TranslationCapability} that holds translation capability.
+     * information, e.g. source spec, target spec.
+     * @param requests fill in with {@link ViewTranslationRequest}s for translation purpose.
+     */
+    public void dispatchCreateViewTranslationRequest(@NonNull Map<AutofillId, long[]> viewIds,
+            @NonNull @DataFormat int[] supportedFormats,
+            @NonNull TranslationCapability capability,
+            @NonNull List<ViewTranslationRequest> requests) {
+        AutofillId autofillId = getAutofillId();
+        if (viewIds.containsKey(autofillId)) {
+            if (viewIds.get(autofillId) == null) {
+                // TODO: avoiding the allocation per view
+                onCreateViewTranslationRequest(supportedFormats,
+                        new ViewTranslationRequestConsumer(requests));
+            } else {
+                onCreateVirtualViewTranslationRequests(viewIds.get(autofillId), supportedFormats,
+                        request -> {
+                            requests.add(request);
+                        });
+            }
+        }
+    }
+
+    private class ViewTranslationRequestConsumer implements Consumer<ViewTranslationRequest> {
+        private final List<ViewTranslationRequest> mRequests;
+        private boolean mCalled;
+
+        ViewTranslationRequestConsumer(List<ViewTranslationRequest> requests) {
+            mRequests = requests;
+        }
+
+        @Override
+        public void accept(ViewTranslationRequest request) {
+            if (mCalled) {
+                throw new IllegalStateException("The translation Consumer is not reusable.");
+            }
+            mCalled = true;
+            if (request != null && request.getKeys().size() > 0) {
+                mRequests.add(request);
+                if (Log.isLoggable(CONTENT_CAPTURE_LOG_TAG, Log.VERBOSE)) {
+                    Log.v(CONTENT_CAPTURE_LOG_TAG, "Calling setHasTransientState(true) for "
+                            + getAutofillId());
+                }
+                setHasTransientState(true);
+                setHasTranslationTransientState(true);
+            }
+        }
+    }
+
+    /**
+     * Called to generate a {@link DisplayHash} for this view.
+     *
+     * @param hashAlgorithm The hash algorithm to use when hashing the display. Must be one of
+     *                      the values returned from
+     *                      {@link DisplayHashManager#getSupportedHashAlgorithms()}
+     * @param bounds The bounds for the content within the View to generate the hash for. If
+     *               bounds are null, the entire View's bounds will be used. If empty, it will
+     *               invoke the callback
+     *               {@link DisplayHashResultCallback#onDisplayHashError} with error
+     *               {@link DisplayHashResultCallback#DISPLAY_HASH_ERROR_INVALID_BOUNDS}
+     * @param executor The executor that the callback should be invoked on.
+     * @param callback The callback to handle the results of generating the display hash
+     */
+    public void generateDisplayHash(@NonNull String hashAlgorithm,
+            @Nullable Rect bounds, @NonNull Executor executor,
+            @NonNull DisplayHashResultCallback callback) {
+        IWindowSession session = getWindowSession();
+        if (session == null) {
+            callback.onDisplayHashError(DISPLAY_HASH_ERROR_MISSING_WINDOW);
+            return;
+        }
+        IWindow window = getWindow();
+        if (window == null) {
+            callback.onDisplayHashError(DISPLAY_HASH_ERROR_MISSING_WINDOW);
+            return;
+        }
+
+        Rect visibleBounds = new Rect();
+        getGlobalVisibleRect(visibleBounds);
+
+        if (bounds != null && bounds.isEmpty()) {
+            callback.onDisplayHashError(DISPLAY_HASH_ERROR_INVALID_BOUNDS);
+            return;
+        }
+
+        if (bounds != null) {
+            bounds.offset(visibleBounds.left, visibleBounds.top);
+            visibleBounds.intersectUnchecked(bounds);
+        }
+
+        if (visibleBounds.isEmpty()) {
+            callback.onDisplayHashError(DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN);
+            return;
+        }
+
+        RemoteCallback remoteCallback = new RemoteCallback(result ->
+                executor.execute(() -> {
+                    DisplayHash displayHash = result.getParcelable(EXTRA_DISPLAY_HASH);
+                    int errorCode = result.getInt(EXTRA_DISPLAY_HASH_ERROR_CODE,
+                            DISPLAY_HASH_ERROR_UNKNOWN);
+                    if (displayHash != null) {
+                        callback.onDisplayHashResult(displayHash);
+                    } else {
+                        callback.onDisplayHashError(errorCode);
+                    }
+                }));
+
+        try {
+            session.generateDisplayHash(window, visibleBounds, hashAlgorithm, remoteCallback);
+        } catch (RemoteException e) {
+            Log.e(VIEW_LOG_TAG, "Failed to call generateDisplayHash");
+            callback.onDisplayHashError(DISPLAY_HASH_ERROR_UNKNOWN);
+        }
+    }
+
+    /**
+     * The AttachedSurfaceControl itself is not a View, it is just the interface to the
+     * windowing-system object that contains the entire view hierarchy.
+     * For the root View of a given hierarchy see {@link #getRootView}.
+
+     * @return The {@link android.view.AttachedSurfaceControl} interface for this View.
+     * This will only return a non-null value when called between {@link #onAttachedToWindow}
+     * and {@link #onDetachedFromWindow}.
+     */
+    public @Nullable AttachedSurfaceControl getRootSurfaceControl() {
+        if (mAttachInfo != null) {
+          return mAttachInfo.getRootSurfaceControl();
+        }
+        return null;
+    }
+}
diff --git a/android/view/ViewAnimationHostBridge.java b/android/view/ViewAnimationHostBridge.java
new file mode 100644
index 0000000..e0fae21
--- /dev/null
+++ b/android/view/ViewAnimationHostBridge.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.graphics.RenderNode;
+
+/**
+ * Maps a View to a RenderNode's AnimationHost
+ *
+ * @hide
+ */
+public class ViewAnimationHostBridge implements RenderNode.AnimationHost {
+    private final View mView;
+
+    /**
+     * @param view the View to bridge to an AnimationHost
+     */
+    public ViewAnimationHostBridge(View view) {
+        mView = view;
+    }
+
+    @Override
+    public void registerAnimatingRenderNode(RenderNode animator) {
+        mView.mAttachInfo.mViewRootImpl.registerAnimatingRenderNode(animator);
+    }
+
+    @Override
+    public void registerVectorDrawableAnimator(NativeVectorDrawableAnimator animator) {
+        mView.mAttachInfo.mViewRootImpl.registerVectorDrawableAnimator(animator);
+    }
+
+    @Override
+    public boolean isAttached() {
+        return mView.mAttachInfo != null;
+    }
+}
diff --git a/android/view/ViewAnimationUtils.java b/android/view/ViewAnimationUtils.java
new file mode 100644
index 0000000..3e277eb
--- /dev/null
+++ b/android/view/ViewAnimationUtils.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.RevealAnimator;
+
+/**
+ * Defines common utilities for working with View's animations.
+ *
+ */
+public final class ViewAnimationUtils {
+    private ViewAnimationUtils() {}
+    /**
+     * Returns an Animator which can animate a clipping circle.
+     * <p>
+     * Any shadow cast by the View will respect the circular clip from this animator.
+     * <p>
+     * Only a single non-rectangular clip can be applied on a View at any time.
+     * Views clipped by a circular reveal animation take priority over
+     * {@link View#setClipToOutline(boolean) View Outline clipping}.
+     * <p>
+     * Note that the animation returned here is a one-shot animation. It cannot
+     * be re-used, and once started it cannot be paused or resumed. It is also
+     * an asynchronous animation that automatically runs off of the UI thread.
+     * As a result {@link AnimatorListener#onAnimationEnd(Animator)}
+     * will occur after the animation has ended, but it may be delayed depending
+     * on thread responsiveness.
+     * <p>
+     * Note that if any start delay is set on the reveal animator, the start radius
+     * will not be applied to the reveal circle until the start delay has passed.
+     * If it's desired to set a start radius on the reveal circle during the start
+     * delay, one workaround could be adding an animator with the same start and
+     * end radius. For example:
+     * <pre><code>
+     * public static Animator createRevealWithDelay(View view, int centerX, int centerY, float startRadius, float endRadius) {
+     *     Animator delayAnimator = ViewAnimationUtils.createCircularReveal(view, centerX, centerY, startRadius, startRadius);
+     *     delayAnimator.setDuration(delayTimeMS);
+     *     Animator revealAnimator = ViewAnimationUtils.createCircularReveal(view, centerX, centerY, startRadius, endRadius);
+     *     AnimatorSet set = new AnimatorSet();
+     *     set.playSequentially(delayAnimator, revealAnimator);
+     *     return set;
+     * }
+     * </code></pre>
+     *
+     * @param view The View will be clipped to the animating circle.
+     * @param centerX The x coordinate of the center of the animating circle, relative to
+     *                <code>view</code>.
+     * @param centerY The y coordinate of the center of the animating circle, relative to
+     *                <code>view</code>.
+     * @param startRadius The starting radius of the animating circle.
+     * @param endRadius The ending radius of the animating circle.
+     */
+    public static Animator createCircularReveal(View view,
+            int centerX,  int centerY, float startRadius, float endRadius) {
+        return new RevealAnimator(view, centerX, centerY, startRadius, endRadius);
+    }
+}
diff --git a/android/view/ViewConfiguration.java b/android/view/ViewConfiguration.java
new file mode 100644
index 0000000..0a3d0da
--- /dev/null
+++ b/android/view/ViewConfiguration.java
@@ -0,0 +1,1134 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.FloatRange;
+import android.annotation.TestApi;
+import android.annotation.UiContext;
+import android.app.Activity;
+import android.app.AppGlobals;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.StrictMode;
+import android.provider.Settings;
+import android.util.DisplayMetrics;
+import android.util.SparseArray;
+import android.util.TypedValue;
+
+/**
+ * Contains methods to standard constants used in the UI for timeouts, sizes, and distances.
+ */
+public class ViewConfiguration {
+    private static final String TAG = "ViewConfiguration";
+
+    /**
+     * Defines the width of the horizontal scrollbar and the height of the vertical scrollbar in
+     * dips
+     */
+    private static final int SCROLL_BAR_SIZE = 4;
+
+    /**
+     * Duration of the fade when scrollbars fade away in milliseconds
+     */
+    private static final int SCROLL_BAR_FADE_DURATION = 250;
+
+    /**
+     * Default delay before the scrollbars fade in milliseconds
+     */
+    private static final int SCROLL_BAR_DEFAULT_DELAY = 300;
+
+    /**
+     * Defines the length of the fading edges in dips
+     */
+    private static final int FADING_EDGE_LENGTH = 12;
+
+    /**
+     * Defines the duration in milliseconds of the pressed state in child
+     * components.
+     */
+    private static final int PRESSED_STATE_DURATION = 64;
+
+    /**
+     * Defines the default duration in milliseconds before a press turns into
+     * a long press
+     * @hide
+     */
+    public static final int DEFAULT_LONG_PRESS_TIMEOUT = 400;
+
+    /**
+     * Defines the default duration in milliseconds between the first tap's up event and the second
+     * tap's down event for an interaction to be considered part of the same multi-press.
+     */
+    private static final int DEFAULT_MULTI_PRESS_TIMEOUT = 300;
+
+    /**
+     * Defines the time between successive key repeats in milliseconds.
+     */
+    private static final int KEY_REPEAT_DELAY = 50;
+
+    /**
+     * Defines the duration in milliseconds a user needs to hold down the
+     * appropriate button to bring up the global actions dialog (power off,
+     * lock screen, etc).
+     */
+    private static final int GLOBAL_ACTIONS_KEY_TIMEOUT = 500;
+
+    /**
+     * Defines the duration in milliseconds a user needs to hold down the
+     * appropriate buttons (power + volume down) to trigger the screenshot chord.
+     */
+    private static final int SCREENSHOT_CHORD_KEY_TIMEOUT = 0;
+
+    /**
+     * Defines the duration in milliseconds a user needs to hold down the
+     * appropriate button to bring up the accessibility shortcut for the first time
+     */
+    private static final int A11Y_SHORTCUT_KEY_TIMEOUT = 3000;
+
+    /**
+     * Defines the duration in milliseconds a user needs to hold down the
+     * appropriate button to enable the accessibility shortcut once it's configured.
+     */
+    private static final int A11Y_SHORTCUT_KEY_TIMEOUT_AFTER_CONFIRMATION = 1000;
+
+    /**
+     * Defines the duration in milliseconds we will wait to see if a touch event
+     * is a tap or a scroll. If the user does not move within this interval, it is
+     * considered to be a tap.
+     */
+    private static final int TAP_TIMEOUT = 100;
+
+    /**
+     * Defines the duration in milliseconds we will wait to see if a touch event
+     * is a jump tap. If the user does not complete the jump tap within this interval, it is
+     * considered to be a tap.
+     */
+    private static final int JUMP_TAP_TIMEOUT = 500;
+
+    /**
+     * Defines the duration in milliseconds between the first tap's up event and
+     * the second tap's down event for an interaction to be considered a
+     * double-tap.
+     */
+    private static final int DOUBLE_TAP_TIMEOUT = 300;
+
+    /**
+     * Defines the minimum duration in milliseconds between the first tap's up event and
+     * the second tap's down event for an interaction to be considered a
+     * double-tap.
+     */
+    private static final int DOUBLE_TAP_MIN_TIME = 40;
+
+    /**
+     * Defines the maximum duration in milliseconds between a touch pad
+     * touch and release for a given touch to be considered a tap (click) as
+     * opposed to a hover movement gesture.
+     */
+    private static final int HOVER_TAP_TIMEOUT = 150;
+
+    /**
+     * Defines the maximum distance in pixels that a touch pad touch can move
+     * before being released for it to be considered a tap (click) as opposed
+     * to a hover movement gesture.
+     */
+    private static final int HOVER_TAP_SLOP = 20;
+
+    /**
+     * Defines the duration in milliseconds we want to display zoom controls in response
+     * to a user panning within an application.
+     */
+    private static final int ZOOM_CONTROLS_TIMEOUT = 3000;
+
+    /**
+     * Inset in dips to look for touchable content when the user touches the edge of the screen
+     */
+    private static final int EDGE_SLOP = 12;
+
+    /**
+     * Distance a touch can wander before we think the user is scrolling in dips.
+     * Note that this value defined here is only used as a fallback by legacy/misbehaving
+     * applications that do not provide a Context for determining density/configuration-dependent
+     * values.
+     *
+     * To alter this value, see the configuration resource config_viewConfigurationTouchSlop
+     * in frameworks/base/core/res/res/values/config.xml or the appropriate device resource overlay.
+     * It may be appropriate to tweak this on a device-specific basis in an overlay based on
+     * the characteristics of the touch panel and firmware.
+     */
+    private static final int TOUCH_SLOP = 8;
+
+    /**
+     * Defines the minimum size of the touch target for a scrollbar in dips
+     */
+    private static final int MIN_SCROLLBAR_TOUCH_TARGET = 48;
+
+    /**
+     * Distance the first touch can wander before we stop considering this event a double tap
+     * (in dips)
+     */
+    private static final int DOUBLE_TAP_TOUCH_SLOP = TOUCH_SLOP;
+
+    /**
+     * Distance a touch can wander before we think the user is attempting a paged scroll
+     * (in dips)
+     *
+     * Note that this value defined here is only used as a fallback by legacy/misbehaving
+     * applications that do not provide a Context for determining density/configuration-dependent
+     * values.
+     *
+     * See the note above on {@link #TOUCH_SLOP} regarding the dimen resource
+     * config_viewConfigurationTouchSlop. ViewConfiguration will report a paging touch slop of
+     * config_viewConfigurationTouchSlop * 2 when provided with a Context.
+     */
+    private static final int PAGING_TOUCH_SLOP = TOUCH_SLOP * 2;
+
+    /**
+     * Distance in dips between the first touch and second touch to still be considered a double tap
+     */
+    private static final int DOUBLE_TAP_SLOP = 100;
+
+    /**
+     * Distance in dips a touch needs to be outside of a window's bounds for it to
+     * count as outside for purposes of dismissing the window.
+     */
+    private static final int WINDOW_TOUCH_SLOP = 16;
+
+    /**
+     * Minimum velocity to initiate a fling, as measured in dips per second
+     */
+    private static final int MINIMUM_FLING_VELOCITY = 50;
+
+    /**
+     * Maximum velocity to initiate a fling, as measured in dips per second
+     */
+    private static final int MAXIMUM_FLING_VELOCITY = 8000;
+
+    /**
+     * Delay before dispatching a recurring accessibility event in milliseconds.
+     * This delay guarantees that a recurring event will be send at most once
+     * during the {@link #SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS} time
+     * frame.
+     */
+    private static final long SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS = 100;
+
+    /**
+     * The maximum size of View's drawing cache, expressed in bytes. This size
+     * should be at least equal to the size of the screen in ARGB888 format.
+     */
+    @Deprecated
+    private static final int MAXIMUM_DRAWING_CACHE_SIZE = 480 * 800 * 4; // ARGB8888
+
+    /**
+     * The coefficient of friction applied to flings/scrolls.
+     */
+    @UnsupportedAppUsage
+    private static final float SCROLL_FRICTION = 0.015f;
+
+    /**
+     * Max distance in dips to overscroll for edge effects
+     */
+    private static final int OVERSCROLL_DISTANCE = 0;
+
+    /**
+     * Max distance in dips to overfling for edge effects
+     */
+    private static final int OVERFLING_DISTANCE = 6;
+
+    /**
+     * Amount to scroll in response to a horizontal {@link MotionEvent#ACTION_SCROLL} event,
+     * in dips per axis value.
+     */
+    private static final float HORIZONTAL_SCROLL_FACTOR = 64;
+
+    /**
+     * Amount to scroll in response to a vertical {@link MotionEvent#ACTION_SCROLL} event,
+     * in dips per axis value.
+     */
+    private static final float VERTICAL_SCROLL_FACTOR = 64;
+
+    /**
+     * Default duration to hide an action mode for.
+     */
+    private static final long ACTION_MODE_HIDE_DURATION_DEFAULT = 2000;
+
+    /**
+     * Defines the duration in milliseconds before an end of a long press causes a tooltip to be
+     * hidden.
+     */
+    private static final int LONG_PRESS_TOOLTIP_HIDE_TIMEOUT = 1500;
+
+    /**
+     * Defines the duration in milliseconds before a hover event causes a tooltip to be shown.
+     */
+    private static final int HOVER_TOOLTIP_SHOW_TIMEOUT = 500;
+
+    /**
+     * Defines the duration in milliseconds before mouse inactivity causes a tooltip to be hidden.
+     * (default variant to be used when {@link View#SYSTEM_UI_FLAG_LOW_PROFILE} is not set).
+     */
+    private static final int HOVER_TOOLTIP_HIDE_TIMEOUT = 15000;
+
+    /**
+     * Defines the duration in milliseconds before mouse inactivity causes a tooltip to be hidden
+     * (short version to be used when {@link View#SYSTEM_UI_FLAG_LOW_PROFILE} is set).
+     */
+    private static final int HOVER_TOOLTIP_HIDE_SHORT_TIMEOUT = 3000;
+
+    /**
+     * Configuration values for overriding {@link #hasPermanentMenuKey()} behavior.
+     * These constants must match the definition in res/values/config.xml.
+     */
+    private static final int HAS_PERMANENT_MENU_KEY_AUTODETECT = 0;
+    private static final int HAS_PERMANENT_MENU_KEY_TRUE = 1;
+    private static final int HAS_PERMANENT_MENU_KEY_FALSE = 2;
+
+    /**
+     * The multiplication factor for inhibiting default gestures.
+     */
+    private static final float AMBIGUOUS_GESTURE_MULTIPLIER = 2f;
+
+    /**
+     * The timeout value in milliseconds to adjust the selection span and actions for the selected
+     * text when TextClassifier has been initialized.
+     */
+    private static final int SMART_SELECTION_INITIALIZED_TIMEOUT_IN_MILLISECOND = 200;
+
+    /**
+     * The timeout value in milliseconds to adjust the selection span and actions for the selected
+     * text when TextClassifier has not been initialized.
+     */
+    private static final int SMART_SELECTION_INITIALIZING_TIMEOUT_IN_MILLISECOND = 500;
+
+    private final boolean mConstructedWithContext;
+    private final int mEdgeSlop;
+    private final int mFadingEdgeLength;
+    private final int mMinimumFlingVelocity;
+    private final int mMaximumFlingVelocity;
+    private final int mScrollbarSize;
+    private final int mTouchSlop;
+    private final int mMinScalingSpan;
+    private final int mHoverSlop;
+    private final int mMinScrollbarTouchTarget;
+    private final int mDoubleTapTouchSlop;
+    private final int mPagingTouchSlop;
+    private final int mDoubleTapSlop;
+    private final int mWindowTouchSlop;
+    private final float mAmbiguousGestureMultiplier;
+    private final int mMaximumDrawingCacheSize;
+    private final int mOverscrollDistance;
+    private final int mOverflingDistance;
+    @UnsupportedAppUsage
+    private final boolean mFadingMarqueeEnabled;
+    private final long mGlobalActionsKeyTimeout;
+    private final float mVerticalScrollFactor;
+    private final float mHorizontalScrollFactor;
+    private final boolean mShowMenuShortcutsWhenKeyboardPresent;
+    private final long mScreenshotChordKeyTimeout;
+    private final int mSmartSelectionInitializedTimeout;
+    private final int mSmartSelectionInitializingTimeout;
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768915)
+    private boolean sHasPermanentMenuKey;
+    @UnsupportedAppUsage
+    private boolean sHasPermanentMenuKeySet;
+
+    @UnsupportedAppUsage
+    static final SparseArray<ViewConfiguration> sConfigurations =
+            new SparseArray<ViewConfiguration>(2);
+
+    /**
+     * @deprecated Use {@link android.view.ViewConfiguration#get(android.content.Context)} instead.
+     */
+    @Deprecated
+    public ViewConfiguration() {
+        mConstructedWithContext = false;
+        mEdgeSlop = EDGE_SLOP;
+        mFadingEdgeLength = FADING_EDGE_LENGTH;
+        mMinimumFlingVelocity = MINIMUM_FLING_VELOCITY;
+        mMaximumFlingVelocity = MAXIMUM_FLING_VELOCITY;
+        mScrollbarSize = SCROLL_BAR_SIZE;
+        mTouchSlop = TOUCH_SLOP;
+        mHoverSlop = TOUCH_SLOP / 2;
+        mMinScrollbarTouchTarget = MIN_SCROLLBAR_TOUCH_TARGET;
+        mDoubleTapTouchSlop = DOUBLE_TAP_TOUCH_SLOP;
+        mPagingTouchSlop = PAGING_TOUCH_SLOP;
+        mDoubleTapSlop = DOUBLE_TAP_SLOP;
+        mWindowTouchSlop = WINDOW_TOUCH_SLOP;
+        mAmbiguousGestureMultiplier = AMBIGUOUS_GESTURE_MULTIPLIER;
+        //noinspection deprecation
+        mMaximumDrawingCacheSize = MAXIMUM_DRAWING_CACHE_SIZE;
+        mOverscrollDistance = OVERSCROLL_DISTANCE;
+        mOverflingDistance = OVERFLING_DISTANCE;
+        mFadingMarqueeEnabled = true;
+        mGlobalActionsKeyTimeout = GLOBAL_ACTIONS_KEY_TIMEOUT;
+        mHorizontalScrollFactor = HORIZONTAL_SCROLL_FACTOR;
+        mVerticalScrollFactor = VERTICAL_SCROLL_FACTOR;
+        mShowMenuShortcutsWhenKeyboardPresent = false;
+        mScreenshotChordKeyTimeout = SCREENSHOT_CHORD_KEY_TIMEOUT;
+
+        // Getter throws if mConstructedWithContext is false so doesn't matter what
+        // this value is.
+        mMinScalingSpan = 0;
+        mSmartSelectionInitializedTimeout = SMART_SELECTION_INITIALIZED_TIMEOUT_IN_MILLISECOND;
+        mSmartSelectionInitializingTimeout = SMART_SELECTION_INITIALIZING_TIMEOUT_IN_MILLISECOND;
+    }
+
+    /**
+     * Creates a new configuration for the specified visual {@link Context}. The configuration
+     * depends on various parameters of the {@link Context}, like the dimension of the display or
+     * the density of the display.
+     *
+     * @param context A visual {@link Context} used to initialize the view configuration. It must
+     *                be {@link Activity} or other {@link Context} created with
+     *                {@link Context#createWindowContext(int, Bundle)}.
+     *
+     * @see #get(android.content.Context)
+     * @see android.util.DisplayMetrics
+     */
+    private ViewConfiguration(@UiContext Context context) {
+        mConstructedWithContext = true;
+        final Resources res = context.getResources();
+        final DisplayMetrics metrics = res.getDisplayMetrics();
+        final Configuration config = res.getConfiguration();
+        final float density = metrics.density;
+        final float sizeAndDensity;
+        if (config.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_XLARGE)) {
+            sizeAndDensity = density * 1.5f;
+        } else {
+            sizeAndDensity = density;
+        }
+
+        mEdgeSlop = (int) (sizeAndDensity * EDGE_SLOP + 0.5f);
+        mFadingEdgeLength = (int) (sizeAndDensity * FADING_EDGE_LENGTH + 0.5f);
+        mScrollbarSize = res.getDimensionPixelSize(
+                com.android.internal.R.dimen.config_scrollbarSize);
+        mDoubleTapSlop = (int) (sizeAndDensity * DOUBLE_TAP_SLOP + 0.5f);
+        mWindowTouchSlop = (int) (sizeAndDensity * WINDOW_TOUCH_SLOP + 0.5f);
+
+        final TypedValue multiplierValue = new TypedValue();
+        res.getValue(
+                com.android.internal.R.dimen.config_ambiguousGestureMultiplier,
+                multiplierValue,
+                true /*resolveRefs*/);
+        mAmbiguousGestureMultiplier = Math.max(1.0f, multiplierValue.getFloat());
+
+        // Size of the screen in bytes, in ARGB_8888 format
+        final WindowManager windowManager = context.getSystemService(WindowManager.class);
+        final Rect maxWindowBounds = windowManager.getMaximumWindowMetrics().getBounds();
+        mMaximumDrawingCacheSize = 4 * maxWindowBounds.width() * maxWindowBounds.height();
+
+        mOverscrollDistance = (int) (sizeAndDensity * OVERSCROLL_DISTANCE + 0.5f);
+        mOverflingDistance = (int) (sizeAndDensity * OVERFLING_DISTANCE + 0.5f);
+
+        if (!sHasPermanentMenuKeySet) {
+            final int configVal = res.getInteger(
+                    com.android.internal.R.integer.config_overrideHasPermanentMenuKey);
+
+            switch (configVal) {
+                default:
+                case HAS_PERMANENT_MENU_KEY_AUTODETECT: {
+                    IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+                    try {
+                        sHasPermanentMenuKey = !wm.hasNavigationBar(context.getDisplayId());
+                        sHasPermanentMenuKeySet = true;
+                    } catch (RemoteException ex) {
+                        sHasPermanentMenuKey = false;
+                    }
+                }
+                break;
+
+                case HAS_PERMANENT_MENU_KEY_TRUE:
+                    sHasPermanentMenuKey = true;
+                    sHasPermanentMenuKeySet = true;
+                    break;
+
+                case HAS_PERMANENT_MENU_KEY_FALSE:
+                    sHasPermanentMenuKey = false;
+                    sHasPermanentMenuKeySet = true;
+                    break;
+            }
+        }
+
+        mFadingMarqueeEnabled = res.getBoolean(
+                com.android.internal.R.bool.config_ui_enableFadingMarquee);
+        mTouchSlop = res.getDimensionPixelSize(
+                com.android.internal.R.dimen.config_viewConfigurationTouchSlop);
+        mHoverSlop = res.getDimensionPixelSize(
+                com.android.internal.R.dimen.config_viewConfigurationHoverSlop);
+        mMinScrollbarTouchTarget = res.getDimensionPixelSize(
+                com.android.internal.R.dimen.config_minScrollbarTouchTarget);
+        mPagingTouchSlop = mTouchSlop * 2;
+
+        mDoubleTapTouchSlop = mTouchSlop;
+
+        mMinimumFlingVelocity = res.getDimensionPixelSize(
+                com.android.internal.R.dimen.config_viewMinFlingVelocity);
+        mMaximumFlingVelocity = res.getDimensionPixelSize(
+                com.android.internal.R.dimen.config_viewMaxFlingVelocity);
+        mGlobalActionsKeyTimeout = res.getInteger(
+                com.android.internal.R.integer.config_globalActionsKeyTimeout);
+
+        mHorizontalScrollFactor = res.getDimensionPixelSize(
+                com.android.internal.R.dimen.config_horizontalScrollFactor);
+        mVerticalScrollFactor = res.getDimensionPixelSize(
+                com.android.internal.R.dimen.config_verticalScrollFactor);
+
+        mShowMenuShortcutsWhenKeyboardPresent = res.getBoolean(
+            com.android.internal.R.bool.config_showMenuShortcutsWhenKeyboardPresent);
+
+        mMinScalingSpan = res.getDimensionPixelSize(
+                com.android.internal.R.dimen.config_minScalingSpan);
+
+        mScreenshotChordKeyTimeout = res.getInteger(
+                com.android.internal.R.integer.config_screenshotChordKeyTimeout);
+
+        mSmartSelectionInitializedTimeout = res.getInteger(
+                com.android.internal.R.integer.config_smartSelectionInitializedTimeoutMillis);
+        mSmartSelectionInitializingTimeout = res.getInteger(
+                com.android.internal.R.integer.config_smartSelectionInitializingTimeoutMillis);
+    }
+
+    /**
+     * Returns a configuration for the specified visual {@link Context}. The configuration depends
+     * on various parameters of the {@link Context}, like the dimension of the display or the
+     * density of the display.
+     *
+     * @param context A visual {@link Context} used to initialize the view configuration. It must
+     *                be {@link Activity} or other {@link Context} created with
+     *                {@link Context#createWindowContext(int, Bundle)}.
+     */
+    // TODO(b/182007470): Use @ConfigurationContext instead
+    public static ViewConfiguration get(@UiContext Context context) {
+        StrictMode.assertConfigurationContext(context, "ViewConfiguration");
+
+        final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+        final int density = (int) (100.0f * metrics.density);
+
+        ViewConfiguration configuration = sConfigurations.get(density);
+        if (configuration == null) {
+            configuration = new ViewConfiguration(context);
+            sConfigurations.put(density, configuration);
+        }
+
+        return configuration;
+    }
+
+    /**
+     * @return The width of the horizontal scrollbar and the height of the vertical
+     *         scrollbar in dips
+     *
+     * @deprecated Use {@link #getScaledScrollBarSize()} instead.
+     */
+    @Deprecated
+    public static int getScrollBarSize() {
+        return SCROLL_BAR_SIZE;
+    }
+
+    /**
+     * @return The width of the horizontal scrollbar and the height of the vertical
+     *         scrollbar in pixels
+     */
+    public int getScaledScrollBarSize() {
+        return mScrollbarSize;
+    }
+
+    /**
+     * @return the minimum size of the scrollbar thumb's touch target in pixels
+     * @hide
+     */
+    public int getScaledMinScrollbarTouchTarget() {
+        return mMinScrollbarTouchTarget;
+    }
+
+    /**
+     * @return Duration of the fade when scrollbars fade away in milliseconds
+     */
+    public static int getScrollBarFadeDuration() {
+        return SCROLL_BAR_FADE_DURATION;
+    }
+
+    /**
+     * @return Default delay before the scrollbars fade in milliseconds
+     */
+    public static int getScrollDefaultDelay() {
+        return SCROLL_BAR_DEFAULT_DELAY;
+    }
+
+    /**
+     * @return the length of the fading edges in dips
+     *
+     * @deprecated Use {@link #getScaledFadingEdgeLength()} instead.
+     */
+    @Deprecated
+    public static int getFadingEdgeLength() {
+        return FADING_EDGE_LENGTH;
+    }
+
+    /**
+     * @return the length of the fading edges in pixels
+     */
+    public int getScaledFadingEdgeLength() {
+        return mFadingEdgeLength;
+    }
+
+    /**
+     * @return the duration in milliseconds of the pressed state in child
+     * components.
+     */
+    public static int getPressedStateDuration() {
+        return PRESSED_STATE_DURATION;
+    }
+
+    /**
+     * @return the duration in milliseconds before a press turns into
+     * a long press
+     */
+    public static int getLongPressTimeout() {
+        return AppGlobals.getIntCoreSetting(Settings.Secure.LONG_PRESS_TIMEOUT,
+                DEFAULT_LONG_PRESS_TIMEOUT);
+    }
+
+    /**
+     * @return the duration in milliseconds between the first tap's up event and the second tap's
+     * down event for an interaction to be considered part of the same multi-press.
+     */
+    public static int getMultiPressTimeout() {
+        return AppGlobals.getIntCoreSetting(Settings.Secure.MULTI_PRESS_TIMEOUT,
+                DEFAULT_MULTI_PRESS_TIMEOUT);
+    }
+
+    /**
+     * @return the time before the first key repeat in milliseconds.
+     */
+    public static int getKeyRepeatTimeout() {
+        return getLongPressTimeout();
+    }
+
+    /**
+     * @return the time between successive key repeats in milliseconds.
+     */
+    public static int getKeyRepeatDelay() {
+        return KEY_REPEAT_DELAY;
+    }
+
+    /**
+     * @return the duration in milliseconds we will wait to see if a touch event
+     * is a tap or a scroll. If the user does not move within this interval, it is
+     * considered to be a tap.
+     */
+    public static int getTapTimeout() {
+        return TAP_TIMEOUT;
+    }
+
+    /**
+     * @return the duration in milliseconds we will wait to see if a touch event
+     * is a jump tap. If the user does not move within this interval, it is
+     * considered to be a tap.
+     */
+    public static int getJumpTapTimeout() {
+        return JUMP_TAP_TIMEOUT;
+    }
+
+    /**
+     * @return the duration in milliseconds between the first tap's up event and
+     * the second tap's down event for an interaction to be considered a
+     * double-tap.
+     */
+    public static int getDoubleTapTimeout() {
+        return DOUBLE_TAP_TIMEOUT;
+    }
+
+    /**
+     * @return the minimum duration in milliseconds between the first tap's
+     * up event and the second tap's down event for an interaction to be considered a
+     * double-tap.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static int getDoubleTapMinTime() {
+        return DOUBLE_TAP_MIN_TIME;
+    }
+
+    /**
+     * @return the maximum duration in milliseconds between a touch pad
+     * touch and release for a given touch to be considered a tap (click) as
+     * opposed to a hover movement gesture.
+     * @hide
+     */
+    public static int getHoverTapTimeout() {
+        return HOVER_TAP_TIMEOUT;
+    }
+
+    /**
+     * @return the maximum distance in pixels that a touch pad touch can move
+     * before being released for it to be considered a tap (click) as opposed
+     * to a hover movement gesture.
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public static int getHoverTapSlop() {
+        return HOVER_TAP_SLOP;
+    }
+
+    /**
+     * @return Inset in dips to look for touchable content when the user touches the edge of the
+     *         screen
+     *
+     * @deprecated Use {@link #getScaledEdgeSlop()} instead.
+     */
+    @Deprecated
+    public static int getEdgeSlop() {
+        return EDGE_SLOP;
+    }
+
+    /**
+     * @return Inset in pixels to look for touchable content when the user touches the edge of the
+     *         screen
+     */
+    public int getScaledEdgeSlop() {
+        return mEdgeSlop;
+    }
+
+    /**
+     * @return Distance in dips a touch can wander before we think the user is scrolling
+     *
+     * @deprecated Use {@link #getScaledTouchSlop()} instead.
+     */
+    @Deprecated
+    public static int getTouchSlop() {
+        return TOUCH_SLOP;
+    }
+
+    /**
+     * @return Distance in pixels a touch can wander before we think the user is scrolling
+     */
+    public int getScaledTouchSlop() {
+        return mTouchSlop;
+    }
+
+    /**
+     * @return Distance in pixels a hover can wander while it is still considered "stationary".
+     *
+     */
+    public int getScaledHoverSlop() {
+        return mHoverSlop;
+    }
+
+    /**
+     * @return Distance in pixels the first touch can wander before we do not consider this a
+     * potential double tap event
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public int getScaledDoubleTapTouchSlop() {
+        return mDoubleTapTouchSlop;
+    }
+
+    /**
+     * @return Distance in pixels a touch can wander before we think the user is scrolling a full
+     * page
+     */
+    public int getScaledPagingTouchSlop() {
+        return mPagingTouchSlop;
+    }
+
+    /**
+     * @return Distance in dips between the first touch and second touch to still be
+     *         considered a double tap
+     * @deprecated Use {@link #getScaledDoubleTapSlop()} instead.
+     * @hide The only client of this should be GestureDetector, which needs this
+     *       for clients that still use its deprecated constructor.
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public static int getDoubleTapSlop() {
+        return DOUBLE_TAP_SLOP;
+    }
+
+    /**
+     * @return Distance in pixels between the first touch and second touch to still be
+     *         considered a double tap
+     */
+    public int getScaledDoubleTapSlop() {
+        return mDoubleTapSlop;
+    }
+
+    /**
+     * Interval for dispatching a recurring accessibility event in milliseconds.
+     * This interval guarantees that a recurring event will be send at most once
+     * during the {@link #getSendRecurringAccessibilityEventsInterval()} time frame.
+     *
+     * @return The delay in milliseconds.
+     *
+     * @hide
+     */
+    public static long getSendRecurringAccessibilityEventsInterval() {
+        return SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS;
+    }
+
+    /**
+     * @return Distance in dips a touch must be outside the bounds of a window for it
+     * to be counted as outside the window for purposes of dismissing that
+     * window.
+     *
+     * @deprecated Use {@link #getScaledWindowTouchSlop()} instead.
+     */
+    @Deprecated
+    public static int getWindowTouchSlop() {
+        return WINDOW_TOUCH_SLOP;
+    }
+
+    /**
+     * @return Distance in pixels a touch must be outside the bounds of a window for it
+     * to be counted as outside the window for purposes of dismissing that window.
+     */
+    public int getScaledWindowTouchSlop() {
+        return mWindowTouchSlop;
+    }
+
+    /**
+     * @return Minimum velocity to initiate a fling, as measured in dips per second.
+     *
+     * @deprecated Use {@link #getScaledMinimumFlingVelocity()} instead.
+     */
+    @Deprecated
+    public static int getMinimumFlingVelocity() {
+        return MINIMUM_FLING_VELOCITY;
+    }
+
+    /**
+     * @return Minimum velocity to initiate a fling, as measured in pixels per second.
+     */
+    public int getScaledMinimumFlingVelocity() {
+        return mMinimumFlingVelocity;
+    }
+
+    /**
+     * @return Maximum velocity to initiate a fling, as measured in dips per second.
+     *
+     * @deprecated Use {@link #getScaledMaximumFlingVelocity()} instead.
+     */
+    @Deprecated
+    public static int getMaximumFlingVelocity() {
+        return MAXIMUM_FLING_VELOCITY;
+    }
+
+    /**
+     * @return Maximum velocity to initiate a fling, as measured in pixels per second.
+     */
+    public int getScaledMaximumFlingVelocity() {
+        return mMaximumFlingVelocity;
+    }
+
+    /**
+     * @return Amount to scroll in response to a {@link MotionEvent#ACTION_SCROLL} event. Multiply
+     * this by the event's axis value to obtain the number of pixels to be scrolled.
+     *
+     * @removed
+     */
+    public int getScaledScrollFactor() {
+        return (int) mVerticalScrollFactor;
+    }
+
+    /**
+     * @return Amount to scroll in response to a horizontal {@link MotionEvent#ACTION_SCROLL} event.
+     * Multiply this by the event's axis value to obtain the number of pixels to be scrolled.
+     */
+    public float getScaledHorizontalScrollFactor() {
+        return mHorizontalScrollFactor;
+    }
+
+    /**
+     * @return Amount to scroll in response to a vertical {@link MotionEvent#ACTION_SCROLL} event.
+     * Multiply this by the event's axis value to obtain the number of pixels to be scrolled.
+     */
+    public float getScaledVerticalScrollFactor() {
+        return mVerticalScrollFactor;
+    }
+
+    /**
+     * The maximum drawing cache size expressed in bytes.
+     *
+     * @return the maximum size of View's drawing cache expressed in bytes
+     *
+     * @deprecated Use {@link #getScaledMaximumDrawingCacheSize()} instead.
+     */
+    @Deprecated
+    public static int getMaximumDrawingCacheSize() {
+        //noinspection deprecation
+        return MAXIMUM_DRAWING_CACHE_SIZE;
+    }
+
+    /**
+     * The maximum drawing cache size expressed in bytes.
+     *
+     * @return the maximum size of View's drawing cache expressed in bytes
+     */
+    public int getScaledMaximumDrawingCacheSize() {
+        return mMaximumDrawingCacheSize;
+    }
+
+    /**
+     * @return The maximum distance a View should overscroll by when showing edge effects (in
+     * pixels).
+     */
+    public int getScaledOverscrollDistance() {
+        return mOverscrollDistance;
+    }
+
+    /**
+     * @return The maximum distance a View should overfling by when showing edge effects (in
+     * pixels).
+     */
+    public int getScaledOverflingDistance() {
+        return mOverflingDistance;
+    }
+
+    /**
+     * The amount of time that the zoom controls should be
+     * displayed on the screen expressed in milliseconds.
+     *
+     * @return the time the zoom controls should be visible expressed
+     * in milliseconds.
+     */
+    public static long getZoomControlsTimeout() {
+        return ZOOM_CONTROLS_TIMEOUT;
+    }
+
+    /**
+     * The amount of time a user needs to press the relevant key to bring up
+     * the global actions dialog.
+     *
+     * @return how long a user needs to press the relevant key to bring up
+     *   the global actions dialog.
+     * @deprecated This timeout should not be used by applications
+     */
+    @Deprecated
+    public static long getGlobalActionKeyTimeout() {
+        return GLOBAL_ACTIONS_KEY_TIMEOUT;
+    }
+
+    /**
+     * The amount of time a user needs to press the relevant key to bring up
+     * the global actions dialog.
+     *
+     * @return how long a user needs to press the relevant key to bring up
+     *   the global actions dialog.
+     * @hide
+     */
+    @TestApi
+    public long getDeviceGlobalActionKeyTimeout() {
+        return mGlobalActionsKeyTimeout;
+    }
+
+    /**
+     * The amount of time a user needs to press the relevant keys to trigger
+     * the screenshot chord.
+     *
+     * @return how long a user needs to press the relevant keys to trigger
+     *   the screenshot chord.
+     * @hide
+     */
+    public long getScreenshotChordKeyTimeout() {
+        return mScreenshotChordKeyTimeout;
+    }
+
+    /**
+     * The amount of time a user needs to press the relevant keys to activate the accessibility
+     * shortcut.
+     *
+     * @return how long a user needs to press the relevant keys to activate the accessibility
+     *   shortcut.
+     * @hide
+     */
+    public long getAccessibilityShortcutKeyTimeout() {
+        return A11Y_SHORTCUT_KEY_TIMEOUT;
+    }
+
+    /**
+     * @return The amount of time a user needs to press the relevant keys to activate the
+     *   accessibility shortcut after it's confirmed that accessibility shortcut is used.
+     * @hide
+     */
+    public long getAccessibilityShortcutKeyTimeoutAfterConfirmation() {
+        return A11Y_SHORTCUT_KEY_TIMEOUT_AFTER_CONFIRMATION;
+    }
+
+    /**
+     * The amount of friction applied to scrolls and flings.
+     *
+     * @return A scalar dimensionless value representing the coefficient of
+     *         friction.
+     */
+    public static float getScrollFriction() {
+        return SCROLL_FRICTION;
+    }
+
+    /**
+     * @return the default duration in milliseconds for {@link ActionMode#hide(long)}.
+     */
+    public static long getDefaultActionModeHideDuration() {
+        return ACTION_MODE_HIDE_DURATION_DEFAULT;
+    }
+
+    /**
+     * The multiplication factor for inhibiting default gestures.
+     *
+     * If a MotionEvent has {@link android.view.MotionEvent#CLASSIFICATION_AMBIGUOUS_GESTURE} set,
+     * then certain actions, such as scrolling, will be inhibited. However, to account for the
+     * possibility of an incorrect classification, existing gesture thresholds (e.g. scrolling
+     * touch slop and the long-press timeout) should be scaled by this factor and remain in effect.
+     *
+     * @deprecated Use {@link #getScaledAmbiguousGestureMultiplier()}.
+     */
+    @Deprecated
+    @FloatRange(from = 1.0)
+    public static float getAmbiguousGestureMultiplier() {
+        return AMBIGUOUS_GESTURE_MULTIPLIER;
+    }
+
+    /**
+     * The multiplication factor for inhibiting default gestures.
+     *
+     * If a MotionEvent has {@link android.view.MotionEvent#CLASSIFICATION_AMBIGUOUS_GESTURE} set,
+     * then certain actions, such as scrolling, will be inhibited. However, to account for the
+     * possibility of an incorrect classification, existing gesture thresholds (e.g. scrolling
+     * touch slop and the long-press timeout) should be scaled by this factor and remain in effect.
+     */
+    @FloatRange(from = 1.0)
+    public float getScaledAmbiguousGestureMultiplier() {
+        return mAmbiguousGestureMultiplier;
+    }
+
+    /**
+     * Report if the device has a permanent menu key available to the user.
+     *
+     * <p>As of Android 3.0, devices may not have a permanent menu key available.
+     * Apps should use the action bar to present menu options to users.
+     * However, there are some apps where the action bar is inappropriate
+     * or undesirable. This method may be used to detect if a menu key is present.
+     * If not, applications should provide another on-screen affordance to access
+     * functionality.
+     *
+     * @return true if a permanent menu key is present, false otherwise.
+     */
+    public boolean hasPermanentMenuKey() {
+        return sHasPermanentMenuKey;
+    }
+
+    /**
+     * Check if shortcuts should be displayed in menus.
+     *
+     * @return {@code True} if shortcuts should be displayed in menus.
+     */
+    public boolean shouldShowMenuShortcutsWhenKeyboardPresent() {
+        return mShowMenuShortcutsWhenKeyboardPresent;
+    }
+
+    /**
+     * Retrieves the distance in pixels between touches that must be reached for a gesture to be
+     * interpreted as scaling.
+     *
+     * In general, scaling shouldn't start until this distance has been met or surpassed, and
+     * scaling should end when the distance in pixels between touches drops below this distance.
+     *
+     * @return The distance in pixels
+     * @throws IllegalStateException if this method is called on a ViewConfiguration that was
+     *         instantiated using a constructor with no Context parameter.
+     */
+    public int getScaledMinimumScalingSpan() {
+        if (!mConstructedWithContext) {
+            throw new IllegalStateException("Min scaling span cannot be determined when this "
+                    + "method is called on a ViewConfiguration that was instantiated using a "
+                    + "constructor with no Context parameter");
+        }
+        return mMinScalingSpan;
+    }
+
+    /**
+     * @hide
+     * @return Whether or not marquee should use fading edges.
+     */
+    @UnsupportedAppUsage
+    public boolean isFadingMarqueeEnabled() {
+        return mFadingMarqueeEnabled;
+    }
+
+    /**
+     * @return the timeout value in milliseconds to adjust the selection span and actions for the
+     *         selected text when TextClassifier has been initialized.
+     * @hide
+     */
+    public int getSmartSelectionInitializedTimeout() {
+        return mSmartSelectionInitializedTimeout;
+    }
+
+    /**
+     * @return the timeout value in milliseconds to adjust the selection span and actions for the
+     *         selected text when TextClassifier has not been initialized.
+     * @hide
+     */
+    public int getSmartSelectionInitializingTimeout() {
+        return mSmartSelectionInitializingTimeout;
+    }
+
+    /**
+     * @return the duration in milliseconds before an end of a long press causes a tooltip to be
+     * hidden
+     * @hide
+     */
+    @TestApi
+    public static int getLongPressTooltipHideTimeout() {
+        return LONG_PRESS_TOOLTIP_HIDE_TIMEOUT;
+    }
+
+    /**
+     * @return the duration in milliseconds before a hover event causes a tooltip to be shown
+     * @hide
+     */
+    @TestApi
+    public static int getHoverTooltipShowTimeout() {
+        return HOVER_TOOLTIP_SHOW_TIMEOUT;
+    }
+
+    /**
+     * @return the duration in milliseconds before mouse inactivity causes a tooltip to be hidden
+     * (default variant to be used when {@link View#SYSTEM_UI_FLAG_LOW_PROFILE} is not set).
+     * @hide
+     */
+    @TestApi
+    public static int getHoverTooltipHideTimeout() {
+        return HOVER_TOOLTIP_HIDE_TIMEOUT;
+    }
+
+    /**
+     * @return the duration in milliseconds before mouse inactivity causes a tooltip to be hidden
+     * (shorter variant to be used when {@link View#SYSTEM_UI_FLAG_LOW_PROFILE} is set).
+     * @hide
+     */
+    @TestApi
+    public static int getHoverTooltipHideShortTimeout() {
+        return HOVER_TOOLTIP_HIDE_SHORT_TIMEOUT;
+    }
+}
diff --git a/android/view/ViewConfiguration_Accessor.java b/android/view/ViewConfiguration_Accessor.java
new file mode 100644
index 0000000..c3533e0
--- /dev/null
+++ b/android/view/ViewConfiguration_Accessor.java
@@ -0,0 +1,29 @@
+/*
+ * 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.view;
+
+/**
+ * Class allowing access to package-protected methods/fields.
+ */
+public class ViewConfiguration_Accessor {
+
+    public static void clearConfigurations() {
+        // clear the stored ViewConfiguration since the map is per density and not per context.
+        ViewConfiguration.sConfigurations.clear();
+    }
+
+}
diff --git a/android/view/ViewDebug.java b/android/view/ViewDebug.java
new file mode 100644
index 0000000..73294b3
--- /dev/null
+++ b/android/view/ViewDebug.java
@@ -0,0 +1,1963 @@
+/*
+ * 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.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.HardwareRenderer;
+import android.graphics.Picture;
+import android.graphics.RecordingCanvas;
+import android.graphics.Rect;
+import android.graphics.RenderNode;
+import android.os.Build;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+
+import libcore.util.HexEncoding;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+/**
+ * Various debugging/tracing tools related to {@link View} and the view hierarchy.
+ */
+public class ViewDebug {
+    /**
+     * @deprecated This flag is now unused
+     */
+    @Deprecated
+    public static final boolean TRACE_HIERARCHY = false;
+
+    /**
+     * @deprecated This flag is now unused
+     */
+    @Deprecated
+    public static final boolean TRACE_RECYCLER = false;
+
+    /**
+     * Enables detailed logging of drag/drop operations.
+     * @hide
+     */
+    public static final boolean DEBUG_DRAG = false;
+
+    /**
+     * Enables detailed logging of task positioning operations.
+     * @hide
+     */
+    public static final boolean DEBUG_POSITIONING = false;
+
+    /**
+     * This annotation can be used to mark fields and methods to be dumped by
+     * the view server. Only non-void methods with no arguments can be annotated
+     * by this annotation.
+     */
+    @Target({ ElementType.FIELD, ElementType.METHOD })
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface ExportedProperty {
+        /**
+         * When resolveId is true, and if the annotated field/method return value
+         * is an int, the value is converted to an Android's resource name.
+         *
+         * @return true if the property's value must be transformed into an Android
+         *         resource name, false otherwise
+         */
+        boolean resolveId() default false;
+
+        /**
+         * A mapping can be defined to map int values to specific strings. For
+         * instance, View.getVisibility() returns 0, 4 or 8. However, these values
+         * actually mean VISIBLE, INVISIBLE and GONE. A mapping can be used to see
+         * these human readable values:
+         *
+         * <pre>
+         * {@literal @}ViewDebug.ExportedProperty(mapping = {
+         *     {@literal @}ViewDebug.IntToString(from = 0, to = "VISIBLE"),
+         *     {@literal @}ViewDebug.IntToString(from = 4, to = "INVISIBLE"),
+         *     {@literal @}ViewDebug.IntToString(from = 8, to = "GONE")
+         * })
+         * public int getVisibility() { ...
+         * <pre>
+         *
+         * @return An array of int to String mappings
+         *
+         * @see android.view.ViewDebug.IntToString
+         */
+        IntToString[] mapping() default { };
+
+        /**
+         * A mapping can be defined to map array indices to specific strings.
+         * A mapping can be used to see human readable values for the indices
+         * of an array:
+         *
+         * <pre>
+         * {@literal @}ViewDebug.ExportedProperty(indexMapping = {
+         *     {@literal @}ViewDebug.IntToString(from = 0, to = "INVALID"),
+         *     {@literal @}ViewDebug.IntToString(from = 1, to = "FIRST"),
+         *     {@literal @}ViewDebug.IntToString(from = 2, to = "SECOND")
+         * })
+         * private int[] mElements;
+         * <pre>
+         *
+         * @return An array of int to String mappings
+         *
+         * @see android.view.ViewDebug.IntToString
+         * @see #mapping()
+         */
+        IntToString[] indexMapping() default { };
+
+        /**
+         * A flags mapping can be defined to map flags encoded in an integer to
+         * specific strings. A mapping can be used to see human readable values
+         * for the flags of an integer:
+         *
+         * <pre>
+         * {@literal @}ViewDebug.ExportedProperty(flagMapping = {
+         *     {@literal @}ViewDebug.FlagToString(mask = ENABLED_MASK, equals = ENABLED,
+         *             name = "ENABLED"),
+         *     {@literal @}ViewDebug.FlagToString(mask = ENABLED_MASK, equals = DISABLED,
+         *             name = "DISABLED"),
+         * })
+         * private int mFlags;
+         * <pre>
+         *
+         * A specified String is output when the following is true:
+         *
+         * @return An array of int to String mappings
+         */
+        FlagToString[] flagMapping() default { };
+
+        /**
+         * When deep export is turned on, this property is not dumped. Instead, the
+         * properties contained in this property are dumped. Each child property
+         * is prefixed with the name of this property.
+         *
+         * @return true if the properties of this property should be dumped
+         *
+         * @see #prefix()
+         */
+        boolean deepExport() default false;
+
+        /**
+         * The prefix to use on child properties when deep export is enabled
+         *
+         * @return a prefix as a String
+         *
+         * @see #deepExport()
+         */
+        String prefix() default "";
+
+        /**
+         * Specifies the category the property falls into, such as measurement,
+         * layout, drawing, etc.
+         *
+         * @return the category as String
+         */
+        String category() default "";
+
+        /**
+         * Indicates whether or not to format an {@code int} or {@code byte} value as a hex string.
+         *
+         * @return true if the supported values should be formatted as a hex string.
+         */
+        boolean formatToHexString() default false;
+
+        /**
+         * Indicates whether or not the key to value mappings are held in adjacent indices.
+         *
+         * Note: Applies only to fields and methods that return String[].
+         *
+         * @return true if the key to value mappings are held in adjacent indices.
+         */
+        boolean hasAdjacentMapping() default false;
+    }
+
+    /**
+     * Defines a mapping from an int value to a String. Such a mapping can be used
+     * in an @ExportedProperty to provide more meaningful values to the end user.
+     *
+     * @see android.view.ViewDebug.ExportedProperty
+     */
+    @Target({ ElementType.TYPE })
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface IntToString {
+        /**
+         * The original int value to map to a String.
+         *
+         * @return An arbitrary int value.
+         */
+        int from();
+
+        /**
+         * The String to use in place of the original int value.
+         *
+         * @return An arbitrary non-null String.
+         */
+        String to();
+    }
+
+    /**
+     * Defines a mapping from a flag to a String. Such a mapping can be used
+     * in an @ExportedProperty to provide more meaningful values to the end user.
+     *
+     * @see android.view.ViewDebug.ExportedProperty
+     */
+    @Target({ ElementType.TYPE })
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface FlagToString {
+        /**
+         * The mask to apply to the original value.
+         *
+         * @return An arbitrary int value.
+         */
+        int mask();
+
+        /**
+         * The value to compare to the result of:
+         * <code>original value &amp; {@link #mask()}</code>.
+         *
+         * @return An arbitrary value.
+         */
+        int equals();
+
+        /**
+         * The String to use in place of the original int value.
+         *
+         * @return An arbitrary non-null String.
+         */
+        String name();
+
+        /**
+         * Indicates whether to output the flag when the test is true,
+         * or false. Defaults to true.
+         */
+        boolean outputIf() default true;
+    }
+
+    /**
+     * This annotation can be used to mark fields and methods to be dumped when
+     * the view is captured. Methods with this annotation must have no arguments
+     * and must return a valid type of data.
+     */
+    @Target({ ElementType.FIELD, ElementType.METHOD })
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface CapturedViewProperty {
+        /**
+         * When retrieveReturn is true, we need to retrieve second level methods
+         * e.g., we need myView.getFirstLevelMethod().getSecondLevelMethod()
+         * we will set retrieveReturn = true on the annotation of
+         * myView.getFirstLevelMethod()
+         * @return true if we need the second level methods
+         */
+        boolean retrieveReturn() default false;
+    }
+
+    /**
+     * Allows a View to inject custom children into HierarchyViewer. For example,
+     * WebView uses this to add its internal layer tree as a child to itself
+     * @hide
+     */
+    public interface HierarchyHandler {
+        /**
+         * Dumps custom children to hierarchy viewer.
+         * See ViewDebug.dumpViewWithProperties(Context, View, BufferedWriter, int)
+         * for the format
+         *
+         * An empty implementation should simply do nothing
+         *
+         * @param out The output writer
+         * @param level The indentation level
+         */
+        public void dumpViewHierarchyWithProperties(BufferedWriter out, int level);
+
+        /**
+         * Returns a View to enable grabbing screenshots from custom children
+         * returned in dumpViewHierarchyWithProperties.
+         *
+         * @param className The className of the view to find
+         * @param hashCode The hashCode of the view to find
+         * @return the View to capture from, or null if not found
+         */
+        public View findHierarchyView(String className, int hashCode);
+    }
+
+    private abstract static class PropertyInfo<T extends Annotation,
+            R extends AccessibleObject & Member> {
+
+        public final R member;
+        public final T property;
+        public final String name;
+        public final Class<?> returnType;
+
+        public String entrySuffix = "";
+        public String valueSuffix = "";
+
+        PropertyInfo(Class<T> property, R member, Class<?> returnType) {
+            this.member = member;
+            this.name = member.getName();
+            this.property = member.getAnnotation(property);
+            this.returnType = returnType;
+        }
+
+        public abstract Object invoke(Object target) throws Exception;
+
+        static <T extends Annotation> PropertyInfo<T, ?> forMethod(Method method,
+                Class<T> property) {
+            // Ensure the method return and parameter types can be resolved.
+            try {
+                if ((method.getReturnType() == Void.class)
+                        || (method.getParameterTypes().length != 0)) {
+                    return null;
+                }
+            } catch (NoClassDefFoundError e) {
+                return null;
+            }
+            if (!method.isAnnotationPresent(property)) {
+                return null;
+            }
+            method.setAccessible(true);
+
+            PropertyInfo info = new MethodPI(method, property);
+            info.entrySuffix = "()";
+            info.valueSuffix = ";";
+            return info;
+        }
+
+        static <T extends Annotation> PropertyInfo<T, ?> forField(Field field, Class<T> property) {
+            if (!field.isAnnotationPresent(property)) {
+                return null;
+            }
+            field.setAccessible(true);
+            return new FieldPI<>(field, property);
+        }
+    }
+
+    private static class MethodPI<T extends Annotation> extends PropertyInfo<T, Method> {
+
+        MethodPI(Method method, Class<T> property) {
+            super(property, method, method.getReturnType());
+        }
+
+        @Override
+        public Object invoke(Object target) throws Exception {
+            return member.invoke(target);
+        }
+    }
+
+    private static class FieldPI<T extends Annotation> extends PropertyInfo<T, Field> {
+
+        FieldPI(Field field, Class<T> property) {
+            super(property, field, field.getType());
+        }
+
+        @Override
+        public Object invoke(Object target) throws Exception {
+            return member.get(target);
+        }
+    }
+
+    // Maximum delay in ms after which we stop trying to capture a View's drawing
+    private static final int CAPTURE_TIMEOUT = 6000;
+
+    private static final String REMOTE_COMMAND_CAPTURE = "CAPTURE";
+    private static final String REMOTE_COMMAND_DUMP = "DUMP";
+    private static final String REMOTE_COMMAND_DUMP_THEME = "DUMP_THEME";
+    /**
+     * Similar to REMOTE_COMMAND_DUMP but uses ViewHierarchyEncoder instead of flat text
+     * @hide
+     */
+    public static final String REMOTE_COMMAND_DUMP_ENCODED = "DUMP_ENCODED";
+    private static final String REMOTE_COMMAND_INVALIDATE = "INVALIDATE";
+    private static final String REMOTE_COMMAND_REQUEST_LAYOUT = "REQUEST_LAYOUT";
+    private static final String REMOTE_PROFILE = "PROFILE";
+    private static final String REMOTE_COMMAND_CAPTURE_LAYERS = "CAPTURE_LAYERS";
+    private static final String REMOTE_COMMAND_OUTPUT_DISPLAYLIST = "OUTPUT_DISPLAYLIST";
+
+    private static HashMap<Class<?>, PropertyInfo<ExportedProperty, ?>[]> sExportProperties;
+    private static HashMap<Class<?>, PropertyInfo<CapturedViewProperty, ?>[]>
+            sCapturedViewProperties;
+
+    /**
+     * @deprecated This enum is now unused
+     */
+    @Deprecated
+    public enum HierarchyTraceType {
+        INVALIDATE,
+        INVALIDATE_CHILD,
+        INVALIDATE_CHILD_IN_PARENT,
+        REQUEST_LAYOUT,
+        ON_LAYOUT,
+        ON_MEASURE,
+        DRAW,
+        BUILD_CACHE
+    }
+
+    /**
+     * @deprecated This enum is now unused
+     */
+    @Deprecated
+    public enum RecyclerTraceType {
+        NEW_VIEW,
+        BIND_VIEW,
+        RECYCLE_FROM_ACTIVE_HEAP,
+        RECYCLE_FROM_SCRAP_HEAP,
+        MOVE_TO_SCRAP_HEAP,
+        MOVE_FROM_ACTIVE_TO_SCRAP_HEAP
+    }
+
+    /**
+     * Returns the number of instanciated Views.
+     *
+     * @return The number of Views instanciated in the current process.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static long getViewInstanceCount() {
+        return Debug.countInstancesOfClass(View.class);
+    }
+
+    /**
+     * Returns the number of instanciated ViewAncestors.
+     *
+     * @return The number of ViewAncestors instanciated in the current process.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public static long getViewRootImplCount() {
+        return Debug.countInstancesOfClass(ViewRootImpl.class);
+    }
+
+    /**
+     * @deprecated This method is now unused and invoking it is a no-op
+     */
+    @Deprecated
+    @SuppressWarnings({ "UnusedParameters", "deprecation" })
+    public static void trace(View view, RecyclerTraceType type, int... parameters) {
+    }
+
+    /**
+     * @deprecated This method is now unused and invoking it is a no-op
+     */
+    @Deprecated
+    @SuppressWarnings("UnusedParameters")
+    public static void startRecyclerTracing(String prefix, View view) {
+    }
+
+    /**
+     * @deprecated This method is now unused and invoking it is a no-op
+     */
+    @Deprecated
+    @SuppressWarnings("UnusedParameters")
+    public static void stopRecyclerTracing() {
+    }
+
+    /**
+     * @deprecated This method is now unused and invoking it is a no-op
+     */
+    @Deprecated
+    @SuppressWarnings({ "UnusedParameters", "deprecation" })
+    public static void trace(View view, HierarchyTraceType type) {
+    }
+
+    /**
+     * @deprecated This method is now unused and invoking it is a no-op
+     */
+    @Deprecated
+    @SuppressWarnings("UnusedParameters")
+    public static void startHierarchyTracing(String prefix, View view) {
+    }
+
+    /**
+     * @deprecated This method is now unused and invoking it is a no-op
+     */
+    @Deprecated
+    public static void stopHierarchyTracing() {
+    }
+
+    @UnsupportedAppUsage
+    static void dispatchCommand(View view, String command, String parameters,
+            OutputStream clientStream) throws IOException {
+        // Just being cautious...
+        view = view.getRootView();
+
+        if (REMOTE_COMMAND_DUMP.equalsIgnoreCase(command)) {
+            dump(view, false, true, clientStream);
+        } else if (REMOTE_COMMAND_DUMP_THEME.equalsIgnoreCase(command)) {
+            dumpTheme(view, clientStream);
+        } else if (REMOTE_COMMAND_DUMP_ENCODED.equalsIgnoreCase(command)) {
+            dumpEncoded(view, clientStream);
+        } else if (REMOTE_COMMAND_CAPTURE_LAYERS.equalsIgnoreCase(command)) {
+            captureLayers(view, new DataOutputStream(clientStream));
+        } else {
+            final String[] params = parameters.split(" ");
+            if (REMOTE_COMMAND_CAPTURE.equalsIgnoreCase(command)) {
+                capture(view, clientStream, params[0]);
+            } else if (REMOTE_COMMAND_OUTPUT_DISPLAYLIST.equalsIgnoreCase(command)) {
+                outputDisplayList(view, params[0]);
+            } else if (REMOTE_COMMAND_INVALIDATE.equalsIgnoreCase(command)) {
+                invalidate(view, params[0]);
+            } else if (REMOTE_COMMAND_REQUEST_LAYOUT.equalsIgnoreCase(command)) {
+                requestLayout(view, params[0]);
+            } else if (REMOTE_PROFILE.equalsIgnoreCase(command)) {
+                profile(view, clientStream, params[0]);
+            }
+        }
+    }
+
+    /** @hide */
+    public static View findView(View root, String parameter) {
+        // Look by type/hashcode
+        if (parameter.indexOf('@') != -1) {
+            final String[] ids = parameter.split("@");
+            final String className = ids[0];
+            final int hashCode = (int) Long.parseLong(ids[1], 16);
+
+            View view = root.getRootView();
+            if (view instanceof ViewGroup) {
+                return findView((ViewGroup) view, className, hashCode);
+            }
+        } else {
+            // Look by id
+            final int id = root.getResources().getIdentifier(parameter, null, null);
+            return root.getRootView().findViewById(id);
+        }
+
+        return null;
+    }
+
+    private static void invalidate(View root, String parameter) {
+        final View view = findView(root, parameter);
+        if (view != null) {
+            view.postInvalidate();
+        }
+    }
+
+    private static void requestLayout(View root, String parameter) {
+        final View view = findView(root, parameter);
+        if (view != null) {
+            root.post(new Runnable() {
+                public void run() {
+                    view.requestLayout();
+                }
+            });
+        }
+    }
+
+    private static void profile(View root, OutputStream clientStream, String parameter)
+            throws IOException {
+
+        final View view = findView(root, parameter);
+        BufferedWriter out = null;
+        try {
+            out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024);
+
+            if (view != null) {
+                profileViewAndChildren(view, out);
+            } else {
+                out.write("-1 -1 -1");
+                out.newLine();
+            }
+            out.write("DONE.");
+            out.newLine();
+        } catch (Exception e) {
+            android.util.Log.w("View", "Problem profiling the view:", e);
+        } finally {
+            if (out != null) {
+                out.close();
+            }
+        }
+    }
+
+    /** @hide */
+    public static void profileViewAndChildren(final View view, BufferedWriter out)
+            throws IOException {
+        RenderNode node = RenderNode.create("ViewDebug", null);
+        profileViewAndChildren(view, node, out, true);
+    }
+
+    private static void profileViewAndChildren(View view, RenderNode node, BufferedWriter out,
+            boolean root) throws IOException {
+        long durationMeasure =
+                (root || (view.mPrivateFlags & View.PFLAG_MEASURED_DIMENSION_SET) != 0)
+                        ? profileViewMeasure(view) : 0;
+        long durationLayout =
+                (root || (view.mPrivateFlags & View.PFLAG_LAYOUT_REQUIRED) != 0)
+                        ? profileViewLayout(view) : 0;
+        long durationDraw =
+                (root || !view.willNotDraw() || (view.mPrivateFlags & View.PFLAG_DRAWN) != 0)
+                        ? profileViewDraw(view, node) : 0;
+
+        out.write(String.valueOf(durationMeasure));
+        out.write(' ');
+        out.write(String.valueOf(durationLayout));
+        out.write(' ');
+        out.write(String.valueOf(durationDraw));
+        out.newLine();
+        if (view instanceof ViewGroup) {
+            ViewGroup group = (ViewGroup) view;
+            final int count = group.getChildCount();
+            for (int i = 0; i < count; i++) {
+                profileViewAndChildren(group.getChildAt(i), node, out, false);
+            }
+        }
+    }
+
+    private static long profileViewMeasure(final View view) {
+        return profileViewOperation(view, new ViewOperation() {
+            @Override
+            public void pre() {
+                forceLayout(view);
+            }
+
+            private void forceLayout(View view) {
+                view.forceLayout();
+                if (view instanceof ViewGroup) {
+                    ViewGroup group = (ViewGroup) view;
+                    final int count = group.getChildCount();
+                    for (int i = 0; i < count; i++) {
+                        forceLayout(group.getChildAt(i));
+                    }
+                }
+            }
+
+            @Override
+            public void run() {
+                view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec);
+            }
+        });
+    }
+
+    private static long profileViewLayout(View view) {
+        return profileViewOperation(view,
+                () -> view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom));
+    }
+
+    private static long profileViewDraw(View view, RenderNode node) {
+        DisplayMetrics dm = view.getResources().getDisplayMetrics();
+        if (dm == null) {
+            return 0;
+        }
+
+        if (view.isHardwareAccelerated()) {
+            RecordingCanvas canvas = node.beginRecording(dm.widthPixels, dm.heightPixels);
+            try {
+                return profileViewOperation(view, () -> view.draw(canvas));
+            } finally {
+                node.endRecording();
+            }
+        } else {
+            Bitmap bitmap = Bitmap.createBitmap(
+                    dm, dm.widthPixels, dm.heightPixels, Bitmap.Config.RGB_565);
+            Canvas canvas = new Canvas(bitmap);
+            try {
+                return profileViewOperation(view, () -> view.draw(canvas));
+            } finally {
+                canvas.setBitmap(null);
+                bitmap.recycle();
+            }
+        }
+    }
+
+    interface ViewOperation {
+        default void pre() {}
+
+        void run();
+    }
+
+    private static long profileViewOperation(View view, final ViewOperation operation) {
+        final CountDownLatch latch = new CountDownLatch(1);
+        final long[] duration = new long[1];
+
+        view.post(() -> {
+            try {
+                operation.pre();
+                long start = Debug.threadCpuTimeNanos();
+                //noinspection unchecked
+                operation.run();
+                duration[0] = Debug.threadCpuTimeNanos() - start;
+            } finally {
+                latch.countDown();
+            }
+        });
+
+        try {
+            if (!latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS)) {
+                Log.w("View", "Could not complete the profiling of the view " + view);
+                return -1;
+            }
+        } catch (InterruptedException e) {
+            Log.w("View", "Could not complete the profiling of the view " + view);
+            Thread.currentThread().interrupt();
+            return -1;
+        }
+
+        return duration[0];
+    }
+
+    /** @hide */
+    public static void captureLayers(View root, final DataOutputStream clientStream)
+            throws IOException {
+
+        try {
+            Rect outRect = new Rect();
+            root.mAttachInfo.mViewRootImpl.getDisplayFrame(outRect);
+
+            clientStream.writeInt(outRect.width());
+            clientStream.writeInt(outRect.height());
+
+            captureViewLayer(root, clientStream, true);
+
+            clientStream.write(2);
+        } finally {
+            clientStream.close();
+        }
+    }
+
+    private static void captureViewLayer(View view, DataOutputStream clientStream, boolean visible)
+            throws IOException {
+
+        final boolean localVisible = view.getVisibility() == View.VISIBLE && visible;
+
+        if ((view.mPrivateFlags & View.PFLAG_SKIP_DRAW) != View.PFLAG_SKIP_DRAW) {
+            final int id = view.getId();
+            String name = view.getClass().getSimpleName();
+            if (id != View.NO_ID) {
+                name = resolveId(view.getContext(), id).toString();
+            }
+
+            clientStream.write(1);
+            clientStream.writeUTF(name);
+            clientStream.writeByte(localVisible ? 1 : 0);
+
+            int[] position = new int[2];
+            // XXX: Should happen on the UI thread
+            view.getLocationInWindow(position);
+
+            clientStream.writeInt(position[0]);
+            clientStream.writeInt(position[1]);
+            clientStream.flush();
+
+            Bitmap b = performViewCapture(view, true);
+            if (b != null) {
+                ByteArrayOutputStream arrayOut = new ByteArrayOutputStream(b.getWidth() *
+                        b.getHeight() * 2);
+                b.compress(Bitmap.CompressFormat.PNG, 100, arrayOut);
+                clientStream.writeInt(arrayOut.size());
+                arrayOut.writeTo(clientStream);
+            }
+            clientStream.flush();
+        }
+
+        if (view instanceof ViewGroup) {
+            ViewGroup group = (ViewGroup) view;
+            int count = group.getChildCount();
+
+            for (int i = 0; i < count; i++) {
+                captureViewLayer(group.getChildAt(i), clientStream, localVisible);
+            }
+        }
+
+        if (view.mOverlay != null) {
+            ViewGroup overlayContainer = view.getOverlay().mOverlayViewGroup;
+            captureViewLayer(overlayContainer, clientStream, localVisible);
+        }
+    }
+
+    private static void outputDisplayList(View root, String parameter) throws IOException {
+        final View view = findView(root, parameter);
+        view.getViewRootImpl().outputDisplayList(view);
+    }
+
+    /** @hide */
+    public static void outputDisplayList(View root, View target) {
+        root.getViewRootImpl().outputDisplayList(target);
+    }
+
+    private static class PictureCallbackHandler implements AutoCloseable,
+            HardwareRenderer.PictureCapturedCallback, Runnable {
+        private final HardwareRenderer mRenderer;
+        private final Function<Picture, Boolean> mCallback;
+        private final Executor mExecutor;
+        private final ReentrantLock mLock = new ReentrantLock(false);
+        private final ArrayDeque<Picture> mQueue = new ArrayDeque<>(3);
+        private boolean mStopListening;
+        private Thread mRenderThread;
+
+        private PictureCallbackHandler(HardwareRenderer renderer,
+                Function<Picture, Boolean> callback, Executor executor) {
+            mRenderer = renderer;
+            mCallback = callback;
+            mExecutor = executor;
+            mRenderer.setPictureCaptureCallback(this);
+        }
+
+        @Override
+        public void close() {
+            mLock.lock();
+            mStopListening = true;
+            mLock.unlock();
+            mRenderer.setPictureCaptureCallback(null);
+        }
+
+        @Override
+        public void onPictureCaptured(Picture picture) {
+            mLock.lock();
+            if (mStopListening) {
+                mLock.unlock();
+                mRenderer.setPictureCaptureCallback(null);
+                return;
+            }
+            if (mRenderThread == null) {
+                mRenderThread = Thread.currentThread();
+            }
+            Picture toDestroy = null;
+            if (mQueue.size() == 3) {
+                toDestroy = mQueue.removeLast();
+            }
+            mQueue.add(picture);
+            mLock.unlock();
+            if (toDestroy == null) {
+                mExecutor.execute(this);
+            } else {
+                toDestroy.close();
+            }
+        }
+
+        @Override
+        public void run() {
+            mLock.lock();
+            final Picture picture = mQueue.poll();
+            final boolean isStopped = mStopListening;
+            mLock.unlock();
+            if (Thread.currentThread() == mRenderThread) {
+                close();
+                throw new IllegalStateException(
+                        "ViewDebug#startRenderingCommandsCapture must be given an executor that "
+                        + "invokes asynchronously");
+            }
+            if (isStopped) {
+                picture.close();
+                return;
+            }
+            final boolean keepReceiving = mCallback.apply(picture);
+            if (!keepReceiving) {
+                close();
+            }
+        }
+    }
+
+    /**
+     * Begins capturing the entire rendering commands for the view tree referenced by the given
+     * view. The view passed may be any View in the tree as long as it is attached. That is,
+     * {@link View#isAttachedToWindow()} must be true.
+     *
+     * Every time a frame is rendered a Picture will be passed to the given callback via the given
+     * executor. As long as the callback returns 'true' it will continue to receive new frames.
+     * The system will only invoke the callback at a rate that the callback is able to keep up with.
+     * That is, if it takes 48ms for the callback to complete and there is a 60fps animation running
+     * then the callback will only receive 33% of the frames produced.
+     *
+     * This method must be called on the same thread as the View tree.
+     *
+     * @param tree The View tree to capture the rendering commands.
+     * @param callback The callback to invoke on every frame produced. Should return true to
+     *                 continue receiving new frames, false to stop capturing.
+     * @param executor The executor to invoke the callback on. Recommend using a background thread
+     *                 to avoid stalling the UI thread. Must be an asynchronous invoke or an
+     *                 exception will be thrown.
+     * @return a closeable that can be used to stop capturing. May be invoked on any thread. Note
+     * that the callback may continue to receive another frame or two depending on thread timings.
+     * Returns null if the capture stream cannot be started, such as if there's no
+     * HardwareRenderer for the given view tree.
+     * @hide
+     * @deprecated use {@link #startRenderingCommandsCapture(View, Executor, Callable)} instead.
+     */
+    @TestApi
+    @Nullable
+    @Deprecated
+    public static AutoCloseable startRenderingCommandsCapture(View tree, Executor executor,
+            Function<Picture, Boolean> callback) {
+        final View.AttachInfo attachInfo = tree.mAttachInfo;
+        if (attachInfo == null) {
+            throw new IllegalArgumentException("Given view isn't attached");
+        }
+        if (attachInfo.mHandler.getLooper() != Looper.myLooper()) {
+            throw new IllegalStateException("Called on the wrong thread."
+                    + " Must be called on the thread that owns the given View");
+        }
+        final HardwareRenderer renderer = attachInfo.mThreadedRenderer;
+        if (renderer != null) {
+            return new PictureCallbackHandler(renderer, callback, executor);
+        }
+        return null;
+    }
+
+    private static class StreamingPictureCallbackHandler implements AutoCloseable,
+            HardwareRenderer.PictureCapturedCallback, Runnable {
+        private final HardwareRenderer mRenderer;
+        private final Callable<OutputStream> mCallback;
+        private final Executor mExecutor;
+        private final ReentrantLock mLock = new ReentrantLock(false);
+        private final ArrayDeque<Picture> mQueue = new ArrayDeque<>(3);
+        private boolean mStopListening;
+        private Thread mRenderThread;
+
+        private StreamingPictureCallbackHandler(HardwareRenderer renderer,
+                Callable<OutputStream> callback, Executor executor) {
+            mRenderer = renderer;
+            mCallback = callback;
+            mExecutor = executor;
+            mRenderer.setPictureCaptureCallback(this);
+        }
+
+        @Override
+        public void close() {
+            mLock.lock();
+            mStopListening = true;
+            mLock.unlock();
+            mRenderer.setPictureCaptureCallback(null);
+        }
+
+        @Override
+        public void onPictureCaptured(Picture picture) {
+            mLock.lock();
+            if (mStopListening) {
+                mLock.unlock();
+                mRenderer.setPictureCaptureCallback(null);
+                return;
+            }
+            if (mRenderThread == null) {
+                mRenderThread = Thread.currentThread();
+            }
+            boolean needsInvoke = true;
+            if (mQueue.size() == 3) {
+                mQueue.removeLast();
+                needsInvoke = false;
+            }
+            mQueue.add(picture);
+            mLock.unlock();
+
+            if (needsInvoke) {
+                mExecutor.execute(this);
+            }
+        }
+
+        @Override
+        public void run() {
+            mLock.lock();
+            final Picture picture = mQueue.poll();
+            final boolean isStopped = mStopListening;
+            mLock.unlock();
+            if (Thread.currentThread() == mRenderThread) {
+                close();
+                throw new IllegalStateException(
+                        "ViewDebug#startRenderingCommandsCapture must be given an executor that "
+                        + "invokes asynchronously");
+            }
+            if (isStopped) {
+                return;
+            }
+            OutputStream stream = null;
+            try {
+                stream = mCallback.call();
+            } catch (Exception ex) {
+                Log.w("ViewDebug", "Aborting rendering commands capture "
+                        + "because callback threw exception", ex);
+            }
+            if (stream != null) {
+                try {
+                    picture.writeToStream(stream);
+                    stream.flush();
+                } catch (IOException ex) {
+                    Log.w("ViewDebug", "Aborting rendering commands capture "
+                            + "due to IOException writing to output stream", ex);
+                }
+            } else {
+                close();
+            }
+        }
+    }
+
+    /**
+     * Begins capturing the entire rendering commands for the view tree referenced by the given
+     * view. The view passed may be any View in the tree as long as it is attached. That is,
+     * {@link View#isAttachedToWindow()} must be true.
+     *
+     * Every time a frame is rendered the callback will be invoked on the given executor to
+     * provide an OutputStream to serialize to. As long as the callback returns a valid
+     * OutputStream the capturing will continue. The system will only invoke the callback at a rate
+     * that the callback & OutputStream is able to keep up with. That is, if it takes 48ms for the
+     * callback & serialization to complete and there is a 60fps animation running
+     * then the callback will only receive 33% of the frames produced.
+     *
+     * This method must be called on the same thread as the View tree.
+     *
+     * @param tree The View tree to capture the rendering commands.
+     * @param callback The callback to invoke on every frame produced. Should return an
+     *                 OutputStream to write the data to. Return null to cancel capture. The
+     *                 same stream may be returned each time as the serialized data contains
+     *                 start & end markers. The callback will not be invoked while a previous
+     *                 serialization is being performed, so if a single continuous stream is being
+     *                 used it is valid for the callback to write its own metadata to that stream
+     *                 in response to callback invocation.
+     * @param executor The executor to invoke the callback on. Recommend using a background thread
+     *                 to avoid stalling the UI thread. Must be an asynchronous invoke or an
+     *                 exception will be thrown.
+     * @return a closeable that can be used to stop capturing. May be invoked on any thread. Note
+     * that the callback may continue to receive another frame or two depending on thread timings.
+     * Returns null if the capture stream cannot be started, such as if there's no
+     * HardwareRenderer for the given view tree.
+     * @hide
+     */
+    @TestApi
+    @Nullable
+    public static AutoCloseable startRenderingCommandsCapture(View tree, Executor executor,
+            Callable<OutputStream> callback) {
+        final View.AttachInfo attachInfo = tree.mAttachInfo;
+        if (attachInfo == null) {
+            throw new IllegalArgumentException("Given view isn't attached");
+        }
+        if (attachInfo.mHandler.getLooper() != Looper.myLooper()) {
+            throw new IllegalStateException("Called on the wrong thread."
+                    + " Must be called on the thread that owns the given View");
+        }
+        final HardwareRenderer renderer = attachInfo.mThreadedRenderer;
+        if (renderer != null) {
+            return new StreamingPictureCallbackHandler(renderer, callback, executor);
+        }
+        return null;
+    }
+
+    private static void capture(View root, final OutputStream clientStream, String parameter)
+            throws IOException {
+
+        final View captureView = findView(root, parameter);
+        capture(root, clientStream, captureView);
+    }
+
+    /** @hide */
+    public static void capture(View root, final OutputStream clientStream, View captureView)
+            throws IOException {
+        Bitmap b = performViewCapture(captureView, false);
+
+        if (b == null) {
+            Log.w("View", "Failed to create capture bitmap!");
+            // Send an empty one so that it doesn't get stuck waiting for
+            // something.
+            b = Bitmap.createBitmap(root.getResources().getDisplayMetrics(),
+                    1, 1, Bitmap.Config.ARGB_8888);
+        }
+
+        BufferedOutputStream out = null;
+        try {
+            out = new BufferedOutputStream(clientStream, 32 * 1024);
+            b.compress(Bitmap.CompressFormat.PNG, 100, out);
+            out.flush();
+        } finally {
+            if (out != null) {
+                out.close();
+            }
+            b.recycle();
+        }
+    }
+
+    private static Bitmap performViewCapture(final View captureView, final boolean skipChildren) {
+        if (captureView != null) {
+            final CountDownLatch latch = new CountDownLatch(1);
+            final Bitmap[] cache = new Bitmap[1];
+
+            captureView.post(() -> {
+                try {
+                    CanvasProvider provider = captureView.isHardwareAccelerated()
+                            ? new HardwareCanvasProvider() : new SoftwareCanvasProvider();
+                    cache[0] = captureView.createSnapshot(provider, skipChildren);
+                } catch (OutOfMemoryError e) {
+                    Log.w("View", "Out of memory for bitmap");
+                } finally {
+                    latch.countDown();
+                }
+            });
+
+            try {
+                latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS);
+                return cache[0];
+            } catch (InterruptedException e) {
+                Log.w("View", "Could not complete the capture of the view " + captureView);
+                Thread.currentThread().interrupt();
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Dumps the view hierarchy starting from the given view.
+     * @deprecated See {@link #dumpv2(View, ByteArrayOutputStream)} below.
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public static void dump(View root, boolean skipChildren, boolean includeProperties,
+            OutputStream clientStream) throws IOException {
+        BufferedWriter out = null;
+        try {
+            out = new BufferedWriter(new OutputStreamWriter(clientStream, "utf-8"), 32 * 1024);
+            View view = root.getRootView();
+            if (view instanceof ViewGroup) {
+                ViewGroup group = (ViewGroup) view;
+                dumpViewHierarchy(group.getContext(), group, out, 0,
+                        skipChildren, includeProperties);
+            }
+            out.write("DONE.");
+            out.newLine();
+        } catch (Exception e) {
+            android.util.Log.w("View", "Problem dumping the view:", e);
+        } finally {
+            if (out != null) {
+                out.close();
+            }
+        }
+    }
+
+    /**
+     * Dumps the view hierarchy starting from the given view.
+     * Rather than using reflection, it uses View's encode method to obtain all the properties.
+     * @hide
+     */
+    public static void dumpv2(@NonNull final View view, @NonNull ByteArrayOutputStream out)
+            throws InterruptedException {
+        final ViewHierarchyEncoder encoder = new ViewHierarchyEncoder(out);
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        view.post(new Runnable() {
+            @Override
+            public void run() {
+                encoder.addProperty("window:left", view.mAttachInfo.mWindowLeft);
+                encoder.addProperty("window:top", view.mAttachInfo.mWindowTop);
+                view.encode(encoder);
+                latch.countDown();
+            }
+        });
+
+        latch.await(2, TimeUnit.SECONDS);
+        encoder.endStream();
+    }
+
+    private static void dumpEncoded(@NonNull final View view, @NonNull OutputStream out)
+            throws IOException {
+        ByteArrayOutputStream baOut = new ByteArrayOutputStream();
+
+        final ViewHierarchyEncoder encoder = new ViewHierarchyEncoder(baOut);
+        encoder.setUserPropertiesEnabled(false);
+        encoder.addProperty("window:left", view.mAttachInfo.mWindowLeft);
+        encoder.addProperty("window:top", view.mAttachInfo.mWindowTop);
+        view.encode(encoder);
+        encoder.endStream();
+        out.write(baOut.toByteArray());
+    }
+
+    /**
+     * Dumps the theme attributes from the given View.
+     * @hide
+     */
+    public static void dumpTheme(View view, OutputStream clientStream) throws IOException {
+        BufferedWriter out = null;
+        try {
+            out = new BufferedWriter(new OutputStreamWriter(clientStream, "utf-8"), 32 * 1024);
+            String[] attributes = getStyleAttributesDump(view.getContext().getResources(),
+                    view.getContext().getTheme());
+            if (attributes != null) {
+                for (int i = 0; i < attributes.length; i += 2) {
+                    if (attributes[i] != null) {
+                        out.write(attributes[i] + "\n");
+                        out.write(attributes[i + 1] + "\n");
+                    }
+                }
+            }
+            out.write("DONE.");
+            out.newLine();
+        } catch (Exception e) {
+            android.util.Log.w("View", "Problem dumping View Theme:", e);
+        } finally {
+            if (out != null) {
+                out.close();
+            }
+        }
+    }
+
+    /**
+     * Gets the style attributes from the {@link Resources.Theme}. For debugging only.
+     *
+     * @param resources Resources to resolve attributes from.
+     * @param theme Theme to dump.
+     * @return a String array containing pairs of adjacent Theme attribute data: name followed by
+     * its value.
+     *
+     * @hide
+     */
+    private static String[] getStyleAttributesDump(Resources resources, Resources.Theme theme) {
+        TypedValue outValue = new TypedValue();
+        String nullString = "null";
+        int i = 0;
+        int[] attributes = theme.getAllAttributes();
+        String[] data = new String[attributes.length * 2];
+        for (int attributeId : attributes) {
+            try {
+                data[i] = resources.getResourceName(attributeId);
+                data[i + 1] = theme.resolveAttribute(attributeId, outValue, true) ?
+                        outValue.coerceToString().toString() :  nullString;
+                i += 2;
+
+                // attempt to replace reference data with its name
+                if (outValue.type == TypedValue.TYPE_REFERENCE) {
+                    data[i - 1] = resources.getResourceName(outValue.resourceId);
+                }
+            } catch (Resources.NotFoundException e) {
+                // ignore resources we can't resolve
+            }
+        }
+        return data;
+    }
+
+    private static View findView(ViewGroup group, String className, int hashCode) {
+        if (isRequestedView(group, className, hashCode)) {
+            return group;
+        }
+
+        final int count = group.getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View view = group.getChildAt(i);
+            if (view instanceof ViewGroup) {
+                final View found = findView((ViewGroup) view, className, hashCode);
+                if (found != null) {
+                    return found;
+                }
+            } else if (isRequestedView(view, className, hashCode)) {
+                return view;
+            }
+            if (view.mOverlay != null) {
+                final View found = findView((ViewGroup) view.mOverlay.mOverlayViewGroup,
+                        className, hashCode);
+                if (found != null) {
+                    return found;
+                }
+            }
+            if (view instanceof HierarchyHandler) {
+                final View found = ((HierarchyHandler)view)
+                        .findHierarchyView(className, hashCode);
+                if (found != null) {
+                    return found;
+                }
+            }
+        }
+        return null;
+    }
+
+    private static boolean isRequestedView(View view, String className, int hashCode) {
+        if (view.hashCode() == hashCode) {
+            String viewClassName = view.getClass().getName();
+            if (className.equals("ViewOverlay")) {
+                return viewClassName.equals("android.view.ViewOverlay$OverlayViewGroup");
+            } else {
+                return className.equals(viewClassName);
+            }
+        }
+        return false;
+    }
+
+    private static void dumpViewHierarchy(Context context, ViewGroup group,
+            BufferedWriter out, int level, boolean skipChildren, boolean includeProperties) {
+        cacheExportedProperties(group.getClass());
+        if (!skipChildren) {
+            cacheExportedPropertiesForChildren(group);
+        }
+        // Try to use the handler provided by the view
+        Handler handler = group.getHandler();
+        // Fall back on using the main thread
+        if (handler == null) {
+            handler = new Handler(Looper.getMainLooper());
+        }
+
+        if (handler.getLooper() == Looper.myLooper()) {
+            dumpViewHierarchyOnUIThread(context, group, out, level, skipChildren,
+                    includeProperties);
+        } else {
+            FutureTask task = new FutureTask(() ->
+                    dumpViewHierarchyOnUIThread(context, group, out, level, skipChildren,
+                            includeProperties), null);
+            Message msg = Message.obtain(handler, task);
+            msg.setAsynchronous(true);
+            handler.sendMessage(msg);
+            while (true) {
+                try {
+                    task.get(CAPTURE_TIMEOUT, java.util.concurrent.TimeUnit.MILLISECONDS);
+                    return;
+                } catch (InterruptedException e) {
+                    // try again
+                } catch (ExecutionException | TimeoutException e) {
+                    // Something unexpected happened.
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+    }
+
+    private static void cacheExportedPropertiesForChildren(ViewGroup group) {
+        final int count = group.getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View view = group.getChildAt(i);
+            cacheExportedProperties(view.getClass());
+            if (view instanceof ViewGroup) {
+                cacheExportedPropertiesForChildren((ViewGroup) view);
+            }
+        }
+    }
+
+    private static void cacheExportedProperties(Class<?> klass) {
+        if (sExportProperties != null && sExportProperties.containsKey(klass)) {
+            return;
+        }
+        do {
+            for (PropertyInfo<ExportedProperty, ?> info : getExportedProperties(klass)) {
+                if (!info.returnType.isPrimitive() && info.property.deepExport()) {
+                    cacheExportedProperties(info.returnType);
+                }
+            }
+            klass = klass.getSuperclass();
+        } while (klass != Object.class);
+    }
+
+
+    private static void dumpViewHierarchyOnUIThread(Context context, ViewGroup group,
+            BufferedWriter out, int level, boolean skipChildren, boolean includeProperties) {
+        if (!dumpView(context, group, out, level, includeProperties)) {
+            return;
+        }
+
+        if (skipChildren) {
+            return;
+        }
+
+        final int count = group.getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View view = group.getChildAt(i);
+            if (view instanceof ViewGroup) {
+                dumpViewHierarchyOnUIThread(context, (ViewGroup) view, out, level + 1,
+                        skipChildren, includeProperties);
+            } else {
+                dumpView(context, view, out, level + 1, includeProperties);
+            }
+            if (view.mOverlay != null) {
+                ViewOverlay overlay = view.getOverlay();
+                ViewGroup overlayContainer = overlay.mOverlayViewGroup;
+                dumpViewHierarchyOnUIThread(context, overlayContainer, out, level + 2,
+                        skipChildren, includeProperties);
+            }
+        }
+        if (group instanceof HierarchyHandler) {
+            ((HierarchyHandler)group).dumpViewHierarchyWithProperties(out, level + 1);
+        }
+    }
+
+    private static boolean dumpView(Context context, View view,
+            BufferedWriter out, int level, boolean includeProperties) {
+
+        try {
+            for (int i = 0; i < level; i++) {
+                out.write(' ');
+            }
+            String className = view.getClass().getName();
+            if (className.equals("android.view.ViewOverlay$OverlayViewGroup")) {
+                className = "ViewOverlay";
+            }
+            out.write(className);
+            out.write('@');
+            out.write(Integer.toHexString(view.hashCode()));
+            out.write(' ');
+            if (includeProperties) {
+                dumpViewProperties(context, view, out);
+            }
+            out.newLine();
+        } catch (IOException e) {
+            Log.w("View", "Error while dumping hierarchy tree");
+            return false;
+        }
+        return true;
+    }
+
+    private static <T extends Annotation> PropertyInfo<T, ?>[] convertToPropertyInfos(
+            Method[] methods, Field[] fields, Class<T> property) {
+        return Stream.of(Arrays.stream(methods).map(m -> PropertyInfo.forMethod(m, property)),
+                Arrays.stream(fields).map(f -> PropertyInfo.forField(f, property)))
+                .flatMap(Function.identity())
+                .filter(i -> i != null)
+                .toArray(PropertyInfo[]::new);
+    }
+
+    private static PropertyInfo<ExportedProperty, ?>[] getExportedProperties(Class<?> klass) {
+        if (sExportProperties == null) {
+            sExportProperties = new HashMap<>();
+        }
+        final HashMap<Class<?>, PropertyInfo<ExportedProperty, ?>[]> map = sExportProperties;
+        PropertyInfo<ExportedProperty, ?>[] properties = sExportProperties.get(klass);
+
+        if (properties == null) {
+            properties = convertToPropertyInfos(klass.getDeclaredMethods(),
+                    klass.getDeclaredFields(), ExportedProperty.class);
+            map.put(klass, properties);
+        }
+        return properties;
+    }
+
+    private static void dumpViewProperties(Context context, Object view,
+            BufferedWriter out) throws IOException {
+
+        dumpViewProperties(context, view, out, "");
+    }
+
+    private static void dumpViewProperties(Context context, Object view,
+            BufferedWriter out, String prefix) throws IOException {
+
+        if (view == null) {
+            out.write(prefix + "=4,null ");
+            return;
+        }
+
+        Class<?> klass = view.getClass();
+        do {
+            writeExportedProperties(context, view, out, klass, prefix);
+            klass = klass.getSuperclass();
+        } while (klass != Object.class);
+    }
+
+    private static String formatIntToHexString(int value) {
+        return "0x" + Integer.toHexString(value).toUpperCase();
+    }
+
+    private static void writeExportedProperties(Context context, Object view, BufferedWriter out,
+            Class<?> klass, String prefix) throws IOException {
+        for (PropertyInfo<ExportedProperty, ?> info : getExportedProperties(klass)) {
+            //noinspection EmptyCatchBlock
+            Object value;
+            try {
+                value = info.invoke(view);
+            } catch (Exception e) {
+                // ignore
+                continue;
+            }
+
+            String categoryPrefix =
+                    info.property.category().length() != 0 ? info.property.category() + ":" : "";
+
+            if (info.returnType == int.class || info.returnType == byte.class) {
+                if (info.property.resolveId() && context != null) {
+                    final int id = (Integer) value;
+                    value = resolveId(context, id);
+
+                } else if (info.property.formatToHexString()) {
+                    if (info.returnType == int.class) {
+                        value = formatIntToHexString((Integer) value);
+                    } else if (info.returnType == byte.class) {
+                        value = "0x"
+                                + HexEncoding.encodeToString((Byte) value, true);
+                    }
+                } else {
+                    final ViewDebug.FlagToString[] flagsMapping = info.property.flagMapping();
+                    if (flagsMapping.length > 0) {
+                        final int intValue = (Integer) value;
+                        final String valuePrefix =
+                                categoryPrefix + prefix + info.name + '_';
+                        exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
+                    }
+
+                    final ViewDebug.IntToString[] mapping = info.property.mapping();
+                    if (mapping.length > 0) {
+                        final int intValue = (Integer) value;
+                        boolean mapped = false;
+                        int mappingCount = mapping.length;
+                        for (int j = 0; j < mappingCount; j++) {
+                            final ViewDebug.IntToString mapper = mapping[j];
+                            if (mapper.from() == intValue) {
+                                value = mapper.to();
+                                mapped = true;
+                                break;
+                            }
+                        }
+
+                        if (!mapped) {
+                            value = intValue;
+                        }
+                    }
+                }
+            } else if (info.returnType == int[].class) {
+                final int[] array = (int[]) value;
+                final String valuePrefix = categoryPrefix + prefix + info.name + '_';
+                exportUnrolledArray(context, out, info.property, array, valuePrefix,
+                        info.entrySuffix);
+
+                continue;
+            } else if (info.returnType == String[].class) {
+                final String[] array = (String[]) value;
+                if (info.property.hasAdjacentMapping() && array != null) {
+                    for (int j = 0; j < array.length; j += 2) {
+                        if (array[j] != null) {
+                            writeEntry(out, categoryPrefix + prefix, array[j],
+                                    info.entrySuffix, array[j + 1] == null ? "null" : array[j + 1]);
+                        }
+                    }
+                }
+
+                continue;
+            } else if (!info.returnType.isPrimitive()) {
+                if (info.property.deepExport()) {
+                    dumpViewProperties(context, value, out, prefix + info.property.prefix());
+                    continue;
+                }
+            }
+
+            writeEntry(out, categoryPrefix + prefix, info.name, info.entrySuffix, value);
+        }
+    }
+
+    private static void writeEntry(BufferedWriter out, String prefix, String name,
+            String suffix, Object value) throws IOException {
+
+        out.write(prefix);
+        out.write(name);
+        out.write(suffix);
+        out.write("=");
+        writeValue(out, value);
+        out.write(' ');
+    }
+
+    private static void exportUnrolledFlags(BufferedWriter out, FlagToString[] mapping,
+            int intValue, String prefix) throws IOException {
+
+        final int count = mapping.length;
+        for (int j = 0; j < count; j++) {
+            final FlagToString flagMapping = mapping[j];
+            final boolean ifTrue = flagMapping.outputIf();
+            final int maskResult = intValue & flagMapping.mask();
+            final boolean test = maskResult == flagMapping.equals();
+            if ((test && ifTrue) || (!test && !ifTrue)) {
+                final String name = flagMapping.name();
+                final String value = formatIntToHexString(maskResult);
+                writeEntry(out, prefix, name, "", value);
+            }
+        }
+    }
+
+    /**
+     * Converts an integer from a field that is mapped with {@link IntToString} to its string
+     * representation.
+     *
+     * @param clazz The class the field is defined on.
+     * @param field The field on which the {@link ExportedProperty} is defined on.
+     * @param integer The value to convert.
+     * @return The value converted into its string representation.
+     * @hide
+     */
+    public static String intToString(Class<?> clazz, String field, int integer) {
+        final IntToString[] mapping = getMapping(clazz, field);
+        if (mapping == null) {
+            return Integer.toString(integer);
+        }
+        final int count = mapping.length;
+        for (int j = 0; j < count; j++) {
+            final IntToString map = mapping[j];
+            if (map.from() == integer) {
+                return map.to();
+            }
+        }
+        return Integer.toString(integer);
+    }
+
+    /**
+     * Converts a set of flags from a field that is mapped with {@link FlagToString} to its string
+     * representation.
+     *
+     * @param clazz The class the field is defined on.
+     * @param field The field on which the {@link ExportedProperty} is defined on.
+     * @param flags The flags to convert.
+     * @return The flags converted into their string representations.
+     * @hide
+     */
+    public static String flagsToString(Class<?> clazz, String field, int flags) {
+        final FlagToString[] mapping = getFlagMapping(clazz, field);
+        if (mapping == null) {
+            return Integer.toHexString(flags);
+        }
+        final StringBuilder result = new StringBuilder();
+        final int count = mapping.length;
+        for (int j = 0; j < count; j++) {
+            final FlagToString flagMapping = mapping[j];
+            final boolean ifTrue = flagMapping.outputIf();
+            final int maskResult = flags & flagMapping.mask();
+            final boolean test = maskResult == flagMapping.equals();
+            if (test && ifTrue) {
+                final String name = flagMapping.name();
+                result.append(name).append(' ');
+            }
+        }
+        if (result.length() > 0) {
+            result.deleteCharAt(result.length() - 1);
+        }
+        return result.toString();
+    }
+
+    private static FlagToString[] getFlagMapping(Class<?> clazz, String field) {
+        try {
+            return clazz.getDeclaredField(field).getAnnotation(ExportedProperty.class)
+                    .flagMapping();
+        } catch (NoSuchFieldException e) {
+            return null;
+        }
+    }
+
+    private static IntToString[] getMapping(Class<?> clazz, String field) {
+        try {
+            return clazz.getDeclaredField(field).getAnnotation(ExportedProperty.class).mapping();
+        } catch (NoSuchFieldException e) {
+            return null;
+        }
+    }
+
+    private static void exportUnrolledArray(Context context, BufferedWriter out,
+            ExportedProperty property, int[] array, String prefix, String suffix)
+            throws IOException {
+
+        final IntToString[] indexMapping = property.indexMapping();
+        final boolean hasIndexMapping = indexMapping.length > 0;
+
+        final IntToString[] mapping = property.mapping();
+        final boolean hasMapping = mapping.length > 0;
+
+        final boolean resolveId = property.resolveId() && context != null;
+        final int valuesCount = array.length;
+
+        for (int j = 0; j < valuesCount; j++) {
+            String name;
+            String value = null;
+
+            final int intValue = array[j];
+
+            name = String.valueOf(j);
+            if (hasIndexMapping) {
+                int mappingCount = indexMapping.length;
+                for (int k = 0; k < mappingCount; k++) {
+                    final IntToString mapped = indexMapping[k];
+                    if (mapped.from() == j) {
+                        name = mapped.to();
+                        break;
+                    }
+                }
+            }
+
+            if (hasMapping) {
+                int mappingCount = mapping.length;
+                for (int k = 0; k < mappingCount; k++) {
+                    final IntToString mapped = mapping[k];
+                    if (mapped.from() == intValue) {
+                        value = mapped.to();
+                        break;
+                    }
+                }
+            }
+
+            if (resolveId) {
+                if (value == null) value = (String) resolveId(context, intValue);
+            } else {
+                value = String.valueOf(intValue);
+            }
+
+            writeEntry(out, prefix, name, suffix, value);
+        }
+    }
+
+    static Object resolveId(Context context, int id) {
+        Object fieldValue;
+        final Resources resources = context.getResources();
+        if (id >= 0) {
+            try {
+                fieldValue = resources.getResourceTypeName(id) + '/' +
+                        resources.getResourceEntryName(id);
+            } catch (Resources.NotFoundException e) {
+                fieldValue = "id/" + formatIntToHexString(id);
+            }
+        } else {
+            fieldValue = "NO_ID";
+        }
+        return fieldValue;
+    }
+
+    private static void writeValue(BufferedWriter out, Object value) throws IOException {
+        if (value != null) {
+            String output = "[EXCEPTION]";
+            try {
+                output = value.toString().replace("\n", "\\n");
+            } finally {
+                out.write(String.valueOf(output.length()));
+                out.write(",");
+                out.write(output);
+            }
+        } else {
+            out.write("4,null");
+        }
+    }
+
+    private static PropertyInfo<CapturedViewProperty, ?>[] getCapturedViewProperties(
+            Class<?> klass) {
+        if (sCapturedViewProperties == null) {
+            sCapturedViewProperties = new HashMap<>();
+        }
+        final HashMap<Class<?>, PropertyInfo<CapturedViewProperty, ?>[]> map =
+                sCapturedViewProperties;
+
+        PropertyInfo<CapturedViewProperty, ?>[] infos = map.get(klass);
+        if (infos == null) {
+            infos = convertToPropertyInfos(klass.getMethods(), klass.getFields(),
+                    CapturedViewProperty.class);
+            map.put(klass, infos);
+        }
+        return infos;
+    }
+
+    private static String exportCapturedViewProperties(Object obj, Class<?> klass, String prefix) {
+        if (obj == null) {
+            return "null";
+        }
+
+        StringBuilder sb = new StringBuilder();
+
+        for (PropertyInfo<CapturedViewProperty, ?> pi : getCapturedViewProperties(klass)) {
+            try {
+                Object methodValue = pi.invoke(obj);
+
+                if (pi.property.retrieveReturn()) {
+                    //we are interested in the second level data only
+                    sb.append(exportCapturedViewProperties(methodValue, pi.returnType,
+                            pi.name + "#"));
+                } else {
+                    sb.append(prefix).append(pi.name).append(pi.entrySuffix).append("=");
+
+                    if (methodValue != null) {
+                        final String value = methodValue.toString().replace("\n", "\\n");
+                        sb.append(value);
+                    } else {
+                        sb.append("null");
+                    }
+                    sb.append(pi.valueSuffix).append(" ");
+                }
+            } catch (Exception e) {
+                //It is OK here, we simply ignore this property
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Dump view info for id based instrument test generation
+     * (and possibly further data analysis). The results are dumped
+     * to the log.
+     * @param tag for log
+     * @param view for dump
+     */
+    public static void dumpCapturedView(String tag, Object view) {
+        Class<?> klass = view.getClass();
+        StringBuilder sb = new StringBuilder(klass.getName() + ": ");
+        sb.append(exportCapturedViewProperties(view, klass, ""));
+        Log.d(tag, sb.toString());
+    }
+
+    /**
+     * Invoke a particular method on given view.
+     * The given method is always invoked on the UI thread. The caller thread will stall until the
+     * method invocation is complete. Returns an object equal to the result of the method
+     * invocation, null if the method is declared to return void
+     * @throws Exception if the method invocation caused any exception
+     * @hide
+     */
+    public static Object invokeViewMethod(final View view, final Method method,
+            final Object[] args) {
+        final CountDownLatch latch = new CountDownLatch(1);
+        final AtomicReference<Object> result = new AtomicReference<Object>();
+        final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
+
+        view.post(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    result.set(method.invoke(view, args));
+                } catch (InvocationTargetException e) {
+                    exception.set(e.getCause());
+                } catch (Exception e) {
+                    exception.set(e);
+                }
+
+                latch.countDown();
+            }
+        });
+
+        try {
+            latch.await();
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+
+        if (exception.get() != null) {
+            throw new RuntimeException(exception.get());
+        }
+
+        return result.get();
+    }
+
+    /**
+     * @hide
+     */
+    public static void setLayoutParameter(final View view, final String param, final int value)
+            throws NoSuchFieldException, IllegalAccessException {
+        final ViewGroup.LayoutParams p = view.getLayoutParams();
+        final Field f = p.getClass().getField(param);
+        if (f.getType() != int.class) {
+            throw new RuntimeException("Only integer layout parameters can be set. Field "
+                    + param + " is of type " + f.getType().getSimpleName());
+        }
+
+        f.set(p, Integer.valueOf(value));
+
+        view.post(new Runnable() {
+            @Override
+            public void run() {
+                view.setLayoutParams(p);
+            }
+        });
+    }
+
+    /**
+     * @hide
+     */
+    public static class SoftwareCanvasProvider implements CanvasProvider {
+
+        private Canvas mCanvas;
+        private Bitmap mBitmap;
+        private boolean mEnabledHwBitmapsInSwMode;
+
+        @Override
+        public Canvas getCanvas(View view, int width, int height) {
+            mBitmap = Bitmap.createBitmap(view.getResources().getDisplayMetrics(),
+                    width, height, Bitmap.Config.ARGB_8888);
+            if (mBitmap == null) {
+                throw new OutOfMemoryError();
+            }
+            mBitmap.setDensity(view.getResources().getDisplayMetrics().densityDpi);
+
+            if (view.mAttachInfo != null) {
+                mCanvas = view.mAttachInfo.mCanvas;
+            }
+            if (mCanvas == null) {
+                mCanvas = new Canvas();
+            }
+            mEnabledHwBitmapsInSwMode = mCanvas.isHwBitmapsInSwModeEnabled();
+            mCanvas.setBitmap(mBitmap);
+            return mCanvas;
+        }
+
+        @Override
+        public Bitmap createBitmap() {
+            mCanvas.setBitmap(null);
+            mCanvas.setHwBitmapsInSwModeEnabled(mEnabledHwBitmapsInSwMode);
+            return mBitmap;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public static class HardwareCanvasProvider implements CanvasProvider {
+        private Picture mPicture;
+
+        @Override
+        public Canvas getCanvas(View view, int width, int height) {
+            mPicture = new Picture();
+            return mPicture.beginRecording(width, height);
+        }
+
+        @Override
+        public Bitmap createBitmap() {
+            mPicture.endRecording();
+            return Bitmap.createBitmap(mPicture);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public interface CanvasProvider {
+
+        /**
+         * Returns a canvas which can be used to draw {@param view}
+         */
+        Canvas getCanvas(View view, int width, int height);
+
+        /**
+         * Creates a bitmap from previously returned canvas
+         * @return
+         */
+        Bitmap createBitmap();
+    }
+}
diff --git a/android/view/ViewFrameInfo.java b/android/view/ViewFrameInfo.java
new file mode 100644
index 0000000..36bf532
--- /dev/null
+++ b/android/view/ViewFrameInfo.java
@@ -0,0 +1,70 @@
+/*
+ * 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.view;
+
+import android.graphics.FrameInfo;
+import android.os.IInputConstants;
+
+/**
+ * The timing information of events taking place in ViewRootImpl
+ * @hide
+ */
+public class ViewFrameInfo {
+    public long drawStart;
+
+
+    // Various flags set to provide extra metadata about the current frame. See flag definitions
+    // inside FrameInfo.
+    // @see android.graphics.FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED
+    public long flags;
+
+    private int mInputEventId;
+
+    /**
+     * Populate the missing fields using the data from ViewFrameInfo
+     * @param frameInfo : the structure FrameInfo object to populate
+     */
+    public void populateFrameInfo(FrameInfo frameInfo) {
+        frameInfo.frameInfo[FrameInfo.FLAGS] |= flags;
+        frameInfo.frameInfo[FrameInfo.DRAW_START] = drawStart;
+        frameInfo.frameInfo[FrameInfo.INPUT_EVENT_ID] = mInputEventId;
+    }
+
+    /**
+     * Reset this data. Should typically be invoked after calling "populateFrameInfo".
+     */
+    public void reset() {
+        drawStart = 0;
+        mInputEventId = IInputConstants.INVALID_INPUT_EVENT_ID;
+        flags = 0;
+    }
+
+    /**
+     * Record the current time, and store it in 'drawStart'
+     */
+    public void markDrawStart() {
+        drawStart = System.nanoTime();
+    }
+
+    /**
+     * Assign the value for input event id
+     * @param eventId the id of the input event
+     */
+    public void setInputEvent(int eventId) {
+        mInputEventId = eventId;
+    }
+}
diff --git a/android/view/ViewGroup.java b/android/view/ViewGroup.java
new file mode 100644
index 0000000..679da31
--- /dev/null
+++ b/android/view/ViewGroup.java
@@ -0,0 +1,9299 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE;
+import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
+
+import android.animation.LayoutTransition;
+import android.annotation.CallSuper;
+import android.annotation.IdRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.annotation.UiThread;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ClipData;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Insets;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.IntArray;
+import android.util.Log;
+import android.util.Pools;
+import android.util.Pools.SynchronizedPool;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.view.WindowInsetsAnimation.Bounds;
+import android.view.WindowInsetsAnimation.Callback.DispatchMode;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.LayoutAnimationController;
+import android.view.animation.Transformation;
+import android.view.autofill.AutofillId;
+import android.view.autofill.Helper;
+import android.view.inspector.InspectableProperty;
+import android.view.inspector.InspectableProperty.EnumEntry;
+import android.view.translation.TranslationCapability;
+import android.view.translation.TranslationSpec.DataFormat;
+import android.view.translation.ViewTranslationRequest;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+/**
+ * <p>
+ * A <code>ViewGroup</code> is a special view that can contain other views
+ * (called children.) The view group is the base class for layouts and views
+ * containers. This class also defines the
+ * {@link android.view.ViewGroup.LayoutParams} class which serves as the base
+ * class for layouts parameters.
+ * </p>
+ *
+ * <p>
+ * Also see {@link LayoutParams} for layout attributes.
+ * </p>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about creating user interface layouts, read the
+ * <a href="{@docRoot}guide/topics/ui/declaring-layout.html">XML Layouts</a> developer
+ * guide.</p></div>
+ *
+ * <p>Here is a complete implementation of a custom ViewGroup that implements
+ * a simple {@link android.widget.FrameLayout} along with the ability to stack
+ * children in left and right gutters.</p>
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/view/CustomLayout.java
+ *      Complete}
+ *
+ * <p>If you are implementing XML layout attributes as shown in the example, this is the
+ * corresponding definition for them that would go in <code>res/values/attrs.xml</code>:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/values/attrs.xml CustomLayout}
+ *
+ * <p>Finally the layout manager can be used in an XML layout like so:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/layout/custom_layout.xml Complete}
+ *
+ * @attr ref android.R.styleable#ViewGroup_clipChildren
+ * @attr ref android.R.styleable#ViewGroup_clipToPadding
+ * @attr ref android.R.styleable#ViewGroup_layoutAnimation
+ * @attr ref android.R.styleable#ViewGroup_animationCache
+ * @attr ref android.R.styleable#ViewGroup_persistentDrawingCache
+ * @attr ref android.R.styleable#ViewGroup_alwaysDrawnWithCache
+ * @attr ref android.R.styleable#ViewGroup_addStatesFromChildren
+ * @attr ref android.R.styleable#ViewGroup_descendantFocusability
+ * @attr ref android.R.styleable#ViewGroup_animateLayoutChanges
+ * @attr ref android.R.styleable#ViewGroup_splitMotionEvents
+ * @attr ref android.R.styleable#ViewGroup_layoutMode
+ */
+@UiThread
+public abstract class ViewGroup extends View implements ViewParent, ViewManager {
+    private static final String TAG = "ViewGroup";
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private static final boolean DBG = false;
+
+    /**
+     * Views which have been hidden or removed which need to be animated on
+     * their way out.
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    @UnsupportedAppUsage
+    protected ArrayList<View> mDisappearingChildren;
+
+    /**
+     * Listener used to propagate events indicating when children are added
+     * and/or removed from a view group.
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768704)
+    protected OnHierarchyChangeListener mOnHierarchyChangeListener;
+
+    // The view contained within this ViewGroup that has or contains focus.
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    private View mFocused;
+    // The view contained within this ViewGroup (excluding nested keyboard navigation clusters)
+    // that is or contains a default-focus view.
+    private View mDefaultFocus;
+    // The last child of this ViewGroup which held focus within the current cluster
+    View mFocusedInCluster;
+
+    /**
+     * A Transformation used when drawing children, to
+     * apply on the child being drawn.
+     */
+    private Transformation mChildTransformation;
+
+    /**
+     * Used to track the current invalidation region.
+     */
+    RectF mInvalidateRegion;
+
+    /**
+     * A Transformation used to calculate a correct
+     * invalidation area when the application is autoscaled.
+     */
+    Transformation mInvalidationTransformation;
+
+    // Current frontmost child that can accept drag and lies under the drag location.
+    // Used only to generate ENTER/EXIT events for pre-Nougat aps.
+    private View mCurrentDragChild;
+
+    // Metadata about the ongoing drag
+    private DragEvent mCurrentDragStartEvent;
+    private boolean mIsInterestedInDrag;
+    private HashSet<View> mChildrenInterestedInDrag;
+
+    // Used during drag dispatch
+    private PointF mLocalPoint;
+
+    // Lazily-created holder for point computations.
+    private float[] mTempPosition;
+
+    // Lazily-created holder for point computations.
+    private Point mTempPoint;
+
+    // Lazily created Rect for dispatch to children
+    private Rect mTempRect;
+
+    // Lazily created int[2] for dispatch to children
+    private int[] mTempLocation;
+
+    // Layout animation
+    private LayoutAnimationController mLayoutAnimationController;
+    private Animation.AnimationListener mAnimationListener;
+
+    // First touch target in the linked list of touch targets.
+    @UnsupportedAppUsage
+    private TouchTarget mFirstTouchTarget;
+
+    // For debugging only.  You can see these in hierarchyviewer.
+    @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
+    @ViewDebug.ExportedProperty(category = "events")
+    private long mLastTouchDownTime;
+    @ViewDebug.ExportedProperty(category = "events")
+    private int mLastTouchDownIndex = -1;
+    @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
+    @ViewDebug.ExportedProperty(category = "events")
+    private float mLastTouchDownX;
+    @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
+    @ViewDebug.ExportedProperty(category = "events")
+    private float mLastTouchDownY;
+
+    // First hover target in the linked list of hover targets.
+    // The hover targets are children which have received ACTION_HOVER_ENTER.
+    // They might not have actually handled the hover event, but we will
+    // continue sending hover events to them as long as the pointer remains over
+    // their bounds and the view group does not intercept hover.
+    private HoverTarget mFirstHoverTarget;
+
+    // True if the view group itself received a hover event.
+    // It might not have actually handled the hover event.
+    private boolean mHoveredSelf;
+
+    // The child capable of showing a tooltip and currently under the pointer.
+    private View mTooltipHoverTarget;
+
+    // True if the view group is capable of showing a tooltip and the pointer is directly
+    // over the view group but not one of its child views.
+    private boolean mTooltipHoveredSelf;
+
+    /**
+     * Internal flags.
+     *
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty(flagMapping = {
+            @ViewDebug.FlagToString(mask = FLAG_CLIP_CHILDREN, equals = FLAG_CLIP_CHILDREN,
+                    name = "CLIP_CHILDREN"),
+            @ViewDebug.FlagToString(mask = FLAG_CLIP_TO_PADDING, equals = FLAG_CLIP_TO_PADDING,
+                    name = "CLIP_TO_PADDING"),
+            @ViewDebug.FlagToString(mask = FLAG_PADDING_NOT_NULL, equals = FLAG_PADDING_NOT_NULL,
+                    name = "PADDING_NOT_NULL")
+    }, formatToHexString = true)
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769411)
+    protected int mGroupFlags;
+
+    /**
+     * Either {@link #LAYOUT_MODE_CLIP_BOUNDS} or {@link #LAYOUT_MODE_OPTICAL_BOUNDS}.
+     */
+    private int mLayoutMode = LAYOUT_MODE_UNDEFINED;
+
+    /**
+     * NOTE: If you change the flags below make sure to reflect the changes
+     *       the DisplayList class
+     */
+
+    // When set, ViewGroup invalidates only the child's rectangle
+    // Set by default
+    static final int FLAG_CLIP_CHILDREN = 0x1;
+
+    // When set, ViewGroup excludes the padding area from the invalidate rectangle
+    // Set by default
+    private static final int FLAG_CLIP_TO_PADDING = 0x2;
+
+    // When set, dispatchDraw() will invoke invalidate(); this is set by drawChild() when
+    // a child needs to be invalidated and FLAG_OPTIMIZE_INVALIDATE is set
+    static final int FLAG_INVALIDATE_REQUIRED  = 0x4;
+
+    // When set, dispatchDraw() will run the layout animation and unset the flag
+    private static final int FLAG_RUN_ANIMATION = 0x8;
+
+    // When set, there is either no layout animation on the ViewGroup or the layout
+    // animation is over
+    // Set by default
+    static final int FLAG_ANIMATION_DONE = 0x10;
+
+    // If set, this ViewGroup has padding; if unset there is no padding and we don't need
+    // to clip it, even if FLAG_CLIP_TO_PADDING is set
+    private static final int FLAG_PADDING_NOT_NULL = 0x20;
+
+    /** @deprecated - functionality removed */
+    @Deprecated
+    private static final int FLAG_ANIMATION_CACHE = 0x40;
+
+    // When set, this ViewGroup converts calls to invalidate(Rect) to invalidate() during a
+    // layout animation; this avoid clobbering the hierarchy
+    // Automatically set when the layout animation starts, depending on the animation's
+    // characteristics
+    static final int FLAG_OPTIMIZE_INVALIDATE = 0x80;
+
+    // When set, the next call to drawChild() will clear mChildTransformation's matrix
+    static final int FLAG_CLEAR_TRANSFORMATION = 0x100;
+
+    // When set, this ViewGroup invokes mAnimationListener.onAnimationEnd() and removes
+    // the children's Bitmap caches if necessary
+    // This flag is set when the layout animation is over (after FLAG_ANIMATION_DONE is set)
+    private static final int FLAG_NOTIFY_ANIMATION_LISTENER = 0x200;
+
+    /**
+     * When set, the drawing method will call {@link #getChildDrawingOrder(int, int)}
+     * to get the index of the child to draw for that iteration.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769377)
+    protected static final int FLAG_USE_CHILD_DRAWING_ORDER = 0x400;
+
+    /**
+     * When set, this ViewGroup supports static transformations on children; this causes
+     * {@link #getChildStaticTransformation(View, android.view.animation.Transformation)} to be
+     * invoked when a child is drawn.
+     *
+     * Any subclass overriding
+     * {@link #getChildStaticTransformation(View, android.view.animation.Transformation)} should
+     * set this flags in {@link #mGroupFlags}.
+     *
+     * {@hide}
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769647)
+    protected static final int FLAG_SUPPORT_STATIC_TRANSFORMATIONS = 0x800;
+
+    // UNUSED FLAG VALUE: 0x1000;
+
+    /**
+     * When set, this ViewGroup's drawable states also include those
+     * of its children.
+     */
+    private static final int FLAG_ADD_STATES_FROM_CHILDREN = 0x2000;
+
+    /** @deprecated functionality removed */
+    @Deprecated
+    private static final int FLAG_ALWAYS_DRAWN_WITH_CACHE = 0x4000;
+
+    /** @deprecated functionality removed */
+    @Deprecated
+    private static final int FLAG_CHILDREN_DRAWN_WITH_CACHE = 0x8000;
+
+    /**
+     * When set, this group will go through its list of children to notify them of
+     * any drawable state change.
+     */
+    private static final int FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE = 0x10000;
+
+    private static final int FLAG_MASK_FOCUSABILITY = 0x60000;
+
+    /**
+     * This view will get focus before any of its descendants.
+     */
+    public static final int FOCUS_BEFORE_DESCENDANTS = 0x20000;
+
+    /**
+     * This view will get focus only if none of its descendants want it.
+     */
+    public static final int FOCUS_AFTER_DESCENDANTS = 0x40000;
+
+    /**
+     * This view will block any of its descendants from getting focus, even
+     * if they are focusable.
+     */
+    public static final int FOCUS_BLOCK_DESCENDANTS = 0x60000;
+
+    /**
+     * Used to map between enum in attrubutes and flag values.
+     */
+    private static final int[] DESCENDANT_FOCUSABILITY_FLAGS =
+            {FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS,
+                    FOCUS_BLOCK_DESCENDANTS};
+
+    /**
+     * When set, this ViewGroup should not intercept touch events.
+     * {@hide}
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123983692)
+    protected static final int FLAG_DISALLOW_INTERCEPT = 0x80000;
+
+    /**
+     * When set, this ViewGroup will split MotionEvents to multiple child Views when appropriate.
+     */
+    private static final int FLAG_SPLIT_MOTION_EVENTS = 0x200000;
+
+    /**
+     * When set, this ViewGroup will not dispatch onAttachedToWindow calls
+     * to children when adding new views. This is used to prevent multiple
+     * onAttached calls when a ViewGroup adds children in its own onAttached method.
+     */
+    private static final int FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW = 0x400000;
+
+    /**
+     * When true, indicates that a layoutMode has been explicitly set, either with
+     * an explicit call to {@link #setLayoutMode(int)} in code or from an XML resource.
+     * This distinguishes the situation in which a layout mode was inherited from
+     * one of the ViewGroup's ancestors and cached locally.
+     */
+    private static final int FLAG_LAYOUT_MODE_WAS_EXPLICITLY_SET = 0x800000;
+
+    static final int FLAG_IS_TRANSITION_GROUP = 0x1000000;
+
+    static final int FLAG_IS_TRANSITION_GROUP_SET = 0x2000000;
+
+    /**
+     * When set, focus will not be permitted to enter this group if a touchscreen is present.
+     */
+    static final int FLAG_TOUCHSCREEN_BLOCKS_FOCUS = 0x4000000;
+
+    /**
+     * When true, indicates that a call to startActionModeForChild was made with the type parameter
+     * and should not be ignored. This helps in backwards compatibility with the existing method
+     * without a type.
+     *
+     * @see #startActionModeForChild(View, android.view.ActionMode.Callback)
+     * @see #startActionModeForChild(View, android.view.ActionMode.Callback, int)
+     */
+    private static final int FLAG_START_ACTION_MODE_FOR_CHILD_IS_TYPED = 0x8000000;
+
+    /**
+     * When true, indicates that a call to startActionModeForChild was made without the type
+     * parameter. This helps in backwards compatibility with the existing method
+     * without a type.
+     *
+     * @see #startActionModeForChild(View, android.view.ActionMode.Callback)
+     * @see #startActionModeForChild(View, android.view.ActionMode.Callback, int)
+     */
+    private static final int FLAG_START_ACTION_MODE_FOR_CHILD_IS_NOT_TYPED = 0x10000000;
+
+    /**
+     * When set, indicates that a call to showContextMenuForChild was made with explicit
+     * coordinates within the initiating child view.
+     */
+    private static final int FLAG_SHOW_CONTEXT_MENU_WITH_COORDS = 0x20000000;
+
+    /**
+     * Indicates which types of drawing caches are to be kept in memory.
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    @UnsupportedAppUsage
+    protected int mPersistentDrawingCache;
+
+    /**
+     * Used to indicate that no drawing cache should be kept in memory.
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
+     */
+    @Deprecated
+    public static final int PERSISTENT_NO_CACHE = 0x0;
+
+    /**
+     * Used to indicate that the animation drawing cache should be kept in memory.
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
+     */
+    @Deprecated
+    public static final int PERSISTENT_ANIMATION_CACHE = 0x1;
+
+    /**
+     * Used to indicate that the scrolling drawing cache should be kept in memory.
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
+     */
+    @Deprecated
+    public static final int PERSISTENT_SCROLLING_CACHE = 0x2;
+
+    /**
+     * Used to indicate that all drawing caches should be kept in memory.
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
+     */
+    @Deprecated
+    public static final int PERSISTENT_ALL_CACHES = 0x3;
+
+    // Layout Modes
+
+    private static final int LAYOUT_MODE_UNDEFINED = -1;
+
+    /**
+     * This constant is a {@link #setLayoutMode(int) layoutMode}.
+     * Clip bounds are the raw values of {@link #getLeft() left}, {@link #getTop() top},
+     * {@link #getRight() right} and {@link #getBottom() bottom}.
+     */
+    public static final int LAYOUT_MODE_CLIP_BOUNDS = 0;
+
+    /**
+     * This constant is a {@link #setLayoutMode(int) layoutMode}.
+     * Optical bounds describe where a widget appears to be. They sit inside the clip
+     * bounds which need to cover a larger area to allow other effects,
+     * such as shadows and glows, to be drawn.
+     */
+    public static final int LAYOUT_MODE_OPTICAL_BOUNDS = 1;
+
+    /** @hide */
+    public static int LAYOUT_MODE_DEFAULT = LAYOUT_MODE_CLIP_BOUNDS;
+
+    /**
+     * We clip to padding when FLAG_CLIP_TO_PADDING and FLAG_PADDING_NOT_NULL
+     * are set at the same time.
+     */
+    protected static final int CLIP_TO_PADDING_MASK = FLAG_CLIP_TO_PADDING | FLAG_PADDING_NOT_NULL;
+
+    // Index of the child's left position in the mLocation array
+    private static final int CHILD_LEFT_INDEX = 0;
+    // Index of the child's top position in the mLocation array
+    private static final int CHILD_TOP_INDEX = 1;
+
+    // Child views of this ViewGroup
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    private View[] mChildren;
+    // Number of valid children in the mChildren array, the rest should be null or not
+    // considered as children
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    private int mChildrenCount;
+
+    // Whether layout calls are currently being suppressed, controlled by calls to
+    // suppressLayout()
+    boolean mSuppressLayout = false;
+
+    // Whether any layout calls have actually been suppressed while mSuppressLayout
+    // has been true. This tracks whether we need to issue a requestLayout() when
+    // layout is later re-enabled.
+    private boolean mLayoutCalledWhileSuppressed = false;
+
+    private static final int ARRAY_INITIAL_CAPACITY = 12;
+    private static final int ARRAY_CAPACITY_INCREMENT = 12;
+
+    private static float[] sDebugLines;
+
+    // Used to draw cached views
+    Paint mCachePaint;
+
+    // Used to animate add/remove changes in layout
+    private LayoutTransition mTransition;
+
+    // The set of views that are currently being transitioned. This list is used to track views
+    // being removed that should not actually be removed from the parent yet because they are
+    // being animated.
+    private ArrayList<View> mTransitioningViews;
+
+    // List of children changing visibility. This is used to potentially keep rendering
+    // views during a transition when they otherwise would have become gone/invisible
+    private ArrayList<View> mVisibilityChangingChildren;
+
+    // Temporary holder of presorted children, only used for
+    // input/software draw dispatch for correctly Z ordering.
+    private ArrayList<View> mPreSortedChildren;
+
+    // Indicates how many of this container's child subtrees contain transient state
+    @ViewDebug.ExportedProperty(category = "layout")
+    private int mChildCountWithTransientState = 0;
+
+    /**
+     * Currently registered axes for nested scrolling. Flag set consisting of
+     * {@link #SCROLL_AXIS_HORIZONTAL} {@link #SCROLL_AXIS_VERTICAL} or {@link #SCROLL_AXIS_NONE}
+     * for null.
+     */
+    private int mNestedScrollAxes;
+
+    // Used to manage the list of transient views, added by addTransientView()
+    private IntArray mTransientIndices = null;
+    private List<View> mTransientViews = null;
+
+    /**
+     * Keeps track of how many child views have UnhandledKeyEventListeners. This should only be
+     * updated on the UI thread so shouldn't require explicit synchronization.
+     */
+    int mChildUnhandledKeyListeners = 0;
+
+    /**
+     * Current dispatch mode of animation events
+     *
+     * @see WindowInsetsAnimation.Callback#getDispatchMode()
+     */
+    private @DispatchMode int mInsetsAnimationDispatchMode = DISPATCH_MODE_CONTINUE_ON_SUBTREE;
+
+    /**
+     * Empty ActionMode used as a sentinel in recursive entries to startActionModeForChild.
+     *
+     * @see #startActionModeForChild(View, android.view.ActionMode.Callback)
+     * @see #startActionModeForChild(View, android.view.ActionMode.Callback, int)
+     */
+    private static final ActionMode SENTINEL_ACTION_MODE = new ActionMode() {
+        @Override
+        public void setTitle(CharSequence title) {}
+
+        @Override
+        public void setTitle(int resId) {}
+
+        @Override
+        public void setSubtitle(CharSequence subtitle) {}
+
+        @Override
+        public void setSubtitle(int resId) {}
+
+        @Override
+        public void setCustomView(View view) {}
+
+        @Override
+        public void invalidate() {}
+
+        @Override
+        public void finish() {}
+
+        @Override
+        public Menu getMenu() {
+            return null;
+        }
+
+        @Override
+        public CharSequence getTitle() {
+            return null;
+        }
+
+        @Override
+        public CharSequence getSubtitle() {
+            return null;
+        }
+
+        @Override
+        public View getCustomView() {
+            return null;
+        }
+
+        @Override
+        public MenuInflater getMenuInflater() {
+            return null;
+        }
+    };
+
+    public ViewGroup(Context context) {
+        this(context, null);
+    }
+
+    public ViewGroup(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+
+        initViewGroup();
+        initFromAttributes(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    private void initViewGroup() {
+        // ViewGroup doesn't draw by default
+        if (!isShowingLayoutBounds()) {
+            setFlags(WILL_NOT_DRAW, DRAW_MASK);
+        }
+        mGroupFlags |= FLAG_CLIP_CHILDREN;
+        mGroupFlags |= FLAG_CLIP_TO_PADDING;
+        mGroupFlags |= FLAG_ANIMATION_DONE;
+        mGroupFlags |= FLAG_ANIMATION_CACHE;
+        mGroupFlags |= FLAG_ALWAYS_DRAWN_WITH_CACHE;
+
+        if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
+            mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS;
+        }
+
+        setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);
+
+        mChildren = new View[ARRAY_INITIAL_CAPACITY];
+        mChildrenCount = 0;
+
+        mPersistentDrawingCache = PERSISTENT_SCROLLING_CACHE;
+    }
+
+    private void initFromAttributes(
+            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewGroup,
+                defStyleAttr, defStyleRes);
+        saveAttributeDataForStyleable(context, R.styleable.ViewGroup, attrs, a, defStyleAttr,
+                defStyleRes);
+
+        final int N = a.getIndexCount();
+        for (int i = 0; i < N; i++) {
+            int attr = a.getIndex(i);
+            switch (attr) {
+                case R.styleable.ViewGroup_clipChildren:
+                    setClipChildren(a.getBoolean(attr, true));
+                    break;
+                case R.styleable.ViewGroup_clipToPadding:
+                    setClipToPadding(a.getBoolean(attr, true));
+                    break;
+                case R.styleable.ViewGroup_animationCache:
+                    setAnimationCacheEnabled(a.getBoolean(attr, true));
+                    break;
+                case R.styleable.ViewGroup_persistentDrawingCache:
+                    setPersistentDrawingCache(a.getInt(attr, PERSISTENT_SCROLLING_CACHE));
+                    break;
+                case R.styleable.ViewGroup_addStatesFromChildren:
+                    setAddStatesFromChildren(a.getBoolean(attr, false));
+                    break;
+                case R.styleable.ViewGroup_alwaysDrawnWithCache:
+                    setAlwaysDrawnWithCacheEnabled(a.getBoolean(attr, true));
+                    break;
+                case R.styleable.ViewGroup_layoutAnimation:
+                    int id = a.getResourceId(attr, -1);
+                    if (id > 0) {
+                        setLayoutAnimation(AnimationUtils.loadLayoutAnimation(mContext, id));
+                    }
+                    break;
+                case R.styleable.ViewGroup_descendantFocusability:
+                    setDescendantFocusability(DESCENDANT_FOCUSABILITY_FLAGS[a.getInt(attr, 0)]);
+                    break;
+                case R.styleable.ViewGroup_splitMotionEvents:
+                    setMotionEventSplittingEnabled(a.getBoolean(attr, false));
+                    break;
+                case R.styleable.ViewGroup_animateLayoutChanges:
+                    boolean animateLayoutChanges = a.getBoolean(attr, false);
+                    if (animateLayoutChanges) {
+                        setLayoutTransition(new LayoutTransition());
+                    }
+                    break;
+                case R.styleable.ViewGroup_layoutMode:
+                    setLayoutMode(a.getInt(attr, LAYOUT_MODE_UNDEFINED));
+                    break;
+                case R.styleable.ViewGroup_transitionGroup:
+                    setTransitionGroup(a.getBoolean(attr, false));
+                    break;
+                case R.styleable.ViewGroup_touchscreenBlocksFocus:
+                    setTouchscreenBlocksFocus(a.getBoolean(attr, false));
+                    break;
+            }
+        }
+
+        a.recycle();
+    }
+
+    /**
+     * Gets the descendant focusability of this view group.  The descendant
+     * focusability defines the relationship between this view group and its
+     * descendants when looking for a view to take focus in
+     * {@link #requestFocus(int, android.graphics.Rect)}.
+     *
+     * @return one of {@link #FOCUS_BEFORE_DESCENDANTS}, {@link #FOCUS_AFTER_DESCENDANTS},
+     *   {@link #FOCUS_BLOCK_DESCENDANTS}.
+     */
+    @ViewDebug.ExportedProperty(category = "focus", mapping = {
+        @ViewDebug.IntToString(from = FOCUS_BEFORE_DESCENDANTS, to = "FOCUS_BEFORE_DESCENDANTS"),
+        @ViewDebug.IntToString(from = FOCUS_AFTER_DESCENDANTS, to = "FOCUS_AFTER_DESCENDANTS"),
+        @ViewDebug.IntToString(from = FOCUS_BLOCK_DESCENDANTS, to = "FOCUS_BLOCK_DESCENDANTS")
+    })
+    @InspectableProperty(enumMapping = {
+            @EnumEntry(value = FOCUS_BEFORE_DESCENDANTS, name = "beforeDescendants"),
+            @EnumEntry(value = FOCUS_AFTER_DESCENDANTS, name = "afterDescendants"),
+            @EnumEntry(value = FOCUS_BLOCK_DESCENDANTS, name = "blocksDescendants")
+    })
+    public int getDescendantFocusability() {
+        return mGroupFlags & FLAG_MASK_FOCUSABILITY;
+    }
+
+    /**
+     * Set the descendant focusability of this view group. This defines the relationship
+     * between this view group and its descendants when looking for a view to
+     * take focus in {@link #requestFocus(int, android.graphics.Rect)}.
+     *
+     * @param focusability one of {@link #FOCUS_BEFORE_DESCENDANTS}, {@link #FOCUS_AFTER_DESCENDANTS},
+     *   {@link #FOCUS_BLOCK_DESCENDANTS}.
+     */
+    public void setDescendantFocusability(int focusability) {
+        switch (focusability) {
+            case FOCUS_BEFORE_DESCENDANTS:
+            case FOCUS_AFTER_DESCENDANTS:
+            case FOCUS_BLOCK_DESCENDANTS:
+                break;
+            default:
+                throw new IllegalArgumentException("must be one of FOCUS_BEFORE_DESCENDANTS, "
+                        + "FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS");
+        }
+        mGroupFlags &= ~FLAG_MASK_FOCUSABILITY;
+        mGroupFlags |= (focusability & FLAG_MASK_FOCUSABILITY);
+    }
+
+    @Override
+    void handleFocusGainInternal(int direction, Rect previouslyFocusedRect) {
+        if (mFocused != null) {
+            mFocused.unFocus(this);
+            mFocused = null;
+            mFocusedInCluster = null;
+        }
+        super.handleFocusGainInternal(direction, previouslyFocusedRect);
+    }
+
+    @Override
+    public void requestChildFocus(View child, View focused) {
+        if (DBG) {
+            System.out.println(this + " requestChildFocus()");
+        }
+        if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
+            return;
+        }
+
+        // Unfocus us, if necessary
+        super.unFocus(focused);
+
+        // We had a previous notion of who had focus. Clear it.
+        if (mFocused != child) {
+            if (mFocused != null) {
+                mFocused.unFocus(focused);
+            }
+
+            mFocused = child;
+        }
+        if (mParent != null) {
+            mParent.requestChildFocus(this, focused);
+        }
+    }
+
+    void setDefaultFocus(View child) {
+        // Stop at any higher view which is explicitly focused-by-default
+        if (mDefaultFocus != null && mDefaultFocus.isFocusedByDefault()) {
+            return;
+        }
+
+        mDefaultFocus = child;
+
+        if (mParent instanceof ViewGroup) {
+            ((ViewGroup) mParent).setDefaultFocus(this);
+        }
+    }
+
+    /**
+     * Clears the default-focus chain from {@param child} up to the first parent which has another
+     * default-focusable branch below it or until there is no default-focus chain.
+     *
+     * @param child
+     */
+    void clearDefaultFocus(View child) {
+        // Stop at any higher view which is explicitly focused-by-default
+        if (mDefaultFocus != child && mDefaultFocus != null
+                && mDefaultFocus.isFocusedByDefault()) {
+            return;
+        }
+
+        mDefaultFocus = null;
+
+        // Search child siblings for default focusables.
+        for (int i = 0; i < mChildrenCount; ++i) {
+            View sibling = mChildren[i];
+            if (sibling.isFocusedByDefault()) {
+                mDefaultFocus = sibling;
+                return;
+            } else if (mDefaultFocus == null && sibling.hasDefaultFocus()) {
+                mDefaultFocus = sibling;
+            }
+        }
+
+        if (mParent instanceof ViewGroup) {
+            ((ViewGroup) mParent).clearDefaultFocus(this);
+        }
+    }
+
+    @Override
+    boolean hasDefaultFocus() {
+        return mDefaultFocus != null || super.hasDefaultFocus();
+    }
+
+    /**
+     * Removes {@code child} (and associated focusedInCluster chain) from the cluster containing
+     * it.
+     * <br>
+     * This is intended to be run on {@code child}'s immediate parent. This is necessary because
+     * the chain is sometimes cleared after {@code child} has been detached.
+     */
+    void clearFocusedInCluster(View child) {
+        if (mFocusedInCluster != child) {
+            return;
+        }
+        clearFocusedInCluster();
+    }
+
+    /**
+     * Removes the focusedInCluster chain from this up to the cluster containing it.
+     */
+    void clearFocusedInCluster() {
+        View top = findKeyboardNavigationCluster();
+        ViewParent parent = this;
+        do {
+            ((ViewGroup) parent).mFocusedInCluster = null;
+            if (parent == top) {
+                break;
+            }
+            parent = parent.getParent();
+        } while (parent instanceof ViewGroup);
+    }
+
+    @Override
+    public void focusableViewAvailable(View v) {
+        if (mParent != null
+                // shortcut: don't report a new focusable view if we block our descendants from
+                // getting focus or if we're not visible
+                && (getDescendantFocusability() != FOCUS_BLOCK_DESCENDANTS)
+                && ((mViewFlags & VISIBILITY_MASK) == VISIBLE)
+                && (isFocusableInTouchMode() || !shouldBlockFocusForTouchscreen())
+                // shortcut: don't report a new focusable view if we already are focused
+                // (and we don't prefer our descendants)
+                //
+                // note: knowing that mFocused is non-null is not a good enough reason
+                // to break the traversal since in that case we'd actually have to find
+                // the focused view and make sure it wasn't FOCUS_AFTER_DESCENDANTS and
+                // an ancestor of v; this will get checked for at ViewAncestor
+                && !(isFocused() && getDescendantFocusability() != FOCUS_AFTER_DESCENDANTS)) {
+            mParent.focusableViewAvailable(v);
+        }
+    }
+
+    @Override
+    public boolean showContextMenuForChild(View originalView) {
+        if (isShowingContextMenuWithCoords()) {
+            // We're being called for compatibility. Return false and let the version
+            // with coordinates recurse up.
+            return false;
+        }
+        return mParent != null && mParent.showContextMenuForChild(originalView);
+    }
+
+    /**
+     * @hide used internally for compatibility with existing app code only
+     */
+    public final boolean isShowingContextMenuWithCoords() {
+        return (mGroupFlags & FLAG_SHOW_CONTEXT_MENU_WITH_COORDS) != 0;
+    }
+
+    @Override
+    public boolean showContextMenuForChild(View originalView, float x, float y) {
+        try {
+            mGroupFlags |= FLAG_SHOW_CONTEXT_MENU_WITH_COORDS;
+            if (showContextMenuForChild(originalView)) {
+                return true;
+            }
+        } finally {
+            mGroupFlags &= ~FLAG_SHOW_CONTEXT_MENU_WITH_COORDS;
+        }
+        return mParent != null && mParent.showContextMenuForChild(originalView, x, y);
+    }
+
+    @Override
+    public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback) {
+        if ((mGroupFlags & FLAG_START_ACTION_MODE_FOR_CHILD_IS_TYPED) == 0) {
+            // This is the original call.
+            try {
+                mGroupFlags |= FLAG_START_ACTION_MODE_FOR_CHILD_IS_NOT_TYPED;
+                return startActionModeForChild(originalView, callback, ActionMode.TYPE_PRIMARY);
+            } finally {
+                mGroupFlags &= ~FLAG_START_ACTION_MODE_FOR_CHILD_IS_NOT_TYPED;
+            }
+        } else {
+            // We are being called from the new method with type.
+            return SENTINEL_ACTION_MODE;
+        }
+    }
+
+    @Override
+    public ActionMode startActionModeForChild(
+            View originalView, ActionMode.Callback callback, int type) {
+        if ((mGroupFlags & FLAG_START_ACTION_MODE_FOR_CHILD_IS_NOT_TYPED) == 0
+                && type == ActionMode.TYPE_PRIMARY) {
+            ActionMode mode;
+            try {
+                mGroupFlags |= FLAG_START_ACTION_MODE_FOR_CHILD_IS_TYPED;
+                mode = startActionModeForChild(originalView, callback);
+            } finally {
+                mGroupFlags &= ~FLAG_START_ACTION_MODE_FOR_CHILD_IS_TYPED;
+            }
+            if (mode != SENTINEL_ACTION_MODE) {
+                return mode;
+            }
+        }
+        if (mParent != null) {
+            try {
+                return mParent.startActionModeForChild(originalView, callback, type);
+            } catch (AbstractMethodError ame) {
+                // Custom view parents might not implement this method.
+                return mParent.startActionModeForChild(originalView, callback);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public boolean dispatchActivityResult(
+            String who, int requestCode, int resultCode, Intent data) {
+        if (super.dispatchActivityResult(who, requestCode, resultCode, data)) {
+            return true;
+        }
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = getChildAt(i);
+            if (child.dispatchActivityResult(who, requestCode, resultCode, data)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Find the nearest view in the specified direction that wants to take
+     * focus.
+     *
+     * @param focused The view that currently has focus
+     * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and
+     *        FOCUS_RIGHT, or 0 for not applicable.
+     */
+    @Override
+    public View focusSearch(View focused, int direction) {
+        if (isRootNamespace()) {
+            // root namespace means we should consider ourselves the top of the
+            // tree for focus searching; otherwise we could be focus searching
+            // into other tabs.  see LocalActivityManager and TabHost for more info.
+            return FocusFinder.getInstance().findNextFocus(this, focused, direction);
+        } else if (mParent != null) {
+            return mParent.focusSearch(focused, direction);
+        }
+        return null;
+    }
+
+    @Override
+    public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
+        return false;
+    }
+
+    @Override
+    public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+        ViewParent parent = mParent;
+        if (parent == null) {
+            return false;
+        }
+        final boolean propagate = onRequestSendAccessibilityEvent(child, event);
+        if (!propagate) {
+            return false;
+        }
+        return parent.requestSendAccessibilityEvent(this, event);
+    }
+
+    /**
+     * Called when a child has requested sending an {@link AccessibilityEvent} and
+     * gives an opportunity to its parent to augment the event.
+     * <p>
+     * If an {@link android.view.View.AccessibilityDelegate} has been specified via calling
+     * {@link android.view.View#setAccessibilityDelegate(android.view.View.AccessibilityDelegate)} its
+     * {@link android.view.View.AccessibilityDelegate#onRequestSendAccessibilityEvent(ViewGroup, View, AccessibilityEvent)}
+     * is responsible for handling this call.
+     * </p>
+     *
+     * @param child The child which requests sending the event.
+     * @param event The event to be sent.
+     * @return True if the event should be sent.
+     *
+     * @see #requestSendAccessibilityEvent(View, AccessibilityEvent)
+     */
+    public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+        if (mAccessibilityDelegate != null) {
+            return mAccessibilityDelegate.onRequestSendAccessibilityEvent(this, child, event);
+        } else {
+            return onRequestSendAccessibilityEventInternal(child, event);
+        }
+    }
+
+    /**
+     * @see #onRequestSendAccessibilityEvent(View, AccessibilityEvent)
+     *
+     * Note: Called from the default {@link View.AccessibilityDelegate}.
+     *
+     * @hide
+     */
+    public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
+        return true;
+    }
+
+    /**
+     * Called when a child view has changed whether or not it is tracking transient state.
+     */
+    @Override
+    public void childHasTransientStateChanged(View child, boolean childHasTransientState) {
+        final boolean oldHasTransientState = hasTransientState();
+        if (childHasTransientState) {
+            mChildCountWithTransientState++;
+        } else {
+            mChildCountWithTransientState--;
+        }
+
+        final boolean newHasTransientState = hasTransientState();
+        if (mParent != null && oldHasTransientState != newHasTransientState) {
+            try {
+                mParent.childHasTransientStateChanged(this, newHasTransientState);
+            } catch (AbstractMethodError e) {
+                Log.e(TAG, mParent.getClass().getSimpleName() +
+                        " does not fully implement ViewParent", e);
+            }
+        }
+    }
+
+    @Override
+    public boolean hasTransientState() {
+        return mChildCountWithTransientState > 0 || super.hasTransientState();
+    }
+
+    @Override
+    public boolean dispatchUnhandledMove(View focused, int direction) {
+        return mFocused != null &&
+                mFocused.dispatchUnhandledMove(focused, direction);
+    }
+
+    @Override
+    public void clearChildFocus(View child) {
+        if (DBG) {
+            System.out.println(this + " clearChildFocus()");
+        }
+
+        mFocused = null;
+        if (mParent != null) {
+            mParent.clearChildFocus(this);
+        }
+    }
+
+    @Override
+    public void clearFocus() {
+        if (DBG) {
+            System.out.println(this + " clearFocus()");
+        }
+        if (mFocused == null) {
+            super.clearFocus();
+        } else {
+            View focused = mFocused;
+            mFocused = null;
+            focused.clearFocus();
+        }
+    }
+
+    @Override
+    void unFocus(View focused) {
+        if (DBG) {
+            System.out.println(this + " unFocus()");
+        }
+        if (mFocused == null) {
+            super.unFocus(focused);
+        } else {
+            mFocused.unFocus(focused);
+            mFocused = null;
+        }
+    }
+
+    /**
+     * Returns the focused child of this view, if any. The child may have focus
+     * or contain focus.
+     *
+     * @return the focused child or null.
+     */
+    public View getFocusedChild() {
+        return mFocused;
+    }
+
+    View getDeepestFocusedChild() {
+        View v = this;
+        while (v != null) {
+            if (v.isFocused()) {
+                return v;
+            }
+            v = v instanceof ViewGroup ? ((ViewGroup) v).getFocusedChild() : null;
+        }
+        return null;
+    }
+
+    /**
+     * Returns true if this view has or contains focus
+     *
+     * @return true if this view has or contains focus
+     */
+    @Override
+    public boolean hasFocus() {
+        return (mPrivateFlags & PFLAG_FOCUSED) != 0 || mFocused != null;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see android.view.View#findFocus()
+     */
+    @Override
+    public View findFocus() {
+        if (DBG) {
+            System.out.println("Find focus in " + this + ": flags="
+                    + isFocused() + ", child=" + mFocused);
+        }
+
+        if (isFocused()) {
+            return this;
+        }
+
+        if (mFocused != null) {
+            return mFocused.findFocus();
+        }
+        return null;
+    }
+
+    @Override
+    boolean hasFocusable(boolean allowAutoFocus, boolean dispatchExplicit) {
+        // This should probably be super.hasFocusable, but that would change
+        // behavior. Historically, we have not checked the ancestor views for
+        // shouldBlockFocusForTouchscreen() in ViewGroup.hasFocusable.
+
+        // Invisible and gone views are never focusable.
+        if ((mViewFlags & VISIBILITY_MASK) != VISIBLE) {
+            return false;
+        }
+
+        // Only use effective focusable value when allowed.
+        if ((allowAutoFocus || getFocusable() != FOCUSABLE_AUTO) && isFocusable()) {
+            return true;
+        }
+
+        // Determine whether we have a focused descendant.
+        final int descendantFocusability = getDescendantFocusability();
+        if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
+            return hasFocusableChild(dispatchExplicit);
+        }
+
+        return false;
+    }
+
+    boolean hasFocusableChild(boolean dispatchExplicit) {
+        // Determine whether we have a focusable descendant.
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+
+        for (int i = 0; i < count; i++) {
+            final View child = children[i];
+
+            // In case the subclass has overridden has[Explicit]Focusable, dispatch
+            // to the expected one for each child even though we share logic here.
+            if ((dispatchExplicit && child.hasExplicitFocusable())
+                    || (!dispatchExplicit && child.hasFocusable())) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
+        final int focusableCount = views.size();
+
+        final int descendantFocusability = getDescendantFocusability();
+        final boolean blockFocusForTouchscreen = shouldBlockFocusForTouchscreen();
+        final boolean focusSelf = (isFocusableInTouchMode() || !blockFocusForTouchscreen);
+
+        if (descendantFocusability == FOCUS_BLOCK_DESCENDANTS) {
+            if (focusSelf) {
+                super.addFocusables(views, direction, focusableMode);
+            }
+            return;
+        }
+
+        if (blockFocusForTouchscreen) {
+            focusableMode |= FOCUSABLES_TOUCH_MODE;
+        }
+
+        if ((descendantFocusability == FOCUS_BEFORE_DESCENDANTS) && focusSelf) {
+            super.addFocusables(views, direction, focusableMode);
+        }
+
+        int count = 0;
+        final View[] children = new View[mChildrenCount];
+        for (int i = 0; i < mChildrenCount; ++i) {
+            View child = mChildren[i];
+            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
+                children[count++] = child;
+            }
+        }
+        FocusFinder.sort(children, 0, count, this, isLayoutRtl());
+        for (int i = 0; i < count; ++i) {
+            children[i].addFocusables(views, direction, focusableMode);
+        }
+
+        // When set to FOCUS_AFTER_DESCENDANTS, we only add ourselves if
+        // there aren't any focusable descendants.  this is
+        // to avoid the focus search finding layouts when a more precise search
+        // among the focusable children would be more interesting.
+        if ((descendantFocusability == FOCUS_AFTER_DESCENDANTS) && focusSelf
+                && focusableCount == views.size()) {
+            super.addFocusables(views, direction, focusableMode);
+        }
+    }
+
+    @Override
+    public void addKeyboardNavigationClusters(Collection<View> views, int direction) {
+        final int focusableCount = views.size();
+
+        if (isKeyboardNavigationCluster()) {
+            // Cluster-navigation can enter a touchscreenBlocksFocus cluster, so temporarily
+            // disable touchscreenBlocksFocus to evaluate whether it contains focusables.
+            final boolean blockedFocus = getTouchscreenBlocksFocus();
+            try {
+                setTouchscreenBlocksFocusNoRefocus(false);
+                super.addKeyboardNavigationClusters(views, direction);
+            } finally {
+                setTouchscreenBlocksFocusNoRefocus(blockedFocus);
+            }
+        } else {
+            super.addKeyboardNavigationClusters(views, direction);
+        }
+
+        if (focusableCount != views.size()) {
+            // No need to look for groups inside a group.
+            return;
+        }
+
+        if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
+            return;
+        }
+
+        int count = 0;
+        final View[] visibleChildren = new View[mChildrenCount];
+        for (int i = 0; i < mChildrenCount; ++i) {
+            final View child = mChildren[i];
+            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
+                visibleChildren[count++] = child;
+            }
+        }
+        FocusFinder.sort(visibleChildren, 0, count, this, isLayoutRtl());
+        for (int i = 0; i < count; ++i) {
+            visibleChildren[i].addKeyboardNavigationClusters(views, direction);
+        }
+    }
+
+    /**
+     * Set whether this ViewGroup should ignore focus requests for itself and its children.
+     * If this option is enabled and the ViewGroup or a descendant currently has focus, focus
+     * will proceed forward.
+     *
+     * @param touchscreenBlocksFocus true to enable blocking focus in the presence of a touchscreen
+     */
+    public void setTouchscreenBlocksFocus(boolean touchscreenBlocksFocus) {
+        if (touchscreenBlocksFocus) {
+            mGroupFlags |= FLAG_TOUCHSCREEN_BLOCKS_FOCUS;
+            if (hasFocus() && !isKeyboardNavigationCluster()) {
+                final View focusedChild = getDeepestFocusedChild();
+                if (!focusedChild.isFocusableInTouchMode()) {
+                    final View newFocus = focusSearch(FOCUS_FORWARD);
+                    if (newFocus != null) {
+                        newFocus.requestFocus();
+                    }
+                }
+            }
+        } else {
+            mGroupFlags &= ~FLAG_TOUCHSCREEN_BLOCKS_FOCUS;
+        }
+    }
+
+    private void setTouchscreenBlocksFocusNoRefocus(boolean touchscreenBlocksFocus) {
+        if (touchscreenBlocksFocus) {
+            mGroupFlags |= FLAG_TOUCHSCREEN_BLOCKS_FOCUS;
+        } else {
+            mGroupFlags &= ~FLAG_TOUCHSCREEN_BLOCKS_FOCUS;
+        }
+    }
+
+    /**
+     * Check whether this ViewGroup should ignore focus requests for itself and its children.
+     */
+    @ViewDebug.ExportedProperty(category = "focus")
+    @InspectableProperty
+    public boolean getTouchscreenBlocksFocus() {
+        return (mGroupFlags & FLAG_TOUCHSCREEN_BLOCKS_FOCUS) != 0;
+    }
+
+    boolean shouldBlockFocusForTouchscreen() {
+        // There is a special case for keyboard-navigation clusters. We allow cluster navigation
+        // to jump into blockFocusForTouchscreen ViewGroups which are clusters. Once in the
+        // cluster, focus is free to move around within it.
+        return getTouchscreenBlocksFocus() &&
+                mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
+                && !(isKeyboardNavigationCluster()
+                        && (hasFocus() || (findKeyboardNavigationCluster() != this)));
+    }
+
+    @Override
+    public void findViewsWithText(ArrayList<View> outViews, CharSequence text, int flags) {
+        super.findViewsWithText(outViews, text, flags);
+        final int childrenCount = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < childrenCount; i++) {
+            View child = children[i];
+            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
+                    && (child.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
+                child.findViewsWithText(outViews, text, flags);
+            }
+        }
+    }
+
+    /** @hide */
+    @Override
+    public View findViewByAccessibilityIdTraversal(int accessibilityId) {
+        View foundView = super.findViewByAccessibilityIdTraversal(accessibilityId);
+        if (foundView != null) {
+            return foundView;
+        }
+
+        if (getAccessibilityNodeProvider() != null) {
+            return null;
+        }
+
+        final int childrenCount = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < childrenCount; i++) {
+            View child = children[i];
+            foundView = child.findViewByAccessibilityIdTraversal(accessibilityId);
+            if (foundView != null) {
+                return foundView;
+            }
+        }
+
+        return null;
+    }
+
+    /** @hide */
+    @Override
+    public View findViewByAutofillIdTraversal(int autofillId) {
+        View foundView = super.findViewByAutofillIdTraversal(autofillId);
+        if (foundView != null) {
+            return foundView;
+        }
+
+        final int childrenCount = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < childrenCount; i++) {
+            View child = children[i];
+            foundView = child.findViewByAutofillIdTraversal(autofillId);
+            if (foundView != null) {
+                return foundView;
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    public void dispatchWindowFocusChanged(boolean hasFocus) {
+        super.dispatchWindowFocusChanged(hasFocus);
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            children[i].dispatchWindowFocusChanged(hasFocus);
+        }
+    }
+
+    @Override
+    public void addTouchables(ArrayList<View> views) {
+        super.addTouchables(views);
+
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+
+        for (int i = 0; i < count; i++) {
+            final View child = children[i];
+            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
+                child.addTouchables(views);
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    @UnsupportedAppUsage
+    public void makeOptionalFitsSystemWindows() {
+        super.makeOptionalFitsSystemWindows();
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            children[i].makeOptionalFitsSystemWindows();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void makeFrameworkOptionalFitsSystemWindows() {
+        super.makeFrameworkOptionalFitsSystemWindows();
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            children[i].makeFrameworkOptionalFitsSystemWindows();
+        }
+    }
+
+    @Override
+    public void dispatchDisplayHint(int hint) {
+        super.dispatchDisplayHint(hint);
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            children[i].dispatchDisplayHint(hint);
+        }
+    }
+
+    /**
+     * Called when a view's visibility has changed. Notify the parent to take any appropriate
+     * action.
+     *
+     * @param child The view whose visibility has changed
+     * @param oldVisibility The previous visibility value (GONE, INVISIBLE, or VISIBLE).
+     * @param newVisibility The new visibility value (GONE, INVISIBLE, or VISIBLE).
+     * @hide
+     */
+    @UnsupportedAppUsage
+    protected void onChildVisibilityChanged(View child, int oldVisibility, int newVisibility) {
+        if (mTransition != null) {
+            if (newVisibility == VISIBLE) {
+                mTransition.showChild(this, child, oldVisibility);
+            } else {
+                mTransition.hideChild(this, child, newVisibility);
+                if (mTransitioningViews != null && mTransitioningViews.contains(child)) {
+                    // Only track this on disappearing views - appearing views are already visible
+                    // and don't need special handling during drawChild()
+                    if (mVisibilityChangingChildren == null) {
+                        mVisibilityChangingChildren = new ArrayList<View>();
+                    }
+                    mVisibilityChangingChildren.add(child);
+                    addDisappearingView(child);
+                }
+            }
+        }
+
+        // in all cases, for drags
+        if (newVisibility == VISIBLE && mCurrentDragStartEvent != null) {
+            if (!mChildrenInterestedInDrag.contains(child)) {
+                notifyChildOfDragStart(child);
+            }
+        }
+    }
+
+    @Override
+    protected void dispatchVisibilityChanged(View changedView, int visibility) {
+        super.dispatchVisibilityChanged(changedView, visibility);
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            children[i].dispatchVisibilityChanged(changedView, visibility);
+        }
+    }
+
+    @Override
+    public void dispatchWindowVisibilityChanged(int visibility) {
+        super.dispatchWindowVisibilityChanged(visibility);
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            children[i].dispatchWindowVisibilityChanged(visibility);
+        }
+    }
+
+    @Override
+    boolean dispatchVisibilityAggregated(boolean isVisible) {
+        isVisible = super.dispatchVisibilityAggregated(isVisible);
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            // Only dispatch to visible children. Not visible children and their subtrees already
+            // know that they aren't visible and that's not going to change as a result of
+            // whatever triggered this dispatch.
+            if (children[i].getVisibility() == VISIBLE) {
+                children[i].dispatchVisibilityAggregated(isVisible);
+            }
+        }
+        return isVisible;
+    }
+
+    @Override
+    public void dispatchConfigurationChanged(Configuration newConfig) {
+        super.dispatchConfigurationChanged(newConfig);
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            children[i].dispatchConfigurationChanged(newConfig);
+        }
+    }
+
+    @Override
+    public void recomputeViewAttributes(View child) {
+        if (mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) {
+            ViewParent parent = mParent;
+            if (parent != null) parent.recomputeViewAttributes(this);
+        }
+    }
+
+    @Override
+    void dispatchCollectViewAttributes(AttachInfo attachInfo, int visibility) {
+        if ((visibility & VISIBILITY_MASK) == VISIBLE) {
+            super.dispatchCollectViewAttributes(attachInfo, visibility);
+            final int count = mChildrenCount;
+            final View[] children = mChildren;
+            for (int i = 0; i < count; i++) {
+                final View child = children[i];
+                child.dispatchCollectViewAttributes(attachInfo,
+                        visibility | (child.mViewFlags&VISIBILITY_MASK));
+            }
+        }
+    }
+
+    @Override
+    public void bringChildToFront(View child) {
+        final int index = indexOfChild(child);
+        if (index >= 0) {
+            removeFromArray(index);
+            addInArray(child, mChildrenCount);
+            child.mParent = this;
+            requestLayout();
+            invalidate();
+        }
+    }
+
+    private PointF getLocalPoint() {
+        if (mLocalPoint == null) mLocalPoint = new PointF();
+        return mLocalPoint;
+    }
+
+    @Override
+    boolean dispatchDragEnterExitInPreN(DragEvent event) {
+        if (event.mAction == DragEvent.ACTION_DRAG_EXITED && mCurrentDragChild != null) {
+            // The drag exited a sub-tree of views; notify of the exit all descendants that are in
+            // entered state.
+            // We don't need this recursive delivery for ENTERED events because they get generated
+            // from the recursive delivery of LOCATION/DROP events, and hence, don't need their own
+            // recursion.
+            mCurrentDragChild.dispatchDragEnterExitInPreN(event);
+            mCurrentDragChild = null;
+        }
+        return mIsInterestedInDrag && super.dispatchDragEnterExitInPreN(event);
+    }
+
+    // TODO: Write real docs
+    @Override
+    public boolean dispatchDragEvent(DragEvent event) {
+        boolean retval = false;
+        final float tx = event.mX;
+        final float ty = event.mY;
+        final ClipData td = event.mClipData;
+
+        // Dispatch down the view hierarchy
+        final PointF localPoint = getLocalPoint();
+
+        switch (event.mAction) {
+        case DragEvent.ACTION_DRAG_STARTED: {
+            // Clear the state to recalculate which views we drag over.
+            mCurrentDragChild = null;
+
+            // Set up our tracking of drag-started notifications
+            mCurrentDragStartEvent = DragEvent.obtain(event);
+            if (mChildrenInterestedInDrag == null) {
+                mChildrenInterestedInDrag = new HashSet<View>();
+            } else {
+                mChildrenInterestedInDrag.clear();
+            }
+
+            // Now dispatch down to our children, caching the responses
+            final int count = mChildrenCount;
+            final View[] children = mChildren;
+            for (int i = 0; i < count; i++) {
+                final View child = children[i];
+                child.mPrivateFlags2 &= ~View.DRAG_MASK;
+                if (child.getVisibility() == VISIBLE) {
+                    if (notifyChildOfDragStart(children[i])) {
+                        retval = true;
+                    }
+                }
+            }
+
+            // Notify itself of the drag start.
+            mIsInterestedInDrag = super.dispatchDragEvent(event);
+            if (mIsInterestedInDrag) {
+                retval = true;
+            }
+
+            if (!retval) {
+                // Neither us nor any of our children are interested in this drag, so stop tracking
+                // the current drag event.
+                mCurrentDragStartEvent.recycle();
+                mCurrentDragStartEvent = null;
+            }
+        } break;
+
+        case DragEvent.ACTION_DRAG_ENDED: {
+            // Release the bookkeeping now that the drag lifecycle has ended
+            final HashSet<View> childrenInterestedInDrag = mChildrenInterestedInDrag;
+            if (childrenInterestedInDrag != null) {
+                for (View child : childrenInterestedInDrag) {
+                    // If a child was interested in the ongoing drag, it's told that it's over
+                    if (child.dispatchDragEvent(event)) {
+                        retval = true;
+                    }
+                }
+                childrenInterestedInDrag.clear();
+            }
+            if (mCurrentDragStartEvent != null) {
+                mCurrentDragStartEvent.recycle();
+                mCurrentDragStartEvent = null;
+            }
+
+            if (mIsInterestedInDrag) {
+                if (super.dispatchDragEvent(event)) {
+                    retval = true;
+                }
+                mIsInterestedInDrag = false;
+            }
+        } break;
+
+        case DragEvent.ACTION_DRAG_LOCATION:
+        case DragEvent.ACTION_DROP: {
+            // Find the [possibly new] drag target
+            View target = findFrontmostDroppableChildAt(event.mX, event.mY, localPoint);
+
+            if (target != mCurrentDragChild) {
+                if (sCascadedDragDrop) {
+                    // For pre-Nougat apps, make sure that the whole hierarchy of views that contain
+                    // the drag location is kept in the state between ENTERED and EXITED events.
+                    // (Starting with N, only the innermost view will be in that state).
+
+                    final int action = event.mAction;
+                    // Position should not be available for ACTION_DRAG_ENTERED and
+                    // ACTION_DRAG_EXITED.
+                    event.mX = 0;
+                    event.mY = 0;
+                    event.mClipData = null;
+
+                    if (mCurrentDragChild != null) {
+                        event.mAction = DragEvent.ACTION_DRAG_EXITED;
+                        mCurrentDragChild.dispatchDragEnterExitInPreN(event);
+                    }
+
+                    if (target != null) {
+                        event.mAction = DragEvent.ACTION_DRAG_ENTERED;
+                        target.dispatchDragEnterExitInPreN(event);
+                    }
+
+                    event.mAction = action;
+                    event.mX = tx;
+                    event.mY = ty;
+                    event.mClipData = td;
+                }
+                mCurrentDragChild = target;
+            }
+
+            if (target == null && mIsInterestedInDrag) {
+                target = this;
+            }
+
+            // Dispatch the actual drag notice, localized into the target coordinates.
+            if (target != null) {
+                if (target != this) {
+                    event.mX = localPoint.x;
+                    event.mY = localPoint.y;
+
+                    retval = target.dispatchDragEvent(event);
+
+                    event.mX = tx;
+                    event.mY = ty;
+
+                    if (mIsInterestedInDrag) {
+                        final boolean eventWasConsumed;
+                        if (sCascadedDragDrop) {
+                            eventWasConsumed = retval;
+                        } else {
+                            eventWasConsumed = event.mEventHandlerWasCalled;
+                        }
+
+                        if (!eventWasConsumed) {
+                            retval = super.dispatchDragEvent(event);
+                        }
+                    }
+                } else {
+                    retval = super.dispatchDragEvent(event);
+                }
+            }
+        } break;
+        }
+
+        return retval;
+    }
+
+    // Find the frontmost child view that lies under the given point, and calculate
+    // the position within its own local coordinate system.
+    View findFrontmostDroppableChildAt(float x, float y, PointF outLocalPoint) {
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = count - 1; i >= 0; i--) {
+            final View child = children[i];
+            if (!child.canAcceptDrag()) {
+                continue;
+            }
+
+            if (isTransformedTouchPointInView(x, y, child, outLocalPoint)) {
+                return child;
+            }
+        }
+        return null;
+    }
+
+    boolean notifyChildOfDragStart(View child) {
+        // The caller guarantees that the child is not in mChildrenInterestedInDrag yet.
+
+        if (ViewDebug.DEBUG_DRAG) {
+            Log.d(View.VIEW_LOG_TAG, "Sending drag-started to view: " + child);
+        }
+
+        final float tx = mCurrentDragStartEvent.mX;
+        final float ty = mCurrentDragStartEvent.mY;
+
+        final float[] point = getTempLocationF();
+        point[0] = tx;
+        point[1] = ty;
+        transformPointToViewLocal(point, child);
+
+        mCurrentDragStartEvent.mX = point[0];
+        mCurrentDragStartEvent.mY = point[1];
+        final boolean canAccept = child.dispatchDragEvent(mCurrentDragStartEvent);
+        mCurrentDragStartEvent.mX = tx;
+        mCurrentDragStartEvent.mY = ty;
+        mCurrentDragStartEvent.mEventHandlerWasCalled = false;
+        if (canAccept) {
+            mChildrenInterestedInDrag.add(child);
+            if (!child.canAcceptDrag()) {
+                child.mPrivateFlags2 |= View.PFLAG2_DRAG_CAN_ACCEPT;
+                child.refreshDrawableState();
+            }
+        }
+        return canAccept;
+    }
+
+    @Override
+    @Deprecated
+    public void dispatchWindowSystemUiVisiblityChanged(int visible) {
+        super.dispatchWindowSystemUiVisiblityChanged(visible);
+
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i=0; i <count; i++) {
+            final View child = children[i];
+            child.dispatchWindowSystemUiVisiblityChanged(visible);
+        }
+    }
+
+    @Override
+    @Deprecated
+    public void dispatchSystemUiVisibilityChanged(int visible) {
+        super.dispatchSystemUiVisibilityChanged(visible);
+
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i=0; i <count; i++) {
+            final View child = children[i];
+            child.dispatchSystemUiVisibilityChanged(visible);
+        }
+    }
+
+    @Override
+    boolean updateLocalSystemUiVisibility(int localValue, int localChanges) {
+        boolean changed = super.updateLocalSystemUiVisibility(localValue, localChanges);
+
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i=0; i <count; i++) {
+            final View child = children[i];
+            changed |= child.updateLocalSystemUiVisibility(localValue, localChanges);
+        }
+        return changed;
+    }
+
+    @Override
+    public boolean dispatchKeyEventPreIme(KeyEvent event) {
+        if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
+                == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
+            return super.dispatchKeyEventPreIme(event);
+        } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
+                == PFLAG_HAS_BOUNDS) {
+            return mFocused.dispatchKeyEventPreIme(event);
+        }
+        return false;
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        if (mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onKeyEvent(event, 1);
+        }
+
+        if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
+                == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
+            if (super.dispatchKeyEvent(event)) {
+                return true;
+            }
+        } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
+                == PFLAG_HAS_BOUNDS) {
+            if (mFocused.dispatchKeyEvent(event)) {
+                return true;
+            }
+        }
+
+        if (mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
+        }
+        return false;
+    }
+
+    @Override
+    public boolean dispatchKeyShortcutEvent(KeyEvent event) {
+        if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
+                == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
+            return super.dispatchKeyShortcutEvent(event);
+        } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
+                == PFLAG_HAS_BOUNDS) {
+            return mFocused.dispatchKeyShortcutEvent(event);
+        }
+        return false;
+    }
+
+    @Override
+    public boolean dispatchTrackballEvent(MotionEvent event) {
+        if (mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onTrackballEvent(event, 1);
+        }
+
+        if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
+                == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
+            if (super.dispatchTrackballEvent(event)) {
+                return true;
+            }
+        } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
+                == PFLAG_HAS_BOUNDS) {
+            if (mFocused.dispatchTrackballEvent(event)) {
+                return true;
+            }
+        }
+
+        if (mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
+        }
+        return false;
+    }
+
+    @Override
+    public boolean dispatchCapturedPointerEvent(MotionEvent event) {
+        if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
+                == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
+            if (super.dispatchCapturedPointerEvent(event)) {
+                return true;
+            }
+        } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
+                == PFLAG_HAS_BOUNDS) {
+            if (mFocused.dispatchCapturedPointerEvent(event)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void dispatchPointerCaptureChanged(boolean hasCapture) {
+        exitHoverTargets();
+
+        super.dispatchPointerCaptureChanged(hasCapture);
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            children[i].dispatchPointerCaptureChanged(hasCapture);
+        }
+    }
+
+    @Override
+    public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
+        final float x = event.getX(pointerIndex);
+        final float y = event.getY(pointerIndex);
+        if (isOnScrollbarThumb(x, y) || isDraggingScrollBar()) {
+            return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_ARROW);
+        }
+        // Check what the child under the pointer says about the pointer.
+        final int childrenCount = mChildrenCount;
+        if (childrenCount != 0) {
+            final ArrayList<View> preorderedList = buildOrderedChildList();
+            final boolean customOrder = preorderedList == null
+                    && isChildrenDrawingOrderEnabled();
+            final View[] children = mChildren;
+            for (int i = childrenCount - 1; i >= 0; i--) {
+                final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+                final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
+
+                if (!child.canReceivePointerEvents()
+                        || !isTransformedTouchPointInView(x, y, child, null)) {
+                    continue;
+                }
+                final PointerIcon pointerIcon =
+                        dispatchResolvePointerIcon(event, pointerIndex, child);
+                if (pointerIcon != null) {
+                    if (preorderedList != null) preorderedList.clear();
+                    return pointerIcon;
+                }
+            }
+            if (preorderedList != null) preorderedList.clear();
+        }
+
+        // The pointer is not a child or the child has no preferences, returning the default
+        // implementation.
+        return super.onResolvePointerIcon(event, pointerIndex);
+    }
+
+    private PointerIcon dispatchResolvePointerIcon(MotionEvent event, int pointerIndex,
+            View child) {
+        final PointerIcon pointerIcon;
+        if (!child.hasIdentityMatrix()) {
+            MotionEvent transformedEvent = getTransformedMotionEvent(event, child);
+            pointerIcon = child.onResolvePointerIcon(transformedEvent, pointerIndex);
+            transformedEvent.recycle();
+        } else {
+            final float offsetX = mScrollX - child.mLeft;
+            final float offsetY = mScrollY - child.mTop;
+            event.offsetLocation(offsetX, offsetY);
+            pointerIcon = child.onResolvePointerIcon(event, pointerIndex);
+            event.offsetLocation(-offsetX, -offsetY);
+        }
+        return pointerIcon;
+    }
+
+    private int getAndVerifyPreorderedIndex(int childrenCount, int i, boolean customOrder) {
+        final int childIndex;
+        if (customOrder) {
+            final int childIndex1 = getChildDrawingOrder(childrenCount, i);
+            if (childIndex1 >= childrenCount) {
+                throw new IndexOutOfBoundsException("getChildDrawingOrder() "
+                        + "returned invalid index " + childIndex1
+                        + " (child count is " + childrenCount + ")");
+            }
+            childIndex = childIndex1;
+        } else {
+            childIndex = i;
+        }
+        return childIndex;
+    }
+
+    @SuppressWarnings({"ConstantConditions"})
+    @Override
+    protected boolean dispatchHoverEvent(MotionEvent event) {
+        final int action = event.getAction();
+
+        // First check whether the view group wants to intercept the hover event.
+        final boolean interceptHover = onInterceptHoverEvent(event);
+        event.setAction(action); // restore action in case it was changed
+
+        MotionEvent eventNoHistory = event;
+        boolean handled = false;
+
+        // Send events to the hovered children and build a new list of hover targets until
+        // one is found that handles the event.
+        HoverTarget firstOldHoverTarget = mFirstHoverTarget;
+        mFirstHoverTarget = null;
+        if (!interceptHover && action != MotionEvent.ACTION_HOVER_EXIT) {
+            final float x = event.getX();
+            final float y = event.getY();
+            final int childrenCount = mChildrenCount;
+            if (childrenCount != 0) {
+                final ArrayList<View> preorderedList = buildOrderedChildList();
+                final boolean customOrder = preorderedList == null
+                        && isChildrenDrawingOrderEnabled();
+                final View[] children = mChildren;
+                HoverTarget lastHoverTarget = null;
+                for (int i = childrenCount - 1; i >= 0; i--) {
+                    final int childIndex = getAndVerifyPreorderedIndex(
+                            childrenCount, i, customOrder);
+                    final View child = getAndVerifyPreorderedView(
+                            preorderedList, children, childIndex);
+                    if (!child.canReceivePointerEvents()
+                            || !isTransformedTouchPointInView(x, y, child, null)) {
+                        continue;
+                    }
+
+                    // Obtain a hover target for this child.  Dequeue it from the
+                    // old hover target list if the child was previously hovered.
+                    HoverTarget hoverTarget = firstOldHoverTarget;
+                    final boolean wasHovered;
+                    for (HoverTarget predecessor = null; ;) {
+                        if (hoverTarget == null) {
+                            hoverTarget = HoverTarget.obtain(child);
+                            wasHovered = false;
+                            break;
+                        }
+
+                        if (hoverTarget.child == child) {
+                            if (predecessor != null) {
+                                predecessor.next = hoverTarget.next;
+                            } else {
+                                firstOldHoverTarget = hoverTarget.next;
+                            }
+                            hoverTarget.next = null;
+                            wasHovered = true;
+                            break;
+                        }
+
+                        predecessor = hoverTarget;
+                        hoverTarget = hoverTarget.next;
+                    }
+
+                    // Enqueue the hover target onto the new hover target list.
+                    if (lastHoverTarget != null) {
+                        lastHoverTarget.next = hoverTarget;
+                    } else {
+                        mFirstHoverTarget = hoverTarget;
+                    }
+                    lastHoverTarget = hoverTarget;
+
+                    // Dispatch the event to the child.
+                    if (action == MotionEvent.ACTION_HOVER_ENTER) {
+                        if (!wasHovered) {
+                            // Send the enter as is.
+                            handled |= dispatchTransformedGenericPointerEvent(
+                                    event, child); // enter
+                        }
+                    } else if (action == MotionEvent.ACTION_HOVER_MOVE) {
+                        if (!wasHovered) {
+                            // Synthesize an enter from a move.
+                            eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory);
+                            eventNoHistory.setAction(MotionEvent.ACTION_HOVER_ENTER);
+                            handled |= dispatchTransformedGenericPointerEvent(
+                                    eventNoHistory, child); // enter
+                            eventNoHistory.setAction(action);
+
+                            handled |= dispatchTransformedGenericPointerEvent(
+                                    eventNoHistory, child); // move
+                        } else {
+                            // Send the move as is.
+                            handled |= dispatchTransformedGenericPointerEvent(event, child);
+                        }
+                    }
+                    if (handled) {
+                        break;
+                    }
+                }
+                if (preorderedList != null) preorderedList.clear();
+            }
+        }
+
+        // Send exit events to all previously hovered children that are no longer hovered.
+        while (firstOldHoverTarget != null) {
+            final View child = firstOldHoverTarget.child;
+
+            // Exit the old hovered child.
+            if (action == MotionEvent.ACTION_HOVER_EXIT) {
+                // Send the exit as is.
+                handled |= dispatchTransformedGenericPointerEvent(
+                        event, child); // exit
+            } else {
+                // Synthesize an exit from a move or enter.
+                // Ignore the result because hover focus has moved to a different view.
+                if (action == MotionEvent.ACTION_HOVER_MOVE) {
+                    final boolean hoverExitPending = event.isHoverExitPending();
+                    event.setHoverExitPending(true);
+                    dispatchTransformedGenericPointerEvent(
+                            event, child); // move
+                    event.setHoverExitPending(hoverExitPending);
+                }
+                eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory);
+                eventNoHistory.setAction(MotionEvent.ACTION_HOVER_EXIT);
+                dispatchTransformedGenericPointerEvent(
+                        eventNoHistory, child); // exit
+                eventNoHistory.setAction(action);
+            }
+
+            final HoverTarget nextOldHoverTarget = firstOldHoverTarget.next;
+            firstOldHoverTarget.recycle();
+            firstOldHoverTarget = nextOldHoverTarget;
+        }
+
+        // Send events to the view group itself if no children have handled it and the view group
+        // itself is not currently being hover-exited.
+        boolean newHoveredSelf = !handled &&
+                (action != MotionEvent.ACTION_HOVER_EXIT) && !event.isHoverExitPending();
+        if (newHoveredSelf == mHoveredSelf) {
+            if (newHoveredSelf) {
+                // Send event to the view group as before.
+                handled |= super.dispatchHoverEvent(event);
+            }
+        } else {
+            if (mHoveredSelf) {
+                // Exit the view group.
+                if (action == MotionEvent.ACTION_HOVER_EXIT) {
+                    // Send the exit as is.
+                    handled |= super.dispatchHoverEvent(event); // exit
+                } else {
+                    // Synthesize an exit from a move or enter.
+                    // Ignore the result because hover focus is moving to a different view.
+                    if (action == MotionEvent.ACTION_HOVER_MOVE) {
+                        super.dispatchHoverEvent(event); // move
+                    }
+                    eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory);
+                    eventNoHistory.setAction(MotionEvent.ACTION_HOVER_EXIT);
+                    super.dispatchHoverEvent(eventNoHistory); // exit
+                    eventNoHistory.setAction(action);
+                }
+                mHoveredSelf = false;
+            }
+
+            if (newHoveredSelf) {
+                // Enter the view group.
+                if (action == MotionEvent.ACTION_HOVER_ENTER) {
+                    // Send the enter as is.
+                    handled |= super.dispatchHoverEvent(event); // enter
+                    mHoveredSelf = true;
+                } else if (action == MotionEvent.ACTION_HOVER_MOVE) {
+                    // Synthesize an enter from a move.
+                    eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory);
+                    eventNoHistory.setAction(MotionEvent.ACTION_HOVER_ENTER);
+                    handled |= super.dispatchHoverEvent(eventNoHistory); // enter
+                    eventNoHistory.setAction(action);
+
+                    handled |= super.dispatchHoverEvent(eventNoHistory); // move
+                    mHoveredSelf = true;
+                }
+            }
+        }
+
+        // Recycle the copy of the event that we made.
+        if (eventNoHistory != event) {
+            eventNoHistory.recycle();
+        }
+
+        // Done.
+        return handled;
+    }
+
+    private void exitHoverTargets() {
+        if (mHoveredSelf || mFirstHoverTarget != null) {
+            final long now = SystemClock.uptimeMillis();
+            MotionEvent event = MotionEvent.obtain(now, now,
+                    MotionEvent.ACTION_HOVER_EXIT, 0.0f, 0.0f, 0);
+            event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+            dispatchHoverEvent(event);
+            event.recycle();
+        }
+    }
+
+    private void cancelHoverTarget(View view) {
+        HoverTarget predecessor = null;
+        HoverTarget target = mFirstHoverTarget;
+        while (target != null) {
+            final HoverTarget next = target.next;
+            if (target.child == view) {
+                if (predecessor == null) {
+                    mFirstHoverTarget = next;
+                } else {
+                    predecessor.next = next;
+                }
+                target.recycle();
+
+                final long now = SystemClock.uptimeMillis();
+                MotionEvent event = MotionEvent.obtain(now, now,
+                        MotionEvent.ACTION_HOVER_EXIT, 0.0f, 0.0f, 0);
+                event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+                view.dispatchHoverEvent(event);
+                event.recycle();
+                return;
+            }
+            predecessor = target;
+            target = next;
+        }
+    }
+
+    @Override
+    boolean dispatchTooltipHoverEvent(MotionEvent event) {
+        final int action = event.getAction();
+        switch (action) {
+            case MotionEvent.ACTION_HOVER_ENTER:
+                break;
+
+            case MotionEvent.ACTION_HOVER_MOVE:
+                View newTarget = null;
+
+                // Check what the child under the pointer says about the tooltip.
+                final int childrenCount = mChildrenCount;
+                if (childrenCount != 0) {
+                    final float x = event.getX();
+                    final float y = event.getY();
+
+                    final ArrayList<View> preorderedList = buildOrderedChildList();
+                    final boolean customOrder = preorderedList == null
+                            && isChildrenDrawingOrderEnabled();
+                    final View[] children = mChildren;
+                    for (int i = childrenCount - 1; i >= 0; i--) {
+                        final int childIndex =
+                                getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+                        final View child =
+                                getAndVerifyPreorderedView(preorderedList, children, childIndex);
+                        if (!child.canReceivePointerEvents()
+                                || !isTransformedTouchPointInView(x, y, child, null)) {
+                            continue;
+                        }
+                        if (dispatchTooltipHoverEvent(event, child)) {
+                            newTarget = child;
+                            break;
+                        }
+                    }
+                    if (preorderedList != null) preorderedList.clear();
+                }
+
+                if (mTooltipHoverTarget != newTarget) {
+                    if (mTooltipHoverTarget != null) {
+                        event.setAction(MotionEvent.ACTION_HOVER_EXIT);
+                        mTooltipHoverTarget.dispatchTooltipHoverEvent(event);
+                        event.setAction(action);
+                    }
+                    mTooltipHoverTarget = newTarget;
+                }
+
+                if (mTooltipHoverTarget != null) {
+                    if (mTooltipHoveredSelf) {
+                        mTooltipHoveredSelf = false;
+                        event.setAction(MotionEvent.ACTION_HOVER_EXIT);
+                        super.dispatchTooltipHoverEvent(event);
+                        event.setAction(action);
+                    }
+                    return true;
+                }
+
+                mTooltipHoveredSelf = super.dispatchTooltipHoverEvent(event);
+                return mTooltipHoveredSelf;
+
+            case MotionEvent.ACTION_HOVER_EXIT:
+                if (mTooltipHoverTarget != null) {
+                    mTooltipHoverTarget.dispatchTooltipHoverEvent(event);
+                    mTooltipHoverTarget = null;
+                } else if (mTooltipHoveredSelf) {
+                    super.dispatchTooltipHoverEvent(event);
+                    mTooltipHoveredSelf = false;
+                }
+                break;
+        }
+        return false;
+    }
+
+    private boolean dispatchTooltipHoverEvent(MotionEvent event, View child) {
+        final boolean result;
+        if (!child.hasIdentityMatrix()) {
+            MotionEvent transformedEvent = getTransformedMotionEvent(event, child);
+            result = child.dispatchTooltipHoverEvent(transformedEvent);
+            transformedEvent.recycle();
+        } else {
+            final float offsetX = mScrollX - child.mLeft;
+            final float offsetY = mScrollY - child.mTop;
+            event.offsetLocation(offsetX, offsetY);
+            result = child.dispatchTooltipHoverEvent(event);
+            event.offsetLocation(-offsetX, -offsetY);
+        }
+        return result;
+    }
+
+    private void exitTooltipHoverTargets() {
+        if (mTooltipHoveredSelf || mTooltipHoverTarget != null) {
+            final long now = SystemClock.uptimeMillis();
+            MotionEvent event = MotionEvent.obtain(now, now,
+                    MotionEvent.ACTION_HOVER_EXIT, 0.0f, 0.0f, 0);
+            event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+            dispatchTooltipHoverEvent(event);
+            event.recycle();
+        }
+    }
+
+    /** @hide */
+    @Override
+    protected boolean hasHoveredChild() {
+        return mFirstHoverTarget != null;
+    }
+
+    /** @hide */
+    @Override
+    protected boolean pointInHoveredChild(MotionEvent event) {
+        if (mFirstHoverTarget != null) {
+            return isTransformedTouchPointInView(event.getX(), event.getY(),
+                mFirstHoverTarget.child, null);
+        }
+        return false;
+    }
+
+    @Override
+    public void addChildrenForAccessibility(ArrayList<View> outChildren) {
+        if (getAccessibilityNodeProvider() != null) {
+            return;
+        }
+        ChildListForAccessibility children = ChildListForAccessibility.obtain(this, true);
+        try {
+            final int childrenCount = children.getChildCount();
+            for (int i = 0; i < childrenCount; i++) {
+                View child = children.getChildAt(i);
+                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
+                    if (child.includeForAccessibility()) {
+                        outChildren.add(child);
+                    } else {
+                        child.addChildrenForAccessibility(outChildren);
+                    }
+                }
+            }
+        } finally {
+            children.recycle();
+        }
+    }
+
+    /**
+     * Implement this method to intercept hover events before they are handled
+     * by child views.
+     * <p>
+     * This method is called before dispatching a hover event to a child of
+     * the view group or to the view group's own {@link #onHoverEvent} to allow
+     * the view group a chance to intercept the hover event.
+     * This method can also be used to watch all pointer motions that occur within
+     * the bounds of the view group even when the pointer is hovering over
+     * a child of the view group rather than over the view group itself.
+     * </p><p>
+     * The view group can prevent its children from receiving hover events by
+     * implementing this method and returning <code>true</code> to indicate
+     * that it would like to intercept hover events.  The view group must
+     * continuously return <code>true</code> from {@link #onInterceptHoverEvent}
+     * for as long as it wishes to continue intercepting hover events from
+     * its children.
+     * </p><p>
+     * Interception preserves the invariant that at most one view can be
+     * hovered at a time by transferring hover focus from the currently hovered
+     * child to the view group or vice-versa as needed.
+     * </p><p>
+     * If this method returns <code>true</code> and a child is already hovered, then the
+     * child view will first receive a hover exit event and then the view group
+     * itself will receive a hover enter event in {@link #onHoverEvent}.
+     * Likewise, if this method had previously returned <code>true</code> to intercept hover
+     * events and instead returns <code>false</code> while the pointer is hovering
+     * within the bounds of one of a child, then the view group will first receive a
+     * hover exit event in {@link #onHoverEvent} and then the hovered child will
+     * receive a hover enter event.
+     * </p><p>
+     * The default implementation handles mouse hover on the scroll bars.
+     * </p>
+     *
+     * @param event The motion event that describes the hover.
+     * @return True if the view group would like to intercept the hover event
+     * and prevent its children from receiving it.
+     */
+    public boolean onInterceptHoverEvent(MotionEvent event) {
+        if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+            final int action = event.getAction();
+            final float x = event.getX();
+            final float y = event.getY();
+            if ((action == MotionEvent.ACTION_HOVER_MOVE
+                    || action == MotionEvent.ACTION_HOVER_ENTER) && isOnScrollbar(x, y)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static MotionEvent obtainMotionEventNoHistoryOrSelf(MotionEvent event) {
+        if (event.getHistorySize() == 0) {
+            return event;
+        }
+        return MotionEvent.obtainNoHistory(event);
+    }
+
+    @Override
+    protected boolean dispatchGenericPointerEvent(MotionEvent event) {
+        // Send the event to the child under the pointer.
+        final int childrenCount = mChildrenCount;
+        if (childrenCount != 0) {
+            final float x = event.getX();
+            final float y = event.getY();
+
+            final ArrayList<View> preorderedList = buildOrderedChildList();
+            final boolean customOrder = preorderedList == null
+                    && isChildrenDrawingOrderEnabled();
+            final View[] children = mChildren;
+            for (int i = childrenCount - 1; i >= 0; i--) {
+                final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+                final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
+                if (!child.canReceivePointerEvents()
+                        || !isTransformedTouchPointInView(x, y, child, null)) {
+                    continue;
+                }
+
+                if (dispatchTransformedGenericPointerEvent(event, child)) {
+                    if (preorderedList != null) preorderedList.clear();
+                    return true;
+                }
+            }
+            if (preorderedList != null) preorderedList.clear();
+        }
+
+        // No child handled the event.  Send it to this view group.
+        return super.dispatchGenericPointerEvent(event);
+    }
+
+    @Override
+    protected boolean dispatchGenericFocusedEvent(MotionEvent event) {
+        // Send the event to the focused child or to this view group if it has focus.
+        if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
+                == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
+            return super.dispatchGenericFocusedEvent(event);
+        } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
+                == PFLAG_HAS_BOUNDS) {
+            return mFocused.dispatchGenericMotionEvent(event);
+        }
+        return false;
+    }
+
+    /**
+     * Dispatches a generic pointer event to a child, taking into account
+     * transformations that apply to the child.
+     *
+     * @param event The event to send.
+     * @param child The view to send the event to.
+     * @return {@code true} if the child handled the event.
+     */
+    private boolean dispatchTransformedGenericPointerEvent(MotionEvent event, View child) {
+        boolean handled;
+        if (!child.hasIdentityMatrix()) {
+            MotionEvent transformedEvent = getTransformedMotionEvent(event, child);
+            handled = child.dispatchGenericMotionEvent(transformedEvent);
+            transformedEvent.recycle();
+        } else {
+            final float offsetX = mScrollX - child.mLeft;
+            final float offsetY = mScrollY - child.mTop;
+            event.offsetLocation(offsetX, offsetY);
+            handled = child.dispatchGenericMotionEvent(event);
+            event.offsetLocation(-offsetX, -offsetY);
+        }
+        return handled;
+    }
+
+    /**
+     * Returns a MotionEvent that's been transformed into the child's local coordinates.
+     *
+     * It's the responsibility of the caller to recycle it once they're finished with it.
+     * @param event The event to transform.
+     * @param child The view whose coordinate space is to be used.
+     * @return A copy of the the given MotionEvent, transformed into the given View's coordinate
+     *         space.
+     */
+    private MotionEvent getTransformedMotionEvent(MotionEvent event, View child) {
+        final float offsetX = mScrollX - child.mLeft;
+        final float offsetY = mScrollY - child.mTop;
+        final MotionEvent transformedEvent = MotionEvent.obtain(event);
+        transformedEvent.offsetLocation(offsetX, offsetY);
+        if (!child.hasIdentityMatrix()) {
+            transformedEvent.transform(child.getInverseMatrix());
+        }
+        return transformedEvent;
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        if (mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
+        }
+
+        // If the event targets the accessibility focused view and this is it, start
+        // normal event dispatch. Maybe a descendant is what will handle the click.
+        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
+            ev.setTargetAccessibilityFocus(false);
+        }
+
+        boolean handled = false;
+        if (onFilterTouchEventForSecurity(ev)) {
+            final int action = ev.getAction();
+            final int actionMasked = action & MotionEvent.ACTION_MASK;
+
+            // Handle an initial down.
+            if (actionMasked == MotionEvent.ACTION_DOWN) {
+                // Throw away all previous state when starting a new touch gesture.
+                // The framework may have dropped the up or cancel event for the previous gesture
+                // due to an app switch, ANR, or some other state change.
+                cancelAndClearTouchTargets(ev);
+                resetTouchState();
+            }
+
+            // Check for interception.
+            final boolean intercepted;
+            if (actionMasked == MotionEvent.ACTION_DOWN
+                    || mFirstTouchTarget != null) {
+                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
+                if (!disallowIntercept) {
+                    intercepted = onInterceptTouchEvent(ev);
+                    ev.setAction(action); // restore action in case it was changed
+                } else {
+                    intercepted = false;
+                }
+            } else {
+                // There are no touch targets and this action is not an initial down
+                // so this view group continues to intercept touches.
+                intercepted = true;
+            }
+
+            // If intercepted, start normal event dispatch. Also if there is already
+            // a view that is handling the gesture, do normal event dispatch.
+            if (intercepted || mFirstTouchTarget != null) {
+                ev.setTargetAccessibilityFocus(false);
+            }
+
+            // Check for cancelation.
+            final boolean canceled = resetCancelNextUpFlag(this)
+                    || actionMasked == MotionEvent.ACTION_CANCEL;
+
+            // Update list of touch targets for pointer down, if needed.
+            final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;
+            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0
+                    && !isMouseEvent;
+            TouchTarget newTouchTarget = null;
+            boolean alreadyDispatchedToNewTouchTarget = false;
+            if (!canceled && !intercepted) {
+                // If the event is targeting accessibility focus we give it to the
+                // view that has accessibility focus and if it does not handle it
+                // we clear the flag and dispatch the event to all children as usual.
+                // We are looking up the accessibility focused host to avoid keeping
+                // state since these events are very rare.
+                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
+                        ? findChildWithAccessibilityFocus() : null;
+
+                if (actionMasked == MotionEvent.ACTION_DOWN
+                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
+                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
+                    final int actionIndex = ev.getActionIndex(); // always 0 for down
+                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
+                            : TouchTarget.ALL_POINTER_IDS;
+
+                    // Clean up earlier touch targets for this pointer id in case they
+                    // have become out of sync.
+                    removePointersFromTouchTargets(idBitsToAssign);
+
+                    final int childrenCount = mChildrenCount;
+                    if (newTouchTarget == null && childrenCount != 0) {
+                        final float x =
+                                isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
+                        final float y =
+                                isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
+                        // Find a child that can receive the event.
+                        // Scan children from front to back.
+                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
+                        final boolean customOrder = preorderedList == null
+                                && isChildrenDrawingOrderEnabled();
+                        final View[] children = mChildren;
+                        for (int i = childrenCount - 1; i >= 0; i--) {
+                            final int childIndex = getAndVerifyPreorderedIndex(
+                                    childrenCount, i, customOrder);
+                            final View child = getAndVerifyPreorderedView(
+                                    preorderedList, children, childIndex);
+
+                            // If there is a view that has accessibility focus we want it
+                            // to get the event first and if not handled we will perform a
+                            // normal dispatch. We may do a double iteration but this is
+                            // safer given the timeframe.
+                            if (childWithAccessibilityFocus != null) {
+                                if (childWithAccessibilityFocus != child) {
+                                    continue;
+                                }
+                                childWithAccessibilityFocus = null;
+                                i = childrenCount - 1;
+                            }
+
+                            if (!child.canReceivePointerEvents()
+                                    || !isTransformedTouchPointInView(x, y, child, null)) {
+                                ev.setTargetAccessibilityFocus(false);
+                                continue;
+                            }
+
+                            newTouchTarget = getTouchTarget(child);
+                            if (newTouchTarget != null) {
+                                // Child is already receiving touch within its bounds.
+                                // Give it the new pointer in addition to the ones it is handling.
+                                newTouchTarget.pointerIdBits |= idBitsToAssign;
+                                break;
+                            }
+
+                            resetCancelNextUpFlag(child);
+                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
+                                // Child wants to receive touch within its bounds.
+                                mLastTouchDownTime = ev.getDownTime();
+                                if (preorderedList != null) {
+                                    // childIndex points into presorted list, find original index
+                                    for (int j = 0; j < childrenCount; j++) {
+                                        if (children[childIndex] == mChildren[j]) {
+                                            mLastTouchDownIndex = j;
+                                            break;
+                                        }
+                                    }
+                                } else {
+                                    mLastTouchDownIndex = childIndex;
+                                }
+                                mLastTouchDownX = ev.getX();
+                                mLastTouchDownY = ev.getY();
+                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
+                                alreadyDispatchedToNewTouchTarget = true;
+                                break;
+                            }
+
+                            // The accessibility focus didn't handle the event, so clear
+                            // the flag and do a normal dispatch to all children.
+                            ev.setTargetAccessibilityFocus(false);
+                        }
+                        if (preorderedList != null) preorderedList.clear();
+                    }
+
+                    if (newTouchTarget == null && mFirstTouchTarget != null) {
+                        // Did not find a child to receive the event.
+                        // Assign the pointer to the least recently added target.
+                        newTouchTarget = mFirstTouchTarget;
+                        while (newTouchTarget.next != null) {
+                            newTouchTarget = newTouchTarget.next;
+                        }
+                        newTouchTarget.pointerIdBits |= idBitsToAssign;
+                    }
+                }
+            }
+
+            // Dispatch to touch targets.
+            if (mFirstTouchTarget == null) {
+                // No touch targets so treat this as an ordinary view.
+                handled = dispatchTransformedTouchEvent(ev, canceled, null,
+                        TouchTarget.ALL_POINTER_IDS);
+            } else {
+                // Dispatch to touch targets, excluding the new touch target if we already
+                // dispatched to it.  Cancel touch targets if necessary.
+                TouchTarget predecessor = null;
+                TouchTarget target = mFirstTouchTarget;
+                while (target != null) {
+                    final TouchTarget next = target.next;
+                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
+                        handled = true;
+                    } else {
+                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
+                                || intercepted;
+                        if (dispatchTransformedTouchEvent(ev, cancelChild,
+                                target.child, target.pointerIdBits)) {
+                            handled = true;
+                        }
+                        if (cancelChild) {
+                            if (predecessor == null) {
+                                mFirstTouchTarget = next;
+                            } else {
+                                predecessor.next = next;
+                            }
+                            target.recycle();
+                            target = next;
+                            continue;
+                        }
+                    }
+                    predecessor = target;
+                    target = next;
+                }
+            }
+
+            // Update list of touch targets for pointer up or cancel, if needed.
+            if (canceled
+                    || actionMasked == MotionEvent.ACTION_UP
+                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
+                resetTouchState();
+            } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
+                final int actionIndex = ev.getActionIndex();
+                final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
+                removePointersFromTouchTargets(idBitsToRemove);
+            }
+        }
+
+        if (!handled && mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
+        }
+        return handled;
+    }
+
+    /**
+     * Provide custom ordering of views in which the touch will be dispatched.
+     *
+     * This is called within a tight loop, so you are not allowed to allocate objects, including
+     * the return array. Instead, you should return a pre-allocated list that will be cleared
+     * after the dispatch is finished.
+     * @hide
+     */
+    public ArrayList<View> buildTouchDispatchChildList() {
+        return buildOrderedChildList();
+    }
+
+     /**
+     * Finds the child which has accessibility focus.
+     *
+     * @return The child that has focus.
+     */
+    private View findChildWithAccessibilityFocus() {
+        ViewRootImpl viewRoot = getViewRootImpl();
+        if (viewRoot == null) {
+            return null;
+        }
+
+        View current = viewRoot.getAccessibilityFocusedHost();
+        if (current == null) {
+            return null;
+        }
+
+        ViewParent parent = current.getParent();
+        while (parent instanceof View) {
+            if (parent == this) {
+                return current;
+            }
+            current = (View) parent;
+            parent = current.getParent();
+        }
+
+        return null;
+    }
+
+    /**
+     * Resets all touch state in preparation for a new cycle.
+     */
+    private void resetTouchState() {
+        clearTouchTargets();
+        resetCancelNextUpFlag(this);
+        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
+        mNestedScrollAxes = SCROLL_AXIS_NONE;
+    }
+
+    /**
+     * Resets the cancel next up flag.
+     * Returns true if the flag was previously set.
+     */
+    private static boolean resetCancelNextUpFlag(@NonNull View view) {
+        if ((view.mPrivateFlags & PFLAG_CANCEL_NEXT_UP_EVENT) != 0) {
+            view.mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Clears all touch targets.
+     */
+    private void clearTouchTargets() {
+        TouchTarget target = mFirstTouchTarget;
+        if (target != null) {
+            do {
+                TouchTarget next = target.next;
+                target.recycle();
+                target = next;
+            } while (target != null);
+            mFirstTouchTarget = null;
+        }
+    }
+
+    /**
+     * Cancels and clears all touch targets.
+     */
+    private void cancelAndClearTouchTargets(MotionEvent event) {
+        if (mFirstTouchTarget != null) {
+            boolean syntheticEvent = false;
+            if (event == null) {
+                final long now = SystemClock.uptimeMillis();
+                event = MotionEvent.obtain(now, now,
+                        MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
+                event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+                syntheticEvent = true;
+            }
+
+            for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
+                resetCancelNextUpFlag(target.child);
+                dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
+            }
+            clearTouchTargets();
+
+            if (syntheticEvent) {
+                event.recycle();
+            }
+        }
+    }
+
+    /**
+     * Gets the touch target for specified child view.
+     * Returns null if not found.
+     */
+    private TouchTarget getTouchTarget(@NonNull View child) {
+        for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
+            if (target.child == child) {
+                return target;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Adds a touch target for specified child to the beginning of the list.
+     * Assumes the target child is not already present.
+     */
+    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
+        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
+        target.next = mFirstTouchTarget;
+        mFirstTouchTarget = target;
+        return target;
+    }
+
+    /**
+     * Removes the pointer ids from consideration.
+     */
+    private void removePointersFromTouchTargets(int pointerIdBits) {
+        TouchTarget predecessor = null;
+        TouchTarget target = mFirstTouchTarget;
+        while (target != null) {
+            final TouchTarget next = target.next;
+            if ((target.pointerIdBits & pointerIdBits) != 0) {
+                target.pointerIdBits &= ~pointerIdBits;
+                if (target.pointerIdBits == 0) {
+                    if (predecessor == null) {
+                        mFirstTouchTarget = next;
+                    } else {
+                        predecessor.next = next;
+                    }
+                    target.recycle();
+                    target = next;
+                    continue;
+                }
+            }
+            predecessor = target;
+            target = next;
+        }
+    }
+
+    @UnsupportedAppUsage
+    private void cancelTouchTarget(View view) {
+        TouchTarget predecessor = null;
+        TouchTarget target = mFirstTouchTarget;
+        while (target != null) {
+            final TouchTarget next = target.next;
+            if (target.child == view) {
+                if (predecessor == null) {
+                    mFirstTouchTarget = next;
+                } else {
+                    predecessor.next = next;
+                }
+                target.recycle();
+
+                final long now = SystemClock.uptimeMillis();
+                MotionEvent event = MotionEvent.obtain(now, now,
+                        MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
+                event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+                view.dispatchTouchEvent(event);
+                event.recycle();
+                return;
+            }
+            predecessor = target;
+            target = next;
+        }
+    }
+
+    private Rect getTempRect() {
+        if (mTempRect == null) {
+            mTempRect = new Rect();
+        }
+        return mTempRect;
+    }
+
+    private float[] getTempLocationF() {
+        if (mTempPosition == null) {
+            mTempPosition = new float[2];
+        }
+        return mTempPosition;
+    }
+
+    private Point getTempPoint() {
+        if (mTempPoint == null) {
+            mTempPoint = new Point();
+        }
+        return mTempPoint;
+    }
+
+    /**
+     * Returns true if a child view contains the specified point when transformed
+     * into its coordinate space.
+     * Child must not be null.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    protected boolean isTransformedTouchPointInView(float x, float y, View child,
+            PointF outLocalPoint) {
+        final float[] point = getTempLocationF();
+        point[0] = x;
+        point[1] = y;
+        transformPointToViewLocal(point, child);
+        final boolean isInView = child.pointInView(point[0], point[1]);
+        if (isInView && outLocalPoint != null) {
+            outLocalPoint.set(point[0], point[1]);
+        }
+        return isInView;
+    }
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public void transformPointToViewLocal(float[] point, View child) {
+        point[0] += mScrollX - child.mLeft;
+        point[1] += mScrollY - child.mTop;
+
+        if (!child.hasIdentityMatrix()) {
+            child.getInverseMatrix().mapPoints(point);
+        }
+    }
+
+    /**
+     * Transforms a motion event into the coordinate space of a particular child view,
+     * filters out irrelevant pointer ids, and overrides its action if necessary.
+     * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
+     */
+    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
+            View child, int desiredPointerIdBits) {
+        final boolean handled;
+
+        // Canceling motions is a special case.  We don't need to perform any transformations
+        // or filtering.  The important part is the action, not the contents.
+        final int oldAction = event.getAction();
+        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
+            event.setAction(MotionEvent.ACTION_CANCEL);
+            if (child == null) {
+                handled = super.dispatchTouchEvent(event);
+            } else {
+                handled = child.dispatchTouchEvent(event);
+            }
+            event.setAction(oldAction);
+            return handled;
+        }
+
+        // Calculate the number of pointers to deliver.
+        final int oldPointerIdBits = event.getPointerIdBits();
+        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
+
+        // If for some reason we ended up in an inconsistent state where it looks like we
+        // might produce a motion event with no pointers in it, then drop the event.
+        if (newPointerIdBits == 0) {
+            return false;
+        }
+
+        // If the number of pointers is the same and we don't need to perform any fancy
+        // irreversible transformations, then we can reuse the motion event for this
+        // dispatch as long as we are careful to revert any changes we make.
+        // Otherwise we need to make a copy.
+        final MotionEvent transformedEvent;
+        if (newPointerIdBits == oldPointerIdBits) {
+            if (child == null || child.hasIdentityMatrix()) {
+                if (child == null) {
+                    handled = super.dispatchTouchEvent(event);
+                } else {
+                    final float offsetX = mScrollX - child.mLeft;
+                    final float offsetY = mScrollY - child.mTop;
+                    event.offsetLocation(offsetX, offsetY);
+
+                    handled = child.dispatchTouchEvent(event);
+
+                    event.offsetLocation(-offsetX, -offsetY);
+                }
+                return handled;
+            }
+            transformedEvent = MotionEvent.obtain(event);
+        } else {
+            transformedEvent = event.split(newPointerIdBits);
+        }
+
+        // Perform any necessary transformations and dispatch.
+        if (child == null) {
+            handled = super.dispatchTouchEvent(transformedEvent);
+        } else {
+            final float offsetX = mScrollX - child.mLeft;
+            final float offsetY = mScrollY - child.mTop;
+            transformedEvent.offsetLocation(offsetX, offsetY);
+            if (! child.hasIdentityMatrix()) {
+                transformedEvent.transform(child.getInverseMatrix());
+            }
+
+            handled = child.dispatchTouchEvent(transformedEvent);
+        }
+
+        // Done.
+        transformedEvent.recycle();
+        return handled;
+    }
+
+    /**
+     * Enable or disable the splitting of MotionEvents to multiple children during touch event
+     * dispatch. This behavior is enabled by default for applications that target an
+     * SDK version of {@link Build.VERSION_CODES#HONEYCOMB} or newer.
+     *
+     * <p>When this option is enabled MotionEvents may be split and dispatched to different child
+     * views depending on where each pointer initially went down. This allows for user interactions
+     * such as scrolling two panes of content independently, chording of buttons, and performing
+     * independent gestures on different pieces of content.
+     *
+     * @param split <code>true</code> to allow MotionEvents to be split and dispatched to multiple
+     *              child views. <code>false</code> to only allow one child view to be the target of
+     *              any MotionEvent received by this ViewGroup.
+     * @attr ref android.R.styleable#ViewGroup_splitMotionEvents
+     */
+    public void setMotionEventSplittingEnabled(boolean split) {
+        // TODO Applications really shouldn't change this setting mid-touch event,
+        // but perhaps this should handle that case and send ACTION_CANCELs to any child views
+        // with gestures in progress when this is changed.
+        if (split) {
+            mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS;
+        } else {
+            mGroupFlags &= ~FLAG_SPLIT_MOTION_EVENTS;
+        }
+    }
+
+    /**
+     * Returns true if MotionEvents dispatched to this ViewGroup can be split to multiple children.
+     * @return true if MotionEvents dispatched to this ViewGroup can be split to multiple children.
+     */
+    @InspectableProperty(name = "splitMotionEvents")
+    public boolean isMotionEventSplittingEnabled() {
+        return (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) == FLAG_SPLIT_MOTION_EVENTS;
+    }
+
+    /**
+     * Returns true if this ViewGroup should be considered as a single entity for removal
+     * when executing an Activity transition. If this is false, child elements will move
+     * individually during the transition.
+     *
+     * @return True if the ViewGroup should be acted on together during an Activity transition.
+     * The default value is true when there is a non-null background or if
+     * {@link #getTransitionName()} is not null or if a
+     * non-null {@link android.view.ViewOutlineProvider} other than
+     * {@link android.view.ViewOutlineProvider#BACKGROUND} was given to
+     * {@link #setOutlineProvider(ViewOutlineProvider)} and false otherwise.
+     */
+    @InspectableProperty
+    public boolean isTransitionGroup() {
+        if ((mGroupFlags & FLAG_IS_TRANSITION_GROUP_SET) != 0) {
+            return ((mGroupFlags & FLAG_IS_TRANSITION_GROUP) != 0);
+        } else {
+            final ViewOutlineProvider outlineProvider = getOutlineProvider();
+            return getBackground() != null || getTransitionName() != null ||
+                    (outlineProvider != null && outlineProvider != ViewOutlineProvider.BACKGROUND);
+        }
+    }
+
+    /**
+     * Changes whether or not this ViewGroup should be treated as a single entity during
+     * Activity Transitions.
+     * @param isTransitionGroup Whether or not the ViewGroup should be treated as a unit
+     *                          in Activity transitions. If false, the ViewGroup won't transition,
+     *                          only its children. If true, the entire ViewGroup will transition
+     *                          together.
+     * @see android.app.ActivityOptions#makeSceneTransitionAnimation(android.app.Activity,
+     * android.util.Pair[])
+     */
+    public void setTransitionGroup(boolean isTransitionGroup) {
+        mGroupFlags |= FLAG_IS_TRANSITION_GROUP_SET;
+        if (isTransitionGroup) {
+            mGroupFlags |= FLAG_IS_TRANSITION_GROUP;
+        } else {
+            mGroupFlags &= ~FLAG_IS_TRANSITION_GROUP;
+        }
+    }
+
+    @Override
+    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+
+        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
+            // We're already in this state, assume our ancestors are too
+            return;
+        }
+
+        if (disallowIntercept) {
+            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
+        } else {
+            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
+        }
+
+        // Pass it up to our parent
+        if (mParent != null) {
+            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
+        }
+    }
+
+    /**
+     * Implement this method to intercept all touch screen motion events.  This
+     * allows you to watch events as they are dispatched to your children, and
+     * take ownership of the current gesture at any point.
+     *
+     * <p>Using this function takes some care, as it has a fairly complicated
+     * interaction with {@link View#onTouchEvent(MotionEvent)
+     * View.onTouchEvent(MotionEvent)}, and using it requires implementing
+     * that method as well as this one in the correct way.  Events will be
+     * received in the following order:
+     *
+     * <ol>
+     * <li> You will receive the down event here.
+     * <li> The down event will be handled either by a child of this view
+     * group, or given to your own onTouchEvent() method to handle; this means
+     * you should implement onTouchEvent() to return true, so you will
+     * continue to see the rest of the gesture (instead of looking for
+     * a parent view to handle it).  Also, by returning true from
+     * onTouchEvent(), you will not receive any following
+     * events in onInterceptTouchEvent() and all touch processing must
+     * happen in onTouchEvent() like normal.
+     * <li> For as long as you return false from this function, each following
+     * event (up to and including the final up) will be delivered first here
+     * and then to the target's onTouchEvent().
+     * <li> If you return true from here, you will not receive any
+     * following events: the target view will receive the same event but
+     * with the action {@link MotionEvent#ACTION_CANCEL}, and all further
+     * events will be delivered to your onTouchEvent() method and no longer
+     * appear here.
+     * </ol>
+     *
+     * @param ev The motion event being dispatched down the hierarchy.
+     * @return Return true to steal motion events from the children and have
+     * them dispatched to this ViewGroup through onTouchEvent().
+     * The current target will receive an ACTION_CANCEL event, and no further
+     * messages will be delivered here.
+     */
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
+                && ev.getAction() == MotionEvent.ACTION_DOWN
+                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
+                && isOnScrollbarThumb(ev.getX(), ev.getY())) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Looks for a view to give focus to respecting the setting specified by
+     * {@link #getDescendantFocusability()}.
+     *
+     * Uses {@link #onRequestFocusInDescendants(int, android.graphics.Rect)} to
+     * find focus within the children of this group when appropriate.
+     *
+     * @see #FOCUS_BEFORE_DESCENDANTS
+     * @see #FOCUS_AFTER_DESCENDANTS
+     * @see #FOCUS_BLOCK_DESCENDANTS
+     * @see #onRequestFocusInDescendants(int, android.graphics.Rect)
+     */
+    @Override
+    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
+        if (DBG) {
+            System.out.println(this + " ViewGroup.requestFocus direction="
+                    + direction);
+        }
+        int descendantFocusability = getDescendantFocusability();
+
+        boolean result;
+        switch (descendantFocusability) {
+            case FOCUS_BLOCK_DESCENDANTS:
+                result = super.requestFocus(direction, previouslyFocusedRect);
+                break;
+            case FOCUS_BEFORE_DESCENDANTS: {
+                final boolean took = super.requestFocus(direction, previouslyFocusedRect);
+                result = took ? took : onRequestFocusInDescendants(direction,
+                        previouslyFocusedRect);
+                break;
+            }
+            case FOCUS_AFTER_DESCENDANTS: {
+                final boolean took = onRequestFocusInDescendants(direction, previouslyFocusedRect);
+                result = took ? took : super.requestFocus(direction, previouslyFocusedRect);
+                break;
+            }
+            default:
+                throw new IllegalStateException("descendant focusability must be "
+            + "one of FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS "
+            + "but is " + descendantFocusability);
+        }
+        if (result && !isLayoutValid() && ((mPrivateFlags & PFLAG_WANTS_FOCUS) == 0)) {
+            mPrivateFlags |= PFLAG_WANTS_FOCUS;
+        }
+        return result;
+    }
+
+    /**
+     * Look for a descendant to call {@link View#requestFocus} on.
+     * Called by {@link ViewGroup#requestFocus(int, android.graphics.Rect)}
+     * when it wants to request focus within its children.  Override this to
+     * customize how your {@link ViewGroup} requests focus within its children.
+     * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT
+     * @param previouslyFocusedRect The rectangle (in this View's coordinate system)
+     *        to give a finer grained hint about where focus is coming from.  May be null
+     *        if there is no hint.
+     * @return Whether focus was taken.
+     */
+    @SuppressWarnings({"ConstantConditions"})
+    protected boolean onRequestFocusInDescendants(int direction,
+            Rect previouslyFocusedRect) {
+        int index;
+        int increment;
+        int end;
+        int count = mChildrenCount;
+        if ((direction & FOCUS_FORWARD) != 0) {
+            index = 0;
+            increment = 1;
+            end = count;
+        } else {
+            index = count - 1;
+            increment = -1;
+            end = -1;
+        }
+        final View[] children = mChildren;
+        for (int i = index; i != end; i += increment) {
+            View child = children[i];
+            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
+                if (child.requestFocus(direction, previouslyFocusedRect)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean restoreDefaultFocus() {
+        if (mDefaultFocus != null
+                && getDescendantFocusability() != FOCUS_BLOCK_DESCENDANTS
+                && (mDefaultFocus.mViewFlags & VISIBILITY_MASK) == VISIBLE
+                && mDefaultFocus.restoreDefaultFocus()) {
+            return true;
+        }
+        return super.restoreDefaultFocus();
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    @Override
+    public boolean restoreFocusInCluster(@FocusRealDirection int direction) {
+        // Allow cluster-navigation to enter touchscreenBlocksFocus ViewGroups.
+        if (isKeyboardNavigationCluster()) {
+            final boolean blockedFocus = getTouchscreenBlocksFocus();
+            try {
+                setTouchscreenBlocksFocusNoRefocus(false);
+                return restoreFocusInClusterInternal(direction);
+            } finally {
+                setTouchscreenBlocksFocusNoRefocus(blockedFocus);
+            }
+        } else {
+            return restoreFocusInClusterInternal(direction);
+        }
+    }
+
+    private boolean restoreFocusInClusterInternal(@FocusRealDirection int direction) {
+        if (mFocusedInCluster != null && getDescendantFocusability() != FOCUS_BLOCK_DESCENDANTS
+                && (mFocusedInCluster.mViewFlags & VISIBILITY_MASK) == VISIBLE
+                && mFocusedInCluster.restoreFocusInCluster(direction)) {
+            return true;
+        }
+        return super.restoreFocusInCluster(direction);
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public boolean restoreFocusNotInCluster() {
+        if (mFocusedInCluster != null) {
+            // since clusters don't nest; we can assume that a non-null mFocusedInCluster
+            // will refer to a view not-in a cluster.
+            return restoreFocusInCluster(View.FOCUS_DOWN);
+        }
+        if (isKeyboardNavigationCluster() || (mViewFlags & VISIBILITY_MASK) != VISIBLE) {
+            return false;
+        }
+        int descendentFocusability = getDescendantFocusability();
+        if (descendentFocusability == FOCUS_BLOCK_DESCENDANTS) {
+            return super.requestFocus(FOCUS_DOWN, null);
+        }
+        if (descendentFocusability == FOCUS_BEFORE_DESCENDANTS
+                && super.requestFocus(FOCUS_DOWN, null)) {
+            return true;
+        }
+        for (int i = 0; i < mChildrenCount; ++i) {
+            View child = mChildren[i];
+            if (!child.isKeyboardNavigationCluster()
+                    && child.restoreFocusNotInCluster()) {
+                return true;
+            }
+        }
+        if (descendentFocusability == FOCUS_AFTER_DESCENDANTS && !hasFocusableChild(false)) {
+            return super.requestFocus(FOCUS_DOWN, null);
+        }
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @hide
+     */
+    @Override
+    public void dispatchStartTemporaryDetach() {
+        super.dispatchStartTemporaryDetach();
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            children[i].dispatchStartTemporaryDetach();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @hide
+     */
+    @Override
+    public void dispatchFinishTemporaryDetach() {
+        super.dispatchFinishTemporaryDetach();
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            children[i].dispatchFinishTemporaryDetach();
+        }
+    }
+
+    @Override
+    @UnsupportedAppUsage
+    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
+        mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
+        super.dispatchAttachedToWindow(info, visibility);
+        mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
+
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            final View child = children[i];
+            child.dispatchAttachedToWindow(info,
+                    combineVisibility(visibility, child.getVisibility()));
+        }
+        final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
+        for (int i = 0; i < transientCount; ++i) {
+            View view = mTransientViews.get(i);
+            view.dispatchAttachedToWindow(info,
+                    combineVisibility(visibility, view.getVisibility()));
+        }
+    }
+
+    @Override
+    void dispatchScreenStateChanged(int screenState) {
+        super.dispatchScreenStateChanged(screenState);
+
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            children[i].dispatchScreenStateChanged(screenState);
+        }
+    }
+
+    @Override
+    void dispatchMovedToDisplay(Display display, Configuration config) {
+        super.dispatchMovedToDisplay(display, config);
+
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            children[i].dispatchMovedToDisplay(display, config);
+        }
+    }
+
+    /** @hide */
+    @Override
+    public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+        boolean handled = false;
+        if (includeForAccessibility()) {
+            handled = super.dispatchPopulateAccessibilityEventInternal(event);
+            if (handled) {
+                return handled;
+            }
+        }
+        // Let our children have a shot in populating the event.
+        ChildListForAccessibility children = ChildListForAccessibility.obtain(this, true);
+        try {
+            final int childCount = children.getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                View child = children.getChildAt(i);
+                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
+                    handled = child.dispatchPopulateAccessibilityEvent(event);
+                    if (handled) {
+                        return handled;
+                    }
+                }
+            }
+        } finally {
+            children.recycle();
+        }
+        return false;
+    }
+
+    /**
+     * Dispatch creation of {@link ViewStructure} down the hierarchy.  This implementation
+     * adds in all child views of the view group, in addition to calling the default View
+     * implementation.
+     */
+    @Override
+    public void dispatchProvideStructure(ViewStructure structure) {
+        super.dispatchProvideStructure(structure);
+        if (isAssistBlocked() || structure.getChildCount() != 0) {
+            return;
+        }
+        final int childrenCount = mChildrenCount;
+        if (childrenCount <= 0) {
+            return;
+        }
+
+        if (!isLaidOut()) {
+            if (Helper.sVerbose) {
+                Log.v(VIEW_LOG_TAG, "dispatchProvideStructure(): not laid out, ignoring "
+                        + childrenCount + " children of " + getAccessibilityViewId());
+            }
+            return;
+        }
+
+        structure.setChildCount(childrenCount);
+        ArrayList<View> tempPreorderedList = buildOrderedChildList();
+        ArrayList<View> preorderedList =
+                tempPreorderedList != null ? new ArrayList<>(tempPreorderedList) : null;
+        boolean customOrder = preorderedList == null
+                && isChildrenDrawingOrderEnabled();
+        for (int i = 0; i < childrenCount; i++) {
+            int childIndex;
+            try {
+                childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+            } catch (IndexOutOfBoundsException e) {
+                childIndex = i;
+                if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.M) {
+                    Log.w(TAG, "Bad getChildDrawingOrder while collecting assist @ "
+                            + i + " of " + childrenCount, e);
+                    // At least one app is failing when we call getChildDrawingOrder
+                    // at this point, so deal semi-gracefully with it by falling back
+                    // on the basic order.
+                    customOrder = false;
+                    if (i > 0) {
+                        // If we failed at the first index, there really isn't
+                        // anything to do -- we will just proceed with the simple
+                        // sequence order.
+                        // Otherwise, we failed in the middle, so need to come up
+                        // with an order for the remaining indices and use that.
+                        // Failed at the first one, easy peasy.
+                        int[] permutation = new int[childrenCount];
+                        SparseBooleanArray usedIndices = new SparseBooleanArray();
+                        // Go back and collected the indices we have done so far.
+                        for (int j = 0; j < i; j++) {
+                            permutation[j] = getChildDrawingOrder(childrenCount, j);
+                            usedIndices.put(permutation[j], true);
+                        }
+                        // Fill in the remaining indices with indices that have not
+                        // yet been used.
+                        int nextIndex = 0;
+                        for (int j = i; j < childrenCount; j++) {
+                            while (usedIndices.get(nextIndex, false)) {
+                                nextIndex++;
+                            }
+                            permutation[j] = nextIndex;
+                            nextIndex++;
+                        }
+                        // Build the final view list.
+                        preorderedList = new ArrayList<>(childrenCount);
+                        for (int j = 0; j < childrenCount; j++) {
+                            final int index = permutation[j];
+                            final View child = mChildren[index];
+                            preorderedList.add(child);
+                        }
+                    }
+                } else {
+                    throw e;
+                }
+            }
+            final View child = getAndVerifyPreorderedView(preorderedList, mChildren,
+                    childIndex);
+            final ViewStructure cstructure = structure.newChild(i);
+            child.dispatchProvideStructure(cstructure);
+        }
+        if (preorderedList != null) {
+            preorderedList.clear();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This implementation adds in all child views of the view group, in addition to calling the
+     * default {@link View} implementation.
+     */
+    @Override
+    public void dispatchProvideAutofillStructure(ViewStructure structure,
+            @AutofillFlags int flags) {
+        super.dispatchProvideAutofillStructure(structure, flags);
+
+        if (structure.getChildCount() != 0) {
+            return;
+        }
+
+        if (!isLaidOut()) {
+            if (Helper.sVerbose) {
+                Log.v(VIEW_LOG_TAG, "dispatchProvideAutofillStructure(): not laid out, ignoring "
+                        + mChildrenCount + " children of " + getAutofillId());
+            }
+            return;
+        }
+
+        final ChildListForAutoFillOrContentCapture children = getChildrenForAutofill(flags);
+        final int childrenCount = children.size();
+        structure.setChildCount(childrenCount);
+        for (int i = 0; i < childrenCount; i++) {
+            final View child = children.get(i);
+            final ViewStructure cstructure = structure.newChild(i);
+            child.dispatchProvideAutofillStructure(cstructure, flags);
+        }
+        children.recycle();
+    }
+
+    /** @hide */
+    @Override
+    public void dispatchProvideContentCaptureStructure() {
+        super.dispatchProvideContentCaptureStructure();
+
+        if (!isLaidOut()) return;
+
+        final ChildListForAutoFillOrContentCapture children = getChildrenForContentCapture();
+        final int childrenCount = children.size();
+        for (int i = 0; i < childrenCount; i++) {
+            final View child = children.get(i);
+            child.dispatchProvideContentCaptureStructure();
+        }
+        children.recycle();
+    }
+
+    /**
+     * Gets the children for autofill. Children for autofill are the first
+     * level descendants that are important for autofill. The returned
+     * child list object is pooled and the caller must recycle it once done.
+     * @hide */
+    private @NonNull ChildListForAutoFillOrContentCapture getChildrenForAutofill(
+            @AutofillFlags int flags) {
+        final ChildListForAutoFillOrContentCapture children = ChildListForAutoFillOrContentCapture
+                .obtain();
+        populateChildrenForAutofill(children, flags);
+        return children;
+    }
+
+    /** @hide */
+    private void populateChildrenForAutofill(ArrayList<View> list, @AutofillFlags int flags) {
+        final int childrenCount = mChildrenCount;
+        if (childrenCount <= 0) {
+            return;
+        }
+        final ArrayList<View> preorderedList = buildOrderedChildList();
+        final boolean customOrder = preorderedList == null
+                && isChildrenDrawingOrderEnabled();
+        for (int i = 0; i < childrenCount; i++) {
+            final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+            final View child = (preorderedList == null)
+                    ? mChildren[childIndex] : preorderedList.get(childIndex);
+            if ((flags & AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0
+                    || child.isImportantForAutofill()) {
+                list.add(child);
+            } else if (child instanceof ViewGroup) {
+                ((ViewGroup) child).populateChildrenForAutofill(list, flags);
+            }
+        }
+    }
+
+    private @NonNull ChildListForAutoFillOrContentCapture getChildrenForContentCapture() {
+        final ChildListForAutoFillOrContentCapture children = ChildListForAutoFillOrContentCapture
+                .obtain();
+        populateChildrenForContentCapture(children);
+        return children;
+    }
+
+    /** @hide */
+    private void populateChildrenForContentCapture(ArrayList<View> list) {
+        final int childrenCount = mChildrenCount;
+        if (childrenCount <= 0) {
+            return;
+        }
+        final ArrayList<View> preorderedList = buildOrderedChildList();
+        final boolean customOrder = preorderedList == null
+                && isChildrenDrawingOrderEnabled();
+        for (int i = 0; i < childrenCount; i++) {
+            final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+            final View child = (preorderedList == null)
+                    ? mChildren[childIndex] : preorderedList.get(childIndex);
+            if (child.isImportantForContentCapture()) {
+                list.add(child);
+            } else if (child instanceof ViewGroup) {
+                ((ViewGroup) child).populateChildrenForContentCapture(list);
+            }
+        }
+    }
+
+    private static View getAndVerifyPreorderedView(ArrayList<View> preorderedList, View[] children,
+            int childIndex) {
+        final View child;
+        if (preorderedList != null) {
+            child = preorderedList.get(childIndex);
+            if (child == null) {
+                throw new RuntimeException("Invalid preorderedList contained null child at index "
+                        + childIndex);
+            }
+        } else {
+            child = children[childIndex];
+        }
+        return child;
+    }
+
+    /** @hide */
+    @Override
+    public void resetSubtreeAutofillIds() {
+        super.resetSubtreeAutofillIds();
+        View[] children = mChildren;
+        final int childCount = mChildrenCount;
+        for (int i = 0; i < childCount; i++) {
+            children[i].resetSubtreeAutofillIds();
+        }
+    }
+
+    /** @hide */
+    @Override
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfoInternal(info);
+        if (getAccessibilityNodeProvider() != null) {
+            return;
+        }
+        if (mAttachInfo != null) {
+            final ArrayList<View> childrenForAccessibility = mAttachInfo.mTempArrayList;
+            childrenForAccessibility.clear();
+            addChildrenForAccessibility(childrenForAccessibility);
+            final int childrenForAccessibilityCount = childrenForAccessibility.size();
+            for (int i = 0; i < childrenForAccessibilityCount; i++) {
+                final View child = childrenForAccessibility.get(i);
+                info.addChildUnchecked(child);
+            }
+            childrenForAccessibility.clear();
+        }
+        info.setAvailableExtraData(Collections.singletonList(
+                AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param info The info to which to add the extra data. Never {@code null}.
+     * @param extraDataKey A key specifying the type of extra data to add to the info. The
+     *                     extra data should be added to the {@link Bundle} returned by
+     *                     the info's {@link AccessibilityNodeInfo#getExtras} method. Never
+     *                     {@code null}.
+     * @param arguments A {@link Bundle} holding any arguments relevant for this request. May be
+     *                  {@code null} if the service provided no arguments.
+     *
+     */
+    @Override
+    public void addExtraDataToAccessibilityNodeInfo(@NonNull AccessibilityNodeInfo info,
+            @NonNull String extraDataKey, @Nullable Bundle arguments) {
+        if (extraDataKey.equals(AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY)) {
+            final AccessibilityNodeInfo.ExtraRenderingInfo extraRenderingInfo =
+                    AccessibilityNodeInfo.ExtraRenderingInfo.obtain();
+            extraRenderingInfo.setLayoutSize(getLayoutParams().width, getLayoutParams().height);
+            info.setExtraRenderingInfo(extraRenderingInfo);
+        }
+    }
+
+    @Override
+    public CharSequence getAccessibilityClassName() {
+        return ViewGroup.class.getName();
+    }
+
+    @Override
+    public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) {
+        // If this is a live region, we should send a subtree change event
+        // from this view. Otherwise, we can let it propagate up.
+        if (getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE) {
+            notifyViewAccessibilityStateChangedIfNeeded(
+                    AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
+        } else if (mParent != null) {
+            try {
+                mParent.notifySubtreeAccessibilityStateChanged(this, source, changeType);
+            } catch (AbstractMethodError e) {
+                Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
+                        " does not fully implement ViewParent", e);
+            }
+        }
+    }
+
+    /** @hide */
+    @Override
+    public void notifySubtreeAccessibilityStateChangedIfNeeded() {
+        if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) {
+            return;
+        }
+        // If something important for a11y is happening in this subtree, make sure it's dispatched
+        // from a view that is important for a11y so it doesn't get lost.
+        if ((getImportantForAccessibility() != IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS)
+                && !isImportantForAccessibility() && (getChildCount() > 0)) {
+            ViewParent a11yParent = getParentForAccessibility();
+            if (a11yParent instanceof View) {
+                ((View) a11yParent).notifySubtreeAccessibilityStateChangedIfNeeded();
+                return;
+            }
+        }
+        super.notifySubtreeAccessibilityStateChangedIfNeeded();
+    }
+
+    @Override
+    void resetSubtreeAccessibilityStateChanged() {
+        super.resetSubtreeAccessibilityStateChanged();
+        View[] children = mChildren;
+        final int childCount = mChildrenCount;
+        for (int i = 0; i < childCount; i++) {
+            children[i].resetSubtreeAccessibilityStateChanged();
+        }
+    }
+
+    /**
+     * Counts the number of children of this View that will be sent to an accessibility service.
+     *
+     * @return The number of children an {@code AccessibilityNodeInfo} rooted at this View
+     * would have.
+     */
+    int getNumChildrenForAccessibility() {
+        int numChildrenForAccessibility = 0;
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+            if (child.includeForAccessibility()) {
+                numChildrenForAccessibility++;
+            } else if (child instanceof ViewGroup) {
+                numChildrenForAccessibility += ((ViewGroup) child)
+                        .getNumChildrenForAccessibility();
+            }
+        }
+        return numChildrenForAccessibility;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>Subclasses should always call <code>super.onNestedPrePerformAccessibilityAction</code></p>
+     *
+     * @param target The target view dispatching this action
+     * @param action Action being performed; see
+     *               {@link android.view.accessibility.AccessibilityNodeInfo}
+     * @param args Optional action arguments
+     * @return false by default. Subclasses should return true if they handle the event.
+     */
+    @Override
+    public boolean onNestedPrePerformAccessibilityAction(View target, int action, Bundle args) {
+        return false;
+    }
+
+    @Override
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    void dispatchDetachedFromWindow() {
+        // If we still have a touch target, we are still in the process of
+        // dispatching motion events to a child; we need to get rid of that
+        // child to avoid dispatching events to it after the window is torn
+        // down. To make sure we keep the child in a consistent state, we
+        // first send it an ACTION_CANCEL motion event.
+        cancelAndClearTouchTargets(null);
+
+        // Similarly, set ACTION_EXIT to all hover targets and clear them.
+        exitHoverTargets();
+        exitTooltipHoverTargets();
+
+        // In case view is detached while transition is running
+        mLayoutCalledWhileSuppressed = false;
+
+        // Tear down our drag tracking
+        mChildrenInterestedInDrag = null;
+        mIsInterestedInDrag = false;
+        if (mCurrentDragStartEvent != null) {
+            mCurrentDragStartEvent.recycle();
+            mCurrentDragStartEvent = null;
+        }
+
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            children[i].dispatchDetachedFromWindow();
+        }
+        clearDisappearingChildren();
+        final int transientCount = mTransientViews == null ? 0 : mTransientIndices.size();
+        for (int i = 0; i < transientCount; ++i) {
+            View view = mTransientViews.get(i);
+            view.dispatchDetachedFromWindow();
+        }
+        super.dispatchDetachedFromWindow();
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    protected void internalSetPadding(int left, int top, int right, int bottom) {
+        super.internalSetPadding(left, top, right, bottom);
+
+        if ((mPaddingLeft | mPaddingTop | mPaddingRight | mPaddingBottom) != 0) {
+            mGroupFlags |= FLAG_PADDING_NOT_NULL;
+        } else {
+            mGroupFlags &= ~FLAG_PADDING_NOT_NULL;
+        }
+    }
+
+    @Override
+    protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
+        super.dispatchSaveInstanceState(container);
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            View c = children[i];
+            if ((c.mViewFlags & PARENT_SAVE_DISABLED_MASK) != PARENT_SAVE_DISABLED) {
+                c.dispatchSaveInstanceState(container);
+            }
+        }
+    }
+
+    /**
+     * Perform dispatching of a {@link #saveHierarchyState(android.util.SparseArray)}  freeze()}
+     * to only this view, not to its children.  For use when overriding
+     * {@link #dispatchSaveInstanceState(android.util.SparseArray)}  dispatchFreeze()} to allow
+     * subclasses to freeze their own state but not the state of their children.
+     *
+     * @param container the container
+     */
+    protected void dispatchFreezeSelfOnly(SparseArray<Parcelable> container) {
+        super.dispatchSaveInstanceState(container);
+    }
+
+    @Override
+    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
+        super.dispatchRestoreInstanceState(container);
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            View c = children[i];
+            if ((c.mViewFlags & PARENT_SAVE_DISABLED_MASK) != PARENT_SAVE_DISABLED) {
+                c.dispatchRestoreInstanceState(container);
+            }
+        }
+    }
+
+    /**
+     * Perform dispatching of a {@link #restoreHierarchyState(android.util.SparseArray)}
+     * to only this view, not to its children.  For use when overriding
+     * {@link #dispatchRestoreInstanceState(android.util.SparseArray)} to allow
+     * subclasses to thaw their own state but not the state of their children.
+     *
+     * @param container the container
+     */
+    protected void dispatchThawSelfOnly(SparseArray<Parcelable> container) {
+        super.dispatchRestoreInstanceState(container);
+    }
+
+    /**
+     * Enables or disables the drawing cache for each child of this view group.
+     *
+     * @param enabled true to enable the cache, false to dispose of it
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
+     */
+    @Deprecated
+    protected void setChildrenDrawingCacheEnabled(boolean enabled) {
+        if (enabled || (mPersistentDrawingCache & PERSISTENT_ALL_CACHES) != PERSISTENT_ALL_CACHES) {
+            final View[] children = mChildren;
+            final int count = mChildrenCount;
+            for (int i = 0; i < count; i++) {
+                children[i].setDrawingCacheEnabled(enabled);
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public Bitmap createSnapshot(ViewDebug.CanvasProvider canvasProvider, boolean skipChildren) {
+        int count = mChildrenCount;
+        int[] visibilities = null;
+
+        if (skipChildren) {
+            visibilities = new int[count];
+            for (int i = 0; i < count; i++) {
+                View child = getChildAt(i);
+                visibilities[i] = child.getVisibility();
+                if (visibilities[i] == View.VISIBLE) {
+                    child.mViewFlags = (child.mViewFlags & ~View.VISIBILITY_MASK)
+                            | (View.INVISIBLE & View.VISIBILITY_MASK);
+                }
+            }
+        }
+
+        try {
+            return super.createSnapshot(canvasProvider, skipChildren);
+        } finally {
+            if (skipChildren) {
+                for (int i = 0; i < count; i++) {
+                    View child = getChildAt(i);
+                    child.mViewFlags = (child.mViewFlags & ~View.VISIBILITY_MASK)
+                            | (visibilities[i] & View.VISIBILITY_MASK);
+                }
+            }
+        }
+    }
+
+    /** Return true if this ViewGroup is laying out using optical bounds. */
+    boolean isLayoutModeOptical() {
+        return mLayoutMode == LAYOUT_MODE_OPTICAL_BOUNDS;
+    }
+
+    @Override
+    Insets computeOpticalInsets() {
+        if (isLayoutModeOptical()) {
+            int left = 0;
+            int top = 0;
+            int right = 0;
+            int bottom = 0;
+            for (int i = 0; i < mChildrenCount; i++) {
+                View child = getChildAt(i);
+                if (child.getVisibility() == VISIBLE) {
+                    Insets insets = child.getOpticalInsets();
+                    left =   Math.max(left,   insets.left);
+                    top =    Math.max(top,    insets.top);
+                    right =  Math.max(right,  insets.right);
+                    bottom = Math.max(bottom, insets.bottom);
+                }
+            }
+            return Insets.of(left, top, right, bottom);
+        } else {
+            return Insets.NONE;
+        }
+    }
+
+    private static void fillRect(Canvas canvas, Paint paint, int x1, int y1, int x2, int y2) {
+        if (x1 != x2 && y1 != y2) {
+            if (x1 > x2) {
+                int tmp = x1; x1 = x2; x2 = tmp;
+            }
+            if (y1 > y2) {
+                int tmp = y1; y1 = y2; y2 = tmp;
+            }
+            canvas.drawRect(x1, y1, x2, y2, paint);
+        }
+    }
+
+    private static int sign(int x) {
+        return (x >= 0) ? 1 : -1;
+    }
+
+    private static void drawCorner(Canvas c, Paint paint, int x1, int y1, int dx, int dy, int lw) {
+        fillRect(c, paint, x1, y1, x1 + dx, y1 + lw * sign(dy));
+        fillRect(c, paint, x1, y1, x1 + lw * sign(dx), y1 + dy);
+    }
+
+    private static void drawRectCorners(Canvas canvas, int x1, int y1, int x2, int y2, Paint paint,
+            int lineLength, int lineWidth) {
+        drawCorner(canvas, paint, x1, y1, lineLength, lineLength, lineWidth);
+        drawCorner(canvas, paint, x1, y2, lineLength, -lineLength, lineWidth);
+        drawCorner(canvas, paint, x2, y1, -lineLength, lineLength, lineWidth);
+        drawCorner(canvas, paint, x2, y2, -lineLength, -lineLength, lineWidth);
+    }
+
+    private static void fillDifference(Canvas canvas,
+            int x2, int y2, int x3, int y3,
+            int dx1, int dy1, int dx2, int dy2, Paint paint) {
+        int x1 = x2 - dx1;
+        int y1 = y2 - dy1;
+
+        int x4 = x3 + dx2;
+        int y4 = y3 + dy2;
+
+        fillRect(canvas, paint, x1, y1, x4, y2);
+        fillRect(canvas, paint, x1, y2, x2, y3);
+        fillRect(canvas, paint, x3, y2, x4, y3);
+        fillRect(canvas, paint, x1, y3, x4, y4);
+    }
+
+    /**
+     * @hide
+     */
+    protected void onDebugDrawMargins(Canvas canvas, Paint paint) {
+        for (int i = 0; i < getChildCount(); i++) {
+            View c = getChildAt(i);
+            c.getLayoutParams().onDebugDraw(c, canvas, paint);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    protected void onDebugDraw(Canvas canvas) {
+        Paint paint = getDebugPaint();
+
+        // Draw optical bounds
+        {
+            paint.setColor(Color.RED);
+            paint.setStyle(Paint.Style.STROKE);
+
+            for (int i = 0; i < getChildCount(); i++) {
+                View c = getChildAt(i);
+                if (c.getVisibility() != View.GONE) {
+                    Insets insets = c.getOpticalInsets();
+
+                    drawRect(canvas, paint,
+                            c.getLeft() + insets.left,
+                            c.getTop() + insets.top,
+                            c.getRight() - insets.right - 1,
+                            c.getBottom() - insets.bottom - 1);
+                }
+            }
+        }
+
+        // Draw margins
+        {
+            paint.setColor(Color.argb(63, 255, 0, 255));
+            paint.setStyle(Paint.Style.FILL);
+
+            onDebugDrawMargins(canvas, paint);
+        }
+
+        // Draw clip bounds
+        {
+            paint.setColor(DEBUG_CORNERS_COLOR);
+            paint.setStyle(Paint.Style.FILL);
+
+            int lineLength = dipsToPixels(DEBUG_CORNERS_SIZE_DIP);
+            int lineWidth = dipsToPixels(1);
+            for (int i = 0; i < getChildCount(); i++) {
+                View c = getChildAt(i);
+                if (c.getVisibility() != View.GONE) {
+                    drawRectCorners(canvas, c.getLeft(), c.getTop(), c.getRight(), c.getBottom(),
+                            paint, lineLength, lineWidth);
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        final int childrenCount = mChildrenCount;
+        final View[] children = mChildren;
+        int flags = mGroupFlags;
+
+        if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
+            for (int i = 0; i < childrenCount; i++) {
+                final View child = children[i];
+                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
+                    final LayoutParams params = child.getLayoutParams();
+                    attachLayoutAnimationParameters(child, params, i, childrenCount);
+                    bindLayoutAnimation(child);
+                }
+            }
+
+            final LayoutAnimationController controller = mLayoutAnimationController;
+            if (controller.willOverlap()) {
+                mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
+            }
+
+            controller.start();
+
+            mGroupFlags &= ~FLAG_RUN_ANIMATION;
+            mGroupFlags &= ~FLAG_ANIMATION_DONE;
+
+            if (mAnimationListener != null) {
+                mAnimationListener.onAnimationStart(controller.getAnimation());
+            }
+        }
+
+        int clipSaveCount = 0;
+        final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
+        if (clipToPadding) {
+            clipSaveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
+            canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
+                    mScrollX + mRight - mLeft - mPaddingRight,
+                    mScrollY + mBottom - mTop - mPaddingBottom);
+        }
+
+        // We will draw our child's animation, let's reset the flag
+        mPrivateFlags &= ~PFLAG_DRAW_ANIMATION;
+        mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;
+
+        boolean more = false;
+        final long drawingTime = getDrawingTime();
+
+        canvas.enableZ();
+        final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
+        int transientIndex = transientCount != 0 ? 0 : -1;
+        // Only use the preordered list if not HW accelerated, since the HW pipeline will do the
+        // draw reordering internally
+        final ArrayList<View> preorderedList = isHardwareAccelerated()
+                ? null : buildOrderedChildList();
+        final boolean customOrder = preorderedList == null
+                && isChildrenDrawingOrderEnabled();
+        for (int i = 0; i < childrenCount; i++) {
+            while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
+                final View transientChild = mTransientViews.get(transientIndex);
+                if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
+                        transientChild.getAnimation() != null) {
+                    more |= drawChild(canvas, transientChild, drawingTime);
+                }
+                transientIndex++;
+                if (transientIndex >= transientCount) {
+                    transientIndex = -1;
+                }
+            }
+
+            final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+            final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
+            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
+                more |= drawChild(canvas, child, drawingTime);
+            }
+        }
+        while (transientIndex >= 0) {
+            // there may be additional transient views after the normal views
+            final View transientChild = mTransientViews.get(transientIndex);
+            if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
+                    transientChild.getAnimation() != null) {
+                more |= drawChild(canvas, transientChild, drawingTime);
+            }
+            transientIndex++;
+            if (transientIndex >= transientCount) {
+                break;
+            }
+        }
+        if (preorderedList != null) preorderedList.clear();
+
+        // Draw any disappearing views that have animations
+        if (mDisappearingChildren != null) {
+            final ArrayList<View> disappearingChildren = mDisappearingChildren;
+            final int disappearingCount = disappearingChildren.size() - 1;
+            // Go backwards -- we may delete as animations finish
+            for (int i = disappearingCount; i >= 0; i--) {
+                final View child = disappearingChildren.get(i);
+                more |= drawChild(canvas, child, drawingTime);
+            }
+        }
+        canvas.disableZ();
+
+        if (isShowingLayoutBounds()) {
+            onDebugDraw(canvas);
+        }
+
+        if (clipToPadding) {
+            canvas.restoreToCount(clipSaveCount);
+        }
+
+        // mGroupFlags might have been updated by drawChild()
+        flags = mGroupFlags;
+
+        if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {
+            invalidate(true);
+        }
+
+        if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&
+                mLayoutAnimationController.isDone() && !more) {
+            // We want to erase the drawing cache and notify the listener after the
+            // next frame is drawn because one extra invalidate() is caused by
+            // drawChild() after the animation is over
+            mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;
+            final Runnable end = new Runnable() {
+               @Override
+               public void run() {
+                   notifyAnimationListener();
+               }
+            };
+            post(end);
+        }
+    }
+
+    /**
+     * Returns the ViewGroupOverlay for this view group, creating it if it does
+     * not yet exist. In addition to {@link ViewOverlay}'s support for drawables,
+     * {@link ViewGroupOverlay} allows views to be added to the overlay. These
+     * views, like overlay drawables, are visual-only; they do not receive input
+     * events and should not be used as anything other than a temporary
+     * representation of a view in a parent container, such as might be used
+     * by an animation effect.
+     *
+     * <p>Note: Overlays do not currently work correctly with {@link
+     * SurfaceView} or {@link TextureView}; contents in overlays for these
+     * types of views may not display correctly.</p>
+     *
+     * @return The ViewGroupOverlay object for this view.
+     * @see ViewGroupOverlay
+     */
+    @Override
+    public ViewGroupOverlay getOverlay() {
+        if (mOverlay == null) {
+            mOverlay = new ViewGroupOverlay(mContext, this);
+        }
+        return (ViewGroupOverlay) mOverlay;
+    }
+
+    /**
+     * Converts drawing order position to container position. Override this
+     * if you want to change the drawing order of children. By default, it
+     * returns drawingPosition.
+     * <p>
+     * NOTE: In order for this method to be called, you must enable child ordering
+     * first by calling {@link #setChildrenDrawingOrderEnabled(boolean)}.
+     *
+     * @param drawingPosition the drawing order position.
+     * @return the container position of a child for this drawing order position.
+     *
+     * @see #setChildrenDrawingOrderEnabled(boolean)
+     * @see #isChildrenDrawingOrderEnabled()
+     */
+    protected int getChildDrawingOrder(int childCount, int drawingPosition) {
+        return drawingPosition;
+    }
+
+    /**
+     * Converts drawing order position to container position.
+     * <p>
+     * Children are not necessarily drawn in the order in which they appear in the container.
+     * ViewGroups can enable a custom ordering via {@link #setChildrenDrawingOrderEnabled(boolean)}.
+     * This method returns the container position of a child that appears in the given position
+     * in the current drawing order.
+     *
+     * @param drawingPosition the drawing order position.
+     * @return the container position of a child for this drawing order position.
+     *
+     * @see #getChildDrawingOrder(int, int)}
+     */
+    public final int getChildDrawingOrder(int drawingPosition) {
+        return getChildDrawingOrder(getChildCount(), drawingPosition);
+    }
+
+    private boolean hasChildWithZ() {
+        for (int i = 0; i < mChildrenCount; i++) {
+            if (mChildren[i].getZ() != 0) return true;
+        }
+        return false;
+    }
+
+    /**
+     * Populates (and returns) mPreSortedChildren with a pre-ordered list of the View's children,
+     * sorted first by Z, then by child drawing order (if applicable). This list must be cleared
+     * after use to avoid leaking child Views.
+     *
+     * Uses a stable, insertion sort which is commonly O(n) for ViewGroups with very few elevated
+     * children.
+     */
+    ArrayList<View> buildOrderedChildList() {
+        final int childrenCount = mChildrenCount;
+        if (childrenCount <= 1 || !hasChildWithZ()) return null;
+
+        if (mPreSortedChildren == null) {
+            mPreSortedChildren = new ArrayList<>(childrenCount);
+        } else {
+            // callers should clear, so clear shouldn't be necessary, but for safety...
+            mPreSortedChildren.clear();
+            mPreSortedChildren.ensureCapacity(childrenCount);
+        }
+
+        final boolean customOrder = isChildrenDrawingOrderEnabled();
+        for (int i = 0; i < childrenCount; i++) {
+            // add next child (in child order) to end of list
+            final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+            final View nextChild = mChildren[childIndex];
+            final float currentZ = nextChild.getZ();
+
+            // insert ahead of any Views with greater Z
+            int insertIndex = i;
+            while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
+                insertIndex--;
+            }
+            mPreSortedChildren.add(insertIndex, nextChild);
+        }
+        return mPreSortedChildren;
+    }
+
+    private void notifyAnimationListener() {
+        mGroupFlags &= ~FLAG_NOTIFY_ANIMATION_LISTENER;
+        mGroupFlags |= FLAG_ANIMATION_DONE;
+
+        if (mAnimationListener != null) {
+           final Runnable end = new Runnable() {
+               @Override
+               public void run() {
+                   mAnimationListener.onAnimationEnd(mLayoutAnimationController.getAnimation());
+               }
+           };
+           post(end);
+        }
+
+        invalidate(true);
+    }
+
+    /**
+     * This method is used to cause children of this ViewGroup to restore or recreate their
+     * display lists. It is called by getDisplayList() when the parent ViewGroup does not need
+     * to recreate its own display list, which would happen if it went through the normal
+     * draw/dispatchDraw mechanisms.
+     *
+     * @hide
+     */
+    @Override
+    @UnsupportedAppUsage
+    protected void dispatchGetDisplayList() {
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            final View child = children[i];
+            if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) {
+                recreateChildDisplayList(child);
+            }
+        }
+        final int transientCount = mTransientViews == null ? 0 : mTransientIndices.size();
+        for (int i = 0; i < transientCount; ++i) {
+            View child = mTransientViews.get(i);
+            if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) {
+                recreateChildDisplayList(child);
+            }
+        }
+        if (mOverlay != null) {
+            View overlayView = mOverlay.getOverlayView();
+            recreateChildDisplayList(overlayView);
+        }
+        if (mDisappearingChildren != null) {
+            final ArrayList<View> disappearingChildren = mDisappearingChildren;
+            final int disappearingCount = disappearingChildren.size();
+            for (int i = 0; i < disappearingCount; ++i) {
+                final View child = disappearingChildren.get(i);
+                recreateChildDisplayList(child);
+            }
+        }
+    }
+
+    private void recreateChildDisplayList(View child) {
+        child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) != 0;
+        child.mPrivateFlags &= ~PFLAG_INVALIDATED;
+        child.updateDisplayListIfDirty();
+        child.mRecreateDisplayList = false;
+    }
+
+    /**
+     * Draw one child of this View Group. This method is responsible for getting
+     * the canvas in the right state. This includes clipping, translating so
+     * that the child's scrolled origin is at 0, 0, and applying any animation
+     * transformations.
+     *
+     * @param canvas The canvas on which to draw the child
+     * @param child Who to draw
+     * @param drawingTime The time at which draw is occurring
+     * @return True if an invalidate() was issued
+     */
+    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+        return child.draw(canvas, this, drawingTime);
+    }
+
+    @Override
+    void getScrollIndicatorBounds(@NonNull Rect out) {
+        super.getScrollIndicatorBounds(out);
+
+        // If we have padding and we're supposed to clip children to that
+        // padding, offset the scroll indicators to match our clip bounds.
+        final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
+        if (clipToPadding) {
+            out.left += mPaddingLeft;
+            out.right -= mPaddingRight;
+            out.top += mPaddingTop;
+            out.bottom -= mPaddingBottom;
+        }
+    }
+
+    /**
+     * Returns whether this group's children are clipped to their bounds before drawing.
+     * The default value is true.
+     * @see #setClipChildren(boolean)
+     *
+     * @return True if the group's children will be clipped to their bounds,
+     * false otherwise.
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    @InspectableProperty
+    public boolean getClipChildren() {
+        return ((mGroupFlags & FLAG_CLIP_CHILDREN) != 0);
+    }
+
+    /**
+     * By default, children are clipped to their bounds before drawing. This
+     * allows view groups to override this behavior for animations, etc.
+     *
+     * @param clipChildren true to clip children to their bounds,
+     *        false otherwise
+     * @attr ref android.R.styleable#ViewGroup_clipChildren
+     */
+    public void setClipChildren(boolean clipChildren) {
+        boolean previousValue = (mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN;
+        if (clipChildren != previousValue) {
+            setBooleanFlag(FLAG_CLIP_CHILDREN, clipChildren);
+            for (int i = 0; i < mChildrenCount; ++i) {
+                View child = getChildAt(i);
+                if (child.mRenderNode != null) {
+                    child.mRenderNode.setClipToBounds(clipChildren);
+                }
+            }
+            invalidate(true);
+        }
+    }
+
+    /**
+     * Sets whether this ViewGroup will clip its children to its padding and resize (but not
+     * clip) any EdgeEffect to the padded region, if padding is present.
+     * <p>
+     * By default, children are clipped to the padding of their parent
+     * ViewGroup. This clipping behavior is only enabled if padding is non-zero.
+     *
+     * @param clipToPadding true to clip children to the padding of the group, and resize (but
+     *        not clip) any EdgeEffect to the padded region. False otherwise.
+     * @attr ref android.R.styleable#ViewGroup_clipToPadding
+     */
+    public void setClipToPadding(boolean clipToPadding) {
+        if (hasBooleanFlag(FLAG_CLIP_TO_PADDING) != clipToPadding) {
+            setBooleanFlag(FLAG_CLIP_TO_PADDING, clipToPadding);
+            invalidate(true);
+        }
+    }
+
+    /**
+     * Returns whether this ViewGroup will clip its children to its padding, and resize (but
+     * not clip) any EdgeEffect to the padded region, if padding is present.
+     * <p>
+     * By default, children are clipped to the padding of their parent
+     * Viewgroup. This clipping behavior is only enabled if padding is non-zero.
+     *
+     * @return true if this ViewGroup clips children to its padding and resizes (but doesn't
+     *         clip) any EdgeEffect to the padded region, false otherwise.
+     *
+     * @attr ref android.R.styleable#ViewGroup_clipToPadding
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    @InspectableProperty
+    public boolean getClipToPadding() {
+        return hasBooleanFlag(FLAG_CLIP_TO_PADDING);
+    }
+
+    @Override
+    public void dispatchSetSelected(boolean selected) {
+        final View[] children = mChildren;
+        final int count = mChildrenCount;
+        for (int i = 0; i < count; i++) {
+            children[i].setSelected(selected);
+        }
+    }
+
+    @Override
+    public void dispatchSetActivated(boolean activated) {
+        final View[] children = mChildren;
+        final int count = mChildrenCount;
+        for (int i = 0; i < count; i++) {
+            children[i].setActivated(activated);
+        }
+    }
+
+    @Override
+    protected void dispatchSetPressed(boolean pressed) {
+        final View[] children = mChildren;
+        final int count = mChildrenCount;
+        for (int i = 0; i < count; i++) {
+            final View child = children[i];
+            // Children that are clickable on their own should not
+            // show a pressed state when their parent view does.
+            // Clearing a pressed state always propagates.
+            if (!pressed || (!child.isClickable() && !child.isLongClickable())) {
+                child.setPressed(pressed);
+            }
+        }
+    }
+
+    /**
+     * Dispatches drawable hotspot changes to child views that meet at least
+     * one of the following criteria:
+     * <ul>
+     *     <li>Returns {@code false} from both {@link View#isClickable()} and
+     *     {@link View#isLongClickable()}</li>
+     *     <li>Requests duplication of parent state via
+     *     {@link View#setDuplicateParentStateEnabled(boolean)}</li>
+     * </ul>
+     *
+     * @param x hotspot x coordinate
+     * @param y hotspot y coordinate
+     * @see #drawableHotspotChanged(float, float)
+     */
+    @Override
+    public void dispatchDrawableHotspotChanged(float x, float y) {
+        final int count = mChildrenCount;
+        if (count == 0) {
+            return;
+        }
+
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            final View child = children[i];
+            // Children that are clickable on their own should not
+            // receive hotspots when their parent view does.
+            final boolean nonActionable = !child.isClickable() && !child.isLongClickable();
+            final boolean duplicatesState = (child.mViewFlags & DUPLICATE_PARENT_STATE) != 0;
+            if (nonActionable || duplicatesState) {
+                final float[] point = getTempLocationF();
+                point[0] = x;
+                point[1] = y;
+                transformPointToViewLocal(point, child);
+                child.drawableHotspotChanged(point[0], point[1]);
+            }
+        }
+    }
+
+    @Override
+    void dispatchCancelPendingInputEvents() {
+        super.dispatchCancelPendingInputEvents();
+
+        final View[] children = mChildren;
+        final int count = mChildrenCount;
+        for (int i = 0; i < count; i++) {
+            children[i].dispatchCancelPendingInputEvents();
+        }
+    }
+
+    /**
+     * When this property is set to true, this ViewGroup supports static transformations on
+     * children; this causes
+     * {@link #getChildStaticTransformation(View, android.view.animation.Transformation)} to be
+     * invoked when a child is drawn.
+     *
+     * Any subclass overriding
+     * {@link #getChildStaticTransformation(View, android.view.animation.Transformation)} should
+     * set this property to true.
+     *
+     * @param enabled True to enable static transformations on children, false otherwise.
+     *
+     * @see #getChildStaticTransformation(View, android.view.animation.Transformation)
+     */
+    protected void setStaticTransformationsEnabled(boolean enabled) {
+        setBooleanFlag(FLAG_SUPPORT_STATIC_TRANSFORMATIONS, enabled);
+    }
+
+    /**
+     * Sets  <code>t</code> to be the static transformation of the child, if set, returning a
+     * boolean to indicate whether a static transform was set. The default implementation
+     * simply returns <code>false</code>; subclasses may override this method for different
+     * behavior. {@link #setStaticTransformationsEnabled(boolean)} must be set to true
+     * for this method to be called.
+     *
+     * @param child The child view whose static transform is being requested
+     * @param t The Transformation which will hold the result
+     * @return true if the transformation was set, false otherwise
+     * @see #setStaticTransformationsEnabled(boolean)
+     */
+    protected boolean getChildStaticTransformation(View child, Transformation t) {
+        return false;
+    }
+
+    Transformation getChildTransformation() {
+        if (mChildTransformation == null) {
+            mChildTransformation = new Transformation();
+        }
+        return mChildTransformation;
+    }
+
+    /**
+     * {@hide}
+     */
+    @Override
+    protected <T extends View> T findViewTraversal(@IdRes int id) {
+        if (id == mID) {
+            return (T) this;
+        }
+
+        final View[] where = mChildren;
+        final int len = mChildrenCount;
+
+        for (int i = 0; i < len; i++) {
+            View v = where[i];
+
+            if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
+                v = v.findViewById(id);
+
+                if (v != null) {
+                    return (T) v;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * {@hide}
+     */
+    @Override
+    protected <T extends View> T findViewWithTagTraversal(Object tag) {
+        if (tag != null && tag.equals(mTag)) {
+            return (T) this;
+        }
+
+        final View[] where = mChildren;
+        final int len = mChildrenCount;
+
+        for (int i = 0; i < len; i++) {
+            View v = where[i];
+
+            if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
+                v = v.findViewWithTag(tag);
+
+                if (v != null) {
+                    return (T) v;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * {@hide}
+     */
+    @Override
+    protected <T extends View> T findViewByPredicateTraversal(Predicate<View> predicate,
+            View childToSkip) {
+        if (predicate.test(this)) {
+            return (T) this;
+        }
+
+        final View[] where = mChildren;
+        final int len = mChildrenCount;
+
+        for (int i = 0; i < len; i++) {
+            View v = where[i];
+
+            if (v != childToSkip && (v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
+                v = v.findViewByPredicate(predicate);
+
+                if (v != null) {
+                    return (T) v;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * This method adds a view to this container at the specified index purely for the
+     * purposes of allowing that view to draw even though it is not a normal child of
+     * the container. That is, the view does not participate in layout, focus, accessibility,
+     * input, or other normal view operations; it is purely an item to be drawn during the normal
+     * rendering operation of this container. The index that it is added at is the order
+     * in which it will be drawn, with respect to the other views in the container.
+     * For example, a transient view added at index 0 will be drawn before all other views
+     * in the container because it will be drawn first (including before any real view
+     * at index 0). There can be more than one transient view at any particular index;
+     * these views will be drawn in the order in which they were added to the list of
+     * transient views. The index of transient views can also be greater than the number
+     * of normal views in the container; that just means that they will be drawn after all
+     * other views are drawn.
+     *
+     * <p>Note that since transient views do not participate in layout, they must be sized
+     * manually or, more typically, they should just use the size that they had before they
+     * were removed from their container.</p>
+     *
+     * <p>Transient views are useful for handling animations of views that have been removed
+     * from the container, but which should be animated out after the removal. Adding these
+     * views as transient views allows them to participate in drawing without side-effecting
+     * the layout of the container.</p>
+     *
+     * <p>Transient views must always be explicitly {@link #removeTransientView(View) removed}
+     * from the container when they are no longer needed. For example, a transient view
+     * which is added in order to fade it out in its old location should be removed
+     * once the animation is complete.</p>
+     *
+     * @param view The view to be added. The view must not have a parent.
+     * @param index The index at which this view should be drawn, must be >= 0.
+     * This value is relative to the {@link #getChildAt(int) index} values in the normal
+     * child list of this container, where any transient view at a particular index will
+     * be drawn before any normal child at that same index.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void addTransientView(View view, int index) {
+        if (index < 0 || view == null) {
+            return;
+        }
+        if (view.mParent != null) {
+            throw new IllegalStateException("The specified view already has a parent "
+                    + view.mParent);
+        }
+
+        if (mTransientIndices == null) {
+            mTransientIndices = new IntArray();
+            mTransientViews = new ArrayList<View>();
+        }
+        final int oldSize = mTransientIndices.size();
+        if (oldSize > 0) {
+            int insertionIndex;
+            for (insertionIndex = 0; insertionIndex < oldSize; ++insertionIndex) {
+                if (index < mTransientIndices.get(insertionIndex)) {
+                    break;
+                }
+            }
+            mTransientIndices.add(insertionIndex, index);
+            mTransientViews.add(insertionIndex, view);
+        } else {
+            mTransientIndices.add(index);
+            mTransientViews.add(view);
+        }
+        view.mParent = this;
+        if (mAttachInfo != null) {
+            view.dispatchAttachedToWindow(mAttachInfo, (mViewFlags & VISIBILITY_MASK));
+        }
+        invalidate(true);
+    }
+
+    /**
+     * Removes a view from the list of transient views in this container. If there is no
+     * such transient view, this method does nothing.
+     *
+     * @param view The transient view to be removed
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void removeTransientView(View view) {
+        if (mTransientViews == null) {
+            return;
+        }
+        final int size = mTransientViews.size();
+        for (int i = 0; i < size; ++i) {
+            if (view == mTransientViews.get(i)) {
+                mTransientViews.remove(i);
+                mTransientIndices.remove(i);
+                view.mParent = null;
+                if (view.mAttachInfo != null) {
+                    view.dispatchDetachedFromWindow();
+                }
+                invalidate(true);
+                return;
+            }
+        }
+    }
+
+    /**
+     * Returns the number of transient views in this container. Specific transient
+     * views and the index at which they were added can be retrieved via
+     * {@link #getTransientView(int)} and {@link #getTransientViewIndex(int)}.
+     *
+     * @see #addTransientView(View, int)
+     * @return The number of transient views in this container
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public int getTransientViewCount() {
+        return mTransientIndices == null ? 0 : mTransientIndices.size();
+    }
+
+    /**
+     * Given a valid position within the list of transient views, returns the index of
+     * the transient view at that position.
+     *
+     * @param position The position of the index being queried. Must be at least 0
+     * and less than the value returned by {@link #getTransientViewCount()}.
+     * @return The index of the transient view stored in the given position if the
+     * position is valid, otherwise -1
+     *
+     * @hide
+     */
+    public int getTransientViewIndex(int position) {
+        if (position < 0 || mTransientIndices == null || position >= mTransientIndices.size()) {
+            return -1;
+        }
+        return mTransientIndices.get(position);
+    }
+
+    /**
+     * Given a valid position within the list of transient views, returns the
+     * transient view at that position.
+     *
+     * @param position The position of the view being queried. Must be at least 0
+     * and less than the value returned by {@link #getTransientViewCount()}.
+     * @return The transient view stored in the given position if the
+     * position is valid, otherwise null
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public View getTransientView(int position) {
+        if (mTransientViews == null || position >= mTransientViews.size()) {
+            return null;
+        }
+        return mTransientViews.get(position);
+    }
+
+    /**
+     * <p>Adds a child view. If no layout parameters are already set on the child, the
+     * default parameters for this ViewGroup are set on the child.</p>
+     *
+     * <p><strong>Note:</strong> do not invoke this method from
+     * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
+     * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
+     *
+     * @param child the child view to add
+     *
+     * @see #generateDefaultLayoutParams()
+     */
+    public void addView(View child) {
+        addView(child, -1);
+    }
+
+    /**
+     * Adds a child view. If no layout parameters are already set on the child, the
+     * default parameters for this ViewGroup are set on the child.
+     *
+     * <p><strong>Note:</strong> do not invoke this method from
+     * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
+     * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
+     *
+     * @param child the child view to add
+     * @param index the position at which to add the child
+     *
+     * @see #generateDefaultLayoutParams()
+     */
+    public void addView(View child, int index) {
+        if (child == null) {
+            throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
+        }
+        LayoutParams params = child.getLayoutParams();
+        if (params == null) {
+            params = generateDefaultLayoutParams();
+            if (params == null) {
+                throw new IllegalArgumentException(
+                        "generateDefaultLayoutParams() cannot return null  ");
+            }
+        }
+        addView(child, index, params);
+    }
+
+    /**
+     * Adds a child view with this ViewGroup's default layout parameters and the
+     * specified width and height.
+     *
+     * <p><strong>Note:</strong> do not invoke this method from
+     * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
+     * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
+     *
+     * @param child the child view to add
+     */
+    public void addView(View child, int width, int height) {
+        final LayoutParams params = generateDefaultLayoutParams();
+        params.width = width;
+        params.height = height;
+        addView(child, -1, params);
+    }
+
+    /**
+     * Adds a child view with the specified layout parameters.
+     *
+     * <p><strong>Note:</strong> do not invoke this method from
+     * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
+     * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
+     *
+     * @param child the child view to add
+     * @param params the layout parameters to set on the child
+     */
+    @Override
+    public void addView(View child, LayoutParams params) {
+        addView(child, -1, params);
+    }
+
+    /**
+     * Adds a child view with the specified layout parameters.
+     *
+     * <p><strong>Note:</strong> do not invoke this method from
+     * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
+     * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
+     *
+     * @param child the child view to add
+     * @param index the position at which to add the child or -1 to add last
+     * @param params the layout parameters to set on the child
+     */
+    public void addView(View child, int index, LayoutParams params) {
+        if (DBG) {
+            System.out.println(this + " addView");
+        }
+
+        if (child == null) {
+            throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
+        }
+
+        // addViewInner() will call child.requestLayout() when setting the new LayoutParams
+        // therefore, we call requestLayout() on ourselves before, so that the child's request
+        // will be blocked at our level
+        requestLayout();
+        invalidate(true);
+        addViewInner(child, index, params, false);
+    }
+
+    @Override
+    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
+        if (!checkLayoutParams(params)) {
+            throw new IllegalArgumentException("Invalid LayoutParams supplied to " + this);
+        }
+        if (view.mParent != this) {
+            throw new IllegalArgumentException("Given view not a child of " + this);
+        }
+        view.setLayoutParams(params);
+    }
+
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return  p != null;
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when the hierarchy
+     * within this view changed. The hierarchy changes whenever a child is added
+     * to or removed from this view.
+     */
+    public interface OnHierarchyChangeListener {
+        /**
+         * Called when a new child is added to a parent view.
+         *
+         * @param parent the view in which a child was added
+         * @param child the new child view added in the hierarchy
+         */
+        void onChildViewAdded(View parent, View child);
+
+        /**
+         * Called when a child is removed from a parent view.
+         *
+         * @param parent the view from which the child was removed
+         * @param child the child removed from the hierarchy
+         */
+        void onChildViewRemoved(View parent, View child);
+    }
+
+    /**
+     * Register a callback to be invoked when a child is added to or removed
+     * from this view.
+     *
+     * @param listener the callback to invoke on hierarchy change
+     */
+    public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
+        mOnHierarchyChangeListener = listener;
+    }
+
+    @UnsupportedAppUsage
+    void dispatchViewAdded(View child) {
+        onViewAdded(child);
+        if (mOnHierarchyChangeListener != null) {
+            mOnHierarchyChangeListener.onChildViewAdded(this, child);
+        }
+    }
+
+    /**
+     * Called when a new child is added to this ViewGroup. Overrides should always
+     * call super.onViewAdded.
+     *
+     * @param child the added child view
+     */
+    public void onViewAdded(View child) {
+    }
+
+    @UnsupportedAppUsage
+    void dispatchViewRemoved(View child) {
+        onViewRemoved(child);
+        if (mOnHierarchyChangeListener != null) {
+            mOnHierarchyChangeListener.onChildViewRemoved(this, child);
+        }
+    }
+
+    /**
+     * Called when a child view is removed from this ViewGroup. Overrides should always
+     * call super.onViewRemoved.
+     *
+     * @param child the removed child view
+     */
+    public void onViewRemoved(View child) {
+    }
+
+    private void clearCachedLayoutMode() {
+        if (!hasBooleanFlag(FLAG_LAYOUT_MODE_WAS_EXPLICITLY_SET)) {
+           mLayoutMode = LAYOUT_MODE_UNDEFINED;
+        }
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        clearCachedLayoutMode();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        clearCachedLayoutMode();
+    }
+
+    /** @hide */
+    @Override
+    protected void destroyHardwareResources() {
+        super.destroyHardwareResources();
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            getChildAt(i).destroyHardwareResources();
+        }
+    }
+
+    /**
+     * Adds a view during layout. This is useful if in your onLayout() method,
+     * you need to add more views (as does the list view for example).
+     *
+     * If index is negative, it means put it at the end of the list.
+     *
+     * @param child the view to add to the group
+     * @param index the index at which the child must be added or -1 to add last
+     * @param params the layout parameters to associate with the child
+     * @return true if the child was added, false otherwise
+     */
+    protected boolean addViewInLayout(View child, int index, LayoutParams params) {
+        return addViewInLayout(child, index, params, false);
+    }
+
+    /**
+     * Adds a view during layout. This is useful if in your onLayout() method,
+     * you need to add more views (as does the list view for example).
+     *
+     * If index is negative, it means put it at the end of the list.
+     *
+     * @param child the view to add to the group
+     * @param index the index at which the child must be added or -1 to add last
+     * @param params the layout parameters to associate with the child
+     * @param preventRequestLayout if true, calling this method will not trigger a
+     *        layout request on child
+     * @return true if the child was added, false otherwise
+     */
+    protected boolean addViewInLayout(View child, int index, LayoutParams params,
+            boolean preventRequestLayout) {
+        if (child == null) {
+            throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
+        }
+        child.mParent = null;
+        addViewInner(child, index, params, preventRequestLayout);
+        child.mPrivateFlags = (child.mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
+        return true;
+    }
+
+    /**
+     * Prevents the specified child to be laid out during the next layout pass.
+     *
+     * @param child the child on which to perform the cleanup
+     */
+    protected void cleanupLayoutState(View child) {
+        child.mPrivateFlags &= ~View.PFLAG_FORCE_LAYOUT;
+    }
+
+    private void addViewInner(View child, int index, LayoutParams params,
+            boolean preventRequestLayout) {
+
+        if (mTransition != null) {
+            // Don't prevent other add transitions from completing, but cancel remove
+            // transitions to let them complete the process before we add to the container
+            mTransition.cancel(LayoutTransition.DISAPPEARING);
+        }
+
+        if (child.getParent() != null) {
+            throw new IllegalStateException("The specified child already has a parent. " +
+                    "You must call removeView() on the child's parent first.");
+        }
+
+        if (mTransition != null) {
+            mTransition.addChild(this, child);
+        }
+
+        if (!checkLayoutParams(params)) {
+            params = generateLayoutParams(params);
+        }
+
+        if (preventRequestLayout) {
+            child.mLayoutParams = params;
+        } else {
+            child.setLayoutParams(params);
+        }
+
+        if (index < 0) {
+            index = mChildrenCount;
+        }
+
+        addInArray(child, index);
+
+        // tell our children
+        if (preventRequestLayout) {
+            child.assignParent(this);
+        } else {
+            child.mParent = this;
+        }
+        if (child.hasUnhandledKeyListener()) {
+            incrementChildUnhandledKeyListeners();
+        }
+
+        final boolean childHasFocus = child.hasFocus();
+        if (childHasFocus) {
+            requestChildFocus(child, child.findFocus());
+        }
+
+        AttachInfo ai = mAttachInfo;
+        if (ai != null && (mGroupFlags & FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW) == 0) {
+            boolean lastKeepOn = ai.mKeepScreenOn;
+            ai.mKeepScreenOn = false;
+            child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));
+            if (ai.mKeepScreenOn) {
+                needGlobalAttributesUpdate(true);
+            }
+            ai.mKeepScreenOn = lastKeepOn;
+        }
+
+        if (child.isLayoutDirectionInherited()) {
+            child.resetRtlProperties();
+        }
+
+        dispatchViewAdded(child);
+
+        if ((child.mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE) {
+            mGroupFlags |= FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE;
+        }
+
+        if (child.hasTransientState()) {
+            childHasTransientStateChanged(child, true);
+        }
+
+        if (child.getVisibility() != View.GONE) {
+            notifySubtreeAccessibilityStateChangedIfNeeded();
+        }
+
+        if (mTransientIndices != null) {
+            final int transientCount = mTransientIndices.size();
+            for (int i = 0; i < transientCount; ++i) {
+                final int oldIndex = mTransientIndices.get(i);
+                if (index <= oldIndex) {
+                    mTransientIndices.set(i, oldIndex + 1);
+                }
+            }
+        }
+
+        if (mCurrentDragStartEvent != null && child.getVisibility() == VISIBLE) {
+            notifyChildOfDragStart(child);
+        }
+
+        if (child.hasDefaultFocus()) {
+            // When adding a child that contains default focus, either during inflation or while
+            // manually assembling the hierarchy, update the ancestor default-focus chain.
+            setDefaultFocus(child);
+        }
+
+        touchAccessibilityNodeProviderIfNeeded(child);
+    }
+
+    /**
+     * We may need to touch the provider to bring up the a11y layer. In a11y mode
+     * clients inspect the screen or the user touches it which triggers bringing up
+     * of the a11y infrastructure while in autofill mode we want the infra up and
+     * running from the beginning since we watch for a11y events to drive autofill.
+     */
+    private void touchAccessibilityNodeProviderIfNeeded(View child) {
+        if (mContext.isAutofillCompatibilityEnabled()) {
+            child.getAccessibilityNodeProvider();
+        }
+    }
+
+    private void addInArray(View child, int index) {
+        View[] children = mChildren;
+        final int count = mChildrenCount;
+        final int size = children.length;
+        if (index == count) {
+            if (size == count) {
+                mChildren = new View[size + ARRAY_CAPACITY_INCREMENT];
+                System.arraycopy(children, 0, mChildren, 0, size);
+                children = mChildren;
+            }
+            children[mChildrenCount++] = child;
+        } else if (index < count) {
+            if (size == count) {
+                mChildren = new View[size + ARRAY_CAPACITY_INCREMENT];
+                System.arraycopy(children, 0, mChildren, 0, index);
+                System.arraycopy(children, index, mChildren, index + 1, count - index);
+                children = mChildren;
+            } else {
+                System.arraycopy(children, index, children, index + 1, count - index);
+            }
+            children[index] = child;
+            mChildrenCount++;
+            if (mLastTouchDownIndex >= index) {
+                mLastTouchDownIndex++;
+            }
+        } else {
+            throw new IndexOutOfBoundsException("index=" + index + " count=" + count);
+        }
+    }
+
+    // This method also sets the child's mParent to null
+    private void removeFromArray(int index) {
+        final View[] children = mChildren;
+        if (!(mTransitioningViews != null && mTransitioningViews.contains(children[index]))) {
+            children[index].mParent = null;
+        }
+        final int count = mChildrenCount;
+        if (index == count - 1) {
+            children[--mChildrenCount] = null;
+        } else if (index >= 0 && index < count) {
+            System.arraycopy(children, index + 1, children, index, count - index - 1);
+            children[--mChildrenCount] = null;
+        } else {
+            throw new IndexOutOfBoundsException();
+        }
+        if (mLastTouchDownIndex == index) {
+            mLastTouchDownTime = 0;
+            mLastTouchDownIndex = -1;
+        } else if (mLastTouchDownIndex > index) {
+            mLastTouchDownIndex--;
+        }
+    }
+
+    // This method also sets the children's mParent to null
+    private void removeFromArray(int start, int count) {
+        final View[] children = mChildren;
+        final int childrenCount = mChildrenCount;
+
+        start = Math.max(0, start);
+        final int end = Math.min(childrenCount, start + count);
+
+        if (start == end) {
+            return;
+        }
+
+        if (end == childrenCount) {
+            for (int i = start; i < end; i++) {
+                children[i].mParent = null;
+                children[i] = null;
+            }
+        } else {
+            for (int i = start; i < end; i++) {
+                children[i].mParent = null;
+            }
+
+            // Since we're looping above, we might as well do the copy, but is arraycopy()
+            // faster than the extra 2 bounds checks we would do in the loop?
+            System.arraycopy(children, end, children, start, childrenCount - end);
+
+            for (int i = childrenCount - (end - start); i < childrenCount; i++) {
+                children[i] = null;
+            }
+        }
+
+        mChildrenCount -= (end - start);
+    }
+
+    private void bindLayoutAnimation(View child) {
+        Animation a = mLayoutAnimationController.getAnimationForView(child);
+        child.setAnimation(a);
+    }
+
+    /**
+     * Subclasses should override this method to set layout animation
+     * parameters on the supplied child.
+     *
+     * @param child the child to associate with animation parameters
+     * @param params the child's layout parameters which hold the animation
+     *        parameters
+     * @param index the index of the child in the view group
+     * @param count the number of children in the view group
+     */
+    protected void attachLayoutAnimationParameters(View child,
+            LayoutParams params, int index, int count) {
+        LayoutAnimationController.AnimationParameters animationParams =
+                    params.layoutAnimationParameters;
+        if (animationParams == null) {
+            animationParams = new LayoutAnimationController.AnimationParameters();
+            params.layoutAnimationParameters = animationParams;
+        }
+
+        animationParams.count = count;
+        animationParams.index = index;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p><strong>Note:</strong> do not invoke this method from
+     * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
+     * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
+     */
+    @Override
+    public void removeView(View view) {
+        if (removeViewInternal(view)) {
+            requestLayout();
+            invalidate(true);
+        }
+    }
+
+    /**
+     * Removes a view during layout. This is useful if in your onLayout() method,
+     * you need to remove more views.
+     *
+     * <p><strong>Note:</strong> do not invoke this method from
+     * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
+     * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
+     *
+     * @param view the view to remove from the group
+     */
+    public void removeViewInLayout(View view) {
+        removeViewInternal(view);
+    }
+
+    /**
+     * Removes a range of views during layout. This is useful if in your onLayout() method,
+     * you need to remove more views.
+     *
+     * <p><strong>Note:</strong> do not invoke this method from
+     * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
+     * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
+     *
+     * @param start the index of the first view to remove from the group
+     * @param count the number of views to remove from the group
+     */
+    public void removeViewsInLayout(int start, int count) {
+        removeViewsInternal(start, count);
+    }
+
+    /**
+     * Removes the view at the specified position in the group.
+     *
+     * <p><strong>Note:</strong> do not invoke this method from
+     * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
+     * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
+     *
+     * @param index the position in the group of the view to remove
+     */
+    public void removeViewAt(int index) {
+        removeViewInternal(index, getChildAt(index));
+        requestLayout();
+        invalidate(true);
+    }
+
+    /**
+     * Removes the specified range of views from the group.
+     *
+     * <p><strong>Note:</strong> do not invoke this method from
+     * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
+     * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
+     *
+     * @param start the first position in the group of the range of views to remove
+     * @param count the number of views to remove
+     */
+    public void removeViews(int start, int count) {
+        removeViewsInternal(start, count);
+        requestLayout();
+        invalidate(true);
+    }
+
+    private boolean removeViewInternal(View view) {
+        final int index = indexOfChild(view);
+        if (index >= 0) {
+            removeViewInternal(index, view);
+            return true;
+        }
+        return false;
+    }
+
+    private void removeViewInternal(int index, View view) {
+        if (mTransition != null) {
+            mTransition.removeChild(this, view);
+        }
+
+        boolean clearChildFocus = false;
+        if (view == mFocused) {
+            view.unFocus(null);
+            clearChildFocus = true;
+        }
+        if (view == mFocusedInCluster) {
+            clearFocusedInCluster(view);
+        }
+
+        view.clearAccessibilityFocus();
+
+        cancelTouchTarget(view);
+        cancelHoverTarget(view);
+
+        if (view.getAnimation() != null ||
+                (mTransitioningViews != null && mTransitioningViews.contains(view))) {
+            addDisappearingView(view);
+        } else if (view.mAttachInfo != null) {
+           view.dispatchDetachedFromWindow();
+        }
+
+        if (view.hasTransientState()) {
+            childHasTransientStateChanged(view, false);
+        }
+
+        needGlobalAttributesUpdate(false);
+
+        removeFromArray(index);
+
+        if (view.hasUnhandledKeyListener()) {
+            decrementChildUnhandledKeyListeners();
+        }
+
+        if (view == mDefaultFocus) {
+            clearDefaultFocus(view);
+        }
+        if (clearChildFocus) {
+            clearChildFocus(view);
+            if (!rootViewRequestFocus()) {
+                notifyGlobalFocusCleared(this);
+            }
+        }
+
+        dispatchViewRemoved(view);
+
+        if (view.getVisibility() != View.GONE) {
+            notifySubtreeAccessibilityStateChangedIfNeeded();
+        }
+
+        int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
+        for (int i = 0; i < transientCount; ++i) {
+            final int oldIndex = mTransientIndices.get(i);
+            if (index < oldIndex) {
+                mTransientIndices.set(i, oldIndex - 1);
+            }
+        }
+
+        if (mCurrentDragStartEvent != null) {
+            mChildrenInterestedInDrag.remove(view);
+        }
+    }
+
+    /**
+     * Sets the LayoutTransition object for this ViewGroup. If the LayoutTransition object is
+     * not null, changes in layout which occur because of children being added to or removed from
+     * the ViewGroup will be animated according to the animations defined in that LayoutTransition
+     * object. By default, the transition object is null (so layout changes are not animated).
+     *
+     * <p>Replacing a non-null transition will cause that previous transition to be
+     * canceled, if it is currently running, to restore this container to
+     * its correct post-transition state.</p>
+     *
+     * @param transition The LayoutTransition object that will animated changes in layout. A value
+     * of <code>null</code> means no transition will run on layout changes.
+     * @attr ref android.R.styleable#ViewGroup_animateLayoutChanges
+     */
+    public void setLayoutTransition(LayoutTransition transition) {
+        if (mTransition != null) {
+            LayoutTransition previousTransition = mTransition;
+            previousTransition.cancel();
+            previousTransition.removeTransitionListener(mLayoutTransitionListener);
+        }
+        mTransition = transition;
+        if (mTransition != null) {
+            mTransition.addTransitionListener(mLayoutTransitionListener);
+        }
+    }
+
+    /**
+     * Gets the LayoutTransition object for this ViewGroup. If the LayoutTransition object is
+     * not null, changes in layout which occur because of children being added to or removed from
+     * the ViewGroup will be animated according to the animations defined in that LayoutTransition
+     * object. By default, the transition object is null (so layout changes are not animated).
+     *
+     * @return LayoutTranstion The LayoutTransition object that will animated changes in layout.
+     * A value of <code>null</code> means no transition will run on layout changes.
+     */
+    public LayoutTransition getLayoutTransition() {
+        return mTransition;
+    }
+
+    private void removeViewsInternal(int start, int count) {
+        final int end = start + count;
+
+        if (start < 0 || count < 0 || end > mChildrenCount) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        final View focused = mFocused;
+        final boolean detach = mAttachInfo != null;
+        boolean clearChildFocus = false;
+        View clearDefaultFocus = null;
+
+        final View[] children = mChildren;
+
+        for (int i = start; i < end; i++) {
+            final View view = children[i];
+
+            if (mTransition != null) {
+                mTransition.removeChild(this, view);
+            }
+
+            if (view == focused) {
+                view.unFocus(null);
+                clearChildFocus = true;
+            }
+            if (view == mDefaultFocus) {
+                clearDefaultFocus = view;
+            }
+            if (view == mFocusedInCluster) {
+                clearFocusedInCluster(view);
+            }
+
+            view.clearAccessibilityFocus();
+
+            cancelTouchTarget(view);
+            cancelHoverTarget(view);
+
+            if (view.getAnimation() != null ||
+                (mTransitioningViews != null && mTransitioningViews.contains(view))) {
+                addDisappearingView(view);
+            } else if (detach) {
+               view.dispatchDetachedFromWindow();
+            }
+
+            if (view.hasTransientState()) {
+                childHasTransientStateChanged(view, false);
+            }
+
+            needGlobalAttributesUpdate(false);
+
+            dispatchViewRemoved(view);
+        }
+
+        removeFromArray(start, count);
+
+        if (clearDefaultFocus != null) {
+            clearDefaultFocus(clearDefaultFocus);
+        }
+        if (clearChildFocus) {
+            clearChildFocus(focused);
+            if (!rootViewRequestFocus()) {
+                notifyGlobalFocusCleared(focused);
+            }
+        }
+    }
+
+    /**
+     * Call this method to remove all child views from the
+     * ViewGroup.
+     *
+     * <p><strong>Note:</strong> do not invoke this method from
+     * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
+     * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
+     */
+    public void removeAllViews() {
+        removeAllViewsInLayout();
+        requestLayout();
+        invalidate(true);
+    }
+
+    /**
+     * Called by a ViewGroup subclass to remove child views from itself,
+     * when it must first know its size on screen before it can calculate how many
+     * child views it will render. An example is a Gallery or a ListView, which
+     * may "have" 50 children, but actually only render the number of children
+     * that can currently fit inside the object on screen. Do not call
+     * this method unless you are extending ViewGroup and understand the
+     * view measuring and layout pipeline.
+     *
+     * <p><strong>Note:</strong> do not invoke this method from
+     * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
+     * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
+     */
+    public void removeAllViewsInLayout() {
+        final int count = mChildrenCount;
+        if (count <= 0) {
+            return;
+        }
+
+        final View[] children = mChildren;
+        mChildrenCount = 0;
+
+        final View focused = mFocused;
+        final boolean detach = mAttachInfo != null;
+        boolean clearChildFocus = false;
+
+        needGlobalAttributesUpdate(false);
+
+        for (int i = count - 1; i >= 0; i--) {
+            final View view = children[i];
+
+            if (mTransition != null) {
+                mTransition.removeChild(this, view);
+            }
+
+            if (view == focused) {
+                view.unFocus(null);
+                clearChildFocus = true;
+            }
+
+            view.clearAccessibilityFocus();
+
+            cancelTouchTarget(view);
+            cancelHoverTarget(view);
+
+            if (view.getAnimation() != null ||
+                    (mTransitioningViews != null && mTransitioningViews.contains(view))) {
+                addDisappearingView(view);
+            } else if (detach) {
+               view.dispatchDetachedFromWindow();
+            }
+
+            if (view.hasTransientState()) {
+                childHasTransientStateChanged(view, false);
+            }
+
+            dispatchViewRemoved(view);
+
+            view.mParent = null;
+            children[i] = null;
+        }
+
+        if (mDefaultFocus != null) {
+            clearDefaultFocus(mDefaultFocus);
+        }
+        if (mFocusedInCluster != null) {
+            clearFocusedInCluster(mFocusedInCluster);
+        }
+        if (clearChildFocus) {
+            clearChildFocus(focused);
+            if (!rootViewRequestFocus()) {
+                notifyGlobalFocusCleared(focused);
+            }
+        }
+    }
+
+    /**
+     * Finishes the removal of a detached view. This method will dispatch the detached from
+     * window event and notify the hierarchy change listener.
+     * <p>
+     * This method is intended to be lightweight and makes no assumptions about whether the
+     * parent or child should be redrawn. Proper use of this method will include also making
+     * any appropriate {@link #requestLayout()} or {@link #invalidate()} calls.
+     * For example, callers can {@link #post(Runnable) post} a {@link Runnable}
+     * which performs a {@link #requestLayout()} on the next frame, after all detach/remove
+     * calls are finished, causing layout to be run prior to redrawing the view hierarchy.
+     *
+     * @param child the child to be definitely removed from the view hierarchy
+     * @param animate if true and the view has an animation, the view is placed in the
+     *                disappearing views list, otherwise, it is detached from the window
+     *
+     * @see #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)
+     * @see #detachAllViewsFromParent()
+     * @see #detachViewFromParent(View)
+     * @see #detachViewFromParent(int)
+     */
+    protected void removeDetachedView(View child, boolean animate) {
+        if (mTransition != null) {
+            mTransition.removeChild(this, child);
+        }
+
+        if (child == mFocused) {
+            child.clearFocus();
+        }
+        if (child == mDefaultFocus) {
+            clearDefaultFocus(child);
+        }
+        if (child == mFocusedInCluster) {
+            clearFocusedInCluster(child);
+        }
+
+        child.clearAccessibilityFocus();
+
+        cancelTouchTarget(child);
+        cancelHoverTarget(child);
+
+        if ((animate && child.getAnimation() != null) ||
+                (mTransitioningViews != null && mTransitioningViews.contains(child))) {
+            addDisappearingView(child);
+        } else if (child.mAttachInfo != null) {
+            child.dispatchDetachedFromWindow();
+        }
+
+        if (child.hasTransientState()) {
+            childHasTransientStateChanged(child, false);
+        }
+
+        dispatchViewRemoved(child);
+    }
+
+    /**
+     * Attaches a view to this view group. Attaching a view assigns this group as the parent,
+     * sets the layout parameters and puts the view in the list of children so that
+     * it can be retrieved by calling {@link #getChildAt(int)}.
+     * <p>
+     * This method is intended to be lightweight and makes no assumptions about whether the
+     * parent or child should be redrawn. Proper use of this method will include also making
+     * any appropriate {@link #requestLayout()} or {@link #invalidate()} calls.
+     * For example, callers can {@link #post(Runnable) post} a {@link Runnable}
+     * which performs a {@link #requestLayout()} on the next frame, after all detach/attach
+     * calls are finished, causing layout to be run prior to redrawing the view hierarchy.
+     * <p>
+     * This method should be called only for views which were detached from their parent.
+     *
+     * @param child the child to attach
+     * @param index the index at which the child should be attached
+     * @param params the layout parameters of the child
+     *
+     * @see #removeDetachedView(View, boolean)
+     * @see #detachAllViewsFromParent()
+     * @see #detachViewFromParent(View)
+     * @see #detachViewFromParent(int)
+     */
+    protected void attachViewToParent(View child, int index, LayoutParams params) {
+        child.mLayoutParams = params;
+
+        if (index < 0) {
+            index = mChildrenCount;
+        }
+
+        addInArray(child, index);
+
+        child.mParent = this;
+        child.mPrivateFlags = (child.mPrivateFlags & ~PFLAG_DIRTY_MASK
+                        & ~PFLAG_DRAWING_CACHE_VALID)
+                | PFLAG_DRAWN | PFLAG_INVALIDATED;
+        child.setDetached(false);
+        this.mPrivateFlags |= PFLAG_INVALIDATED;
+
+        if (child.hasFocus()) {
+            requestChildFocus(child, child.findFocus());
+        }
+        dispatchVisibilityAggregated(isAttachedToWindow() && getWindowVisibility() == VISIBLE
+                && isShown());
+        notifySubtreeAccessibilityStateChangedIfNeeded();
+    }
+
+    /**
+     * Detaches a view from its parent. Detaching a view should be followed
+     * either by a call to
+     * {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)}
+     * or a call to {@link #removeDetachedView(View, boolean)}. Detachment should only be
+     * temporary; reattachment or removal should happen within the same drawing cycle as
+     * detachment. When a view is detached, its parent is null and cannot be retrieved by a
+     * call to {@link #getChildAt(int)}.
+     *
+     * @param child the child to detach
+     *
+     * @see #detachViewFromParent(int)
+     * @see #detachViewsFromParent(int, int)
+     * @see #detachAllViewsFromParent()
+     * @see #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)
+     * @see #removeDetachedView(View, boolean)
+     */
+    protected void detachViewFromParent(View child) {
+        child.setDetached(true);
+        removeFromArray(indexOfChild(child));
+    }
+
+    /**
+     * Detaches a view from its parent. Detaching a view should be followed
+     * either by a call to
+     * {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)}
+     * or a call to {@link #removeDetachedView(View, boolean)}. Detachment should only be
+     * temporary; reattachment or removal should happen within the same drawing cycle as
+     * detachment. When a view is detached, its parent is null and cannot be retrieved by a
+     * call to {@link #getChildAt(int)}.
+     *
+     * @param index the index of the child to detach
+     *
+     * @see #detachViewFromParent(View)
+     * @see #detachAllViewsFromParent()
+     * @see #detachViewsFromParent(int, int)
+     * @see #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)
+     * @see #removeDetachedView(View, boolean)
+     */
+    protected void detachViewFromParent(int index) {
+        if (index >= 0 && index < mChildrenCount) {
+            mChildren[index].setDetached(true);
+        }
+        removeFromArray(index);
+    }
+
+    /**
+     * Detaches a range of views from their parents. Detaching a view should be followed
+     * either by a call to
+     * {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)}
+     * or a call to {@link #removeDetachedView(View, boolean)}. Detachment should only be
+     * temporary; reattachment or removal should happen within the same drawing cycle as
+     * detachment. When a view is detached, its parent is null and cannot be retrieved by a
+     * call to {@link #getChildAt(int)}.
+     *
+     * @param start the first index of the childrend range to detach
+     * @param count the number of children to detach
+     *
+     * @see #detachViewFromParent(View)
+     * @see #detachViewFromParent(int)
+     * @see #detachAllViewsFromParent()
+     * @see #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)
+     * @see #removeDetachedView(View, boolean)
+     */
+    protected void detachViewsFromParent(int start, int count) {
+        start = Math.max(0, start);
+        final int end = Math.min(mChildrenCount, start + count);
+        for (int i = start; i < end; i++) {
+            mChildren[i].setDetached(true);
+        }
+        removeFromArray(start, count);
+    }
+
+    /**
+     * Detaches all views from the parent. Detaching a view should be followed
+     * either by a call to
+     * {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)}
+     * or a call to {@link #removeDetachedView(View, boolean)}. Detachment should only be
+     * temporary; reattachment or removal should happen within the same drawing cycle as
+     * detachment. When a view is detached, its parent is null and cannot be retrieved by a
+     * call to {@link #getChildAt(int)}.
+     *
+     * @see #detachViewFromParent(View)
+     * @see #detachViewFromParent(int)
+     * @see #detachViewsFromParent(int, int)
+     * @see #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)
+     * @see #removeDetachedView(View, boolean)
+     */
+    protected void detachAllViewsFromParent() {
+        final int count = mChildrenCount;
+        if (count <= 0) {
+            return;
+        }
+
+        final View[] children = mChildren;
+        mChildrenCount = 0;
+
+        for (int i = count - 1; i >= 0; i--) {
+            children[i].mParent = null;
+            children[i].setDetached(true);
+            children[i] = null;
+        }
+    }
+
+    @Override
+    @CallSuper
+    public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
+        /*
+         * HW-only, Rect-ignoring damage codepath
+         *
+         * We don't deal with rectangles here, since RenderThread native code computes damage for
+         * everything drawn by HWUI (and SW layer / drawing cache doesn't keep track of damage area)
+         */
+
+        // if set, combine the animation flag into the parent
+        mPrivateFlags |= (target.mPrivateFlags & PFLAG_DRAW_ANIMATION);
+
+        if ((target.mPrivateFlags & ~PFLAG_DIRTY_MASK) != 0) {
+            // We lazily use PFLAG_DIRTY, since computing opaque isn't worth the potential
+            // optimization in provides in a DisplayList world.
+            mPrivateFlags = (mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;
+
+            // simplified invalidateChildInParent behavior: clear cache validity to be safe...
+            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
+        }
+
+        // ... and mark inval if in software layer that needs to repaint (hw handled in native)
+        if (mLayerType == LAYER_TYPE_SOFTWARE) {
+            // Layered parents should be invalidated. Escalate to a full invalidate (and note that
+            // we do this after consuming any relevant flags from the originating descendant)
+            mPrivateFlags |= PFLAG_INVALIDATED | PFLAG_DIRTY;
+            target = this;
+        }
+
+        if (mParent != null) {
+            mParent.onDescendantInvalidated(this, target);
+        }
+    }
+
+
+    /**
+     * Don't call or override this method. It is used for the implementation of
+     * the view hierarchy.
+     *
+     * @deprecated Use {@link #onDescendantInvalidated(View, View)} instead to observe updates to
+     * draw state in descendants.
+     */
+    @Deprecated
+    @Override
+    public final void invalidateChild(View child, final Rect dirty) {
+        final AttachInfo attachInfo = mAttachInfo;
+        if (attachInfo != null && attachInfo.mHardwareAccelerated) {
+            // HW accelerated fast path
+            onDescendantInvalidated(child, child);
+            return;
+        }
+
+        ViewParent parent = this;
+        if (attachInfo != null) {
+            // If the child is drawing an animation, we want to copy this flag onto
+            // ourselves and the parent to make sure the invalidate request goes
+            // through
+            final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0;
+
+            // Check whether the child that requests the invalidate is fully opaque
+            // Views being animated or transformed are not considered opaque because we may
+            // be invalidating their old position and need the parent to paint behind them.
+            Matrix childMatrix = child.getMatrix();
+            // Mark the child as dirty, using the appropriate flag
+            // Make sure we do not set both flags at the same time
+
+            if (child.mLayerType != LAYER_TYPE_NONE) {
+                mPrivateFlags |= PFLAG_INVALIDATED;
+                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
+            }
+
+            final int[] location = attachInfo.mInvalidateChildLocation;
+            location[CHILD_LEFT_INDEX] = child.mLeft;
+            location[CHILD_TOP_INDEX] = child.mTop;
+            if (!childMatrix.isIdentity() ||
+                    (mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
+                RectF boundingRect = attachInfo.mTmpTransformRect;
+                boundingRect.set(dirty);
+                Matrix transformMatrix;
+                if ((mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
+                    Transformation t = attachInfo.mTmpTransformation;
+                    boolean transformed = getChildStaticTransformation(child, t);
+                    if (transformed) {
+                        transformMatrix = attachInfo.mTmpMatrix;
+                        transformMatrix.set(t.getMatrix());
+                        if (!childMatrix.isIdentity()) {
+                            transformMatrix.preConcat(childMatrix);
+                        }
+                    } else {
+                        transformMatrix = childMatrix;
+                    }
+                } else {
+                    transformMatrix = childMatrix;
+                }
+                transformMatrix.mapRect(boundingRect);
+                dirty.set((int) Math.floor(boundingRect.left),
+                        (int) Math.floor(boundingRect.top),
+                        (int) Math.ceil(boundingRect.right),
+                        (int) Math.ceil(boundingRect.bottom));
+            }
+
+            do {
+                View view = null;
+                if (parent instanceof View) {
+                    view = (View) parent;
+                }
+
+                if (drawAnimation) {
+                    if (view != null) {
+                        view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
+                    } else if (parent instanceof ViewRootImpl) {
+                        ((ViewRootImpl) parent).mIsAnimating = true;
+                    }
+                }
+
+                // If the parent is dirty opaque or not dirty, mark it dirty with the opaque
+                // flag coming from the child that initiated the invalidate
+                if (view != null) {
+                    if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
+                        view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;
+                    }
+                }
+
+                parent = parent.invalidateChildInParent(location, dirty);
+                if (view != null) {
+                    // Account for transform on current parent
+                    Matrix m = view.getMatrix();
+                    if (!m.isIdentity()) {
+                        RectF boundingRect = attachInfo.mTmpTransformRect;
+                        boundingRect.set(dirty);
+                        m.mapRect(boundingRect);
+                        dirty.set((int) Math.floor(boundingRect.left),
+                                (int) Math.floor(boundingRect.top),
+                                (int) Math.ceil(boundingRect.right),
+                                (int) Math.ceil(boundingRect.bottom));
+                    }
+                }
+            } while (parent != null);
+        }
+    }
+
+    /**
+     * Don't call or override this method. It is used for the implementation of
+     * the view hierarchy.
+     *
+     * This implementation returns null if this ViewGroup does not have a parent,
+     * if this ViewGroup is already fully invalidated or if the dirty rectangle
+     * does not intersect with this ViewGroup's bounds.
+     *
+     * @deprecated Use {@link #onDescendantInvalidated(View, View)} instead to observe updates to
+     * draw state in descendants.
+     */
+    @Deprecated
+    @Override
+    public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
+        if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
+            // either DRAWN, or DRAWING_CACHE_VALID
+            if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE))
+                    != FLAG_OPTIMIZE_INVALIDATE) {
+                dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
+                        location[CHILD_TOP_INDEX] - mScrollY);
+                if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {
+                    dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
+                }
+
+                final int left = mLeft;
+                final int top = mTop;
+
+                if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
+                    if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) {
+                        dirty.setEmpty();
+                    }
+                }
+
+                location[CHILD_LEFT_INDEX] = left;
+                location[CHILD_TOP_INDEX] = top;
+            } else {
+
+                if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
+                    dirty.set(0, 0, mRight - mLeft, mBottom - mTop);
+                } else {
+                    // in case the dirty rect extends outside the bounds of this container
+                    dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
+                }
+                location[CHILD_LEFT_INDEX] = mLeft;
+                location[CHILD_TOP_INDEX] = mTop;
+
+                mPrivateFlags &= ~PFLAG_DRAWN;
+            }
+            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
+            if (mLayerType != LAYER_TYPE_NONE) {
+                mPrivateFlags |= PFLAG_INVALIDATED;
+            }
+
+            return mParent;
+        }
+
+        return null;
+    }
+
+    /**
+     * Offset a rectangle that is in a descendant's coordinate
+     * space into our coordinate space.
+     * @param descendant A descendant of this view
+     * @param rect A rectangle defined in descendant's coordinate space.
+     */
+    public final void offsetDescendantRectToMyCoords(View descendant, Rect rect) {
+        offsetRectBetweenParentAndChild(descendant, rect, true, false);
+    }
+
+    /**
+     * Offset a rectangle that is in our coordinate space into an ancestor's
+     * coordinate space.
+     * @param descendant A descendant of this view
+     * @param rect A rectangle defined in descendant's coordinate space.
+     */
+    public final void offsetRectIntoDescendantCoords(View descendant, Rect rect) {
+        offsetRectBetweenParentAndChild(descendant, rect, false, false);
+    }
+
+    /**
+     * Helper method that offsets a rect either from parent to descendant or
+     * descendant to parent.
+     */
+    void offsetRectBetweenParentAndChild(View descendant, Rect rect,
+            boolean offsetFromChildToParent, boolean clipToBounds) {
+
+        // already in the same coord system :)
+        if (descendant == this) {
+            return;
+        }
+
+        ViewParent theParent = descendant.mParent;
+
+        // search and offset up to the parent
+        while ((theParent != null)
+                && (theParent instanceof View)
+                && (theParent != this)) {
+
+            if (offsetFromChildToParent) {
+                rect.offset(descendant.mLeft - descendant.mScrollX,
+                        descendant.mTop - descendant.mScrollY);
+                if (clipToBounds) {
+                    View p = (View) theParent;
+                    boolean intersected = rect.intersect(0, 0, p.mRight - p.mLeft,
+                            p.mBottom - p.mTop);
+                    if (!intersected) {
+                        rect.setEmpty();
+                    }
+                }
+            } else {
+                if (clipToBounds) {
+                    View p = (View) theParent;
+                    boolean intersected = rect.intersect(0, 0, p.mRight - p.mLeft,
+                            p.mBottom - p.mTop);
+                    if (!intersected) {
+                        rect.setEmpty();
+                    }
+                }
+                rect.offset(descendant.mScrollX - descendant.mLeft,
+                        descendant.mScrollY - descendant.mTop);
+            }
+
+            descendant = (View) theParent;
+            theParent = descendant.mParent;
+        }
+
+        // now that we are up to this view, need to offset one more time
+        // to get into our coordinate space
+        if (theParent == this) {
+            if (offsetFromChildToParent) {
+                rect.offset(descendant.mLeft - descendant.mScrollX,
+                        descendant.mTop - descendant.mScrollY);
+            } else {
+                rect.offset(descendant.mScrollX - descendant.mLeft,
+                        descendant.mScrollY - descendant.mTop);
+            }
+        } else {
+            throw new IllegalArgumentException("parameter must be a descendant of this view");
+        }
+    }
+
+    /**
+     * Offset the vertical location of all children of this view by the specified number of pixels.
+     *
+     * @param offset the number of pixels to offset
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void offsetChildrenTopAndBottom(int offset) {
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        boolean invalidate = false;
+
+        for (int i = 0; i < count; i++) {
+            final View v = children[i];
+            v.mTop += offset;
+            v.mBottom += offset;
+            if (v.mRenderNode != null) {
+                invalidate = true;
+                v.mRenderNode.offsetTopAndBottom(offset);
+            }
+        }
+
+        if (invalidate) {
+            invalidateViewProperty(false, false);
+        }
+        notifySubtreeAccessibilityStateChangedIfNeeded();
+    }
+
+    @Override
+    public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) {
+        return getChildVisibleRect(child, r, offset, false);
+    }
+
+    /**
+     * @param forceParentCheck true to guarantee that this call will propagate to all ancestors,
+     *      false otherwise
+     *
+     * @hide
+     */
+    public boolean getChildVisibleRect(
+            View child, Rect r, android.graphics.Point offset, boolean forceParentCheck) {
+        // It doesn't make a whole lot of sense to call this on a view that isn't attached,
+        // but for some simple tests it can be useful. If we don't have attach info this
+        // will allocate memory.
+        final RectF rect = mAttachInfo != null ? mAttachInfo.mTmpTransformRect : new RectF();
+        rect.set(r);
+
+        if (!child.hasIdentityMatrix()) {
+            child.getMatrix().mapRect(rect);
+        }
+
+        final int dx = child.mLeft - mScrollX;
+        final int dy = child.mTop - mScrollY;
+
+        rect.offset(dx, dy);
+
+        if (offset != null) {
+            if (!child.hasIdentityMatrix()) {
+                float[] position = mAttachInfo != null ? mAttachInfo.mTmpTransformLocation
+                        : new float[2];
+                position[0] = offset.x;
+                position[1] = offset.y;
+                child.getMatrix().mapPoints(position);
+                offset.x = Math.round(position[0]);
+                offset.y = Math.round(position[1]);
+            }
+            offset.x += dx;
+            offset.y += dy;
+        }
+
+        final int width = mRight - mLeft;
+        final int height = mBottom - mTop;
+
+        boolean rectIsVisible = true;
+        if (mParent == null ||
+                (mParent instanceof ViewGroup && ((ViewGroup) mParent).getClipChildren())) {
+            // Clip to bounds.
+            rectIsVisible = rect.intersect(0, 0, width, height);
+        }
+
+        if ((forceParentCheck || rectIsVisible)
+                && (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
+            // Clip to padding.
+            rectIsVisible = rect.intersect(mPaddingLeft, mPaddingTop,
+                    width - mPaddingRight, height - mPaddingBottom);
+        }
+
+        if ((forceParentCheck || rectIsVisible) && mClipBounds != null) {
+            // Clip to clipBounds.
+            rectIsVisible = rect.intersect(mClipBounds.left, mClipBounds.top, mClipBounds.right,
+                    mClipBounds.bottom);
+        }
+        r.set((int) Math.floor(rect.left), (int) Math.floor(rect.top),
+                (int) Math.ceil(rect.right), (int) Math.ceil(rect.bottom));
+
+        if ((forceParentCheck || rectIsVisible) && mParent != null) {
+            if (mParent instanceof ViewGroup) {
+                rectIsVisible = ((ViewGroup) mParent)
+                        .getChildVisibleRect(this, r, offset, forceParentCheck);
+            } else {
+                rectIsVisible = mParent.getChildVisibleRect(this, r, offset);
+            }
+        }
+        return rectIsVisible;
+    }
+
+    @Override
+    public final void layout(int l, int t, int r, int b) {
+        if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
+            if (mTransition != null) {
+                mTransition.layoutChange(this);
+            }
+            super.layout(l, t, r, b);
+        } else {
+            // record the fact that we noop'd it; request layout when transition finishes
+            mLayoutCalledWhileSuppressed = true;
+        }
+    }
+
+    @Override
+    protected abstract void onLayout(boolean changed,
+            int l, int t, int r, int b);
+
+    /**
+     * Indicates whether the view group has the ability to animate its children
+     * after the first layout.
+     *
+     * @return true if the children can be animated, false otherwise
+     */
+    protected boolean canAnimate() {
+        return mLayoutAnimationController != null;
+    }
+
+    /**
+     * Runs the layout animation. Calling this method triggers a relayout of
+     * this view group.
+     */
+    public void startLayoutAnimation() {
+        if (mLayoutAnimationController != null) {
+            mGroupFlags |= FLAG_RUN_ANIMATION;
+            requestLayout();
+        }
+    }
+
+    /**
+     * Schedules the layout animation to be played after the next layout pass
+     * of this view group. This can be used to restart the layout animation
+     * when the content of the view group changes or when the activity is
+     * paused and resumed.
+     */
+    public void scheduleLayoutAnimation() {
+        mGroupFlags |= FLAG_RUN_ANIMATION;
+    }
+
+    /**
+     * Sets the layout animation controller used to animate the group's
+     * children after the first layout.
+     *
+     * @param controller the animation controller
+     */
+    public void setLayoutAnimation(LayoutAnimationController controller) {
+        mLayoutAnimationController = controller;
+        if (mLayoutAnimationController != null) {
+            mGroupFlags |= FLAG_RUN_ANIMATION;
+        }
+    }
+
+    /**
+     * Returns the layout animation controller used to animate the group's
+     * children.
+     *
+     * @return the current animation controller
+     */
+    @InspectableProperty
+    public LayoutAnimationController getLayoutAnimation() {
+        return mLayoutAnimationController;
+    }
+
+    /**
+     * Indicates whether the children's drawing cache is used during a layout
+     * animation. By default, the drawing cache is enabled but this will prevent
+     * nested layout animations from working. To nest animations, you must disable
+     * the cache.
+     *
+     * @return true if the animation cache is enabled, false otherwise
+     *
+     * @see #setAnimationCacheEnabled(boolean)
+     * @see View#setDrawingCacheEnabled(boolean)
+     *
+     * @deprecated As of {@link android.os.Build.VERSION_CODES#M}, this property is ignored.
+     * Caching behavior of children may be controlled through {@link View#setLayerType(int, Paint)}.
+     */
+    @Deprecated
+    @InspectableProperty(name = "animationCache")
+    public boolean isAnimationCacheEnabled() {
+        return (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE;
+    }
+
+    /**
+     * Enables or disables the children's drawing cache during a layout animation.
+     * By default, the drawing cache is enabled but this will prevent nested
+     * layout animations from working. To nest animations, you must disable the
+     * cache.
+     *
+     * @param enabled true to enable the animation cache, false otherwise
+     *
+     * @see #isAnimationCacheEnabled()
+     * @see View#setDrawingCacheEnabled(boolean)
+     *
+     * @deprecated As of {@link android.os.Build.VERSION_CODES#M}, this property is ignored.
+     * Caching behavior of children may be controlled through {@link View#setLayerType(int, Paint)}.
+     */
+    @Deprecated
+    public void setAnimationCacheEnabled(boolean enabled) {
+        setBooleanFlag(FLAG_ANIMATION_CACHE, enabled);
+    }
+
+    /**
+     * Indicates whether this ViewGroup will always try to draw its children using their
+     * drawing cache. By default this property is enabled.
+     *
+     * @return true if the animation cache is enabled, false otherwise
+     *
+     * @see #setAlwaysDrawnWithCacheEnabled(boolean)
+     * @see #setChildrenDrawnWithCacheEnabled(boolean)
+     * @see View#setDrawingCacheEnabled(boolean)
+     *
+     * @deprecated As of {@link android.os.Build.VERSION_CODES#M}, this property is ignored.
+     * Child views may no longer have their caching behavior disabled by parents.
+     */
+    @Deprecated
+    @InspectableProperty(name = "alwaysDrawnWithCache")
+    public boolean isAlwaysDrawnWithCacheEnabled() {
+        return (mGroupFlags & FLAG_ALWAYS_DRAWN_WITH_CACHE) == FLAG_ALWAYS_DRAWN_WITH_CACHE;
+    }
+
+    /**
+     * Indicates whether this ViewGroup will always try to draw its children using their
+     * drawing cache. This property can be set to true when the cache rendering is
+     * slightly different from the children's normal rendering. Renderings can be different,
+     * for instance, when the cache's quality is set to low.
+     *
+     * When this property is disabled, the ViewGroup will use the drawing cache of its
+     * children only when asked to. It's usually the task of subclasses to tell ViewGroup
+     * when to start using the drawing cache and when to stop using it.
+     *
+     * @param always true to always draw with the drawing cache, false otherwise
+     *
+     * @see #isAlwaysDrawnWithCacheEnabled()
+     * @see #setChildrenDrawnWithCacheEnabled(boolean)
+     * @see View#setDrawingCacheEnabled(boolean)
+     * @see View#setDrawingCacheQuality(int)
+     *
+     * @deprecated As of {@link android.os.Build.VERSION_CODES#M}, this property is ignored.
+     * Child views may no longer have their caching behavior disabled by parents.
+     */
+    @Deprecated
+    public void setAlwaysDrawnWithCacheEnabled(boolean always) {
+        setBooleanFlag(FLAG_ALWAYS_DRAWN_WITH_CACHE, always);
+    }
+
+    /**
+     * Indicates whether the ViewGroup is currently drawing its children using
+     * their drawing cache.
+     *
+     * @return true if children should be drawn with their cache, false otherwise
+     *
+     * @see #setAlwaysDrawnWithCacheEnabled(boolean)
+     * @see #setChildrenDrawnWithCacheEnabled(boolean)
+     *
+     * @deprecated As of {@link android.os.Build.VERSION_CODES#M}, this property is ignored.
+     * Child views may no longer be forced to cache their rendering state by their parents.
+     * Use {@link View#setLayerType(int, Paint)} on individual Views instead.
+     */
+    @Deprecated
+    protected boolean isChildrenDrawnWithCacheEnabled() {
+        return (mGroupFlags & FLAG_CHILDREN_DRAWN_WITH_CACHE) == FLAG_CHILDREN_DRAWN_WITH_CACHE;
+    }
+
+    /**
+     * Tells the ViewGroup to draw its children using their drawing cache. This property
+     * is ignored when {@link #isAlwaysDrawnWithCacheEnabled()} is true. A child's drawing cache
+     * will be used only if it has been enabled.
+     *
+     * Subclasses should call this method to start and stop using the drawing cache when
+     * they perform performance sensitive operations, like scrolling or animating.
+     *
+     * @param enabled true if children should be drawn with their cache, false otherwise
+     *
+     * @see #setAlwaysDrawnWithCacheEnabled(boolean)
+     * @see #isChildrenDrawnWithCacheEnabled()
+     *
+     * @deprecated As of {@link android.os.Build.VERSION_CODES#M}, this property is ignored.
+     * Child views may no longer be forced to cache their rendering state by their parents.
+     * Use {@link View#setLayerType(int, Paint)} on individual Views instead.
+     */
+    @Deprecated
+    protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
+        setBooleanFlag(FLAG_CHILDREN_DRAWN_WITH_CACHE, enabled);
+    }
+
+    /**
+     * Indicates whether the ViewGroup is drawing its children in the order defined by
+     * {@link #getChildDrawingOrder(int, int)}.
+     *
+     * @return true if children drawing order is defined by {@link #getChildDrawingOrder(int, int)},
+     *         false otherwise
+     *
+     * @see #setChildrenDrawingOrderEnabled(boolean)
+     * @see #getChildDrawingOrder(int, int)
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    protected boolean isChildrenDrawingOrderEnabled() {
+        return (mGroupFlags & FLAG_USE_CHILD_DRAWING_ORDER) == FLAG_USE_CHILD_DRAWING_ORDER;
+    }
+
+    /**
+     * Tells the ViewGroup whether to draw its children in the order defined by the method
+     * {@link #getChildDrawingOrder(int, int)}.
+     * <p>
+     * Note that {@link View#getZ() Z} reordering, done by {@link #dispatchDraw(Canvas)},
+     * will override custom child ordering done via this method.
+     *
+     * @param enabled true if the order of the children when drawing is determined by
+     *        {@link #getChildDrawingOrder(int, int)}, false otherwise
+     *
+     * @see #isChildrenDrawingOrderEnabled()
+     * @see #getChildDrawingOrder(int, int)
+     */
+    protected void setChildrenDrawingOrderEnabled(boolean enabled) {
+        setBooleanFlag(FLAG_USE_CHILD_DRAWING_ORDER, enabled);
+    }
+
+    private boolean hasBooleanFlag(int flag) {
+        return (mGroupFlags & flag) == flag;
+    }
+
+    private void setBooleanFlag(int flag, boolean value) {
+        if (value) {
+            mGroupFlags |= flag;
+        } else {
+            mGroupFlags &= ~flag;
+        }
+    }
+
+    /**
+     * Returns an integer indicating what types of drawing caches are kept in memory.
+     *
+     * @see #setPersistentDrawingCache(int)
+     * @see #setAnimationCacheEnabled(boolean)
+     *
+     * @return one or a combination of {@link #PERSISTENT_NO_CACHE},
+     *         {@link #PERSISTENT_ANIMATION_CACHE}, {@link #PERSISTENT_SCROLLING_CACHE}
+     *         and {@link #PERSISTENT_ALL_CACHES}
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
+     */
+    @Deprecated
+    @ViewDebug.ExportedProperty(category = "drawing", mapping = {
+        @ViewDebug.IntToString(from = PERSISTENT_NO_CACHE,        to = "NONE"),
+        @ViewDebug.IntToString(from = PERSISTENT_ANIMATION_CACHE, to = "ANIMATION"),
+        @ViewDebug.IntToString(from = PERSISTENT_SCROLLING_CACHE, to = "SCROLLING"),
+        @ViewDebug.IntToString(from = PERSISTENT_ALL_CACHES,      to = "ALL")
+    })
+    @InspectableProperty(enumMapping = {
+            @EnumEntry(value = PERSISTENT_NO_CACHE, name = "none"),
+            @EnumEntry(value = PERSISTENT_ANIMATION_CACHE, name = "animation"),
+            @EnumEntry(value = PERSISTENT_SCROLLING_CACHE, name = "scrolling"),
+            @EnumEntry(value = PERSISTENT_ALL_CACHES, name = "all"),
+    })
+    public int getPersistentDrawingCache() {
+        return mPersistentDrawingCache;
+    }
+
+    /**
+     * Indicates what types of drawing caches should be kept in memory after
+     * they have been created.
+     *
+     * @see #getPersistentDrawingCache()
+     * @see #setAnimationCacheEnabled(boolean)
+     *
+     * @param drawingCacheToKeep one or a combination of {@link #PERSISTENT_NO_CACHE},
+     *        {@link #PERSISTENT_ANIMATION_CACHE}, {@link #PERSISTENT_SCROLLING_CACHE}
+     *        and {@link #PERSISTENT_ALL_CACHES}
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
+     */
+    @Deprecated
+    public void setPersistentDrawingCache(int drawingCacheToKeep) {
+        mPersistentDrawingCache = drawingCacheToKeep & PERSISTENT_ALL_CACHES;
+    }
+
+    private void setLayoutMode(int layoutMode, boolean explicitly) {
+        mLayoutMode = layoutMode;
+        setBooleanFlag(FLAG_LAYOUT_MODE_WAS_EXPLICITLY_SET, explicitly);
+    }
+
+    /**
+     * Recursively traverse the view hierarchy, resetting the layoutMode of any
+     * descendants that had inherited a different layoutMode from a previous parent.
+     * Recursion terminates when a descendant's mode is:
+     * <ul>
+     *     <li>Undefined</li>
+     *     <li>The same as the root node's</li>
+     *     <li>A mode that had been explicitly set</li>
+     * <ul/>
+     * The first two clauses are optimizations.
+     * @param layoutModeOfRoot
+     */
+    @Override
+    void invalidateInheritedLayoutMode(int layoutModeOfRoot) {
+        if (mLayoutMode == LAYOUT_MODE_UNDEFINED ||
+            mLayoutMode == layoutModeOfRoot ||
+            hasBooleanFlag(FLAG_LAYOUT_MODE_WAS_EXPLICITLY_SET)) {
+            return;
+        }
+        setLayoutMode(LAYOUT_MODE_UNDEFINED, false);
+
+        // apply recursively
+        for (int i = 0, N = getChildCount(); i < N; i++) {
+            getChildAt(i).invalidateInheritedLayoutMode(layoutModeOfRoot);
+        }
+    }
+
+    /**
+     * Returns the basis of alignment during layout operations on this ViewGroup:
+     * either {@link #LAYOUT_MODE_CLIP_BOUNDS} or {@link #LAYOUT_MODE_OPTICAL_BOUNDS}.
+     * <p>
+     * If no layoutMode was explicitly set, either programmatically or in an XML resource,
+     * the method returns the layoutMode of the view's parent ViewGroup if such a parent exists,
+     * otherwise the method returns a default value of {@link #LAYOUT_MODE_CLIP_BOUNDS}.
+     *
+     * @return the layout mode to use during layout operations
+     *
+     * @see #setLayoutMode(int)
+     */
+    @InspectableProperty(enumMapping = {
+            @EnumEntry(value = LAYOUT_MODE_CLIP_BOUNDS, name = "clipBounds"),
+            @EnumEntry(value = LAYOUT_MODE_OPTICAL_BOUNDS, name = "opticalBounds")
+    })
+    public int getLayoutMode() {
+        if (mLayoutMode == LAYOUT_MODE_UNDEFINED) {
+            int inheritedLayoutMode = (mParent instanceof ViewGroup) ?
+                    ((ViewGroup) mParent).getLayoutMode() : LAYOUT_MODE_DEFAULT;
+            setLayoutMode(inheritedLayoutMode, false);
+        }
+        return mLayoutMode;
+    }
+
+    /**
+     * Sets the basis of alignment during the layout of this ViewGroup.
+     * Valid values are either {@link #LAYOUT_MODE_CLIP_BOUNDS} or
+     * {@link #LAYOUT_MODE_OPTICAL_BOUNDS}.
+     *
+     * @param layoutMode the layout mode to use during layout operations
+     *
+     * @see #getLayoutMode()
+     * @attr ref android.R.styleable#ViewGroup_layoutMode
+     */
+    public void setLayoutMode(int layoutMode) {
+        if (mLayoutMode != layoutMode) {
+            invalidateInheritedLayoutMode(layoutMode);
+            setLayoutMode(layoutMode, layoutMode != LAYOUT_MODE_UNDEFINED);
+            requestLayout();
+        }
+    }
+
+    /**
+     * Returns a new set of layout parameters based on the supplied attributes set.
+     *
+     * @param attrs the attributes to build the layout parameters from
+     *
+     * @return an instance of {@link android.view.ViewGroup.LayoutParams} or one
+     *         of its descendants
+     */
+    public LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new LayoutParams(getContext(), attrs);
+    }
+
+    /**
+     * Returns a safe set of layout parameters based on the supplied layout params.
+     * When a ViewGroup is passed a View whose layout params do not pass the test of
+     * {@link #checkLayoutParams(android.view.ViewGroup.LayoutParams)}, this method
+     * is invoked. This method should return a new set of layout params suitable for
+     * this ViewGroup, possibly by copying the appropriate attributes from the
+     * specified set of layout params.
+     *
+     * @param p The layout parameters to convert into a suitable set of layout parameters
+     *          for this ViewGroup.
+     *
+     * @return an instance of {@link android.view.ViewGroup.LayoutParams} or one
+     *         of its descendants
+     */
+    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+        return p;
+    }
+
+    /**
+     * Returns a set of default layout parameters. These parameters are requested
+     * when the View passed to {@link #addView(View)} has no layout parameters
+     * already set. If null is returned, an exception is thrown from addView.
+     *
+     * @return a set of default layout parameters or null
+     */
+    protected LayoutParams generateDefaultLayoutParams() {
+        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+    }
+
+    @Override
+    protected void debug(int depth) {
+        super.debug(depth);
+        String output;
+
+        if (mFocused != null) {
+            output = debugIndent(depth);
+            output += "mFocused";
+            Log.d(VIEW_LOG_TAG, output);
+            mFocused.debug(depth + 1);
+        }
+        if (mDefaultFocus != null) {
+            output = debugIndent(depth);
+            output += "mDefaultFocus";
+            Log.d(VIEW_LOG_TAG, output);
+            mDefaultFocus.debug(depth + 1);
+        }
+        if (mFocusedInCluster != null) {
+            output = debugIndent(depth);
+            output += "mFocusedInCluster";
+            Log.d(VIEW_LOG_TAG, output);
+            mFocusedInCluster.debug(depth + 1);
+        }
+        if (mChildrenCount != 0) {
+            output = debugIndent(depth);
+            output += "{";
+            Log.d(VIEW_LOG_TAG, output);
+        }
+        int count = mChildrenCount;
+        for (int i = 0; i < count; i++) {
+            View child = mChildren[i];
+            child.debug(depth + 1);
+        }
+
+        if (mChildrenCount != 0) {
+            output = debugIndent(depth);
+            output += "}";
+            Log.d(VIEW_LOG_TAG, output);
+        }
+    }
+
+    /**
+     * Returns the position in the group of the specified child view.
+     *
+     * @param child the view for which to get the position
+     * @return a positive integer representing the position of the view in the
+     *         group, or -1 if the view does not exist in the group
+     */
+    public int indexOfChild(View child) {
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            if (children[i] == child) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Returns the number of children in the group.
+     *
+     * @return a positive integer representing the number of children in
+     *         the group
+     */
+    public int getChildCount() {
+        return mChildrenCount;
+    }
+
+    /**
+     * Returns the view at the specified position in the group.
+     *
+     * @param index the position at which to get the view from
+     * @return the view at the specified position or null if the position
+     *         does not exist within the group
+     */
+    public View getChildAt(int index) {
+        if (index < 0 || index >= mChildrenCount) {
+            return null;
+        }
+        return mChildren[index];
+    }
+
+    /**
+     * Ask all of the children of this view to measure themselves, taking into
+     * account both the MeasureSpec requirements for this view and its padding.
+     * We skip children that are in the GONE state The heavy lifting is done in
+     * getChildMeasureSpec.
+     *
+     * @param widthMeasureSpec The width requirements for this view
+     * @param heightMeasureSpec The height requirements for this view
+     */
+    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
+        final int size = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < size; ++i) {
+            final View child = children[i];
+            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
+                measureChild(child, widthMeasureSpec, heightMeasureSpec);
+            }
+        }
+    }
+
+    /**
+     * Ask one of the children of this view to measure itself, taking into
+     * account both the MeasureSpec requirements for this view and its padding.
+     * The heavy lifting is done in getChildMeasureSpec.
+     *
+     * @param child The child to measure
+     * @param parentWidthMeasureSpec The width requirements for this view
+     * @param parentHeightMeasureSpec The height requirements for this view
+     */
+    protected void measureChild(View child, int parentWidthMeasureSpec,
+            int parentHeightMeasureSpec) {
+        final LayoutParams lp = child.getLayoutParams();
+
+        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
+                mPaddingLeft + mPaddingRight, lp.width);
+        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
+                mPaddingTop + mPaddingBottom, lp.height);
+
+        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+    }
+
+    /**
+     * Ask one of the children of this view to measure itself, taking into
+     * account both the MeasureSpec requirements for this view and its padding
+     * and margins. The child must have MarginLayoutParams The heavy lifting is
+     * done in getChildMeasureSpec.
+     *
+     * @param child The child to measure
+     * @param parentWidthMeasureSpec The width requirements for this view
+     * @param widthUsed Extra space that has been used up by the parent
+     *        horizontally (possibly by other children of the parent)
+     * @param parentHeightMeasureSpec The height requirements for this view
+     * @param heightUsed Extra space that has been used up by the parent
+     *        vertically (possibly by other children of the parent)
+     */
+    protected void measureChildWithMargins(View child,
+            int parentWidthMeasureSpec, int widthUsed,
+            int parentHeightMeasureSpec, int heightUsed) {
+        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+
+        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
+                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+                        + widthUsed, lp.width);
+        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
+                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+                        + heightUsed, lp.height);
+
+        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+    }
+
+    /**
+     * Does the hard part of measureChildren: figuring out the MeasureSpec to
+     * pass to a particular child. This method figures out the right MeasureSpec
+     * for one dimension (height or width) of one child view.
+     *
+     * The goal is to combine information from our MeasureSpec with the
+     * LayoutParams of the child to get the best possible results. For example,
+     * if the this view knows its size (because its MeasureSpec has a mode of
+     * EXACTLY), and the child has indicated in its LayoutParams that it wants
+     * to be the same size as the parent, the parent should ask the child to
+     * layout given an exact size.
+     *
+     * @param spec The requirements for this view
+     * @param padding The padding of this view for the current dimension and
+     *        margins, if applicable
+     * @param childDimension How big the child wants to be in the current
+     *        dimension
+     * @return a MeasureSpec integer for the child
+     */
+    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
+        int specMode = MeasureSpec.getMode(spec);
+        int specSize = MeasureSpec.getSize(spec);
+
+        int size = Math.max(0, specSize - padding);
+
+        int resultSize = 0;
+        int resultMode = 0;
+
+        switch (specMode) {
+        // Parent has imposed an exact size on us
+        case MeasureSpec.EXACTLY:
+            if (childDimension >= 0) {
+                resultSize = childDimension;
+                resultMode = MeasureSpec.EXACTLY;
+            } else if (childDimension == LayoutParams.MATCH_PARENT) {
+                // Child wants to be our size. So be it.
+                resultSize = size;
+                resultMode = MeasureSpec.EXACTLY;
+            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
+                // Child wants to determine its own size. It can't be
+                // bigger than us.
+                resultSize = size;
+                resultMode = MeasureSpec.AT_MOST;
+            }
+            break;
+
+        // Parent has imposed a maximum size on us
+        case MeasureSpec.AT_MOST:
+            if (childDimension >= 0) {
+                // Child wants a specific size... so be it
+                resultSize = childDimension;
+                resultMode = MeasureSpec.EXACTLY;
+            } else if (childDimension == LayoutParams.MATCH_PARENT) {
+                // Child wants to be our size, but our size is not fixed.
+                // Constrain child to not be bigger than us.
+                resultSize = size;
+                resultMode = MeasureSpec.AT_MOST;
+            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
+                // Child wants to determine its own size. It can't be
+                // bigger than us.
+                resultSize = size;
+                resultMode = MeasureSpec.AT_MOST;
+            }
+            break;
+
+        // Parent asked to see how big we want to be
+        case MeasureSpec.UNSPECIFIED:
+            if (childDimension >= 0) {
+                // Child wants a specific size... let them have it
+                resultSize = childDimension;
+                resultMode = MeasureSpec.EXACTLY;
+            } else if (childDimension == LayoutParams.MATCH_PARENT) {
+                // Child wants to be our size... find out how big it should
+                // be
+                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
+                resultMode = MeasureSpec.UNSPECIFIED;
+            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
+                // Child wants to determine its own size.... find out how
+                // big it should be
+                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
+                resultMode = MeasureSpec.UNSPECIFIED;
+            }
+            break;
+        }
+        //noinspection ResourceType
+        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
+    }
+
+
+    /**
+     * Removes any pending animations for views that have been removed. Call
+     * this if you don't want animations for exiting views to stack up.
+     */
+    public void clearDisappearingChildren() {
+        final ArrayList<View> disappearingChildren = mDisappearingChildren;
+        if (disappearingChildren != null) {
+            final int count = disappearingChildren.size();
+            for (int i = 0; i < count; i++) {
+                final View view = disappearingChildren.get(i);
+                if (view.mAttachInfo != null) {
+                    view.dispatchDetachedFromWindow();
+                }
+                view.clearAnimation();
+            }
+            disappearingChildren.clear();
+            invalidate();
+        }
+    }
+
+    /**
+     * Add a view which is removed from mChildren but still needs animation
+     *
+     * @param v View to add
+     */
+    private void addDisappearingView(View v) {
+        ArrayList<View> disappearingChildren = mDisappearingChildren;
+
+        if (disappearingChildren == null) {
+            disappearingChildren = mDisappearingChildren = new ArrayList<View>();
+        }
+
+        disappearingChildren.add(v);
+    }
+
+    /**
+     * Cleanup a view when its animation is done. This may mean removing it from
+     * the list of disappearing views.
+     *
+     * @param view The view whose animation has finished
+     * @param animation The animation, cannot be null
+     */
+    void finishAnimatingView(final View view, Animation animation) {
+        final ArrayList<View> disappearingChildren = mDisappearingChildren;
+        if (disappearingChildren != null) {
+            if (disappearingChildren.contains(view)) {
+                disappearingChildren.remove(view);
+
+                if (view.mAttachInfo != null) {
+                    view.dispatchDetachedFromWindow();
+                }
+
+                view.clearAnimation();
+                mGroupFlags |= FLAG_INVALIDATE_REQUIRED;
+            }
+        }
+
+        if (animation != null && !animation.getFillAfter()) {
+            view.clearAnimation();
+        }
+
+        if ((view.mPrivateFlags & PFLAG_ANIMATION_STARTED) == PFLAG_ANIMATION_STARTED) {
+            view.onAnimationEnd();
+            // Should be performed by onAnimationEnd() but this avoid an infinite loop,
+            // so we'd rather be safe than sorry
+            view.mPrivateFlags &= ~PFLAG_ANIMATION_STARTED;
+            // Draw one more frame after the animation is done
+            mGroupFlags |= FLAG_INVALIDATE_REQUIRED;
+        }
+    }
+
+    /**
+     * Utility function called by View during invalidation to determine whether a view that
+     * is invisible or gone should still be invalidated because it is being transitioned (and
+     * therefore still needs to be drawn).
+     */
+    boolean isViewTransitioning(View view) {
+        return (mTransitioningViews != null && mTransitioningViews.contains(view));
+    }
+
+    /**
+     * This method tells the ViewGroup that the given View object, which should have this
+     * ViewGroup as its parent,
+     * should be kept around  (re-displayed when the ViewGroup draws its children) even if it
+     * is removed from its parent. This allows animations, such as those used by
+     * {@link android.app.Fragment} and {@link android.animation.LayoutTransition} to animate
+     * the removal of views. A call to this method should always be accompanied by a later call
+     * to {@link #endViewTransition(View)}, such as after an animation on the View has finished,
+     * so that the View finally gets removed.
+     *
+     * @param view The View object to be kept visible even if it gets removed from its parent.
+     */
+    public void startViewTransition(View view) {
+        if (view.mParent == this) {
+            if (mTransitioningViews == null) {
+                mTransitioningViews = new ArrayList<View>();
+            }
+            mTransitioningViews.add(view);
+        }
+    }
+
+    /**
+     * This method should always be called following an earlier call to
+     * {@link #startViewTransition(View)}. The given View is finally removed from its parent
+     * and will no longer be displayed. Note that this method does not perform the functionality
+     * of removing a view from its parent; it just discontinues the display of a View that
+     * has previously been removed.
+     *
+     * @return view The View object that has been removed but is being kept around in the visible
+     * hierarchy by an earlier call to {@link #startViewTransition(View)}.
+     */
+    public void endViewTransition(View view) {
+        if (mTransitioningViews != null) {
+            mTransitioningViews.remove(view);
+            final ArrayList<View> disappearingChildren = mDisappearingChildren;
+            if (disappearingChildren != null && disappearingChildren.contains(view)) {
+                disappearingChildren.remove(view);
+                if (mVisibilityChangingChildren != null &&
+                        mVisibilityChangingChildren.contains(view)) {
+                    mVisibilityChangingChildren.remove(view);
+                } else {
+                    if (view.mAttachInfo != null) {
+                        view.dispatchDetachedFromWindow();
+                    }
+                    if (view.mParent != null) {
+                        view.mParent = null;
+                    }
+                }
+                invalidate();
+            }
+        }
+    }
+
+    private LayoutTransition.TransitionListener mLayoutTransitionListener =
+            new LayoutTransition.TransitionListener() {
+        @Override
+        public void startTransition(LayoutTransition transition, ViewGroup container,
+                View view, int transitionType) {
+            // We only care about disappearing items, since we need special logic to keep
+            // those items visible after they've been 'removed'
+            if (transitionType == LayoutTransition.DISAPPEARING) {
+                startViewTransition(view);
+            }
+        }
+
+        @Override
+        public void endTransition(LayoutTransition transition, ViewGroup container,
+                View view, int transitionType) {
+            if (mLayoutCalledWhileSuppressed && !transition.isChangingLayout()) {
+                requestLayout();
+                mLayoutCalledWhileSuppressed = false;
+            }
+            if (transitionType == LayoutTransition.DISAPPEARING && mTransitioningViews != null) {
+                endViewTransition(view);
+            }
+        }
+    };
+
+    /**
+     * Tells this ViewGroup to suppress all layout() calls until layout
+     * suppression is disabled with a later call to suppressLayout(false).
+     * When layout suppression is disabled, a requestLayout() call is sent
+     * if layout() was attempted while layout was being suppressed.
+     */
+    public void suppressLayout(boolean suppress) {
+        mSuppressLayout = suppress;
+        if (!suppress) {
+            if (mLayoutCalledWhileSuppressed) {
+                requestLayout();
+                mLayoutCalledWhileSuppressed = false;
+            }
+        }
+    }
+
+    /**
+     * Returns whether layout calls on this container are currently being
+     * suppressed, due to an earlier call to {@link #suppressLayout(boolean)}.
+     *
+     * @return true if layout calls are currently suppressed, false otherwise.
+     */
+    public boolean isLayoutSuppressed() {
+        return mSuppressLayout;
+    }
+
+    @Override
+    public boolean gatherTransparentRegion(Region region) {
+        // If no transparent regions requested, we are always opaque.
+        final boolean meOpaque = (mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0;
+        if (meOpaque && region == null) {
+            // The caller doesn't care about the region, so stop now.
+            return true;
+        }
+        super.gatherTransparentRegion(region);
+        // Instead of naively traversing the view tree, we have to traverse according to the Z
+        // order here. We need to go with the same order as dispatchDraw().
+        // One example is that after surfaceView punch a hole, we will still allow other views drawn
+        // on top of that hole. In this case, those other views should be able to cut the
+        // transparent region into smaller area.
+        final int childrenCount = mChildrenCount;
+        boolean noneOfTheChildrenAreTransparent = true;
+        if (childrenCount > 0) {
+            final ArrayList<View> preorderedList = buildOrderedChildList();
+            final boolean customOrder = preorderedList == null
+                    && isChildrenDrawingOrderEnabled();
+            final View[] children = mChildren;
+            for (int i = 0; i < childrenCount; i++) {
+                final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+                final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
+                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
+                    if (!child.gatherTransparentRegion(region)) {
+                        noneOfTheChildrenAreTransparent = false;
+                    }
+                }
+            }
+            if (preorderedList != null) preorderedList.clear();
+        }
+        return meOpaque || noneOfTheChildrenAreTransparent;
+    }
+
+    @Override
+    public void requestTransparentRegion(View child) {
+        if (child != null) {
+            child.mPrivateFlags |= View.PFLAG_REQUEST_TRANSPARENT_REGIONS;
+            if (mParent != null) {
+                mParent.requestTransparentRegion(this);
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void subtractObscuredTouchableRegion(Region touchableRegion, View view) {
+        final int childrenCount = mChildrenCount;
+        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
+        final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled();
+        final View[] children = mChildren;
+        for (int i = childrenCount - 1; i >= 0; i--) {
+            final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+            final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
+            if (child == view) {
+                // We've reached the target view.
+                break;
+            }
+            if (!child.canReceivePointerEvents()) {
+                // This child cannot be touched. Skip it.
+                continue;
+            }
+            applyOpToRegionByBounds(touchableRegion, child, Region.Op.DIFFERENCE);
+        }
+
+        // The touchable region should not exceed the bounds of its container.
+        applyOpToRegionByBounds(touchableRegion, this, Region.Op.INTERSECT);
+
+        final ViewParent parent = getParent();
+        if (parent != null) {
+            parent.subtractObscuredTouchableRegion(touchableRegion, this);
+        }
+    }
+
+    private static void applyOpToRegionByBounds(Region region, View view, Region.Op op) {
+        final int[] locationInWindow = new int[2];
+        view.getLocationInWindow(locationInWindow);
+        final int x = locationInWindow[0];
+        final int y = locationInWindow[1];
+        region.op(x, y, x + view.getWidth(), y + view.getHeight(), op);
+    }
+
+    @Override
+    public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
+        insets = super.dispatchApplyWindowInsets(insets);
+        if (insets.isConsumed()) {
+            return insets;
+        }
+        if (View.sBrokenInsetsDispatch) {
+            return brokenDispatchApplyWindowInsets(insets);
+        } else {
+            return newDispatchApplyWindowInsets(insets);
+        }
+    }
+
+    private WindowInsets brokenDispatchApplyWindowInsets(WindowInsets insets) {
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            insets = getChildAt(i).dispatchApplyWindowInsets(insets);
+            if (insets.isConsumed()) {
+                break;
+            }
+        }
+        return insets;
+    }
+
+    private WindowInsets newDispatchApplyWindowInsets(WindowInsets insets) {
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            getChildAt(i).dispatchApplyWindowInsets(insets);
+        }
+        return insets;
+    }
+
+    @Override
+    public void setWindowInsetsAnimationCallback(
+            @Nullable WindowInsetsAnimation.Callback callback) {
+        super.setWindowInsetsAnimationCallback(callback);
+        mInsetsAnimationDispatchMode = callback != null
+                ? callback.getDispatchMode()
+                : DISPATCH_MODE_CONTINUE_ON_SUBTREE;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public boolean hasWindowInsetsAnimationCallback() {
+        if (super.hasWindowInsetsAnimationCallback()) {
+            return true;
+        }
+
+        // If we are root-level content view that fits insets, we imitate consuming behavior, so
+        // no child will retrieve window insets animation callback.
+        // See dispatchWindowInsetsAnimationPrepare.
+        boolean isOptionalFitSystemWindows = (mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS) != 0
+                || isFrameworkOptionalFitsSystemWindows();
+        if (isOptionalFitSystemWindows && mAttachInfo != null
+                && mAttachInfo.mContentOnApplyWindowInsetsListener != null
+                && (getWindowSystemUiVisibility() & SYSTEM_UI_LAYOUT_FLAGS) == 0) {
+            return false;
+        }
+
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            if (getChildAt(i).hasWindowInsetsAnimationCallback()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void dispatchWindowInsetsAnimationPrepare(
+            @NonNull WindowInsetsAnimation animation) {
+        super.dispatchWindowInsetsAnimationPrepare(animation);
+
+        // If we are root-level content view that fits insets, set dispatch mode to stop to imitate
+        // consume behavior.
+        boolean isOptionalFitSystemWindows = (mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS) != 0
+                || isFrameworkOptionalFitsSystemWindows();
+        if (isOptionalFitSystemWindows && mAttachInfo != null
+                && getListenerInfo().mWindowInsetsAnimationCallback == null
+                && mAttachInfo.mContentOnApplyWindowInsetsListener != null
+                && (getWindowSystemUiVisibility() & SYSTEM_UI_LAYOUT_FLAGS) == 0) {
+            mInsetsAnimationDispatchMode = DISPATCH_MODE_STOP;
+            return;
+        }
+
+        if (mInsetsAnimationDispatchMode == DISPATCH_MODE_STOP) {
+            return;
+        }
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            getChildAt(i).dispatchWindowInsetsAnimationPrepare(animation);
+        }
+    }
+
+    @Override
+    @NonNull
+    public Bounds dispatchWindowInsetsAnimationStart(
+            @NonNull WindowInsetsAnimation animation, @NonNull Bounds bounds) {
+        bounds = super.dispatchWindowInsetsAnimationStart(animation, bounds);
+        if (mInsetsAnimationDispatchMode == DISPATCH_MODE_STOP) {
+            return bounds;
+        }
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            getChildAt(i).dispatchWindowInsetsAnimationStart(animation, bounds);
+        }
+        return bounds;
+    }
+
+    @Override
+    @NonNull
+    public WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull WindowInsets insets,
+            @NonNull List<WindowInsetsAnimation> runningAnimations) {
+        insets = super.dispatchWindowInsetsAnimationProgress(insets, runningAnimations);
+        if (mInsetsAnimationDispatchMode == DISPATCH_MODE_STOP) {
+            return insets;
+        }
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            getChildAt(i).dispatchWindowInsetsAnimationProgress(insets, runningAnimations);
+        }
+        return insets;
+    }
+
+    @Override
+    public void dispatchWindowInsetsAnimationEnd(@NonNull WindowInsetsAnimation animation) {
+        super.dispatchWindowInsetsAnimationEnd(animation);
+        if (mInsetsAnimationDispatchMode == DISPATCH_MODE_STOP) {
+            return;
+        }
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            getChildAt(i).dispatchWindowInsetsAnimationEnd(animation);
+        }
+    }
+
+    /**
+     * Handle the scroll capture search request by checking this view if applicable, then to each
+     * child view.
+     *
+     * @param localVisibleRect the visible area of this ViewGroup in local coordinates, according to
+     *                         the parent
+     * @param windowOffset     the offset of this view within the window
+     * @param targets          accepts potential scroll capture targets; {@link Consumer#accept
+     *                         results.accept} may be called zero or more times on the calling
+     *                         thread before onScrollCaptureSearch returns
+     */
+    @Override
+    public void dispatchScrollCaptureSearch(
+            @NonNull Rect localVisibleRect, @NonNull Point windowOffset,
+            @NonNull Consumer<ScrollCaptureTarget> targets) {
+
+        if (getClipToPadding() && !localVisibleRect.intersect(mPaddingLeft, mPaddingTop,
+                    (mRight - mLeft)  - mPaddingRight, (mBottom - mTop) - mPaddingBottom)) {
+            return;
+        }
+
+        // Dispatch to self first.
+        super.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targets);
+
+        // Skip children if descendants excluded.
+        if ((getScrollCaptureHint() & SCROLL_CAPTURE_HINT_EXCLUDE_DESCENDANTS) != 0) {
+            return;
+        }
+
+        final Rect tmpRect = getTempRect();
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = getChildAt(i);
+            // Only visible views can be captured.
+            if (child.getVisibility() != View.VISIBLE) {
+                continue;
+            }
+            // Offset the given rectangle (in parent's local coordinates) into child's coordinate
+            // space and clip the result to the child View's bounds, padding and clipRect as needed.
+            // If the resulting rectangle is not empty, the request is forwarded to the child.
+
+            // copy local visible rect for modification and dispatch
+            tmpRect.set(localVisibleRect);
+
+            // transform to child coords
+            final Point childWindowOffset = getTempPoint();
+            childWindowOffset.set(windowOffset.x, windowOffset.y);
+
+            final int dx = child.mLeft - mScrollX;
+            final int dy = child.mTop - mScrollY;
+
+            tmpRect.offset(-dx, -dy);
+            childWindowOffset.offset(dx, dy);
+
+            boolean rectIsVisible = true;
+
+            // Clip to child bounds
+            if (getClipChildren()) {
+                rectIsVisible = tmpRect.intersect(0, 0, child.getWidth(), child.getHeight());
+            }
+
+            if (rectIsVisible) {
+                child.dispatchScrollCaptureSearch(tmpRect, childWindowOffset, targets);
+            }
+        }
+    }
+
+    /**
+     * Returns the animation listener to which layout animation events are
+     * sent.
+     *
+     * @return an {@link android.view.animation.Animation.AnimationListener}
+     */
+    public Animation.AnimationListener getLayoutAnimationListener() {
+        return mAnimationListener;
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+
+        if ((mGroupFlags & FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE) != 0) {
+            if ((mGroupFlags & FLAG_ADD_STATES_FROM_CHILDREN) != 0) {
+                throw new IllegalStateException("addStateFromChildren cannot be enabled if a"
+                        + " child has duplicateParentState set to true");
+            }
+
+            final View[] children = mChildren;
+            final int count = mChildrenCount;
+
+            for (int i = 0; i < count; i++) {
+                final View child = children[i];
+                if ((child.mViewFlags & DUPLICATE_PARENT_STATE) != 0) {
+                    child.refreshDrawableState();
+                }
+            }
+        }
+    }
+
+    @Override
+    public void jumpDrawablesToCurrentState() {
+        super.jumpDrawablesToCurrentState();
+        final View[] children = mChildren;
+        final int count = mChildrenCount;
+        for (int i = 0; i < count; i++) {
+            children[i].jumpDrawablesToCurrentState();
+        }
+    }
+
+    @Override
+    protected int[] onCreateDrawableState(int extraSpace) {
+        if ((mGroupFlags & FLAG_ADD_STATES_FROM_CHILDREN) == 0) {
+            return super.onCreateDrawableState(extraSpace);
+        }
+
+        int need = 0;
+        int n = getChildCount();
+        for (int i = 0; i < n; i++) {
+            int[] childState = getChildAt(i).getDrawableState();
+
+            if (childState != null) {
+                need += childState.length;
+            }
+        }
+
+        int[] state = super.onCreateDrawableState(extraSpace + need);
+
+        for (int i = 0; i < n; i++) {
+            int[] childState = getChildAt(i).getDrawableState();
+
+            if (childState != null) {
+                state = mergeDrawableStates(state, childState);
+            }
+        }
+
+        return state;
+    }
+
+    /**
+     * Sets whether this ViewGroup's drawable states also include
+     * its children's drawable states.  This is used, for example, to
+     * make a group appear to be focused when its child EditText or button
+     * is focused.
+     */
+    public void setAddStatesFromChildren(boolean addsStates) {
+        if (addsStates) {
+            mGroupFlags |= FLAG_ADD_STATES_FROM_CHILDREN;
+        } else {
+            mGroupFlags &= ~FLAG_ADD_STATES_FROM_CHILDREN;
+        }
+
+        refreshDrawableState();
+    }
+
+    /**
+     * Returns whether this ViewGroup's drawable states also include
+     * its children's drawable states.  This is used, for example, to
+     * make a group appear to be focused when its child EditText or button
+     * is focused.
+     */
+    @InspectableProperty
+    public boolean addStatesFromChildren() {
+        return (mGroupFlags & FLAG_ADD_STATES_FROM_CHILDREN) != 0;
+    }
+
+    /**
+     * If {@link #addStatesFromChildren} is true, refreshes this group's
+     * drawable state (to include the states from its children).
+     */
+    @Override
+    public void childDrawableStateChanged(View child) {
+        if ((mGroupFlags & FLAG_ADD_STATES_FROM_CHILDREN) != 0) {
+            refreshDrawableState();
+        }
+    }
+
+    /**
+     * Specifies the animation listener to which layout animation events must
+     * be sent. Only
+     * {@link android.view.animation.Animation.AnimationListener#onAnimationStart(Animation)}
+     * and
+     * {@link android.view.animation.Animation.AnimationListener#onAnimationEnd(Animation)}
+     * are invoked.
+     *
+     * @param animationListener the layout animation listener
+     */
+    public void setLayoutAnimationListener(Animation.AnimationListener animationListener) {
+        mAnimationListener = animationListener;
+    }
+
+    /**
+     * This method is called by LayoutTransition when there are 'changing' animations that need
+     * to start after the layout/setup phase. The request is forwarded to the ViewAncestor, who
+     * starts all pending transitions prior to the drawing phase in the current traversal.
+     *
+     * @param transition The LayoutTransition to be started on the next traversal.
+     *
+     * @hide
+     */
+    public void requestTransitionStart(LayoutTransition transition) {
+        ViewRootImpl viewAncestor = getViewRootImpl();
+        if (viewAncestor != null) {
+            viewAncestor.requestTransitionStart(transition);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public boolean resolveRtlPropertiesIfNeeded() {
+        final boolean result = super.resolveRtlPropertiesIfNeeded();
+        // We dont need to resolve the children RTL properties if nothing has changed for the parent
+        if (result) {
+            int count = getChildCount();
+            for (int i = 0; i < count; i++) {
+                final View child = getChildAt(i);
+                if (child.isLayoutDirectionInherited()) {
+                    child.resolveRtlPropertiesIfNeeded();
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public boolean resolveLayoutDirection() {
+        final boolean result = super.resolveLayoutDirection();
+        if (result) {
+            int count = getChildCount();
+            for (int i = 0; i < count; i++) {
+                final View child = getChildAt(i);
+                if (child.isLayoutDirectionInherited()) {
+                    child.resolveLayoutDirection();
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public boolean resolveTextDirection() {
+        final boolean result = super.resolveTextDirection();
+        if (result) {
+            int count = getChildCount();
+            for (int i = 0; i < count; i++) {
+                final View child = getChildAt(i);
+                if (child.isTextDirectionInherited()) {
+                    child.resolveTextDirection();
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public boolean resolveTextAlignment() {
+        final boolean result = super.resolveTextAlignment();
+        if (result) {
+            int count = getChildCount();
+            for (int i = 0; i < count; i++) {
+                final View child = getChildAt(i);
+                if (child.isTextAlignmentInherited()) {
+                    child.resolveTextAlignment();
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public void resolvePadding() {
+        super.resolvePadding();
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.isLayoutDirectionInherited() && !child.isPaddingResolved()) {
+                child.resolvePadding();
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    protected void resolveDrawables() {
+        super.resolveDrawables();
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.isLayoutDirectionInherited() && !child.areDrawablesResolved()) {
+                child.resolveDrawables();
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void resolveLayoutParams() {
+        super.resolveLayoutParams();
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            child.resolveLayoutParams();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    @Override
+    public void resetResolvedLayoutDirection() {
+        super.resetResolvedLayoutDirection();
+
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.isLayoutDirectionInherited()) {
+                child.resetResolvedLayoutDirection();
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    @Override
+    public void resetResolvedTextDirection() {
+        super.resetResolvedTextDirection();
+
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.isTextDirectionInherited()) {
+                child.resetResolvedTextDirection();
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    @Override
+    public void resetResolvedTextAlignment() {
+        super.resetResolvedTextAlignment();
+
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.isTextAlignmentInherited()) {
+                child.resetResolvedTextAlignment();
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    @Override
+    public void resetResolvedPadding() {
+        super.resetResolvedPadding();
+
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.isLayoutDirectionInherited()) {
+                child.resetResolvedPadding();
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    @Override
+    protected void resetResolvedDrawables() {
+        super.resetResolvedDrawables();
+
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.isLayoutDirectionInherited()) {
+                child.resetResolvedDrawables();
+            }
+        }
+    }
+
+    /**
+     * Return true if the pressed state should be delayed for children or descendants of this
+     * ViewGroup. Generally, this should be done for containers that can scroll, such as a List.
+     * This prevents the pressed state from appearing when the user is actually trying to scroll
+     * the content.
+     *
+     * The default implementation returns true for compatibility reasons. Subclasses that do
+     * not scroll should generally override this method and return false.
+     */
+    public boolean shouldDelayChildPressedState() {
+        return true;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
+        return false;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public void onNestedScrollAccepted(View child, View target, int axes) {
+        mNestedScrollAxes = axes;
+    }
+
+    /**
+     * @inheritDoc
+     *
+     * <p>The default implementation of onStopNestedScroll calls
+     * {@link #stopNestedScroll()} to halt any recursive nested scrolling in progress.</p>
+     */
+    @Override
+    public void onStopNestedScroll(View child) {
+        // Stop any recursive nested scrolling.
+        stopNestedScroll();
+        mNestedScrollAxes = 0;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
+            int dxUnconsumed, int dyUnconsumed) {
+        // Re-dispatch up the tree by default
+        dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, null);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
+        // Re-dispatch up the tree by default
+        dispatchNestedPreScroll(dx, dy, consumed, null);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
+        // Re-dispatch up the tree by default
+        return dispatchNestedFling(velocityX, velocityY, consumed);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
+        // Re-dispatch up the tree by default
+        return dispatchNestedPreFling(velocityX, velocityY);
+    }
+
+    /**
+     * Return the current axes of nested scrolling for this ViewGroup.
+     *
+     * <p>A ViewGroup returning something other than {@link #SCROLL_AXIS_NONE} is currently
+     * acting as a nested scrolling parent for one or more descendant views in the hierarchy.</p>
+     *
+     * @return Flags indicating the current axes of nested scrolling
+     * @see #SCROLL_AXIS_HORIZONTAL
+     * @see #SCROLL_AXIS_VERTICAL
+     * @see #SCROLL_AXIS_NONE
+     */
+    public int getNestedScrollAxes() {
+        return mNestedScrollAxes;
+    }
+
+    /** @hide */
+    protected void onSetLayoutParams(View child, LayoutParams layoutParams) {
+        requestLayout();
+    }
+
+    /** @hide */
+    @Override
+    public void captureTransitioningViews(List<View> transitioningViews) {
+        if (getVisibility() != View.VISIBLE) {
+            return;
+        }
+        if (isTransitionGroup()) {
+            transitioningViews.add(this);
+        } else {
+            int count = getChildCount();
+            for (int i = 0; i < count; i++) {
+                View child = getChildAt(i);
+                child.captureTransitioningViews(transitioningViews);
+            }
+        }
+    }
+
+    /** @hide */
+    @Override
+    public void findNamedViews(Map<String, View> namedElements) {
+        if (getVisibility() != VISIBLE && mGhostView == null) {
+            return;
+        }
+        super.findNamedViews(namedElements);
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            View child = getChildAt(i);
+            child.findNamedViews(namedElements);
+        }
+    }
+
+    @Override
+    boolean hasUnhandledKeyListener() {
+        return (mChildUnhandledKeyListeners > 0) || super.hasUnhandledKeyListener();
+    }
+
+    void incrementChildUnhandledKeyListeners() {
+        mChildUnhandledKeyListeners += 1;
+        if (mChildUnhandledKeyListeners == 1) {
+            if (mParent instanceof ViewGroup) {
+                ((ViewGroup) mParent).incrementChildUnhandledKeyListeners();
+            }
+        }
+    }
+
+    void decrementChildUnhandledKeyListeners() {
+        mChildUnhandledKeyListeners -= 1;
+        if (mChildUnhandledKeyListeners == 0) {
+            if (mParent instanceof ViewGroup) {
+                ((ViewGroup) mParent).decrementChildUnhandledKeyListeners();
+            }
+        }
+    }
+
+    @Override
+    View dispatchUnhandledKeyEvent(KeyEvent evt) {
+        if (!hasUnhandledKeyListener()) {
+            return null;
+        }
+        ArrayList<View> orderedViews = buildOrderedChildList();
+        if (orderedViews != null) {
+            try {
+                for (int i = orderedViews.size() - 1; i >= 0; --i) {
+                    View v = orderedViews.get(i);
+                    View consumer = v.dispatchUnhandledKeyEvent(evt);
+                    if (consumer != null) {
+                        return consumer;
+                    }
+                }
+            } finally {
+                orderedViews.clear();
+            }
+        } else {
+            for (int i = getChildCount() - 1; i >= 0; --i) {
+                View v = getChildAt(i);
+                View consumer = v.dispatchUnhandledKeyEvent(evt);
+                if (consumer != null) {
+                    return consumer;
+                }
+            }
+        }
+        if (onUnhandledKeyEvent(evt)) {
+            return this;
+        }
+        return null;
+    }
+
+    /**
+     * LayoutParams are used by views to tell their parents how they want to be
+     * laid out. See
+     * {@link android.R.styleable#ViewGroup_Layout ViewGroup Layout Attributes}
+     * for a list of all child view attributes that this class supports.
+     *
+     * <p>
+     * The base LayoutParams class just describes how big the view wants to be
+     * for both width and height. For each dimension, it can specify one of:
+     * <ul>
+     * <li>FILL_PARENT (renamed MATCH_PARENT in API Level 8 and higher), which
+     * means that the view wants to be as big as its parent (minus padding)
+     * <li> WRAP_CONTENT, which means that the view wants to be just big enough
+     * to enclose its content (plus padding)
+     * <li> an exact number
+     * </ul>
+     * There are subclasses of LayoutParams for different subclasses of
+     * ViewGroup. For example, AbsoluteLayout has its own subclass of
+     * LayoutParams which adds an X and Y value.</p>
+     *
+     * <div class="special reference">
+     * <h3>Developer Guides</h3>
+     * <p>For more information about creating user interface layouts, read the
+     * <a href="{@docRoot}guide/topics/ui/declaring-layout.html">XML Layouts</a> developer
+     * guide.</p></div>
+     *
+     * @attr ref android.R.styleable#ViewGroup_Layout_layout_height
+     * @attr ref android.R.styleable#ViewGroup_Layout_layout_width
+     */
+    public static class LayoutParams {
+        /**
+         * Special value for the height or width requested by a View.
+         * FILL_PARENT means that the view wants to be as big as its parent,
+         * minus the parent's padding, if any. This value is deprecated
+         * starting in API Level 8 and replaced by {@link #MATCH_PARENT}.
+         */
+        @SuppressWarnings({"UnusedDeclaration"})
+        @Deprecated
+        public static final int FILL_PARENT = -1;
+
+        /**
+         * Special value for the height or width requested by a View.
+         * MATCH_PARENT means that the view wants to be as big as its parent,
+         * minus the parent's padding, if any. Introduced in API Level 8.
+         */
+        public static final int MATCH_PARENT = -1;
+
+        /**
+         * Special value for the height or width requested by a View.
+         * WRAP_CONTENT means that the view wants to be just large enough to fit
+         * its own internal content, taking its own padding into account.
+         */
+        public static final int WRAP_CONTENT = -2;
+
+        /**
+         * Information about how wide the view wants to be. Can be one of the
+         * constants FILL_PARENT (replaced by MATCH_PARENT
+         * in API Level 8) or WRAP_CONTENT, or an exact size.
+         */
+        @ViewDebug.ExportedProperty(category = "layout", mapping = {
+            @ViewDebug.IntToString(from = MATCH_PARENT, to = "MATCH_PARENT"),
+            @ViewDebug.IntToString(from = WRAP_CONTENT, to = "WRAP_CONTENT")
+        })
+        @InspectableProperty(name = "layout_width", enumMapping = {
+                @EnumEntry(name = "match_parent", value = MATCH_PARENT),
+                @EnumEntry(name = "wrap_content", value = WRAP_CONTENT)
+        })
+        public int width;
+
+        /**
+         * Information about how tall the view wants to be. Can be one of the
+         * constants FILL_PARENT (replaced by MATCH_PARENT
+         * in API Level 8) or WRAP_CONTENT, or an exact size.
+         */
+        @ViewDebug.ExportedProperty(category = "layout", mapping = {
+            @ViewDebug.IntToString(from = MATCH_PARENT, to = "MATCH_PARENT"),
+            @ViewDebug.IntToString(from = WRAP_CONTENT, to = "WRAP_CONTENT")
+        })
+        @InspectableProperty(name = "layout_height", enumMapping = {
+                @EnumEntry(name = "match_parent", value = MATCH_PARENT),
+                @EnumEntry(name = "wrap_content", value = WRAP_CONTENT)
+        })
+        public int height;
+
+        /**
+         * Used to animate layouts.
+         */
+        public LayoutAnimationController.AnimationParameters layoutAnimationParameters;
+
+        /**
+         * Creates a new set of layout parameters. The values are extracted from
+         * the supplied attributes set and context. The XML attributes mapped
+         * to this set of layout parameters are:
+         *
+         * <ul>
+         *   <li><code>layout_width</code>: the width, either an exact value,
+         *   {@link #WRAP_CONTENT}, or {@link #FILL_PARENT} (replaced by
+         *   {@link #MATCH_PARENT} in API Level 8)</li>
+         *   <li><code>layout_height</code>: the height, either an exact value,
+         *   {@link #WRAP_CONTENT}, or {@link #FILL_PARENT} (replaced by
+         *   {@link #MATCH_PARENT} in API Level 8)</li>
+         * </ul>
+         *
+         * @param c the application environment
+         * @param attrs the set of attributes from which to extract the layout
+         *              parameters' values
+         */
+        public LayoutParams(Context c, AttributeSet attrs) {
+            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
+            setBaseAttributes(a,
+                    R.styleable.ViewGroup_Layout_layout_width,
+                    R.styleable.ViewGroup_Layout_layout_height);
+            a.recycle();
+        }
+
+        /**
+         * Creates a new set of layout parameters with the specified width
+         * and height.
+         *
+         * @param width the width, either {@link #WRAP_CONTENT},
+         *        {@link #FILL_PARENT} (replaced by {@link #MATCH_PARENT} in
+         *        API Level 8), or a fixed size in pixels
+         * @param height the height, either {@link #WRAP_CONTENT},
+         *        {@link #FILL_PARENT} (replaced by {@link #MATCH_PARENT} in
+         *        API Level 8), or a fixed size in pixels
+         */
+        public LayoutParams(int width, int height) {
+            this.width = width;
+            this.height = height;
+        }
+
+        /**
+         * Copy constructor. Clones the width and height values of the source.
+         *
+         * @param source The layout params to copy from.
+         */
+        public LayoutParams(LayoutParams source) {
+            this.width = source.width;
+            this.height = source.height;
+        }
+
+        /**
+         * Used internally by MarginLayoutParams.
+         * @hide
+         */
+        @UnsupportedAppUsage
+        LayoutParams() {
+        }
+
+        /**
+         * Extracts the layout parameters from the supplied attributes.
+         *
+         * @param a the style attributes to extract the parameters from
+         * @param widthAttr the identifier of the width attribute
+         * @param heightAttr the identifier of the height attribute
+         */
+        protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
+            width = a.getLayoutDimension(widthAttr, "layout_width");
+            height = a.getLayoutDimension(heightAttr, "layout_height");
+        }
+
+        /**
+         * Resolve layout parameters depending on the layout direction. Subclasses that care about
+         * layoutDirection changes should override this method. The default implementation does
+         * nothing.
+         *
+         * @param layoutDirection the direction of the layout
+         *
+         * {@link View#LAYOUT_DIRECTION_LTR}
+         * {@link View#LAYOUT_DIRECTION_RTL}
+         */
+        public void resolveLayoutDirection(int layoutDirection) {
+        }
+
+        /**
+         * Returns a String representation of this set of layout parameters.
+         *
+         * @param output the String to prepend to the internal representation
+         * @return a String with the following format: output +
+         *         "ViewGroup.LayoutParams={ width=WIDTH, height=HEIGHT }"
+         *
+         * @hide
+         */
+        public String debug(String output) {
+            return output + "ViewGroup.LayoutParams={ width="
+                    + sizeToString(width) + ", height=" + sizeToString(height) + " }";
+        }
+
+        /**
+         * Use {@code canvas} to draw suitable debugging annotations for these LayoutParameters.
+         *
+         * @param view the view that contains these layout parameters
+         * @param canvas the canvas on which to draw
+         *
+         * @hide
+         */
+        public void onDebugDraw(View view, Canvas canvas, Paint paint) {
+        }
+
+        /**
+         * Converts the specified size to a readable String.
+         *
+         * @param size the size to convert
+         * @return a String instance representing the supplied size
+         *
+         * @hide
+         */
+        protected static String sizeToString(int size) {
+            if (size == WRAP_CONTENT) {
+                return "wrap-content";
+            }
+            if (size == MATCH_PARENT) {
+                return "match-parent";
+            }
+            return String.valueOf(size);
+        }
+
+        /** @hide */
+        void encode(@NonNull ViewHierarchyEncoder encoder) {
+            encoder.beginObject(this);
+            encodeProperties(encoder);
+            encoder.endObject();
+        }
+
+        /** @hide */
+        protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+            encoder.addProperty("width", width);
+            encoder.addProperty("height", height);
+        }
+    }
+
+    /**
+     * Per-child layout information for layouts that support margins.
+     * See
+     * {@link android.R.styleable#ViewGroup_MarginLayout ViewGroup Margin Layout Attributes}
+     * for a list of all child view attributes that this class supports.
+     *
+     * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_margin
+     * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginHorizontal
+     * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginVertical
+     * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginLeft
+     * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginTop
+     * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginRight
+     * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginBottom
+     * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart
+     * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd
+     */
+    public static class MarginLayoutParams extends ViewGroup.LayoutParams {
+        /**
+         * The left margin in pixels of the child. Margin values should be positive.
+         * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
+         * to this field.
+         */
+        @ViewDebug.ExportedProperty(category = "layout")
+        @InspectableProperty(name = "layout_marginLeft")
+        public int leftMargin;
+
+        /**
+         * The top margin in pixels of the child. Margin values should be positive.
+         * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
+         * to this field.
+         */
+        @ViewDebug.ExportedProperty(category = "layout")
+        @InspectableProperty(name = "layout_marginTop")
+        public int topMargin;
+
+        /**
+         * The right margin in pixels of the child. Margin values should be positive.
+         * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
+         * to this field.
+         */
+        @ViewDebug.ExportedProperty(category = "layout")
+        @InspectableProperty(name = "layout_marginRight")
+        public int rightMargin;
+
+        /**
+         * The bottom margin in pixels of the child. Margin values should be positive.
+         * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
+         * to this field.
+         */
+        @ViewDebug.ExportedProperty(category = "layout")
+        @InspectableProperty(name = "layout_marginBottom")
+        public int bottomMargin;
+
+        /**
+         * The start margin in pixels of the child. Margin values should be positive.
+         * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
+         * to this field.
+         */
+        @ViewDebug.ExportedProperty(category = "layout")
+        @UnsupportedAppUsage
+        private int startMargin = DEFAULT_MARGIN_RELATIVE;
+
+        /**
+         * The end margin in pixels of the child. Margin values should be positive.
+         * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
+         * to this field.
+         */
+        @ViewDebug.ExportedProperty(category = "layout")
+        @UnsupportedAppUsage
+        private int endMargin = DEFAULT_MARGIN_RELATIVE;
+
+        /**
+         * The default start and end margin.
+         * @hide
+         */
+        public static final int DEFAULT_MARGIN_RELATIVE = Integer.MIN_VALUE;
+
+        /**
+         * Bit  0: layout direction
+         * Bit  1: layout direction
+         * Bit  2: left margin undefined
+         * Bit  3: right margin undefined
+         * Bit  4: is RTL compatibility mode
+         * Bit  5: need resolution
+         *
+         * Bit 6 to 7 not used
+         *
+         * @hide
+         */
+        @ViewDebug.ExportedProperty(category = "layout", flagMapping = {
+                @ViewDebug.FlagToString(mask = LAYOUT_DIRECTION_MASK,
+                        equals = LAYOUT_DIRECTION_MASK, name = "LAYOUT_DIRECTION"),
+                @ViewDebug.FlagToString(mask = LEFT_MARGIN_UNDEFINED_MASK,
+                        equals = LEFT_MARGIN_UNDEFINED_MASK, name = "LEFT_MARGIN_UNDEFINED_MASK"),
+                @ViewDebug.FlagToString(mask = RIGHT_MARGIN_UNDEFINED_MASK,
+                        equals = RIGHT_MARGIN_UNDEFINED_MASK, name = "RIGHT_MARGIN_UNDEFINED_MASK"),
+                @ViewDebug.FlagToString(mask = RTL_COMPATIBILITY_MODE_MASK,
+                        equals = RTL_COMPATIBILITY_MODE_MASK, name = "RTL_COMPATIBILITY_MODE_MASK"),
+                @ViewDebug.FlagToString(mask = NEED_RESOLUTION_MASK,
+                        equals = NEED_RESOLUTION_MASK, name = "NEED_RESOLUTION_MASK")
+        }, formatToHexString = true)
+        byte mMarginFlags;
+
+        private static final int LAYOUT_DIRECTION_MASK = 0x00000003;
+        private static final int LEFT_MARGIN_UNDEFINED_MASK = 0x00000004;
+        private static final int RIGHT_MARGIN_UNDEFINED_MASK = 0x00000008;
+        private static final int RTL_COMPATIBILITY_MODE_MASK = 0x00000010;
+        private static final int NEED_RESOLUTION_MASK = 0x00000020;
+
+        private static final int DEFAULT_MARGIN_RESOLVED = 0;
+        private static final int UNDEFINED_MARGIN = DEFAULT_MARGIN_RELATIVE;
+
+        /**
+         * Creates a new set of layout parameters. The values are extracted from
+         * the supplied attributes set and context.
+         *
+         * @param c the application environment
+         * @param attrs the set of attributes from which to extract the layout
+         *              parameters' values
+         */
+        public MarginLayoutParams(Context c, AttributeSet attrs) {
+            super();
+
+            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout);
+            setBaseAttributes(a,
+                    R.styleable.ViewGroup_MarginLayout_layout_width,
+                    R.styleable.ViewGroup_MarginLayout_layout_height);
+
+            int margin = a.getDimensionPixelSize(
+                    com.android.internal.R.styleable.ViewGroup_MarginLayout_layout_margin, -1);
+            if (margin >= 0) {
+                leftMargin = margin;
+                topMargin = margin;
+                rightMargin= margin;
+                bottomMargin = margin;
+            } else {
+                int horizontalMargin = a.getDimensionPixelSize(
+                        R.styleable.ViewGroup_MarginLayout_layout_marginHorizontal, -1);
+                int verticalMargin = a.getDimensionPixelSize(
+                        R.styleable.ViewGroup_MarginLayout_layout_marginVertical, -1);
+
+                if (horizontalMargin >= 0) {
+                    leftMargin = horizontalMargin;
+                    rightMargin = horizontalMargin;
+                } else {
+                    leftMargin = a.getDimensionPixelSize(
+                            R.styleable.ViewGroup_MarginLayout_layout_marginLeft,
+                            UNDEFINED_MARGIN);
+                    if (leftMargin == UNDEFINED_MARGIN) {
+                        mMarginFlags |= LEFT_MARGIN_UNDEFINED_MASK;
+                        leftMargin = DEFAULT_MARGIN_RESOLVED;
+                    }
+                    rightMargin = a.getDimensionPixelSize(
+                            R.styleable.ViewGroup_MarginLayout_layout_marginRight,
+                            UNDEFINED_MARGIN);
+                    if (rightMargin == UNDEFINED_MARGIN) {
+                        mMarginFlags |= RIGHT_MARGIN_UNDEFINED_MASK;
+                        rightMargin = DEFAULT_MARGIN_RESOLVED;
+                    }
+                }
+
+                startMargin = a.getDimensionPixelSize(
+                        R.styleable.ViewGroup_MarginLayout_layout_marginStart,
+                        DEFAULT_MARGIN_RELATIVE);
+                endMargin = a.getDimensionPixelSize(
+                        R.styleable.ViewGroup_MarginLayout_layout_marginEnd,
+                        DEFAULT_MARGIN_RELATIVE);
+
+                if (verticalMargin >= 0) {
+                    topMargin = verticalMargin;
+                    bottomMargin = verticalMargin;
+                } else {
+                    topMargin = a.getDimensionPixelSize(
+                            R.styleable.ViewGroup_MarginLayout_layout_marginTop,
+                            DEFAULT_MARGIN_RESOLVED);
+                    bottomMargin = a.getDimensionPixelSize(
+                            R.styleable.ViewGroup_MarginLayout_layout_marginBottom,
+                            DEFAULT_MARGIN_RESOLVED);
+                }
+
+                if (isMarginRelative()) {
+                   mMarginFlags |= NEED_RESOLUTION_MASK;
+                }
+            }
+
+            final boolean hasRtlSupport = c.getApplicationInfo().hasRtlSupport();
+            final int targetSdkVersion = c.getApplicationInfo().targetSdkVersion;
+            if (targetSdkVersion < JELLY_BEAN_MR1 || !hasRtlSupport) {
+                mMarginFlags |= RTL_COMPATIBILITY_MODE_MASK;
+            }
+
+            // Layout direction is LTR by default
+            mMarginFlags |= LAYOUT_DIRECTION_LTR;
+
+            a.recycle();
+        }
+
+        public MarginLayoutParams(int width, int height) {
+            super(width, height);
+
+            mMarginFlags |= LEFT_MARGIN_UNDEFINED_MASK;
+            mMarginFlags |= RIGHT_MARGIN_UNDEFINED_MASK;
+
+            mMarginFlags &= ~NEED_RESOLUTION_MASK;
+            mMarginFlags &= ~RTL_COMPATIBILITY_MODE_MASK;
+        }
+
+        /**
+         * Copy constructor. Clones the width, height and margin values of the source.
+         *
+         * @param source The layout params to copy from.
+         */
+        public MarginLayoutParams(MarginLayoutParams source) {
+            this.width = source.width;
+            this.height = source.height;
+
+            this.leftMargin = source.leftMargin;
+            this.topMargin = source.topMargin;
+            this.rightMargin = source.rightMargin;
+            this.bottomMargin = source.bottomMargin;
+            this.startMargin = source.startMargin;
+            this.endMargin = source.endMargin;
+
+            this.mMarginFlags = source.mMarginFlags;
+        }
+
+        public MarginLayoutParams(LayoutParams source) {
+            super(source);
+
+            mMarginFlags |= LEFT_MARGIN_UNDEFINED_MASK;
+            mMarginFlags |= RIGHT_MARGIN_UNDEFINED_MASK;
+
+            mMarginFlags &= ~NEED_RESOLUTION_MASK;
+            mMarginFlags &= ~RTL_COMPATIBILITY_MODE_MASK;
+        }
+
+        /**
+         * @hide Used internally.
+         */
+        public final void copyMarginsFrom(MarginLayoutParams source) {
+            this.leftMargin = source.leftMargin;
+            this.topMargin = source.topMargin;
+            this.rightMargin = source.rightMargin;
+            this.bottomMargin = source.bottomMargin;
+            this.startMargin = source.startMargin;
+            this.endMargin = source.endMargin;
+
+            this.mMarginFlags = source.mMarginFlags;
+        }
+
+        /**
+         * Sets the margins, in pixels. A call to {@link android.view.View#requestLayout()} needs
+         * to be done so that the new margins are taken into account. Left and right margins may be
+         * overridden by {@link android.view.View#requestLayout()} depending on layout direction.
+         * Margin values should be positive.
+         *
+         * @param left the left margin size
+         * @param top the top margin size
+         * @param right the right margin size
+         * @param bottom the bottom margin size
+         *
+         * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginLeft
+         * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginTop
+         * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginRight
+         * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginBottom
+         */
+        public void setMargins(int left, int top, int right, int bottom) {
+            leftMargin = left;
+            topMargin = top;
+            rightMargin = right;
+            bottomMargin = bottom;
+            mMarginFlags &= ~LEFT_MARGIN_UNDEFINED_MASK;
+            mMarginFlags &= ~RIGHT_MARGIN_UNDEFINED_MASK;
+            if (isMarginRelative()) {
+                mMarginFlags |= NEED_RESOLUTION_MASK;
+            } else {
+                mMarginFlags &= ~NEED_RESOLUTION_MASK;
+            }
+        }
+
+        /**
+         * Sets the relative margins, in pixels. A call to {@link android.view.View#requestLayout()}
+         * needs to be done so that the new relative margins are taken into account. Left and right
+         * margins may be overridden by {@link android.view.View#requestLayout()} depending on
+         * layout direction. Margin values should be positive.
+         *
+         * @param start the start margin size
+         * @param top the top margin size
+         * @param end the right margin size
+         * @param bottom the bottom margin size
+         *
+         * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart
+         * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginTop
+         * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd
+         * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginBottom
+         *
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public void setMarginsRelative(int start, int top, int end, int bottom) {
+            startMargin = start;
+            topMargin = top;
+            endMargin = end;
+            bottomMargin = bottom;
+            mMarginFlags |= NEED_RESOLUTION_MASK;
+        }
+
+        /**
+         * Sets the relative start margin. Margin values should be positive.
+         *
+         * @param start the start margin size
+         *
+         * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart
+         */
+        public void setMarginStart(int start) {
+            startMargin = start;
+            mMarginFlags |= NEED_RESOLUTION_MASK;
+        }
+
+        /**
+         * Returns the start margin in pixels.
+         *
+         * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart
+         *
+         * @return the start margin in pixels.
+         */
+        public int getMarginStart() {
+            if (startMargin != DEFAULT_MARGIN_RELATIVE) return startMargin;
+            if ((mMarginFlags & NEED_RESOLUTION_MASK) == NEED_RESOLUTION_MASK) {
+                doResolveMargins();
+            }
+            switch(mMarginFlags & LAYOUT_DIRECTION_MASK) {
+                case View.LAYOUT_DIRECTION_RTL:
+                    return rightMargin;
+                case View.LAYOUT_DIRECTION_LTR:
+                default:
+                    return leftMargin;
+            }
+        }
+
+        /**
+         * Sets the relative end margin. Margin values should be positive.
+         *
+         * @param end the end margin size
+         *
+         * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd
+         */
+        public void setMarginEnd(int end) {
+            endMargin = end;
+            mMarginFlags |= NEED_RESOLUTION_MASK;
+        }
+
+        /**
+         * Returns the end margin in pixels.
+         *
+         * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd
+         *
+         * @return the end margin in pixels.
+         */
+        public int getMarginEnd() {
+            if (endMargin != DEFAULT_MARGIN_RELATIVE) return endMargin;
+            if ((mMarginFlags & NEED_RESOLUTION_MASK) == NEED_RESOLUTION_MASK) {
+                doResolveMargins();
+            }
+            switch(mMarginFlags & LAYOUT_DIRECTION_MASK) {
+                case View.LAYOUT_DIRECTION_RTL:
+                    return leftMargin;
+                case View.LAYOUT_DIRECTION_LTR:
+                default:
+                    return rightMargin;
+            }
+        }
+
+        /**
+         * Check if margins are relative.
+         *
+         * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart
+         * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd
+         *
+         * @return true if either marginStart or marginEnd has been set.
+         */
+        public boolean isMarginRelative() {
+            return (startMargin != DEFAULT_MARGIN_RELATIVE || endMargin != DEFAULT_MARGIN_RELATIVE);
+        }
+
+        /**
+         * Set the layout direction
+         * @param layoutDirection the layout direction.
+         *        Should be either {@link View#LAYOUT_DIRECTION_LTR}
+         *                     or {@link View#LAYOUT_DIRECTION_RTL}.
+         */
+        public void setLayoutDirection(int layoutDirection) {
+            if (layoutDirection != View.LAYOUT_DIRECTION_LTR &&
+                    layoutDirection != View.LAYOUT_DIRECTION_RTL) return;
+            if (layoutDirection != (mMarginFlags & LAYOUT_DIRECTION_MASK)) {
+                mMarginFlags &= ~LAYOUT_DIRECTION_MASK;
+                mMarginFlags |= (layoutDirection & LAYOUT_DIRECTION_MASK);
+                if (isMarginRelative()) {
+                    mMarginFlags |= NEED_RESOLUTION_MASK;
+                } else {
+                    mMarginFlags &= ~NEED_RESOLUTION_MASK;
+                }
+            }
+        }
+
+        /**
+         * Retuns the layout direction. Can be either {@link View#LAYOUT_DIRECTION_LTR} or
+         * {@link View#LAYOUT_DIRECTION_RTL}.
+         *
+         * @return the layout direction.
+         */
+        public int getLayoutDirection() {
+            return (mMarginFlags & LAYOUT_DIRECTION_MASK);
+        }
+
+        /**
+         * This will be called by {@link android.view.View#requestLayout()}. Left and Right margins
+         * may be overridden depending on layout direction.
+         */
+        @Override
+        public void resolveLayoutDirection(int layoutDirection) {
+            setLayoutDirection(layoutDirection);
+
+            // No relative margin or pre JB-MR1 case or no need to resolve, just dont do anything
+            // Will use the left and right margins if no relative margin is defined.
+            if (!isMarginRelative() ||
+                    (mMarginFlags & NEED_RESOLUTION_MASK) != NEED_RESOLUTION_MASK) return;
+
+            // Proceed with resolution
+            doResolveMargins();
+        }
+
+        private void doResolveMargins() {
+            if ((mMarginFlags & RTL_COMPATIBILITY_MODE_MASK) == RTL_COMPATIBILITY_MODE_MASK) {
+                // if left or right margins are not defined and if we have some start or end margin
+                // defined then use those start and end margins.
+                if ((mMarginFlags & LEFT_MARGIN_UNDEFINED_MASK) == LEFT_MARGIN_UNDEFINED_MASK
+                        && startMargin > DEFAULT_MARGIN_RELATIVE) {
+                    leftMargin = startMargin;
+                }
+                if ((mMarginFlags & RIGHT_MARGIN_UNDEFINED_MASK) == RIGHT_MARGIN_UNDEFINED_MASK
+                        && endMargin > DEFAULT_MARGIN_RELATIVE) {
+                    rightMargin = endMargin;
+                }
+            } else {
+                // We have some relative margins (either the start one or the end one or both). So use
+                // them and override what has been defined for left and right margins. If either start
+                // or end margin is not defined, just set it to default "0".
+                switch(mMarginFlags & LAYOUT_DIRECTION_MASK) {
+                    case View.LAYOUT_DIRECTION_RTL:
+                        leftMargin = (endMargin > DEFAULT_MARGIN_RELATIVE) ?
+                                endMargin : DEFAULT_MARGIN_RESOLVED;
+                        rightMargin = (startMargin > DEFAULT_MARGIN_RELATIVE) ?
+                                startMargin : DEFAULT_MARGIN_RESOLVED;
+                        break;
+                    case View.LAYOUT_DIRECTION_LTR:
+                    default:
+                        leftMargin = (startMargin > DEFAULT_MARGIN_RELATIVE) ?
+                                startMargin : DEFAULT_MARGIN_RESOLVED;
+                        rightMargin = (endMargin > DEFAULT_MARGIN_RELATIVE) ?
+                                endMargin : DEFAULT_MARGIN_RESOLVED;
+                        break;
+                }
+            }
+            mMarginFlags &= ~NEED_RESOLUTION_MASK;
+        }
+
+        /**
+         * @hide
+         */
+        public boolean isLayoutRtl() {
+            return ((mMarginFlags & LAYOUT_DIRECTION_MASK) == View.LAYOUT_DIRECTION_RTL);
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public void onDebugDraw(View view, Canvas canvas, Paint paint) {
+            Insets oi = isLayoutModeOptical(view.mParent) ? view.getOpticalInsets() : Insets.NONE;
+
+            fillDifference(canvas,
+                    view.getLeft()   + oi.left,
+                    view.getTop()    + oi.top,
+                    view.getRight()  - oi.right,
+                    view.getBottom() - oi.bottom,
+                    leftMargin,
+                    topMargin,
+                    rightMargin,
+                    bottomMargin,
+                    paint);
+        }
+
+        /** @hide */
+        @Override
+        protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+            super.encodeProperties(encoder);
+            encoder.addProperty("leftMargin", leftMargin);
+            encoder.addProperty("topMargin", topMargin);
+            encoder.addProperty("rightMargin", rightMargin);
+            encoder.addProperty("bottomMargin", bottomMargin);
+            encoder.addProperty("startMargin", startMargin);
+            encoder.addProperty("endMargin", endMargin);
+        }
+    }
+
+    /* Describes a touched view and the ids of the pointers that it has captured.
+     *
+     * This code assumes that pointer ids are always in the range 0..31 such that
+     * it can use a bitfield to track which pointer ids are present.
+     * As it happens, the lower layers of the input dispatch pipeline also use the
+     * same trick so the assumption should be safe here...
+     */
+    private static final class TouchTarget {
+        private static final int MAX_RECYCLED = 32;
+        private static final Object sRecycleLock = new Object[0];
+        private static TouchTarget sRecycleBin;
+        private static int sRecycledCount;
+
+        public static final int ALL_POINTER_IDS = -1; // all ones
+
+        // The touched child view.
+        @UnsupportedAppUsage
+        public View child;
+
+        // The combined bit mask of pointer ids for all pointers captured by the target.
+        public int pointerIdBits;
+
+        // The next target in the target list.
+        public TouchTarget next;
+
+        @UnsupportedAppUsage
+        private TouchTarget() {
+        }
+
+        public static TouchTarget obtain(@NonNull View child, int pointerIdBits) {
+            if (child == null) {
+                throw new IllegalArgumentException("child must be non-null");
+            }
+
+            final TouchTarget target;
+            synchronized (sRecycleLock) {
+                if (sRecycleBin == null) {
+                    target = new TouchTarget();
+                } else {
+                    target = sRecycleBin;
+                    sRecycleBin = target.next;
+                     sRecycledCount--;
+                    target.next = null;
+                }
+            }
+            target.child = child;
+            target.pointerIdBits = pointerIdBits;
+            return target;
+        }
+
+        public void recycle() {
+            if (child == null) {
+                throw new IllegalStateException("already recycled once");
+            }
+
+            synchronized (sRecycleLock) {
+                if (sRecycledCount < MAX_RECYCLED) {
+                    next = sRecycleBin;
+                    sRecycleBin = this;
+                    sRecycledCount += 1;
+                } else {
+                    next = null;
+                }
+                child = null;
+            }
+        }
+    }
+
+    /* Describes a hovered view. */
+    private static final class HoverTarget {
+        private static final int MAX_RECYCLED = 32;
+        private static final Object sRecycleLock = new Object[0];
+        private static HoverTarget sRecycleBin;
+        private static int sRecycledCount;
+
+        // The hovered child view.
+        public View child;
+
+        // The next target in the target list.
+        public HoverTarget next;
+
+        private HoverTarget() {
+        }
+
+        public static HoverTarget obtain(@NonNull View child) {
+            if (child == null) {
+                throw new IllegalArgumentException("child must be non-null");
+            }
+
+            final HoverTarget target;
+            synchronized (sRecycleLock) {
+                if (sRecycleBin == null) {
+                    target = new HoverTarget();
+                } else {
+                    target = sRecycleBin;
+                    sRecycleBin = target.next;
+                    sRecycledCount--;
+                    target.next = null;
+                }
+            }
+            target.child = child;
+            return target;
+        }
+
+        public void recycle() {
+            if (child == null) {
+                throw new IllegalStateException("already recycled once");
+            }
+
+            synchronized (sRecycleLock) {
+                if (sRecycledCount < MAX_RECYCLED) {
+                    next = sRecycleBin;
+                    sRecycleBin = this;
+                    sRecycledCount += 1;
+                } else {
+                    next = null;
+                }
+                child = null;
+            }
+        }
+    }
+
+    /**
+     * Pooled class that to hold the children for autifill.
+     */
+    private static class ChildListForAutoFillOrContentCapture extends ArrayList<View> {
+        private static final int MAX_POOL_SIZE = 32;
+
+        private static final Pools.SimplePool<ChildListForAutoFillOrContentCapture> sPool =
+                new Pools.SimplePool<>(MAX_POOL_SIZE);
+
+        public static ChildListForAutoFillOrContentCapture obtain() {
+            ChildListForAutoFillOrContentCapture list = sPool.acquire();
+            if (list == null) {
+                list = new ChildListForAutoFillOrContentCapture();
+            }
+            return list;
+        }
+
+        public void recycle() {
+            clear();
+            sPool.release(this);
+        }
+    }
+
+    /**
+     * Pooled class that orderes the children of a ViewGroup from start
+     * to end based on how they are laid out and the layout direction.
+     */
+    static class ChildListForAccessibility {
+
+        private static final int MAX_POOL_SIZE = 32;
+
+        private static final SynchronizedPool<ChildListForAccessibility> sPool =
+                new SynchronizedPool<ChildListForAccessibility>(MAX_POOL_SIZE);
+
+        private final ArrayList<View> mChildren = new ArrayList<View>();
+
+        private final ArrayList<ViewLocationHolder> mHolders = new ArrayList<ViewLocationHolder>();
+
+        public static ChildListForAccessibility obtain(ViewGroup parent, boolean sort) {
+            ChildListForAccessibility list = sPool.acquire();
+            if (list == null) {
+                list = new ChildListForAccessibility();
+            }
+            list.init(parent, sort);
+            return list;
+        }
+
+        public void recycle() {
+            clear();
+            sPool.release(this);
+        }
+
+        public int getChildCount() {
+            return mChildren.size();
+        }
+
+        public View getChildAt(int index) {
+            return mChildren.get(index);
+        }
+
+        private void init(ViewGroup parent, boolean sort) {
+            ArrayList<View> children = mChildren;
+            final int childCount = parent.getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                View child = parent.getChildAt(i);
+                children.add(child);
+            }
+            if (sort) {
+                ArrayList<ViewLocationHolder> holders = mHolders;
+                for (int i = 0; i < childCount; i++) {
+                    View child = children.get(i);
+                    ViewLocationHolder holder = ViewLocationHolder.obtain(parent, child);
+                    holders.add(holder);
+                }
+                sort(holders);
+                for (int i = 0; i < childCount; i++) {
+                    ViewLocationHolder holder = holders.get(i);
+                    children.set(i, holder.mView);
+                    holder.recycle();
+                }
+                holders.clear();
+            }
+        }
+
+        private void sort(ArrayList<ViewLocationHolder> holders) {
+            // This is gross but the least risky solution. The current comparison
+            // strategy breaks transitivity but produces very good results. Coming
+            // up with a new strategy requires time which we do not have, so ...
+            try {
+                ViewLocationHolder.setComparisonStrategy(
+                        ViewLocationHolder.COMPARISON_STRATEGY_STRIPE);
+                Collections.sort(holders);
+            } catch (IllegalArgumentException iae) {
+                // Note that in practice this occurs extremely rarely in a couple
+                // of pathological cases.
+                ViewLocationHolder.setComparisonStrategy(
+                        ViewLocationHolder.COMPARISON_STRATEGY_LOCATION);
+                Collections.sort(holders);
+            }
+        }
+
+        private void clear() {
+            mChildren.clear();
+        }
+    }
+
+    /**
+     * Pooled class that holds a View and its location with respect to
+     * a specified root. This enables sorting of views based on their
+     * coordinates without recomputing the position relative to the root
+     * on every comparison.
+     */
+    static class ViewLocationHolder implements Comparable<ViewLocationHolder> {
+
+        private static final int MAX_POOL_SIZE = 32;
+
+        private static final SynchronizedPool<ViewLocationHolder> sPool =
+                new SynchronizedPool<ViewLocationHolder>(MAX_POOL_SIZE);
+
+        public static final int COMPARISON_STRATEGY_STRIPE = 1;
+
+        public static final int COMPARISON_STRATEGY_LOCATION = 2;
+
+        private static int sComparisonStrategy = COMPARISON_STRATEGY_STRIPE;
+
+        private final Rect mLocation = new Rect();
+
+        private ViewGroup mRoot;
+
+        public View mView;
+
+        private int mLayoutDirection;
+
+        public static ViewLocationHolder obtain(ViewGroup root, View view) {
+            ViewLocationHolder holder = sPool.acquire();
+            if (holder == null) {
+                holder = new ViewLocationHolder();
+            }
+            holder.init(root, view);
+            return holder;
+        }
+
+        public static void setComparisonStrategy(int strategy) {
+            sComparisonStrategy = strategy;
+        }
+
+        public void recycle() {
+            clear();
+            sPool.release(this);
+        }
+
+        @Override
+        public int compareTo(ViewLocationHolder another) {
+            // This instance is greater than an invalid argument.
+            if (another == null) {
+                return 1;
+            }
+
+            int boundsResult = compareBoundsOfTree(this, another);
+            if (boundsResult != 0) {
+                return boundsResult;
+            }
+
+            // Just break the tie somehow. The accessibility ids are unique
+            // and stable, hence this is deterministic tie breaking.
+            return mView.getAccessibilityViewId() - another.mView.getAccessibilityViewId();
+        }
+
+        /**
+         * Compare two views based on their bounds. Use the bounds of their children to break ties.
+         *
+         * @param holder1 Holder of first view to compare
+         * @param holder2 Holder of second view to compare. Must have the same root as holder1.
+         * @return The compare result, with equality if no good comparison was found.
+         */
+        private static int compareBoundsOfTree(
+                ViewLocationHolder holder1, ViewLocationHolder holder2) {
+            if (sComparisonStrategy == COMPARISON_STRATEGY_STRIPE) {
+                // First is above second.
+                if (holder1.mLocation.bottom - holder2.mLocation.top <= 0) {
+                    return -1;
+                }
+                // First is below second.
+                if (holder1.mLocation.top - holder2.mLocation.bottom >= 0) {
+                    return 1;
+                }
+            }
+
+            // We are ordering left-to-right, top-to-bottom.
+            if (holder1.mLayoutDirection == LAYOUT_DIRECTION_LTR) {
+                final int leftDifference = holder1.mLocation.left - holder2.mLocation.left;
+                if (leftDifference != 0) {
+                    return leftDifference;
+                }
+            } else { // RTL
+                final int rightDifference = holder1.mLocation.right - holder2.mLocation.right;
+                if (rightDifference != 0) {
+                    return -rightDifference;
+                }
+            }
+            // We are ordering left-to-right, top-to-bottom.
+            final int topDifference = holder1.mLocation.top - holder2.mLocation.top;
+            if (topDifference != 0) {
+                return topDifference;
+            }
+            // Break tie by height.
+            final int heightDiference = holder1.mLocation.height() - holder2.mLocation.height();
+            if (heightDiference != 0) {
+                return -heightDiference;
+            }
+            // Break tie by width.
+            final int widthDifference = holder1.mLocation.width() - holder2.mLocation.width();
+            if (widthDifference != 0) {
+                return -widthDifference;
+            }
+
+            // Find a child of each view with different screen bounds.
+            final Rect view1Bounds = new Rect();
+            final Rect view2Bounds = new Rect();
+            final Rect tempRect = new Rect();
+            holder1.mView.getBoundsOnScreen(view1Bounds, true);
+            holder2.mView.getBoundsOnScreen(view2Bounds, true);
+            final View child1 = holder1.mView.findViewByPredicateTraversal((view) -> {
+                view.getBoundsOnScreen(tempRect, true);
+                return !tempRect.equals(view1Bounds);
+            }, null);
+            final View child2 = holder2.mView.findViewByPredicateTraversal((view) -> {
+                view.getBoundsOnScreen(tempRect, true);
+                return !tempRect.equals(view2Bounds);
+            }, null);
+
+
+            // Compare the children recursively
+            if ((child1 != null) && (child2 != null)) {
+                final ViewLocationHolder childHolder1 =
+                        ViewLocationHolder.obtain(holder1.mRoot, child1);
+                final ViewLocationHolder childHolder2 =
+                        ViewLocationHolder.obtain(holder1.mRoot, child2);
+                return compareBoundsOfTree(childHolder1, childHolder2);
+            }
+
+            // If only one has a child, use that one
+            if (child1 != null) {
+                return 1;
+            }
+
+            if (child2 != null) {
+                return -1;
+            }
+
+            // Give up
+            return 0;
+        }
+
+        private void init(ViewGroup root, View view) {
+            Rect viewLocation = mLocation;
+            view.getDrawingRect(viewLocation);
+            root.offsetDescendantRectToMyCoords(view, viewLocation);
+            mView = view;
+            mRoot = root;
+            mLayoutDirection = root.getLayoutDirection();
+        }
+
+        private void clear() {
+            mView = null;
+            mRoot = null;
+            mLocation.set(0, 0, 0, 0);
+        }
+    }
+
+    private static void drawRect(Canvas canvas, Paint paint, int x1, int y1, int x2, int y2) {
+        if (sDebugLines== null) {
+            // TODO: This won't work with multiple UI threads in a single process
+            sDebugLines = new float[16];
+        }
+
+        sDebugLines[0] = x1;
+        sDebugLines[1] = y1;
+        sDebugLines[2] = x2;
+        sDebugLines[3] = y1;
+
+        sDebugLines[4] = x2;
+        sDebugLines[5] = y1;
+        sDebugLines[6] = x2;
+        sDebugLines[7] = y2;
+
+        sDebugLines[8] = x2;
+        sDebugLines[9] = y2;
+        sDebugLines[10] = x1;
+        sDebugLines[11] = y2;
+
+        sDebugLines[12] = x1;
+        sDebugLines[13] = y2;
+        sDebugLines[14] = x1;
+        sDebugLines[15] = y1;
+
+        canvas.drawLines(sDebugLines, paint);
+    }
+
+    /** @hide */
+    @Override
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+        super.encodeProperties(encoder);
+
+        encoder.addProperty("focus:descendantFocusability", getDescendantFocusability());
+        encoder.addProperty("drawing:clipChildren", getClipChildren());
+        encoder.addProperty("drawing:clipToPadding", getClipToPadding());
+        encoder.addProperty("drawing:childrenDrawingOrderEnabled", isChildrenDrawingOrderEnabled());
+        encoder.addProperty("drawing:persistentDrawingCache", getPersistentDrawingCache());
+
+        int n = getChildCount();
+        encoder.addProperty("meta:__childCount__", (short)n);
+        for (int i = 0; i < n; i++) {
+            encoder.addPropertyKey("meta:__child__" + i);
+            getChildAt(i).encode(encoder);
+        }
+    }
+
+    /** @hide */
+    @Override
+    public final void onDescendantUnbufferedRequested() {
+        // First look at the focused child for focused events
+        int focusedChildNonPointerSource = InputDevice.SOURCE_CLASS_NONE;
+        if (mFocused != null) {
+            focusedChildNonPointerSource = mFocused.mUnbufferedInputSource
+                    & (~InputDevice.SOURCE_CLASS_POINTER);
+        }
+        mUnbufferedInputSource = focusedChildNonPointerSource;
+
+        // Request unbuffered dispatch for pointer events for this view if any child requested
+        // unbuffered dispatch for pointer events. This is because we can't expect that the pointer
+        // source would dispatch to the focused view.
+        for (int i = 0; i < mChildrenCount; i++) {
+            final View child = mChildren[i];
+            if ((child.mUnbufferedInputSource & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+                mUnbufferedInputSource |= InputDevice.SOURCE_CLASS_POINTER;
+                break;
+            }
+        }
+        if (mParent != null) {
+            mParent.onDescendantUnbufferedRequested();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * The implementation calls {@link #dispatchCreateViewTranslationRequest} for all the child
+     * views.
+     */
+    @Override
+    public void dispatchCreateViewTranslationRequest(@NonNull Map<AutofillId, long[]> viewIds,
+            @NonNull @DataFormat int[] supportedFormats,
+            @Nullable TranslationCapability capability,
+            @NonNull List<ViewTranslationRequest> requests) {
+        super.dispatchCreateViewTranslationRequest(viewIds, supportedFormats, capability, requests);
+        final int childCount = getChildCount();
+        if (childCount == 0) {
+            return;
+        }
+        for (int i = 0; i < childCount; ++i) {
+            final View child = getChildAt(i);
+            child.dispatchCreateViewTranslationRequest(viewIds, supportedFormats, capability,
+                    requests);
+        }
+    }
+}
diff --git a/android/view/ViewGroupOverlay.java b/android/view/ViewGroupOverlay.java
new file mode 100644
index 0000000..c2807ba
--- /dev/null
+++ b/android/view/ViewGroupOverlay.java
@@ -0,0 +1,86 @@
+/*
+ * 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.view;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+
+/**
+ * A group overlay is an extra layer that sits on top of a ViewGroup
+ * (the "host view") which is drawn after all other content in that view
+ * (including the view group's children). Interaction with the overlay
+ * layer is done by adding and removing views and drawables.
+ *
+ * <p>ViewGroupOverlay is a subclass of {@link ViewOverlay}, adding the ability to
+ * manage views for overlays on ViewGroups, in addition to the drawable
+ * support in ViewOverlay.</p>
+ *
+ * @see ViewGroup#getOverlay()
+ */
+public class ViewGroupOverlay extends ViewOverlay {
+
+    ViewGroupOverlay(Context context, View hostView) {
+        super(context, hostView);
+    }
+
+    /**
+     * Adds a {@code View} to the overlay. The bounds of the added view should be
+     * relative to the host view. Any view added to the overlay should be
+     * removed when it is no longer needed or no longer visible.
+     *
+     * <p>Views in the overlay are visual-only; they do not receive input
+     * events and do not participate in focus traversal. Overlay views
+     * are intended to be transient, such as might be needed by a temporary
+     * animation effect.</p>
+     *
+     * <p>If the view has a parent, the view will be removed from that parent
+     * before being added to the overlay. Also, if that parent is attached
+     * in the current view hierarchy, the view will be repositioned
+     * such that it is in the same relative location inside the activity. For
+     * example, if the view's current parent lies 100 pixels to the right
+     * and 200 pixels down from the origin of the overlay's
+     * host view, then the view will be offset by (100, 200).</p>
+     *
+     * <p>{@code View}s added with this API will be drawn in the order they were
+     * added. Drawing of the overlay views will happen before drawing of any of the
+     * {@code Drawable}s added with {@link #add(Drawable)} API even if a call to
+     * this API happened after the call to {@link #add(Drawable)}.</p>
+     *
+     * <p>Passing <code>null</code> parameter will result in an
+     * {@link IllegalArgumentException} being thrown.</p>
+     *
+     * @param view The {@code View} to be added to the overlay. The added view will be
+     * drawn when the overlay is drawn.
+     * @see #remove(View)
+     * @see ViewOverlay#add(Drawable)
+     */
+    public void add(@NonNull View view) {
+        mOverlayViewGroup.add(view);
+    }
+
+    /**
+     * Removes the specified {@code View} from the overlay. Passing <code>null</code> parameter
+     * will result in an {@link IllegalArgumentException} being thrown.
+     *
+     * @param view The {@code View} to be removed from the overlay.
+     * @see #add(View)
+     * @see ViewOverlay#remove(Drawable)
+     */
+    public void remove(@NonNull View view) {
+        mOverlayViewGroup.remove(view);
+    }
+}
diff --git a/android/view/ViewGroup_Delegate.java b/android/view/ViewGroup_Delegate.java
new file mode 100644
index 0000000..e71af61
--- /dev/null
+++ b/android/view/ViewGroup_Delegate.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.resources.Density;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap_Delegate;
+import android.graphics.Canvas;
+import android.graphics.Outline;
+import android.graphics.Path_Delegate;
+import android.graphics.Rect;
+import android.view.animation.Transformation;
+import android.view.shadow.HighQualityShadowPainter;
+
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link ViewGroup}
+ * <p/>
+ * Through the layoutlib_create tool, the original  methods of ViewGroup have been replaced by calls
+ * to methods of the same name in this delegate class.
+ */
+public class ViewGroup_Delegate {
+
+    /**
+     * Overrides the original drawChild call in ViewGroup to draw the shadow.
+     */
+    @LayoutlibDelegate
+    /*package*/ static boolean drawChild(ViewGroup thisVG, Canvas canvas, View child,
+            long drawingTime) {
+        if (child.getZ() > thisVG.getZ()) {
+            // The background's bounds are set lazily. Make sure they are set correctly so that
+            // the outline obtained is correct.
+            child.setBackgroundBounds();
+            ViewOutlineProvider outlineProvider = child.getOutlineProvider();
+            if (outlineProvider != null) {
+                Outline outline = child.mAttachInfo.mTmpOutline;
+                outlineProvider.getOutline(child, outline);
+                if (outline.mPath != null || (outline.mRect != null && !outline.mRect.isEmpty())) {
+                    int restoreTo = transformCanvas(thisVG, canvas, child);
+                    drawShadow(thisVG, canvas, child, outline);
+                    canvas.restoreToCount(restoreTo);
+                }
+            }
+        }
+        return thisVG.drawChild_Original(canvas, child, drawingTime);
+    }
+
+    private static void drawShadow(ViewGroup parent, Canvas canvas, View child,
+            Outline outline) {
+        boolean highQualityShadow = false;
+        boolean enableShadow = true;
+        float elevation = getElevation(child, parent);
+        Context bridgeContext = parent.getContext();
+        if (bridgeContext instanceof BridgeContext) {
+            highQualityShadow = ((BridgeContext) bridgeContext).isHighQualityShadows();
+            enableShadow = ((BridgeContext) bridgeContext).isShadowsEnabled();
+        }
+
+        if (!enableShadow) {
+            return;
+        }
+
+        if(outline.mMode == Outline.MODE_ROUND_RECT && outline.mRect != null) {
+            if (highQualityShadow) {
+                float densityDpi = bridgeContext.getResources().getDisplayMetrics().densityDpi;
+                HighQualityShadowPainter.paintRectShadow(
+                        parent, outline, elevation, canvas, child.getAlpha(), densityDpi);
+            } else {
+                RectShadowPainter.paintShadow(outline, elevation, canvas, child.getAlpha());
+            }
+            return;
+        }
+
+        BufferedImage shadow = null;
+        if (outline.mPath != null) {
+            shadow = getPathShadow(outline, canvas, elevation, child.getAlpha());
+        }
+        if (shadow == null) {
+            return;
+        }
+        Bitmap bitmap = Bitmap_Delegate.createBitmap(shadow, false,
+                Density.getEnum(canvas.getDensity()));
+        canvas.save();
+        Rect clipBounds = canvas.getClipBounds();
+        Rect newBounds = new Rect(clipBounds);
+        newBounds.inset((int)-elevation, (int)-elevation);
+        canvas.clipRectUnion(newBounds);
+        canvas.drawBitmap(bitmap, 0, 0, null);
+        canvas.restore();
+    }
+
+    private static float getElevation(View child, ViewGroup parent) {
+        return child.getZ() - parent.getZ();
+    }
+
+    private static BufferedImage getPathShadow(Outline outline, Canvas canvas, float elevation,
+            float alpha) {
+        Rect clipBounds = canvas.getClipBounds();
+        if (clipBounds.isEmpty()) {
+          return null;
+        }
+        BufferedImage image = new BufferedImage(clipBounds.width(), clipBounds.height(),
+                BufferedImage.TYPE_INT_ARGB);
+        Graphics2D graphics = image.createGraphics();
+        graphics.draw(Path_Delegate.getDelegate(outline.mPath.mNativePath).getJavaShape());
+        graphics.dispose();
+        return ShadowPainter.createDropShadow(image, (int) elevation, alpha);
+    }
+
+    // Copied from android.view.View#draw(Canvas, ViewGroup, long) and removed code paths
+    // which were never taken. Ideally, we should hook up the shadow code in the same method so
+    // that we don't have to transform the canvas twice.
+    private static int transformCanvas(ViewGroup thisVG, Canvas canvas, View child) {
+        final int restoreTo = canvas.save();
+        final boolean childHasIdentityMatrix = child.hasIdentityMatrix();
+        int flags = thisVG.mGroupFlags;
+        Transformation transformToApply = null;
+        boolean concatMatrix = false;
+        if ((flags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
+            final Transformation t = thisVG.getChildTransformation();
+            final boolean hasTransform = thisVG.getChildStaticTransformation(child, t);
+            if (hasTransform) {
+                final int transformType = t.getTransformationType();
+                transformToApply = transformType != Transformation.TYPE_IDENTITY ? t : null;
+                concatMatrix = (transformType & Transformation.TYPE_MATRIX) != 0;
+            }
+        }
+        concatMatrix |= childHasIdentityMatrix;
+
+        canvas.translate(child.mLeft, child.mTop);
+        float alpha = child.getAlpha() * child.getTransitionAlpha();
+
+        if (transformToApply != null || alpha < 1 || !childHasIdentityMatrix) {
+            if (transformToApply != null || !childHasIdentityMatrix) {
+
+                if (transformToApply != null) {
+                    if (concatMatrix) {
+                        canvas.concat(transformToApply.getMatrix());
+                    }
+                }
+                if (!childHasIdentityMatrix) {
+                    canvas.concat(child.getMatrix());
+                }
+
+            }
+        }
+        return restoreTo;
+    }
+}
diff --git a/android/view/ViewHierarchyEncoder.java b/android/view/ViewHierarchyEncoder.java
new file mode 100644
index 0000000..ace05a6
--- /dev/null
+++ b/android/view/ViewHierarchyEncoder.java
@@ -0,0 +1,223 @@
+package android.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * {@link ViewHierarchyEncoder} is a serializer that is tailored towards writing out
+ * view hierarchies (the view tree, along with the properties for each view) to a stream.
+ *
+ * It is typically used as follows:
+ * <pre>
+ *   ViewHierarchyEncoder e = new ViewHierarchyEncoder();
+ *
+ *   for (View view : views) {
+ *      e.beginObject(view);
+ *      e.addProperty("prop1", value);
+ *      ...
+ *      e.endObject();
+ *   }
+ *
+ *   // repeat above snippet for each view, finally end with:
+ *   e.endStream();
+ * </pre>
+ *
+ * <p>On the stream, a snippet such as the above gets encoded as a series of Map's (one
+ * corresponding to each view) with the property name as the key and the property value
+ * as the value.
+ *
+ * <p>Since the property names are practically the same across all views, rather than using
+ * the property name directly as the key, we use a short integer id corresponding to each
+ * property name as the key. A final map is added at the end which contains the mapping
+ * from the integer to its property name.
+ *
+ * <p>A value is encoded as a single byte type identifier followed by the encoding of the
+ * value. Only primitive types are supported as values, in addition to the Map type.
+ *
+ * @hide
+ */
+public class ViewHierarchyEncoder {
+    // Prefixes for simple primitives. These match the JNI definitions.
+    private static final byte SIG_BOOLEAN = 'Z';
+    private static final byte SIG_BYTE = 'B';
+    private static final byte SIG_SHORT = 'S';
+    private static final byte SIG_INT = 'I';
+    private static final byte SIG_LONG = 'J';
+    private static final byte SIG_FLOAT = 'F';
+    private static final byte SIG_DOUBLE = 'D';
+
+    // Prefixes for some commonly used objects
+    private static final byte SIG_STRING = 'R';
+
+    private static final byte SIG_MAP = 'M'; // a map with an short key
+    private static final short SIG_END_MAP = 0;
+
+    private final DataOutputStream mStream;
+
+    private final Map<String,Short> mPropertyNames = new HashMap<String, Short>(200);
+    private short mPropertyId = 1;
+    private Charset mCharset = Charset.forName("utf-8");
+
+    private boolean mUserPropertiesEnabled = true;
+
+    public ViewHierarchyEncoder(@NonNull ByteArrayOutputStream stream) {
+        mStream = new DataOutputStream(stream);
+    }
+
+    public void setUserPropertiesEnabled(boolean enabled) {
+        mUserPropertiesEnabled = enabled;
+    }
+
+    public void beginObject(@NonNull Object o) {
+        startPropertyMap();
+        addProperty("meta:__name__", o.getClass().getName());
+        addProperty("meta:__hash__", o.hashCode());
+    }
+
+    public void endObject() {
+        endPropertyMap();
+    }
+
+    public void endStream() {
+        // write out the string table
+        startPropertyMap();
+        addProperty("__name__", "propertyIndex");
+        for (Map.Entry<String,Short> entry : mPropertyNames.entrySet()) {
+            writeShort(entry.getValue());
+            writeString(entry.getKey());
+        }
+        endPropertyMap();
+    }
+
+    @UnsupportedAppUsage
+    public void addProperty(@NonNull String name, boolean v) {
+        writeShort(createPropertyIndex(name));
+        writeBoolean(v);
+    }
+
+    public void addProperty(@NonNull String name, short s) {
+        writeShort(createPropertyIndex(name));
+        writeShort(s);
+    }
+
+    @UnsupportedAppUsage
+    public void addProperty(@NonNull String name, int v) {
+        writeShort(createPropertyIndex(name));
+        writeInt(v);
+    }
+
+    @UnsupportedAppUsage
+    public void addProperty(@NonNull String name, float v) {
+        writeShort(createPropertyIndex(name));
+        writeFloat(v);
+    }
+
+    @UnsupportedAppUsage
+    public void addProperty(@NonNull String name, @Nullable String s) {
+        writeShort(createPropertyIndex(name));
+        writeString(s);
+    }
+
+    /**
+     * Encodes a user defined property if they are allowed to be encoded
+     *
+     * @see #setUserPropertiesEnabled(boolean)
+     */
+    public void addUserProperty(@NonNull String name, @Nullable String s) {
+        if (mUserPropertiesEnabled) {
+            addProperty(name, s);
+        }
+    }
+
+    /**
+     * Writes the given name as the property name, and leaves it to the callee
+     * to fill in value for this property.
+     */
+    public void addPropertyKey(@NonNull String name) {
+        writeShort(createPropertyIndex(name));
+    }
+
+    private short createPropertyIndex(@NonNull String name) {
+        Short index = mPropertyNames.get(name);
+        if (index == null) {
+            index = mPropertyId++;
+            mPropertyNames.put(name, index);
+        }
+
+        return index;
+    }
+
+    private void startPropertyMap() {
+        try {
+            mStream.write(SIG_MAP);
+        } catch (IOException e) {
+            // does not happen since the stream simply wraps a ByteArrayOutputStream
+        }
+    }
+
+    private void endPropertyMap() {
+        writeShort(SIG_END_MAP);
+    }
+
+    private void writeBoolean(boolean v) {
+        try {
+            mStream.write(SIG_BOOLEAN);
+            mStream.write(v ? 1 : 0);
+        } catch (IOException e) {
+            // does not happen since the stream simply wraps a ByteArrayOutputStream
+        }
+    }
+
+    private void writeShort(short s) {
+        try {
+            mStream.write(SIG_SHORT);
+            mStream.writeShort(s);
+        } catch (IOException e) {
+            // does not happen since the stream simply wraps a ByteArrayOutputStream
+        }
+    }
+
+    private void writeInt(int i) {
+        try {
+            mStream.write(SIG_INT);
+            mStream.writeInt(i);
+        } catch (IOException e) {
+            // does not happen since the stream simply wraps a ByteArrayOutputStream
+        }
+    }
+
+    private void writeFloat(float v) {
+        try {
+            mStream.write(SIG_FLOAT);
+            mStream.writeFloat(v);
+        } catch (IOException e) {
+            // does not happen since the stream simply wraps a ByteArrayOutputStream
+        }
+    }
+
+    private void writeString(@Nullable String s) {
+        if (s == null) {
+            s = "";
+        }
+
+        try {
+            mStream.write(SIG_STRING);
+            byte[] bytes = s.getBytes(mCharset);
+
+            short len = (short)Math.min(bytes.length, Short.MAX_VALUE);
+            mStream.writeShort(len);
+
+            mStream.write(bytes, 0, len);
+        } catch (IOException e) {
+            // does not happen since the stream simply wraps a ByteArrayOutputStream
+        }
+    }
+}
diff --git a/android/view/ViewManager.java b/android/view/ViewManager.java
new file mode 100644
index 0000000..ab6856f
--- /dev/null
+++ b/android/view/ViewManager.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+/** Interface to let you add and remove child views to an Activity. To get an instance
+  * of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
+  */
+public interface ViewManager
+{
+    /**
+     * Assign the passed LayoutParams to the passed View and add the view to the window.
+     * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
+     * errors, such as adding a second view to a window without removing the first view.
+     * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
+     * secondary {@link Display} and the specified display can't be found
+     * (see {@link android.app.Presentation}).
+     * @param view The view to be added to this window.
+     * @param params The LayoutParams to assign to view.
+     */
+    public void addView(View view, ViewGroup.LayoutParams params);
+    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
+    public void removeView(View view);
+}
diff --git a/android/view/ViewOutlineProvider.java b/android/view/ViewOutlineProvider.java
new file mode 100644
index 0000000..a1a02f6
--- /dev/null
+++ b/android/view/ViewOutlineProvider.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.graphics.Outline;
+import android.graphics.drawable.Drawable;
+
+/**
+ * Interface by which a View builds its {@link Outline}, used for shadow casting and clipping.
+ */
+public abstract class ViewOutlineProvider {
+    /**
+     * Default outline provider for Views, which queries the Outline from the View's background,
+     * or generates a 0 alpha, rectangular Outline the size of the View if a background
+     * isn't present.
+     *
+     * @see Drawable#getOutline(Outline)
+     */
+    public static final ViewOutlineProvider BACKGROUND = new ViewOutlineProvider() {
+        @Override
+        public void getOutline(View view, Outline outline) {
+            Drawable background = view.getBackground();
+            if (background != null) {
+                background.getOutline(outline);
+            } else {
+                outline.setRect(0, 0, view.getWidth(), view.getHeight());
+                outline.setAlpha(0.0f);
+            }
+        }
+    };
+
+    /**
+     * Maintains the outline of the View to match its rectangular bounds,
+     * at <code>1.0f</code> alpha.
+     *
+     * This can be used to enable Views that are opaque but lacking a background cast a shadow.
+     */
+    public static final ViewOutlineProvider BOUNDS = new ViewOutlineProvider() {
+        @Override
+        public void getOutline(View view, Outline outline) {
+            outline.setRect(0, 0, view.getWidth(), view.getHeight());
+        }
+    };
+
+    /**
+     * Maintains the outline of the View to match its rectangular padded bounds,
+     * at <code>1.0f</code> alpha.
+     *
+     * This can be used to enable Views that are opaque but lacking a background cast a shadow.
+     */
+    public static final ViewOutlineProvider PADDED_BOUNDS = new ViewOutlineProvider() {
+        @Override
+        public void getOutline(View view, Outline outline) {
+            outline.setRect(view.getPaddingLeft(),
+                    view.getPaddingTop(),
+                    view.getWidth() - view.getPaddingRight(),
+                    view.getHeight() - view.getPaddingBottom());
+        }
+    };
+
+    /**
+     * Called to get the provider to populate the Outline.
+     *
+     * This method will be called by a View when its owned Drawables are invalidated, when the
+     * View's size changes, or if {@link View#invalidateOutline()} is called
+     * explicitly.
+     *
+     * The input outline is empty and has an alpha of <code>1.0f</code>.
+     *
+     * @param view The view building the outline.
+     * @param outline The empty outline to be populated.
+     */
+    public abstract void getOutline(View view, Outline outline);
+}
diff --git a/android/view/ViewOverlay.java b/android/view/ViewOverlay.java
new file mode 100644
index 0000000..02f7e95
--- /dev/null
+++ b/android/view/ViewOverlay.java
@@ -0,0 +1,370 @@
+/*
+ * 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.view;
+
+import android.animation.LayoutTransition;
+import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+
+import java.util.ArrayList;
+
+/**
+ * An overlay is an extra layer that sits on top of a View (the "host view")
+ * which is drawn after all other content in that view (including children,
+ * if the view is a ViewGroup). Interaction with the overlay layer is done
+ * by adding and removing drawables.
+ *
+ * <p>An overlay requested from a ViewGroup is of type {@link ViewGroupOverlay},
+ * which also supports adding and removing views.</p>
+ *
+ * @see View#getOverlay() View.getOverlay()
+ * @see ViewGroup#getOverlay() ViewGroup.getOverlay()
+ * @see ViewGroupOverlay
+ */
+public class ViewOverlay {
+
+    /**
+     * The actual container for the drawables (and views, if it's a ViewGroupOverlay).
+     * All of the management and rendering details for the overlay are handled in
+     * OverlayViewGroup.
+     */
+    OverlayViewGroup mOverlayViewGroup;
+
+    ViewOverlay(Context context, View hostView) {
+        mOverlayViewGroup = new OverlayViewGroup(context, hostView);
+    }
+
+    /**
+     * Used internally by View and ViewGroup to handle drawing and invalidation
+     * of the overlay
+     * @return
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    ViewGroup getOverlayView() {
+        return mOverlayViewGroup;
+    }
+
+    /**
+     * Adds a {@link Drawable} to the overlay. The bounds of the drawable should be relative to
+     * the host view. Any drawable added to the overlay should be removed when it is no longer
+     * needed or no longer visible. Adding an already existing {@link Drawable}
+     * is a no-op. Passing <code>null</code> parameter will result in an
+     * {@link IllegalArgumentException} being thrown.
+     *
+     * @param drawable The {@link Drawable} to be added to the overlay. This drawable will be
+     * drawn when the view redraws its overlay. {@link Drawable}s will be drawn in the order that
+     * they were added.
+     * @see #remove(Drawable)
+     */
+    public void add(@NonNull Drawable drawable) {
+        mOverlayViewGroup.add(drawable);
+    }
+
+    /**
+     * Removes the specified {@link Drawable} from the overlay. Removing a {@link Drawable} that was
+     * not added with {@link #add(Drawable)} is a no-op. Passing <code>null</code> parameter will
+     * result in an {@link IllegalArgumentException} being thrown.
+     *
+     * @param drawable The {@link Drawable} to be removed from the overlay.
+     * @see #add(Drawable)
+     */
+    public void remove(@NonNull Drawable drawable) {
+        mOverlayViewGroup.remove(drawable);
+    }
+
+    /**
+     * Removes all content from the overlay.
+     */
+    public void clear() {
+        mOverlayViewGroup.clear();
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    boolean isEmpty() {
+        return mOverlayViewGroup.isEmpty();
+    }
+
+    /**
+     * OverlayViewGroup is a container that View and ViewGroup use to host
+     * drawables and views added to their overlays  ({@link ViewOverlay} and
+     * {@link ViewGroupOverlay}, respectively). Drawables are added to the overlay
+     * via the add/remove methods in ViewOverlay, Views are added/removed via
+     * ViewGroupOverlay. These drawable and view objects are
+     * drawn whenever the view itself is drawn; first the view draws its own
+     * content (and children, if it is a ViewGroup), then it draws its overlay
+     * (if it has one).
+     *
+     * <p>Besides managing and drawing the list of drawables, this class serves
+     * two purposes:
+     * (1) it noops layout calls because children are absolutely positioned and
+     * (2) it forwards all invalidation calls to its host view. The invalidation
+     * redirect is necessary because the overlay is not a child of the host view
+     * and invalidation cannot therefore follow the normal path up through the
+     * parent hierarchy.</p>
+     *
+     * @see View#getOverlay()
+     * @see ViewGroup#getOverlay()
+     */
+    static class OverlayViewGroup extends ViewGroup {
+
+        /**
+         * The View for which this is an overlay. Invalidations of the overlay are redirected to
+         * this host view.
+         */
+        final View mHostView;
+
+        /**
+         * The set of drawables to draw when the overlay is rendered.
+         */
+        ArrayList<Drawable> mDrawables = null;
+
+        OverlayViewGroup(Context context, View hostView) {
+            super(context);
+            mHostView = hostView;
+            mAttachInfo = mHostView.mAttachInfo;
+
+            mRight = hostView.getWidth();
+            mBottom = hostView.getHeight();
+            // pass right+bottom directly to RenderNode, since not going through setters
+            mRenderNode.setLeftTopRightBottom(0, 0, mRight, mBottom);
+        }
+
+        public void add(@NonNull Drawable drawable) {
+            if (drawable == null) {
+                throw new IllegalArgumentException("drawable must be non-null");
+            }
+            if (mDrawables == null) {
+                mDrawables = new ArrayList<>();
+            }
+            if (!mDrawables.contains(drawable)) {
+                // Make each drawable unique in the overlay; can't add it more than once
+                mDrawables.add(drawable);
+                invalidate(drawable.getBounds());
+                drawable.setCallback(this);
+            }
+        }
+
+        public void remove(@NonNull Drawable drawable) {
+            if (drawable == null) {
+                throw new IllegalArgumentException("drawable must be non-null");
+            }
+            if (mDrawables != null) {
+                mDrawables.remove(drawable);
+                invalidate(drawable.getBounds());
+                drawable.setCallback(null);
+            }
+        }
+
+        @Override
+        protected boolean verifyDrawable(@NonNull Drawable who) {
+            return super.verifyDrawable(who) || (mDrawables != null && mDrawables.contains(who));
+        }
+
+        public void add(@NonNull View child) {
+            if (child == null) {
+                throw new IllegalArgumentException("view must be non-null");
+            }
+
+            if (child.getParent() instanceof ViewGroup) {
+                ViewGroup parent = (ViewGroup) child.getParent();
+                if (parent != mHostView && parent.getParent() != null &&
+                        parent.mAttachInfo != null) {
+                    // Moving to different container; figure out how to position child such that
+                    // it is in the same location on the screen
+                    int[] parentLocation = new int[2];
+                    int[] hostViewLocation = new int[2];
+                    parent.getLocationOnScreen(parentLocation);
+                    mHostView.getLocationOnScreen(hostViewLocation);
+                    child.offsetLeftAndRight(parentLocation[0] - hostViewLocation[0]);
+                    child.offsetTopAndBottom(parentLocation[1] - hostViewLocation[1]);
+                }
+                parent.removeView(child);
+                if (parent.getLayoutTransition() != null) {
+                    // LayoutTransition will cause the child to delay removal - cancel it
+                    parent.getLayoutTransition().cancel(LayoutTransition.DISAPPEARING);
+                }
+                // fail-safe if view is still attached for any reason
+                if (child.getParent() != null) {
+                    child.mParent = null;
+                }
+            }
+            super.addView(child);
+        }
+
+        public void remove(@NonNull View view) {
+            if (view == null) {
+                throw new IllegalArgumentException("view must be non-null");
+            }
+
+            super.removeView(view);
+        }
+
+        public void clear() {
+            removeAllViews();
+            if (mDrawables != null) {
+                for (Drawable drawable : mDrawables) {
+                    drawable.setCallback(null);
+                }
+                mDrawables.clear();
+            }
+        }
+
+        boolean isEmpty() {
+            if (getChildCount() == 0 &&
+                    (mDrawables == null || mDrawables.size() == 0)) {
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public void invalidateDrawable(@NonNull Drawable drawable) {
+            invalidate(drawable.getBounds());
+        }
+
+        @Override
+        protected void dispatchDraw(Canvas canvas) {
+            /*
+             * The OverlayViewGroup doesn't draw with a DisplayList, because
+             * draw(Canvas, View, long) is never called on it. This is fine, since it doesn't need
+             * RenderNode/DisplayList features, and can just draw into the owner's Canvas.
+             *
+             * This means that we need to insert reorder barriers manually though, so that children
+             * of the OverlayViewGroup can cast shadows and Z reorder with each other.
+             */
+            canvas.enableZ();
+
+            super.dispatchDraw(canvas);
+
+            canvas.disableZ();
+            final int numDrawables = (mDrawables == null) ? 0 : mDrawables.size();
+            for (int i = 0; i < numDrawables; ++i) {
+                mDrawables.get(i).draw(canvas);
+            }
+        }
+
+        @Override
+        protected void onLayout(boolean changed, int l, int t, int r, int b) {
+            // Noop: children are positioned absolutely
+        }
+
+        /*
+         The following invalidation overrides exist for the purpose of redirecting invalidation to
+         the host view. The overlay is not parented to the host view (since a View cannot be a
+         parent), so the invalidation cannot proceed through the normal parent hierarchy.
+         There is a built-in assumption that the overlay exactly covers the host view, therefore
+         the invalidation rectangles received do not need to be adjusted when forwarded to
+         the host view.
+         */
+
+        @Override
+        public void invalidate(Rect dirty) {
+            super.invalidate(dirty);
+            if (mHostView != null) {
+                mHostView.invalidate(dirty);
+            }
+        }
+
+        @Override
+        public void invalidate(int l, int t, int r, int b) {
+            super.invalidate(l, t, r, b);
+            if (mHostView != null) {
+                mHostView.invalidate(l, t, r, b);
+            }
+        }
+
+        @Override
+        public void invalidate() {
+            super.invalidate();
+            if (mHostView != null) {
+                mHostView.invalidate();
+            }
+        }
+
+        /** @hide */
+        @Override
+        public void invalidate(boolean invalidateCache) {
+            super.invalidate(invalidateCache);
+            if (mHostView != null) {
+                mHostView.invalidate(invalidateCache);
+            }
+        }
+
+        @Override
+        void invalidateViewProperty(boolean invalidateParent, boolean forceRedraw) {
+            super.invalidateViewProperty(invalidateParent, forceRedraw);
+            if (mHostView != null) {
+                mHostView.invalidateViewProperty(invalidateParent, forceRedraw);
+            }
+        }
+
+        @Override
+        protected void invalidateParentCaches() {
+            super.invalidateParentCaches();
+            if (mHostView != null) {
+                mHostView.invalidateParentCaches();
+            }
+        }
+
+        @Override
+        protected void invalidateParentIfNeeded() {
+            super.invalidateParentIfNeeded();
+            if (mHostView != null) {
+                mHostView.invalidateParentIfNeeded();
+            }
+        }
+
+        @Override
+        public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
+            if (mHostView != null) {
+                if (mHostView instanceof ViewGroup) {
+                    // Propagate invalidate through the host...
+                    ((ViewGroup) mHostView).onDescendantInvalidated(mHostView, target);
+
+                    // ...and also this view, since it will hold the descendant, and must later
+                    // propagate the calls to update display lists if dirty
+                    super.onDescendantInvalidated(child, target);
+                } else {
+                    // Can't use onDescendantInvalidated because host isn't a ViewGroup - fall back
+                    // to invalidating.
+                    invalidate();
+                }
+            }
+        }
+
+        @Override
+        public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
+            if (mHostView != null) {
+                dirty.offset(location[0], location[1]);
+                if (mHostView instanceof ViewGroup) {
+                    location[0] = 0;
+                    location[1] = 0;
+                    super.invalidateChildInParent(location, dirty);
+                    return ((ViewGroup) mHostView).invalidateChildInParent(location, dirty);
+                } else {
+                    invalidate(dirty);
+                }
+            }
+            return null;
+        }
+    }
+
+}
diff --git a/android/view/ViewParent.java b/android/view/ViewParent.java
new file mode 100644
index 0000000..775c15e
--- /dev/null
+++ b/android/view/ViewParent.java
@@ -0,0 +1,692 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.NonNull;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.Bundle;
+import android.view.accessibility.AccessibilityEvent;
+
+/**
+ * Defines the responsibilities for a class that will be a parent of a View.
+ * This is the API that a view sees when it wants to interact with its parent.
+ * 
+ */
+public interface ViewParent {
+    /**
+     * Called when something has changed which has invalidated the layout of a
+     * child of this view parent. This will schedule a layout pass of the view
+     * tree.
+     */
+    public void requestLayout();
+
+    /**
+     * Indicates whether layout was requested on this view parent.
+     *
+     * @return true if layout was requested, false otherwise
+     */
+    public boolean isLayoutRequested();
+
+    /**
+     * Called when a child wants the view hierarchy to gather and report
+     * transparent regions to the window compositor. Views that "punch" holes in
+     * the view hierarchy, such as SurfaceView can use this API to improve
+     * performance of the system. When no such a view is present in the
+     * hierarchy, this optimization in unnecessary and might slightly reduce the
+     * view hierarchy performance.
+     * 
+     * @param child the view requesting the transparent region computation
+     * 
+     */
+    public void requestTransparentRegion(View child);
+
+
+    /**
+     * The target View has been invalidated, or has had a drawing property changed that
+     * requires the hierarchy to re-render.
+     *
+     * This method is called by the View hierarchy to signal ancestors that a View either needs to
+     * re-record its drawing commands, or drawing properties have changed. This is how Views
+     * schedule a drawing traversal.
+     *
+     * This signal is generally only dispatched for attached Views, since only they need to draw.
+     *
+     * @param child Direct child of this ViewParent containing target
+     * @param target The view that needs to redraw
+     */
+    default void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
+        if (getParent() != null) {
+            // Note: should pass 'this' as default, but can't since we may not be a View
+            getParent().onDescendantInvalidated(child, target);
+        }
+    }
+
+    /**
+     * All or part of a child is dirty and needs to be redrawn.
+     * 
+     * @param child The child which is dirty
+     * @param r The area within the child that is invalid
+     *
+     * @deprecated Use {@link #onDescendantInvalidated(View, View)} instead.
+     */
+    @Deprecated
+    public void invalidateChild(View child, Rect r);
+
+    /**
+     * All or part of a child is dirty and needs to be redrawn.
+     *
+     * <p>The location array is an array of two int values which respectively
+     * define the left and the top position of the dirty child.</p>
+     *
+     * <p>This method must return the parent of this ViewParent if the specified
+     * rectangle must be invalidated in the parent. If the specified rectangle
+     * does not require invalidation in the parent or if the parent does not
+     * exist, this method must return null.</p>
+     *
+     * <p>When this method returns a non-null value, the location array must
+     * have been updated with the left and top coordinates of this ViewParent.</p>
+     *
+     * @param location An array of 2 ints containing the left and top
+     *        coordinates of the child to invalidate
+     * @param r The area within the child that is invalid
+     *
+     * @return the parent of this ViewParent or null
+     *
+     * @deprecated Use {@link #onDescendantInvalidated(View, View)} instead.
+     */
+    @Deprecated
+    public ViewParent invalidateChildInParent(int[] location, Rect r);
+
+    /**
+     * Returns the parent if it exists, or null.
+     *
+     * @return a ViewParent or null if this ViewParent does not have a parent
+     */
+    public ViewParent getParent();
+
+    /**
+     * Called when a child of this parent wants focus
+     * 
+     * @param child The child of this ViewParent that wants focus. This view
+     *        will contain the focused view. It is not necessarily the view that
+     *        actually has focus.
+     * @param focused The view that is a descendant of child that actually has
+     *        focus
+     */
+    public void requestChildFocus(View child, View focused);
+
+    /**
+     * Tell view hierarchy that the global view attributes need to be
+     * re-evaluated.
+     * 
+     * @param child View whose attributes have changed.
+     */
+    public void recomputeViewAttributes(View child);
+    
+    /**
+     * Called when a child of this parent is giving up focus
+     * 
+     * @param child The view that is giving up focus
+     */
+    public void clearChildFocus(View child);
+
+    /**
+     * Compute the visible part of a rectangular region defined in terms of a child view's
+     * coordinates.
+     *
+     * <p>Returns the clipped visible part of the rectangle <code>r</code>, defined in the
+     * <code>child</code>'s local coordinate system. <code>r</code> is modified by this method to
+     * contain the result, expressed in the global (root) coordinate system.</p>
+     *
+     * <p>The resulting rectangle is always axis aligned. If a rotation is applied to a node in the
+     * View hierarchy, the result is the axis-aligned bounding box of the visible rectangle.</p>
+     *
+     * @param child A child View, whose rectangular visible region we want to compute
+     * @param r The input rectangle, defined in the child coordinate system. Will be overwritten to
+     * contain the resulting visible rectangle, expressed in global (root) coordinates
+     * @param offset The input coordinates of a point, defined in the child coordinate system.
+     * As with the <code>r</code> parameter, this will be overwritten to contain the global (root)
+     * coordinates of that point.
+     * A <code>null</code> value is valid (in case you are not interested in this result)
+     * @return true if the resulting rectangle is not empty, false otherwise
+     */
+    public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset);
+
+    /**
+     * Find the nearest view in the specified direction that wants to take focus
+     * 
+     * @param v The view that currently has focus
+     * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT
+     */
+    public View focusSearch(View v, int direction);
+
+    /**
+     * Find the nearest keyboard navigation cluster in the specified direction.
+     * This does not actually give focus to that cluster.
+     *
+     * @param currentCluster The starting point of the search. Null means the current cluster is not
+     *                       found yet
+     * @param direction Direction to look
+     *
+     * @return The nearest keyboard navigation cluster in the specified direction, or null if none
+     *         can be found
+     */
+    View keyboardNavigationClusterSearch(View currentCluster, int direction);
+
+    /**
+     * Change the z order of the child so it's on top of all other children.
+     * This ordering change may affect layout, if this container
+     * uses an order-dependent layout scheme (e.g., LinearLayout). Prior
+     * to {@link android.os.Build.VERSION_CODES#KITKAT} this
+     * method should be followed by calls to {@link #requestLayout()} and
+     * {@link View#invalidate()} on this parent to force the parent to redraw
+     * with the new child ordering.
+     * 
+     * @param child The child to bring to the top of the z order
+     */
+    public void bringChildToFront(View child);
+
+    /**
+     * Tells the parent that a new focusable view has become available. This is
+     * to handle transitions from the case where there are no focusable views to
+     * the case where the first focusable view appears.
+     * 
+     * @param v The view that has become newly focusable
+     */
+    public void focusableViewAvailable(View v);
+
+    /**
+     * Shows the context menu for the specified view or its ancestors.
+     * <p>
+     * In most cases, a subclass does not need to override this. However, if
+     * the subclass is added directly to the window manager (for example,
+     * {@link ViewManager#addView(View, android.view.ViewGroup.LayoutParams)})
+     * then it should override this and show the context menu.
+     *
+     * @param originalView the source view where the context menu was first
+     *                     invoked
+     * @return {@code true} if the context menu was shown, {@code false}
+     *         otherwise
+     * @see #showContextMenuForChild(View, float, float)
+     */
+    public boolean showContextMenuForChild(View originalView);
+
+    /**
+     * Shows the context menu for the specified view or its ancestors anchored
+     * to the specified view-relative coordinate.
+     * <p>
+     * In most cases, a subclass does not need to override this. However, if
+     * the subclass is added directly to the window manager (for example,
+     * {@link ViewManager#addView(View, android.view.ViewGroup.LayoutParams)})
+     * then it should override this and show the context menu.
+     * <p>
+     * If a subclass overrides this method it should also override
+     * {@link #showContextMenuForChild(View)}.
+     *
+     * @param originalView the source view where the context menu was first
+     *                     invoked
+     * @param x the X coordinate in pixels relative to the original view to
+     *          which the menu should be anchored, or {@link Float#NaN} to
+     *          disable anchoring
+     * @param y the Y coordinate in pixels relative to the original view to
+     *          which the menu should be anchored, or {@link Float#NaN} to
+     *          disable anchoring
+     * @return {@code true} if the context menu was shown, {@code false}
+     *         otherwise
+     */
+    boolean showContextMenuForChild(View originalView, float x, float y);
+
+    /**
+     * Have the parent populate the specified context menu if it has anything to
+     * add (and then recurse on its parent).
+     * 
+     * @param menu The menu to populate
+     */
+    public void createContextMenu(ContextMenu menu);
+
+    /**
+     * Start an action mode for the specified view with the default type
+     * {@link ActionMode#TYPE_PRIMARY}.
+     *
+     * <p>In most cases, a subclass does not need to override this. However, if the
+     * subclass is added directly to the window manager (for example,
+     * {@link ViewManager#addView(View, android.view.ViewGroup.LayoutParams)})
+     * then it should override this and start the action mode.</p>
+     *
+     * @param originalView The source view where the action mode was first invoked
+     * @param callback The callback that will handle lifecycle events for the action mode
+     * @return The new action mode if it was started, null otherwise
+     *
+     * @see #startActionModeForChild(View, android.view.ActionMode.Callback, int)
+     */
+    public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback);
+
+    /**
+     * Start an action mode of a specific type for the specified view.
+     *
+     * <p>In most cases, a subclass does not need to override this. However, if the
+     * subclass is added directly to the window manager (for example,
+     * {@link ViewManager#addView(View, android.view.ViewGroup.LayoutParams)})
+     * then it should override this and start the action mode.</p>
+     *
+     * @param originalView The source view where the action mode was first invoked
+     * @param callback The callback that will handle lifecycle events for the action mode
+     * @param type One of {@link ActionMode#TYPE_PRIMARY} or {@link ActionMode#TYPE_FLOATING}.
+     * @return The new action mode if it was started, null otherwise
+     */
+    public ActionMode startActionModeForChild(
+            View originalView, ActionMode.Callback callback, int type);
+
+    /**
+     * This method is called on the parent when a child's drawable state
+     * has changed.
+     *
+     * @param child The child whose drawable state has changed.
+     */
+    public void childDrawableStateChanged(View child);
+
+    /**
+     * Called when a child does not want this parent and its ancestors to
+     * intercept touch events with
+     * {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}.
+     *
+     * <p>This parent should pass this call onto its parents. This parent must obey
+     * this request for the duration of the touch (that is, only clear the flag
+     * after this parent has received an up or a cancel.</p>
+     * 
+     * @param disallowIntercept True if the child does not want the parent to
+     *            intercept touch events.
+     */
+    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept);
+
+    /**
+     * Called when a child of this group wants a particular rectangle to be
+     * positioned onto the screen.  {@link ViewGroup}s overriding this can trust
+     * that:
+     * <ul>
+     *   <li>child will be a direct child of this group</li>
+     *   <li>rectangle will be in the child's content coordinates</li>
+     * </ul>
+     *
+     * <p>{@link ViewGroup}s overriding this should uphold the contract:</p>
+     * <ul>
+     *   <li>nothing will change if the rectangle is already visible</li>
+     *   <li>the view port will be scrolled only just enough to make the
+     *       rectangle visible</li>
+     * <ul>
+     *
+     * @param child The direct child making the request.
+     * @param rectangle The rectangle in the child's coordinates the child
+     *        wishes to be on the screen.
+     * @param immediate True to forbid animated or delayed scrolling,
+     *        false otherwise
+     * @return Whether the group scrolled to handle the operation
+     */
+    public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
+            boolean immediate);
+
+    /**
+     * Called by a child to request from its parent to send an {@link AccessibilityEvent}.
+     * The child has already populated a record for itself in the event and is delegating
+     * to its parent to send the event. The parent can optionally add a record for itself.
+     * <p>
+     * Note: An accessibility event is fired by an individual view which populates the
+     *       event with a record for its state and requests from its parent to perform
+     *       the sending. The parent can optionally add a record for itself before
+     *       dispatching the request to its parent. A parent can also choose not to
+     *       respect the request for sending the event. The accessibility event is sent
+     *       by the topmost view in the view tree.</p>
+     *
+     * @param child The child which requests sending the event.
+     * @param event The event to be sent.
+     * @return True if the event was sent.
+     */
+    public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event);
+
+    /**
+     * Called when a child view now has or no longer is tracking transient state.
+     *
+     * <p>"Transient state" is any state that a View might hold that is not expected to
+     * be reflected in the data model that the View currently presents. This state only
+     * affects the presentation to the user within the View itself, such as the current
+     * state of animations in progress or the state of a text selection operation.</p>
+     *
+     * <p>Transient state is useful for hinting to other components of the View system
+     * that a particular view is tracking something complex but encapsulated.
+     * A <code>ListView</code> for example may acknowledge that list item Views
+     * with transient state should be preserved within their position or stable item ID
+     * instead of treating that view as trivially replaceable by the backing adapter.
+     * This allows adapter implementations to be simpler instead of needing to track
+     * the state of item view animations in progress such that they could be restored
+     * in the event of an unexpected recycling and rebinding of attached item views.</p>
+     *
+     * <p>This method is called on a parent view when a child view or a view within
+     * its subtree begins or ends tracking of internal transient state.</p>
+     *
+     * @param child Child view whose state has changed
+     * @param hasTransientState true if this child has transient state
+     */
+    public void childHasTransientStateChanged(View child, boolean hasTransientState);
+
+    /**
+     * Ask that a new dispatch of {@link View#fitSystemWindows(Rect)
+     * View.fitSystemWindows(Rect)} be performed.
+     */
+    public void requestFitSystemWindows();
+
+    /**
+     * Gets the parent of a given View for accessibility. Since some Views are not
+     * exposed to the accessibility layer the parent for accessibility is not
+     * necessarily the direct parent of the View, rather it is a predecessor.
+     *
+     * @return The parent or <code>null</code> if no such is found.
+     */
+    public ViewParent getParentForAccessibility();
+
+    /**
+     * Notifies a view parent that the accessibility state of one of its
+     * descendants has changed and that the structure of the subtree is
+     * different.
+     * @param child The direct child whose subtree has changed.
+     * @param source The descendant view that changed. May not be {@code null}.
+     * @param changeType A bit mask of the types of changes that occurred. One
+     *            or more of:
+     *            <ul>
+     *            <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION}
+     *            <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_STATE_DESCRIPTION}
+     *            <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_SUBTREE}
+     *            <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_TEXT}
+     *            <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_UNDEFINED}
+     *            </ul>
+     */
+    public void notifySubtreeAccessibilityStateChanged(
+            View child, @NonNull View source, int changeType);
+
+    /**
+     * Tells if this view parent can resolve the layout direction.
+     * See {@link View#setLayoutDirection(int)}
+     *
+     * @return True if this view parent can resolve the layout direction.
+     */
+    public boolean canResolveLayoutDirection();
+
+    /**
+     * Tells if this view parent layout direction is resolved.
+     * See {@link View#setLayoutDirection(int)}
+     *
+     * @return True if this view parent layout direction is resolved.
+     */
+    public boolean isLayoutDirectionResolved();
+
+    /**
+     * Return this view parent layout direction. See {@link View#getLayoutDirection()}
+     *
+     * @return {@link View#LAYOUT_DIRECTION_RTL} if the layout direction is RTL or returns
+     * {@link View#LAYOUT_DIRECTION_LTR} if the layout direction is not RTL.
+     */
+    public int getLayoutDirection();
+
+    /**
+     * Tells if this view parent can resolve the text direction.
+     * See {@link View#setTextDirection(int)}
+     *
+     * @return True if this view parent can resolve the text direction.
+     */
+    public boolean canResolveTextDirection();
+
+    /**
+     * Tells if this view parent text direction is resolved.
+     * See {@link View#setTextDirection(int)}
+     *
+     * @return True if this view parent text direction is resolved.
+     */
+    public boolean isTextDirectionResolved();
+
+    /**
+     * Return this view parent text direction. See {@link View#getTextDirection()}
+     *
+     * @return the resolved text direction. Returns one of:
+     *
+     * {@link View#TEXT_DIRECTION_FIRST_STRONG}
+     * {@link View#TEXT_DIRECTION_ANY_RTL},
+     * {@link View#TEXT_DIRECTION_LTR},
+     * {@link View#TEXT_DIRECTION_RTL},
+     * {@link View#TEXT_DIRECTION_LOCALE}
+     */
+    public int getTextDirection();
+
+    /**
+     * Tells if this view parent can resolve the text alignment.
+     * See {@link View#setTextAlignment(int)}
+     *
+     * @return True if this view parent can resolve the text alignment.
+     */
+    public boolean canResolveTextAlignment();
+
+    /**
+     * Tells if this view parent text alignment is resolved.
+     * See {@link View#setTextAlignment(int)}
+     *
+     * @return True if this view parent text alignment is resolved.
+     */
+    public boolean isTextAlignmentResolved();
+
+    /**
+     * Return this view parent text alignment. See {@link android.view.View#getTextAlignment()}
+     *
+     * @return the resolved text alignment. Returns one of:
+     *
+     * {@link View#TEXT_ALIGNMENT_GRAVITY},
+     * {@link View#TEXT_ALIGNMENT_CENTER},
+     * {@link View#TEXT_ALIGNMENT_TEXT_START},
+     * {@link View#TEXT_ALIGNMENT_TEXT_END},
+     * {@link View#TEXT_ALIGNMENT_VIEW_START},
+     * {@link View#TEXT_ALIGNMENT_VIEW_END}
+     */
+    public int getTextAlignment();
+
+    /**
+     * React to a descendant view initiating a nestable scroll operation, claiming the
+     * nested scroll operation if appropriate.
+     *
+     * <p>This method will be called in response to a descendant view invoking
+     * {@link View#startNestedScroll(int)}. Each parent up the view hierarchy will be
+     * given an opportunity to respond and claim the nested scrolling operation by returning
+     * <code>true</code>.</p>
+     *
+     * <p>This method may be overridden by ViewParent implementations to indicate when the view
+     * is willing to support a nested scrolling operation that is about to begin. If it returns
+     * true, this ViewParent will become the target view's nested scrolling parent for the duration
+     * of the scroll operation in progress. When the nested scroll is finished this ViewParent
+     * will receive a call to {@link #onStopNestedScroll(View)}.
+     * </p>
+     *
+     * @param child Direct child of this ViewParent containing target
+     * @param target View that initiated the nested scroll
+     * @param nestedScrollAxes Flags consisting of {@link View#SCROLL_AXIS_HORIZONTAL},
+     *                         {@link View#SCROLL_AXIS_VERTICAL} or both
+     * @return true if this ViewParent accepts the nested scroll operation
+     */
+    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);
+
+    /**
+     * React to the successful claiming of a nested scroll operation.
+     *
+     * <p>This method will be called after
+     * {@link #onStartNestedScroll(View, View, int) onStartNestedScroll} returns true. It offers
+     * an opportunity for the view and its superclasses to perform initial configuration
+     * for the nested scroll. Implementations of this method should always call their superclass's
+     * implementation of this method if one is present.</p>
+     *
+     * @param child Direct child of this ViewParent containing target
+     * @param target View that initiated the nested scroll
+     * @param nestedScrollAxes Flags consisting of {@link View#SCROLL_AXIS_HORIZONTAL},
+     *                         {@link View#SCROLL_AXIS_VERTICAL} or both
+     * @see #onStartNestedScroll(View, View, int)
+     * @see #onStopNestedScroll(View)
+     */
+    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);
+
+    /**
+     * React to a nested scroll operation ending.
+     *
+     * <p>Perform cleanup after a nested scrolling operation.
+     * This method will be called when a nested scroll stops, for example when a nested touch
+     * scroll ends with a {@link MotionEvent#ACTION_UP} or {@link MotionEvent#ACTION_CANCEL} event.
+     * Implementations of this method should always call their superclass's implementation of this
+     * method if one is present.</p>
+     *
+     * @param target View that initiated the nested scroll
+     */
+    public void onStopNestedScroll(View target);
+
+    /**
+     * React to a nested scroll in progress.
+     *
+     * <p>This method will be called when the ViewParent's current nested scrolling child view
+     * dispatches a nested scroll event. To receive calls to this method the ViewParent must have
+     * previously returned <code>true</code> for a call to
+     * {@link #onStartNestedScroll(View, View, int)}.</p>
+     *
+     * <p>Both the consumed and unconsumed portions of the scroll distance are reported to the
+     * ViewParent. An implementation may choose to use the consumed portion to match or chase scroll
+     * position of multiple child elements, for example. The unconsumed portion may be used to
+     * allow continuous dragging of multiple scrolling or draggable elements, such as scrolling
+     * a list within a vertical drawer where the drawer begins dragging once the edge of inner
+     * scrolling content is reached.</p>
+     *
+     * @param target The descendent view controlling the nested scroll
+     * @param dxConsumed Horizontal scroll distance in pixels already consumed by target
+     * @param dyConsumed Vertical scroll distance in pixels already consumed by target
+     * @param dxUnconsumed Horizontal scroll distance in pixels not consumed by target
+     * @param dyUnconsumed Vertical scroll distance in pixels not consumed by target
+     */
+    public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
+            int dxUnconsumed, int dyUnconsumed);
+
+    /**
+     * React to a nested scroll in progress before the target view consumes a portion of the scroll.
+     *
+     * <p>When working with nested scrolling often the parent view may want an opportunity
+     * to consume the scroll before the nested scrolling child does. An example of this is a
+     * drawer that contains a scrollable list. The user will want to be able to scroll the list
+     * fully into view before the list itself begins scrolling.</p>
+     *
+     * <p><code>onNestedPreScroll</code> is called when a nested scrolling child invokes
+     * {@link View#dispatchNestedPreScroll(int, int, int[], int[])}. The implementation should
+     * report how any pixels of the scroll reported by dx, dy were consumed in the
+     * <code>consumed</code> array. Index 0 corresponds to dx and index 1 corresponds to dy.
+     * This parameter will never be null. Initial values for consumed[0] and consumed[1]
+     * will always be 0.</p>
+     *
+     * @param target View that initiated the nested scroll
+     * @param dx Horizontal scroll distance in pixels
+     * @param dy Vertical scroll distance in pixels
+     * @param consumed Output. The horizontal and vertical scroll distance consumed by this parent
+     */
+    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);
+
+    /**
+     * Request a fling from a nested scroll.
+     *
+     * <p>This method signifies that a nested scrolling child has detected suitable conditions
+     * for a fling. Generally this means that a touch scroll has ended with a
+     * {@link VelocityTracker velocity} in the direction of scrolling that meets or exceeds
+     * the {@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity}
+     * along a scrollable axis.</p>
+     *
+     * <p>If a nested scrolling child view would normally fling but it is at the edge of
+     * its own content, it can use this method to delegate the fling to its nested scrolling
+     * parent instead. The parent may optionally consume the fling or observe a child fling.</p>
+     *
+     * @param target View that initiated the nested scroll
+     * @param velocityX Horizontal velocity in pixels per second
+     * @param velocityY Vertical velocity in pixels per second
+     * @param consumed true if the child consumed the fling, false otherwise
+     * @return true if this parent consumed or otherwise reacted to the fling
+     */
+    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);
+
+    /**
+     * React to a nested fling before the target view consumes it.
+     *
+     * <p>This method siginfies that a nested scrolling child has detected a fling with the given
+     * velocity along each axis. Generally this means that a touch scroll has ended with a
+     * {@link VelocityTracker velocity} in the direction of scrolling that meets or exceeds
+     * the {@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity}
+     * along a scrollable axis.</p>
+     *
+     * <p>If a nested scrolling parent is consuming motion as part of a
+     * {@link #onNestedPreScroll(View, int, int, int[]) pre-scroll}, it may be appropriate for
+     * it to also consume the pre-fling to complete that same motion. By returning
+     * <code>true</code> from this method, the parent indicates that the child should not
+     * fling its own internal content as well.</p>
+     *
+     * @param target View that initiated the nested scroll
+     * @param velocityX Horizontal velocity in pixels per second
+     * @param velocityY Vertical velocity in pixels per second
+     * @return true if this parent consumed the fling ahead of the target view
+     */
+    public boolean onNestedPreFling(View target, float velocityX, float velocityY);
+
+    /**
+     * React to an accessibility action delegated by a target descendant view before the target
+     * processes it.
+     *
+     * <p>This method may be called by a target descendant view if the target wishes to give
+     * a view in its parent chain a chance to react to the event before normal processing occurs.
+     * Most commonly this will be a scroll event such as
+     * {@link android.view.accessibility.AccessibilityNodeInfo#ACTION_SCROLL_FORWARD}.
+     * A ViewParent that supports acting as a nested scrolling parent should override this
+     * method and act accordingly to implement scrolling via accesibility systems.</p>
+     *
+     * @param target The target view dispatching this action
+     * @param action Action being performed; see
+     *               {@link android.view.accessibility.AccessibilityNodeInfo}
+     * @param arguments Optional action arguments
+     * @return true if the action was consumed by this ViewParent
+     */
+    public boolean onNestedPrePerformAccessibilityAction(View target, int action, Bundle arguments);
+
+    /**
+     * Given a touchable region of a child, this method reduces region by the bounds of all views on
+     * top of the child for which {@link View#canReceivePointerEvents} returns {@code true}. This
+     * applies recursively for all views in the view hierarchy on top of this one.
+     *
+     * @param touchableRegion The touchable region we want to modify.
+     * @param view A child view of this ViewGroup which indicates the z-order of the touchable
+     *             region.
+     * @hide
+     */
+    default void subtractObscuredTouchableRegion(Region touchableRegion, View view) {
+    }
+
+    /**
+     * Unbuffered dispatch has been requested by a child of this view parent.
+     * This method is called by the View hierarchy to signal ancestors that a View needs to
+     * request unbuffered dispatch.
+     *
+     * @see View#requestUnbufferedDispatch(int)
+     * @hide
+     */
+    default void onDescendantUnbufferedRequested() {
+        if (getParent() != null) {
+            getParent().onDescendantUnbufferedRequested();
+        }
+    }
+}
diff --git a/android/view/ViewPerfTest.java b/android/view/ViewPerfTest.java
new file mode 100644
index 0000000..a2aeb31
--- /dev/null
+++ b/android/view/ViewPerfTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.view;
+
+import static junit.framework.Assert.assertTrue;
+
+import android.content.Context;
+import android.perftests.utils.PerfTestActivity;
+import android.widget.FrameLayout;
+
+import androidx.benchmark.BenchmarkState;
+import androidx.benchmark.junit4.BenchmarkRule;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.perftests.core.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+@LargeTest
+public class ViewPerfTest {
+    @Rule
+    public final BenchmarkRule mBenchmarkRule = new BenchmarkRule();
+
+    @Rule
+    public final ActivityTestRule<PerfTestActivity> mActivityRule =
+            new ActivityTestRule<>(PerfTestActivity.class);
+
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    @Test
+    public void testSimpleViewInflate() {
+        final BenchmarkState state = mBenchmarkRule.getState();
+        LayoutInflater inflater = LayoutInflater.from(mContext);
+        FrameLayout root = new FrameLayout(mContext);
+        while (state.keepRunning()) {
+            inflater.inflate(R.layout.test_simple_view, root, false);
+        }
+    }
+
+    @Test
+    public void testTwelveKeyInflate() {
+        final BenchmarkState state = mBenchmarkRule.getState();
+        LayoutInflater inflater = LayoutInflater.from(mContext);
+        FrameLayout root = new FrameLayout(mContext);
+        while (state.keepRunning()) {
+            inflater.inflate(R.layout.twelve_key_entry, root, false);
+        }
+    }
+
+    @Test
+    public void testPerformHapticFeedback() throws Throwable {
+        final BenchmarkState state = mBenchmarkRule.getState();
+        mActivityRule.runOnUiThread(() -> {
+            state.pauseTiming();
+            View view = new View(mContext);
+            mActivityRule.getActivity().setContentView(view);
+            assertTrue("View needs to be attached to Window to perform haptic feedback",
+                    view.isAttachedToWindow());
+            state.resumeTiming();
+
+            // Disable settings so perform will never be ignored.
+            int flags = HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING
+                    | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING;
+
+            while (state.keepRunning()) {
+                assertTrue("Call to performHapticFeedback was ignored",
+                        view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_PRESS, flags));
+            }
+        });
+    }
+}
diff --git a/android/view/ViewPropertyAnimator.java b/android/view/ViewPropertyAnimator.java
new file mode 100644
index 0000000..65cc2f8
--- /dev/null
+++ b/android/view/ViewPropertyAnimator.java
@@ -0,0 +1,1186 @@
+/*
+ * 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.view;
+
+import android.animation.Animator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.annotation.FloatRange;
+import android.graphics.RenderNode;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Set;
+
+/**
+ * This class enables automatic and optimized animation of select properties on View objects.
+ * If only one or two properties on a View object are being animated, then using an
+ * {@link android.animation.ObjectAnimator} is fine; the property setters called by ObjectAnimator
+ * are well equipped to do the right thing to set the property and invalidate the view
+ * appropriately. But if several properties are animated simultaneously, or if you just want a
+ * more convenient syntax to animate a specific property, then ViewPropertyAnimator might be
+ * more well-suited to the task.
+ *
+ * <p>This class may provide better performance for several simultaneous animations, because
+ * it will optimize invalidate calls to take place only once for several properties instead of each
+ * animated property independently causing its own invalidation. Also, the syntax of using this
+ * class could be easier to use because the caller need only tell the View object which
+ * property to animate, and the value to animate either to or by, and this class handles the
+ * details of configuring the underlying Animator class and starting it.</p>
+ *
+ * <p>This class is not constructed by the caller, but rather by the View whose properties
+ * it will animate. Calls to {@link android.view.View#animate()} will return a reference
+ * to the appropriate ViewPropertyAnimator object for that View.</p>
+ *
+ */
+public class ViewPropertyAnimator {
+
+    /**
+     * The View whose properties are being animated by this class. This is set at
+     * construction time.
+     */
+    final View mView;
+
+    /**
+     * The duration of the underlying Animator object. By default, we don't set the duration
+     * on the Animator and just use its default duration. If the duration is ever set on this
+     * Animator, then we use the duration that it was set to.
+     */
+    private long mDuration;
+
+    /**
+     * A flag indicating whether the duration has been set on this object. If not, we don't set
+     * the duration on the underlying Animator, but instead just use its default duration.
+     */
+    private boolean mDurationSet = false;
+
+    /**
+     * The startDelay of the underlying Animator object. By default, we don't set the startDelay
+     * on the Animator and just use its default startDelay. If the startDelay is ever set on this
+     * Animator, then we use the startDelay that it was set to.
+     */
+    private long mStartDelay = 0;
+
+    /**
+     * A flag indicating whether the startDelay has been set on this object. If not, we don't set
+     * the startDelay on the underlying Animator, but instead just use its default startDelay.
+     */
+    private boolean mStartDelaySet = false;
+
+    /**
+     * The interpolator of the underlying Animator object. By default, we don't set the interpolator
+     * on the Animator and just use its default interpolator. If the interpolator is ever set on
+     * this Animator, then we use the interpolator that it was set to.
+     */
+    private TimeInterpolator mInterpolator;
+
+    /**
+     * A flag indicating whether the interpolator has been set on this object. If not, we don't set
+     * the interpolator on the underlying Animator, but instead just use its default interpolator.
+     */
+    private boolean mInterpolatorSet = false;
+
+    /**
+     * Listener for the lifecycle events of the underlying ValueAnimator object.
+     */
+    private Animator.AnimatorListener mListener = null;
+
+    /**
+     * Listener for the update events of the underlying ValueAnimator object.
+     */
+    private ValueAnimator.AnimatorUpdateListener mUpdateListener = null;
+
+    /**
+     * A lazily-created ValueAnimator used in order to get some default animator properties
+     * (duration, start delay, interpolator, etc.).
+     */
+    private ValueAnimator mTempValueAnimator;
+
+    /**
+     * This listener is the mechanism by which the underlying Animator causes changes to the
+     * properties currently being animated, as well as the cleanup after an animation is
+     * complete.
+     */
+    private AnimatorEventListener mAnimatorEventListener = new AnimatorEventListener();
+
+    /**
+     * This list holds the properties that have been asked to animate. We allow the caller to
+     * request several animations prior to actually starting the underlying animator. This
+     * enables us to run one single animator to handle several properties in parallel. Each
+     * property is tossed onto the pending list until the animation actually starts (which is
+     * done by posting it onto mView), at which time the pending list is cleared and the properties
+     * on that list are added to the list of properties associated with that animator.
+     */
+    ArrayList<NameValuesHolder> mPendingAnimations = new ArrayList<NameValuesHolder>();
+    private Runnable mPendingSetupAction;
+    private Runnable mPendingCleanupAction;
+    private Runnable mPendingOnStartAction;
+    private Runnable mPendingOnEndAction;
+
+    /**
+     * Constants used to associate a property being requested and the mechanism used to set
+     * the property (this class calls directly into View to set the properties in question).
+     */
+    static final int NONE           = 0x0000;
+    static final int TRANSLATION_X  = 0x0001;
+    static final int TRANSLATION_Y  = 0x0002;
+    static final int TRANSLATION_Z  = 0x0004;
+    static final int SCALE_X        = 0x0008;
+    static final int SCALE_Y        = 0x0010;
+    static final int ROTATION       = 0x0020;
+    static final int ROTATION_X     = 0x0040;
+    static final int ROTATION_Y     = 0x0080;
+    static final int X              = 0x0100;
+    static final int Y              = 0x0200;
+    static final int Z              = 0x0400;
+    static final int ALPHA          = 0x0800;
+
+    private static final int TRANSFORM_MASK = TRANSLATION_X | TRANSLATION_Y | TRANSLATION_Z |
+            SCALE_X | SCALE_Y | ROTATION | ROTATION_X | ROTATION_Y | X | Y | Z;
+
+    /**
+     * The mechanism by which the user can request several properties that are then animated
+     * together works by posting this Runnable to start the underlying Animator. Every time
+     * a property animation is requested, we cancel any previous postings of the Runnable
+     * and re-post it. This means that we will only ever run the Runnable (and thus start the
+     * underlying animator) after the caller is done setting the properties that should be
+     * animated together.
+     */
+    private Runnable mAnimationStarter = new Runnable() {
+        @Override
+        public void run() {
+            startAnimation();
+        }
+    };
+
+    /**
+     * This class holds information about the overall animation being run on the set of
+     * properties. The mask describes which properties are being animated and the
+     * values holder is the list of all property/value objects.
+     */
+    private static class PropertyBundle {
+        int mPropertyMask;
+        ArrayList<NameValuesHolder> mNameValuesHolder;
+
+        PropertyBundle(int propertyMask, ArrayList<NameValuesHolder> nameValuesHolder) {
+            mPropertyMask = propertyMask;
+            mNameValuesHolder = nameValuesHolder;
+        }
+
+        /**
+         * Removes the given property from being animated as a part of this
+         * PropertyBundle. If the property was a part of this bundle, it returns
+         * true to indicate that it was, in fact, canceled. This is an indication
+         * to the caller that a cancellation actually occurred.
+         *
+         * @param propertyConstant The property whose cancellation is requested.
+         * @return true if the given property is a part of this bundle and if it
+         * has therefore been canceled.
+         */
+        boolean cancel(int propertyConstant) {
+            if ((mPropertyMask & propertyConstant) != 0 && mNameValuesHolder != null) {
+                int count = mNameValuesHolder.size();
+                for (int i = 0; i < count; ++i) {
+                    NameValuesHolder nameValuesHolder = mNameValuesHolder.get(i);
+                    if (nameValuesHolder.mNameConstant == propertyConstant) {
+                        mNameValuesHolder.remove(i);
+                        mPropertyMask &= ~propertyConstant;
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * This list tracks the list of properties being animated by any particular animator.
+     * In most situations, there would only ever be one animator running at a time. But it is
+     * possible to request some properties to animate together, then while those properties
+     * are animating, to request some other properties to animate together. The way that
+     * works is by having this map associate the group of properties being animated with the
+     * animator handling the animation. On every update event for an Animator, we ask the
+     * map for the associated properties and set them accordingly.
+     */
+    private HashMap<Animator, PropertyBundle> mAnimatorMap =
+            new HashMap<Animator, PropertyBundle>();
+    private HashMap<Animator, Runnable> mAnimatorSetupMap;
+    private HashMap<Animator, Runnable> mAnimatorCleanupMap;
+    private HashMap<Animator, Runnable> mAnimatorOnStartMap;
+    private HashMap<Animator, Runnable> mAnimatorOnEndMap;
+
+    /**
+     * This is the information we need to set each property during the animation.
+     * mNameConstant is used to set the appropriate field in View, and the from/delta
+     * values are used to calculate the animated value for a given animation fraction
+     * during the animation.
+     */
+    static class NameValuesHolder {
+        int mNameConstant;
+        float mFromValue;
+        float mDeltaValue;
+        NameValuesHolder(int nameConstant, float fromValue, float deltaValue) {
+            mNameConstant = nameConstant;
+            mFromValue = fromValue;
+            mDeltaValue = deltaValue;
+        }
+    }
+
+    /**
+     * Constructor, called by View. This is private by design, as the user should only
+     * get a ViewPropertyAnimator by calling View.animate().
+     *
+     * @param view The View associated with this ViewPropertyAnimator
+     */
+    ViewPropertyAnimator(View view) {
+        mView = view;
+        view.ensureTransformationInfo();
+    }
+
+    /**
+     * Sets the duration for the underlying animator that animates the requested properties.
+     * By default, the animator uses the default value for ValueAnimator. Calling this method
+     * will cause the declared value to be used instead.
+     * @param duration The length of ensuing property animations, in milliseconds. The value
+     * cannot be negative.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator setDuration(long duration) {
+        if (duration < 0) {
+            throw new IllegalArgumentException("Animators cannot have negative duration: " +
+                    duration);
+        }
+        mDurationSet = true;
+        mDuration = duration;
+        return this;
+    }
+
+    /**
+     * Returns the current duration of property animations. If the duration was set on this
+     * object, that value is returned. Otherwise, the default value of the underlying Animator
+     * is returned.
+     *
+     * @see #setDuration(long)
+     * @return The duration of animations, in milliseconds.
+     */
+    public long getDuration() {
+        if (mDurationSet) {
+            return mDuration;
+        } else {
+            // Just return the default from ValueAnimator, since that's what we'd get if
+            // the value has not been set otherwise
+            if (mTempValueAnimator == null) {
+                mTempValueAnimator = new ValueAnimator();
+            }
+            return mTempValueAnimator.getDuration();
+        }
+    }
+
+    /**
+     * Returns the current startDelay of property animations. If the startDelay was set on this
+     * object, that value is returned. Otherwise, the default value of the underlying Animator
+     * is returned.
+     *
+     * @see #setStartDelay(long)
+     * @return The startDelay of animations, in milliseconds.
+     */
+    public long getStartDelay() {
+        if (mStartDelaySet) {
+            return mStartDelay;
+        } else {
+            // Just return the default from ValueAnimator (0), since that's what we'd get if
+            // the value has not been set otherwise
+            return 0;
+        }
+    }
+
+    /**
+     * Sets the startDelay for the underlying animator that animates the requested properties.
+     * By default, the animator uses the default value for ValueAnimator. Calling this method
+     * will cause the declared value to be used instead.
+     * @param startDelay The delay of ensuing property animations, in milliseconds. The value
+     * cannot be negative.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator setStartDelay(long startDelay) {
+        if (startDelay < 0) {
+            throw new IllegalArgumentException("Animators cannot have negative start " +
+                "delay: " + startDelay);
+        }
+        mStartDelaySet = true;
+        mStartDelay = startDelay;
+        return this;
+    }
+
+    /**
+     * Sets the interpolator for the underlying animator that animates the requested properties.
+     * By default, the animator uses the default interpolator for ValueAnimator. Calling this method
+     * will cause the declared object to be used instead.
+     *
+     * @param interpolator The TimeInterpolator to be used for ensuing property animations. A value
+     * of <code>null</code> will result in linear interpolation.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator setInterpolator(TimeInterpolator interpolator) {
+        mInterpolatorSet = true;
+        mInterpolator = interpolator;
+        return this;
+    }
+
+    /**
+     * Returns the timing interpolator that this animation uses.
+     *
+     * @return The timing interpolator for this animation.
+     */
+    public TimeInterpolator getInterpolator() {
+        if (mInterpolatorSet) {
+            return mInterpolator;
+        } else {
+            // Just return the default from ValueAnimator, since that's what we'd get if
+            // the value has not been set otherwise
+            if (mTempValueAnimator == null) {
+                mTempValueAnimator = new ValueAnimator();
+            }
+            return mTempValueAnimator.getInterpolator();
+        }
+    }
+
+    /**
+     * Sets a listener for events in the underlying Animators that run the property
+     * animations.
+     *
+     * @see Animator.AnimatorListener
+     *
+     * @param listener The listener to be called with AnimatorListener events. A value of
+     * <code>null</code> removes any existing listener.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator setListener(Animator.AnimatorListener listener) {
+        mListener = listener;
+        return this;
+    }
+
+    Animator.AnimatorListener getListener() {
+        return mListener;
+    }
+
+    /**
+     * Sets a listener for update events in the underlying ValueAnimator that runs
+     * the property animations. Note that the underlying animator is animating between
+     * 0 and 1 (these values are then turned into the actual property values internally
+     * by ViewPropertyAnimator). So the animator cannot give information on the current
+     * values of the properties being animated by this ViewPropertyAnimator, although
+     * the view object itself can be queried to get the current values.
+     *
+     * @see android.animation.ValueAnimator.AnimatorUpdateListener
+     *
+     * @param listener The listener to be called with update events. A value of
+     * <code>null</code> removes any existing listener.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator setUpdateListener(ValueAnimator.AnimatorUpdateListener listener) {
+        mUpdateListener = listener;
+        return this;
+    }
+
+    ValueAnimator.AnimatorUpdateListener getUpdateListener() {
+        return mUpdateListener;
+    }
+
+    /**
+     * Starts the currently pending property animations immediately. Calling <code>start()</code>
+     * is optional because all animations start automatically at the next opportunity. However,
+     * if the animations are needed to start immediately and synchronously (not at the time when
+     * the next event is processed by the hierarchy, which is when the animations would begin
+     * otherwise), then this method can be used.
+     */
+    public void start() {
+        mView.removeCallbacks(mAnimationStarter);
+        startAnimation();
+    }
+
+    /**
+     * Cancels all property animations that are currently running or pending.
+     */
+    public void cancel() {
+        if (mAnimatorMap.size() > 0) {
+            HashMap<Animator, PropertyBundle> mAnimatorMapCopy =
+                    (HashMap<Animator, PropertyBundle>)mAnimatorMap.clone();
+            Set<Animator> animatorSet = mAnimatorMapCopy.keySet();
+            for (Animator runningAnim : animatorSet) {
+                runningAnim.cancel();
+            }
+        }
+        mPendingAnimations.clear();
+        mPendingSetupAction = null;
+        mPendingCleanupAction = null;
+        mPendingOnStartAction = null;
+        mPendingOnEndAction = null;
+        mView.removeCallbacks(mAnimationStarter);
+    }
+
+    /**
+     * This method will cause the View's <code>x</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The value to be animated to.
+     * @see View#setX(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator x(float value) {
+        animateProperty(X, value);
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>x</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @see View#setX(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator xBy(float value) {
+        animatePropertyBy(X, value);
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>y</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The value to be animated to.
+     * @see View#setY(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator y(float value) {
+        animateProperty(Y, value);
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>y</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @see View#setY(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator yBy(float value) {
+        animatePropertyBy(Y, value);
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>z</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The value to be animated to.
+     * @see View#setZ(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator z(float value) {
+        animateProperty(Z, value);
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>z</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @see View#setZ(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator zBy(float value) {
+        animatePropertyBy(Z, value);
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>rotation</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The value to be animated to.
+     * @see View#setRotation(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator rotation(float value) {
+        animateProperty(ROTATION, value);
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>rotation</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @see View#setRotation(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator rotationBy(float value) {
+        animatePropertyBy(ROTATION, value);
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>rotationX</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The value to be animated to.
+     * @see View#setRotationX(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator rotationX(float value) {
+        animateProperty(ROTATION_X, value);
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>rotationX</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @see View#setRotationX(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator rotationXBy(float value) {
+        animatePropertyBy(ROTATION_X, value);
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>rotationY</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The value to be animated to.
+     * @see View#setRotationY(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator rotationY(float value) {
+        animateProperty(ROTATION_Y, value);
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>rotationY</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @see View#setRotationY(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator rotationYBy(float value) {
+        animatePropertyBy(ROTATION_Y, value);
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>translationX</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The value to be animated to.
+     * @see View#setTranslationX(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator translationX(float value) {
+        animateProperty(TRANSLATION_X, value);
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>translationX</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @see View#setTranslationX(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator translationXBy(float value) {
+        animatePropertyBy(TRANSLATION_X, value);
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>translationY</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The value to be animated to.
+     * @see View#setTranslationY(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator translationY(float value) {
+        animateProperty(TRANSLATION_Y, value);
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>translationY</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @see View#setTranslationY(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator translationYBy(float value) {
+        animatePropertyBy(TRANSLATION_Y, value);
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>translationZ</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The value to be animated to.
+     * @see View#setTranslationZ(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator translationZ(float value) {
+        animateProperty(TRANSLATION_Z, value);
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>translationZ</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @see View#setTranslationZ(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator translationZBy(float value) {
+        animatePropertyBy(TRANSLATION_Z, value);
+        return this;
+    }
+    /**
+     * This method will cause the View's <code>scaleX</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The value to be animated to.
+     * @see View#setScaleX(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator scaleX(float value) {
+        animateProperty(SCALE_X, value);
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>scaleX</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @see View#setScaleX(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator scaleXBy(float value) {
+        animatePropertyBy(SCALE_X, value);
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>scaleY</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The value to be animated to.
+     * @see View#setScaleY(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator scaleY(float value) {
+        animateProperty(SCALE_Y, value);
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>scaleY</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @see View#setScaleY(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator scaleYBy(float value) {
+        animatePropertyBy(SCALE_Y, value);
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>alpha</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The value to be animated to.
+     * @see View#setAlpha(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator alpha(@FloatRange(from = 0.0f, to = 1.0f) float value) {
+        animateProperty(ALPHA, value);
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>alpha</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @see View#setAlpha(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator alphaBy(float value) {
+        animatePropertyBy(ALPHA, value);
+        return this;
+    }
+
+    /**
+     * The View associated with this ViewPropertyAnimator will have its
+     * {@link View#setLayerType(int, android.graphics.Paint) layer type} set to
+     * {@link View#LAYER_TYPE_HARDWARE} for the duration of the next animation.
+     * As stated in the documentation for {@link View#LAYER_TYPE_HARDWARE},
+     * the actual type of layer used internally depends on the runtime situation of the
+     * view. If the activity and this view are hardware-accelerated, then the layer will be
+     * accelerated as well. If the activity or the view is not accelerated, then the layer will
+     * effectively be the same as {@link View#LAYER_TYPE_SOFTWARE}.
+     *
+     * <p>This state is not persistent, either on the View or on this ViewPropertyAnimator: the
+     * layer type of the View will be restored when the animation ends to what it was when this
+     * method was called, and this setting on ViewPropertyAnimator is only valid for the next
+     * animation. Note that calling this method and then independently setting the layer type of
+     * the View (by a direct call to {@link View#setLayerType(int, android.graphics.Paint)}) will
+     * result in some inconsistency, including having the layer type restored to its pre-withLayer()
+     * value when the animation ends.</p>
+     *
+     * @see View#setLayerType(int, android.graphics.Paint)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator withLayer() {
+         mPendingSetupAction= new Runnable() {
+            @Override
+            public void run() {
+                mView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+                if (mView.isAttachedToWindow()) {
+                    mView.buildLayer();
+                }
+            }
+        };
+        final int currentLayerType = mView.getLayerType();
+        mPendingCleanupAction = new Runnable() {
+            @Override
+            public void run() {
+                mView.setLayerType(currentLayerType, null);
+            }
+        };
+        if (mAnimatorSetupMap == null) {
+            mAnimatorSetupMap = new HashMap<Animator, Runnable>();
+        }
+        if (mAnimatorCleanupMap == null) {
+            mAnimatorCleanupMap = new HashMap<Animator, Runnable>();
+        }
+
+        return this;
+    }
+
+    /**
+     * Specifies an action to take place when the next animation runs. If there is a
+     * {@link #setStartDelay(long) startDelay} set on this ViewPropertyAnimator, then the
+     * action will run after that startDelay expires, when the actual animation begins.
+     * This method, along with {@link #withEndAction(Runnable)}, is intended to help facilitate
+     * choreographing ViewPropertyAnimator animations with other animations or actions
+     * in the application.
+     *
+     * @param runnable The action to run when the next animation starts.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator withStartAction(Runnable runnable) {
+        mPendingOnStartAction = runnable;
+        if (runnable != null && mAnimatorOnStartMap == null) {
+            mAnimatorOnStartMap = new HashMap<Animator, Runnable>();
+        }
+        return this;
+    }
+
+    /**
+     * Specifies an action to take place when the next animation ends. The action is only
+     * run if the animation ends normally; if the ViewPropertyAnimator is canceled during
+     * that animation, the runnable will not run.
+     * This method, along with {@link #withStartAction(Runnable)}, is intended to help facilitate
+     * choreographing ViewPropertyAnimator animations with other animations or actions
+     * in the application.
+     *
+     * <p>For example, the following code animates a view to x=200 and then back to 0:</p>
+     * <pre>
+     *     Runnable endAction = new Runnable() {
+     *         public void run() {
+     *             view.animate().x(0);
+     *         }
+     *     };
+     *     view.animate().x(200).withEndAction(endAction);
+     * </pre>
+     *
+     * @param runnable The action to run when the next animation ends.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator withEndAction(Runnable runnable) {
+        mPendingOnEndAction = runnable;
+        if (runnable != null && mAnimatorOnEndMap == null) {
+            mAnimatorOnEndMap = new HashMap<Animator, Runnable>();
+        }
+        return this;
+    }
+
+    boolean hasActions() {
+        return mPendingSetupAction != null
+                || mPendingCleanupAction != null
+                || mPendingOnStartAction != null
+                || mPendingOnEndAction != null;
+    }
+
+    /**
+     * Starts the underlying Animator for a set of properties. We use a single animator that
+     * simply runs from 0 to 1, and then use that fractional value to set each property
+     * value accordingly.
+     */
+    private void startAnimation() {
+        mView.setHasTransientState(true);
+        ValueAnimator animator = ValueAnimator.ofFloat(1.0f);
+        ArrayList<NameValuesHolder> nameValueList =
+                (ArrayList<NameValuesHolder>) mPendingAnimations.clone();
+        mPendingAnimations.clear();
+        int propertyMask = 0;
+        int propertyCount = nameValueList.size();
+        for (int i = 0; i < propertyCount; ++i) {
+            NameValuesHolder nameValuesHolder = nameValueList.get(i);
+            propertyMask |= nameValuesHolder.mNameConstant;
+        }
+        mAnimatorMap.put(animator, new PropertyBundle(propertyMask, nameValueList));
+        if (mPendingSetupAction != null) {
+            mAnimatorSetupMap.put(animator, mPendingSetupAction);
+            mPendingSetupAction = null;
+        }
+        if (mPendingCleanupAction != null) {
+            mAnimatorCleanupMap.put(animator, mPendingCleanupAction);
+            mPendingCleanupAction = null;
+        }
+        if (mPendingOnStartAction != null) {
+            mAnimatorOnStartMap.put(animator, mPendingOnStartAction);
+            mPendingOnStartAction = null;
+        }
+        if (mPendingOnEndAction != null) {
+            mAnimatorOnEndMap.put(animator, mPendingOnEndAction);
+            mPendingOnEndAction = null;
+        }
+        animator.addUpdateListener(mAnimatorEventListener);
+        animator.addListener(mAnimatorEventListener);
+        if (mStartDelaySet) {
+            animator.setStartDelay(mStartDelay);
+        }
+        if (mDurationSet) {
+            animator.setDuration(mDuration);
+        }
+        if (mInterpolatorSet) {
+            animator.setInterpolator(mInterpolator);
+        }
+        animator.start();
+    }
+
+    /**
+     * Utility function, called by the various x(), y(), etc. methods. This stores the
+     * constant name for the property along with the from/delta values that will be used to
+     * calculate and set the property during the animation. This structure is added to the
+     * pending animations, awaiting the eventual start() of the underlying animator. A
+     * Runnable is posted to start the animation, and any pending such Runnable is canceled
+     * (which enables us to end up starting just one animator for all of the properties
+     * specified at one time).
+     *
+     * @param constantName The specifier for the property being animated
+     * @param toValue The value to which the property will animate
+     */
+    private void animateProperty(int constantName, float toValue) {
+        float fromValue = getValue(constantName);
+        float deltaValue = toValue - fromValue;
+        animatePropertyBy(constantName, fromValue, deltaValue);
+    }
+
+    /**
+     * Utility function, called by the various xBy(), yBy(), etc. methods. This method is
+     * just like animateProperty(), except the value is an offset from the property's
+     * current value, instead of an absolute "to" value.
+     *
+     * @param constantName The specifier for the property being animated
+     * @param byValue The amount by which the property will change
+     */
+    private void animatePropertyBy(int constantName, float byValue) {
+        float fromValue = getValue(constantName);
+        animatePropertyBy(constantName, fromValue, byValue);
+    }
+
+    /**
+     * Utility function, called by animateProperty() and animatePropertyBy(), which handles the
+     * details of adding a pending animation and posting the request to start the animation.
+     *
+     * @param constantName The specifier for the property being animated
+     * @param startValue The starting value of the property
+     * @param byValue The amount by which the property will change
+     */
+    private void animatePropertyBy(int constantName, float startValue, float byValue) {
+        // First, cancel any existing animations on this property
+        if (mAnimatorMap.size() > 0) {
+            Animator animatorToCancel = null;
+            Set<Animator> animatorSet = mAnimatorMap.keySet();
+            for (Animator runningAnim : animatorSet) {
+                PropertyBundle bundle = mAnimatorMap.get(runningAnim);
+                if (bundle.cancel(constantName)) {
+                    // property was canceled - cancel the animation if it's now empty
+                    // Note that it's safe to break out here because every new animation
+                    // on a property will cancel a previous animation on that property, so
+                    // there can only ever be one such animation running.
+                    if (bundle.mPropertyMask == NONE) {
+                        // the animation is no longer changing anything - cancel it
+                        animatorToCancel = runningAnim;
+                        break;
+                    }
+                }
+            }
+            if (animatorToCancel != null) {
+                animatorToCancel.cancel();
+            }
+        }
+
+        NameValuesHolder nameValuePair = new NameValuesHolder(constantName, startValue, byValue);
+        mPendingAnimations.add(nameValuePair);
+        mView.removeCallbacks(mAnimationStarter);
+        mView.postOnAnimation(mAnimationStarter);
+    }
+
+    /**
+     * This method handles setting the property values directly in the View object's fields.
+     * propertyConstant tells it which property should be set, value is the value to set
+     * the property to.
+     *
+     * @param propertyConstant The property to be set
+     * @param value The value to set the property to
+     */
+    private void setValue(int propertyConstant, float value) {
+        final RenderNode renderNode = mView.mRenderNode;
+        switch (propertyConstant) {
+            case TRANSLATION_X:
+                renderNode.setTranslationX(value);
+                break;
+            case TRANSLATION_Y:
+                renderNode.setTranslationY(value);
+                break;
+            case TRANSLATION_Z:
+                renderNode.setTranslationZ(value);
+                break;
+            case ROTATION:
+                renderNode.setRotationZ(value);
+                break;
+            case ROTATION_X:
+                renderNode.setRotationX(value);
+                break;
+            case ROTATION_Y:
+                renderNode.setRotationY(value);
+                break;
+            case SCALE_X:
+                renderNode.setScaleX(value);
+                break;
+            case SCALE_Y:
+                renderNode.setScaleY(value);
+                break;
+            case X:
+                renderNode.setTranslationX(value - mView.mLeft);
+                break;
+            case Y:
+                renderNode.setTranslationY(value - mView.mTop);
+                break;
+            case Z:
+                renderNode.setTranslationZ(value - renderNode.getElevation());
+                break;
+            case ALPHA:
+                mView.setAlphaInternal(value);
+                renderNode.setAlpha(value);
+                break;
+        }
+    }
+
+    /**
+     * This method gets the value of the named property from the View object.
+     *
+     * @param propertyConstant The property whose value should be returned
+     * @return float The value of the named property
+     */
+    private float getValue(int propertyConstant) {
+        final RenderNode node = mView.mRenderNode;
+        switch (propertyConstant) {
+            case TRANSLATION_X:
+                return node.getTranslationX();
+            case TRANSLATION_Y:
+                return node.getTranslationY();
+            case TRANSLATION_Z:
+                return node.getTranslationZ();
+            case ROTATION:
+                return node.getRotationZ();
+            case ROTATION_X:
+                return node.getRotationX();
+            case ROTATION_Y:
+                return node.getRotationY();
+            case SCALE_X:
+                return node.getScaleX();
+            case SCALE_Y:
+                return node.getScaleY();
+            case X:
+                return mView.mLeft + node.getTranslationX();
+            case Y:
+                return mView.mTop + node.getTranslationY();
+            case Z:
+                return node.getElevation() + node.getTranslationZ();
+            case ALPHA:
+                return mView.getAlpha();
+        }
+        return 0;
+    }
+
+    /**
+     * Utility class that handles the various Animator events. The only ones we care
+     * about are the end event (which we use to clean up the animator map when an animator
+     * finishes) and the update event (which we use to calculate the current value of each
+     * property and then set it on the view object).
+     */
+    private class AnimatorEventListener
+            implements Animator.AnimatorListener, ValueAnimator.AnimatorUpdateListener {
+        @Override
+        public void onAnimationStart(Animator animation) {
+            if (mAnimatorSetupMap != null) {
+                Runnable r = mAnimatorSetupMap.get(animation);
+                if (r != null) {
+                    r.run();
+                }
+                mAnimatorSetupMap.remove(animation);
+            }
+            if (mAnimatorOnStartMap != null) {
+                Runnable r = mAnimatorOnStartMap.get(animation);
+                if (r != null) {
+                    r.run();
+                }
+                mAnimatorOnStartMap.remove(animation);
+            }
+            if (mListener != null) {
+                mListener.onAnimationStart(animation);
+            }
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+            if (mListener != null) {
+                mListener.onAnimationCancel(animation);
+            }
+            if (mAnimatorOnEndMap != null) {
+                mAnimatorOnEndMap.remove(animation);
+            }
+        }
+
+        @Override
+        public void onAnimationRepeat(Animator animation) {
+            if (mListener != null) {
+                mListener.onAnimationRepeat(animation);
+            }
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            mView.setHasTransientState(false);
+            if (mAnimatorCleanupMap != null) {
+                Runnable r = mAnimatorCleanupMap.get(animation);
+                if (r != null) {
+                    r.run();
+                }
+                mAnimatorCleanupMap.remove(animation);
+            }
+            if (mListener != null) {
+                mListener.onAnimationEnd(animation);
+            }
+            if (mAnimatorOnEndMap != null) {
+                Runnable r = mAnimatorOnEndMap.get(animation);
+                if (r != null) {
+                    r.run();
+                }
+                mAnimatorOnEndMap.remove(animation);
+            }
+            mAnimatorMap.remove(animation);
+        }
+
+        /**
+         * Calculate the current value for each property and set it on the view. Invalidate
+         * the view object appropriately, depending on which properties are being animated.
+         *
+         * @param animation The animator associated with the properties that need to be
+         * set. This animator holds the animation fraction which we will use to calculate
+         * the current value of each property.
+         */
+        @Override
+        public void onAnimationUpdate(ValueAnimator animation) {
+            PropertyBundle propertyBundle = mAnimatorMap.get(animation);
+            if (propertyBundle == null) {
+                // Shouldn't happen, but just to play it safe
+                return;
+            }
+
+            boolean hardwareAccelerated = mView.isHardwareAccelerated();
+
+            // alpha requires slightly different treatment than the other (transform) properties.
+            // The logic in setAlpha() is not simply setting mAlpha, plus the invalidation
+            // logic is dependent on how the view handles an internal call to onSetAlpha().
+            // We track what kinds of properties are set, and how alpha is handled when it is
+            // set, and perform the invalidation steps appropriately.
+            boolean alphaHandled = false;
+            if (!hardwareAccelerated) {
+                mView.invalidateParentCaches();
+            }
+            float fraction = animation.getAnimatedFraction();
+            int propertyMask = propertyBundle.mPropertyMask;
+            if ((propertyMask & TRANSFORM_MASK) != 0) {
+                mView.invalidateViewProperty(hardwareAccelerated, false);
+            }
+            ArrayList<NameValuesHolder> valueList = propertyBundle.mNameValuesHolder;
+            if (valueList != null) {
+                int count = valueList.size();
+                for (int i = 0; i < count; ++i) {
+                    NameValuesHolder values = valueList.get(i);
+                    float value = values.mFromValue + fraction * values.mDeltaValue;
+                    if (values.mNameConstant == ALPHA) {
+                        alphaHandled = mView.setAlphaNoInvalidation(value);
+                    } else {
+                        setValue(values.mNameConstant, value);
+                    }
+                }
+            }
+            if ((propertyMask & TRANSFORM_MASK) != 0) {
+                if (!hardwareAccelerated) {
+                    mView.mPrivateFlags |= View.PFLAG_DRAWN; // force another invalidation
+                }
+            }
+            // invalidate(false) in all cases except if alphaHandled gets set to true
+            // via the call to setAlphaNoInvalidation(), above
+            if (alphaHandled) {
+                mView.invalidate(true);
+            } else {
+                mView.invalidateViewProperty(false, false);
+            }
+            if (mUpdateListener != null) {
+                mUpdateListener.onAnimationUpdate(animation);
+            }
+        }
+    }
+}
diff --git a/android/view/ViewRootImpl.java b/android/view/ViewRootImpl.java
new file mode 100644
index 0000000..3550a31
--- /dev/null
+++ b/android/view/ViewRootImpl.java
@@ -0,0 +1,10425 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.os.IInputConstants.INVALID_INPUT_EVENT_ID;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
+import static android.view.InputDevice.SOURCE_CLASS_NONE;
+import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
+import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.InsetsState.SIZE;
+import static android.view.View.PFLAG_DRAW_ANIMATION;
+import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
+import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+import static android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+import static android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewRootImplProto.ADDED;
+import static android.view.ViewRootImplProto.APP_VISIBLE;
+import static android.view.ViewRootImplProto.CUR_SCROLL_Y;
+import static android.view.ViewRootImplProto.DISPLAY_ID;
+import static android.view.ViewRootImplProto.HEIGHT;
+import static android.view.ViewRootImplProto.IS_ANIMATING;
+import static android.view.ViewRootImplProto.IS_DRAWING;
+import static android.view.ViewRootImplProto.LAST_WINDOW_INSETS;
+import static android.view.ViewRootImplProto.REMOVED;
+import static android.view.ViewRootImplProto.SCROLL_Y;
+import static android.view.ViewRootImplProto.SOFT_INPUT_MODE;
+import static android.view.ViewRootImplProto.VIEW;
+import static android.view.ViewRootImplProto.VISIBLE_RECT;
+import static android.view.ViewRootImplProto.WIDTH;
+import static android.view.ViewRootImplProto.WINDOW_ATTRIBUTES;
+import static android.view.ViewRootImplProto.WIN_FRAME;
+import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER;
+import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
+import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT;
+import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
+import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
+import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
+import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_APPEARANCE_CONTROLLED;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FIT_INSETS_CONTROLLED;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
+import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
+import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
+import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
+import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
+
+import android.Manifest;
+import android.animation.LayoutTransition;
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UiContext;
+import android.app.ActivityManager;
+import android.app.ActivityThread;
+import android.app.ResourcesManager;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.BLASTBufferQueue;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.FrameInfo;
+import android.graphics.HardwareRenderer;
+import android.graphics.HardwareRenderer.FrameDrawingCallback;
+import android.graphics.HardwareRendererObserver;
+import android.graphics.Insets;
+import android.graphics.Matrix;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.PorterDuff;
+import android.graphics.RecordingCanvas;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.RenderNode;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.hardware.input.InputManager;
+import android.media.AudioManager;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.sysprop.DisplayProperties;
+import android.util.AndroidRuntimeException;
+import android.util.ArraySet;
+import android.util.DisplayMetrics;
+import android.util.EventLog;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+import android.util.LongArray;
+import android.util.MergedConfiguration;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+import android.util.TypedValue;
+import android.util.imetracing.ImeTracing;
+import android.util.proto.ProtoOutputStream;
+import android.view.InputDevice.InputSourceClass;
+import android.view.InsetsState.InternalInsetsType;
+import android.view.Surface.OutOfResourcesException;
+import android.view.SurfaceControl.Transaction;
+import android.view.View.AttachInfo;
+import android.view.View.FocusDirection;
+import android.view.View.MeasureSpec;
+import android.view.Window.OnContentApplyWindowInsetsListener;
+import android.view.WindowInsets.Side.InsetsSide;
+import android.view.WindowInsets.Type;
+import android.view.WindowInsets.Type.InsetsType;
+import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
+import android.view.accessibility.AccessibilityManager.HighTextContrastChangeListener;
+import android.view.accessibility.AccessibilityNodeIdManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import android.view.accessibility.AccessibilityNodeProvider;
+import android.view.accessibility.AccessibilityWindowInfo;
+import android.view.accessibility.IAccessibilityEmbeddedConnection;
+import android.view.accessibility.IAccessibilityInteractionConnection;
+import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager;
+import android.view.contentcapture.ContentCaptureManager;
+import android.view.contentcapture.ContentCaptureSession;
+import android.view.contentcapture.MainContentCaptureSession;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Scroller;
+import android.window.ClientWindowFrames;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.drawable.BackgroundBlurDrawable;
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.internal.os.IResultReceiver;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.policy.DecorView;
+import com.android.internal.policy.PhoneFallbackEventHandler;
+import com.android.internal.util.Preconditions;
+import com.android.internal.view.BaseSurfaceHolder;
+import com.android.internal.view.RootViewSurfaceTaker;
+import com.android.internal.view.SurfaceCallbackHelper;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * The top of a view hierarchy, implementing the needed protocol between View
+ * and the WindowManager.  This is for the most part an internal implementation
+ * detail of {@link WindowManagerGlobal}.
+ *
+ * {@hide}
+ */
+@SuppressWarnings({"EmptyCatchBlock", "PointlessBooleanExpression"})
+public final class ViewRootImpl implements ViewParent,
+        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
+        AttachedSurfaceControl {
+    private static final String TAG = "ViewRootImpl";
+    private static final boolean DBG = false;
+    private static final boolean LOCAL_LOGV = false;
+    /** @noinspection PointlessBooleanExpression*/
+    private static final boolean DEBUG_DRAW = false || LOCAL_LOGV;
+    private static final boolean DEBUG_LAYOUT = false || LOCAL_LOGV;
+    private static final boolean DEBUG_DIALOG = false || LOCAL_LOGV;
+    private static final boolean DEBUG_INPUT_RESIZE = false || LOCAL_LOGV;
+    private static final boolean DEBUG_ORIENTATION = false || LOCAL_LOGV;
+    private static final boolean DEBUG_TRACKBALL = false || LOCAL_LOGV;
+    private static final boolean DEBUG_IMF = false || LOCAL_LOGV;
+    private static final boolean DEBUG_CONFIGURATION = false || LOCAL_LOGV;
+    private static final boolean DEBUG_FPS = false;
+    private static final boolean DEBUG_INPUT_STAGES = false || LOCAL_LOGV;
+    private static final boolean DEBUG_KEEP_SCREEN_ON = false || LOCAL_LOGV;
+    private static final boolean DEBUG_CONTENT_CAPTURE = false || LOCAL_LOGV;
+    private static final boolean DEBUG_SCROLL_CAPTURE = false || LOCAL_LOGV;
+    private static final boolean DEBUG_BLAST = false || LOCAL_LOGV;
+
+    /**
+     * Set to false if we do not want to use the multi threaded renderer even though
+     * threaded renderer (aka hardware renderering) is used. Note that by disabling
+     * this, WindowCallbacks will not fire.
+     */
+    private static final boolean MT_RENDERER_AVAILABLE = true;
+
+    /**
+     * Whether or not to report end-to-end input latency. Disabled temporarily as a
+     * risk mitigation against potential jank caused by acquiring a weak reference
+     * per frame
+     */
+    private static final boolean ENABLE_INPUT_LATENCY_TRACKING = false;
+
+    /**
+     * Set this system property to true to force the view hierarchy to render
+     * at 60 Hz. This can be used to measure the potential framerate.
+     */
+    private static final String PROPERTY_PROFILE_RENDERING = "viewroot.profile_rendering";
+
+    /**
+     * Maximum time we allow the user to roll the trackball enough to generate
+     * a key event, before resetting the counters.
+     */
+    static final int MAX_TRACKBALL_DELAY = 250;
+
+    /**
+     * Initial value for {@link #mContentCaptureEnabled}.
+     */
+    private static final int CONTENT_CAPTURE_ENABLED_NOT_CHECKED = 0;
+
+    /**
+     * Value for {@link #mContentCaptureEnabled} when it was checked and set to {@code true}.
+     */
+    private static final int CONTENT_CAPTURE_ENABLED_TRUE = 1;
+
+    /**
+     * Value for {@link #mContentCaptureEnabled} when it was checked and set to {@code false}.
+     */
+    private static final int CONTENT_CAPTURE_ENABLED_FALSE = 2;
+
+    /**
+     * Maximum time to wait for {@link View#dispatchScrollCaptureSearch} to complete.
+     */
+    private static final int SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS = 2500;
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>();
+
+    static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList<>();
+    static boolean sFirstDrawComplete = false;
+
+    /**
+     * Callback for notifying about global configuration changes.
+     */
+    public interface ConfigChangedCallback {
+
+        /** Notifies about global config change. */
+        void onConfigurationChanged(Configuration globalConfig);
+    }
+
+    private static final ArrayList<ConfigChangedCallback> sConfigCallbacks = new ArrayList<>();
+
+    /**
+     * Callback for notifying activities about override configuration changes.
+     */
+    public interface ActivityConfigCallback {
+
+        /**
+         * Notifies about override config change and/or move to different display.
+         * @param overrideConfig New override config to apply to activity.
+         * @param newDisplayId New display id, {@link Display#INVALID_DISPLAY} if not changed.
+         */
+        void onConfigurationChanged(Configuration overrideConfig, int newDisplayId);
+    }
+
+    /**
+     * Callback used to notify corresponding activity about override configuration change and make
+     * sure that all resources are set correctly before updating the ViewRootImpl's internal state.
+     */
+    private ActivityConfigCallback mActivityConfigCallback;
+
+    /**
+     * Used when configuration change first updates the config of corresponding activity.
+     * In that case we receive a call back from {@link ActivityThread} and this flag is used to
+     * preserve the initial value.
+     *
+     * @see #performConfigurationChange(MergedConfiguration, boolean, int)
+     */
+    private boolean mForceNextConfigUpdate;
+
+    private boolean mUseBLASTAdapter;
+    private boolean mForceDisableBLAST;
+
+    private boolean mFastScrollSoundEffectsEnabled;
+
+    /**
+     * Signals that compatibility booleans have been initialized according to
+     * target SDK versions.
+     */
+    private static boolean sCompatibilityDone = false;
+
+    /**
+     * Always assign focus if a focusable View is available.
+     */
+    private static boolean sAlwaysAssignFocus;
+
+    /**
+     * This list must only be modified by the main thread, so a lock is only needed when changing
+     * the list or when accessing the list from a non-main thread.
+     */
+    @GuardedBy("mWindowCallbacks")
+    final ArrayList<WindowCallbacks> mWindowCallbacks = new ArrayList<>();
+    @UnsupportedAppUsage
+    @UiContext
+    public final Context mContext;
+
+    @UnsupportedAppUsage
+    final IWindowSession mWindowSession;
+    @NonNull Display mDisplay;
+    final DisplayManager mDisplayManager;
+    final String mBasePackageName;
+
+    final int[] mTmpLocation = new int[2];
+
+    final TypedValue mTmpValue = new TypedValue();
+
+    final Thread mThread;
+
+    final WindowLeaked mLocation;
+
+    public final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();
+
+    final W mWindow;
+
+    final IBinder mLeashToken;
+
+    final int mTargetSdkVersion;
+
+    @UnsupportedAppUsage
+    View mView;
+
+    View mAccessibilityFocusedHost;
+    AccessibilityNodeInfo mAccessibilityFocusedVirtualView;
+
+    // True if the window currently has pointer capture enabled.
+    boolean mPointerCapture;
+
+    int mViewVisibility;
+    boolean mAppVisible = true;
+    // For recents to freeform transition we need to keep drawing after the app receives information
+    // that it became invisible. This will ignore that information and depend on the decor view
+    // visibility to control drawing. The decor view visibility will get adjusted when the app get
+    // stopped and that's when the app will stop drawing further frames.
+    private boolean mForceDecorViewVisibility = false;
+    // Used for tracking app visibility updates separately in case we get double change. This will
+    // make sure that we always call relayout for the corresponding window.
+    private boolean mAppVisibilityChanged;
+    int mOrigWindowType = -1;
+
+    /** Whether the window had focus during the most recent traversal. */
+    boolean mHadWindowFocus;
+
+    /**
+     * Whether the window lost focus during a previous traversal and has not
+     * yet gained it back. Used to determine whether a WINDOW_STATE_CHANGE
+     * accessibility events should be sent during traversal.
+     */
+    boolean mLostWindowFocus;
+
+    // Set to true if the owner of this window is in the stopped state,
+    // so the window should no longer be active.
+    @UnsupportedAppUsage
+    boolean mStopped = false;
+
+    // Set to true if the owner of this window is in ambient mode,
+    // which means it won't receive input events.
+    boolean mIsAmbientMode = false;
+
+    // Set to true to stop input during an Activity Transition.
+    boolean mPausedForTransition = false;
+
+    boolean mLastInCompatMode = false;
+
+    SurfaceHolder.Callback2 mSurfaceHolderCallback;
+    BaseSurfaceHolder mSurfaceHolder;
+    boolean mIsCreating;
+    boolean mDrawingAllowed;
+
+    final Region mTransparentRegion;
+    final Region mPreviousTransparentRegion;
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    int mWidth;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    int mHeight;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private Rect mDirty;
+    public boolean mIsAnimating;
+
+    private boolean mUseMTRenderer;
+    private boolean mDragResizing;
+    private boolean mInvalidateRootRequested;
+    private int mResizeMode;
+    private int mCanvasOffsetX;
+    private int mCanvasOffsetY;
+    private boolean mActivityRelaunched;
+
+    CompatibilityInfo.Translator mTranslator;
+
+    @UnsupportedAppUsage
+    final View.AttachInfo mAttachInfo;
+    final SystemUiVisibilityInfo mCompatibleVisibilityInfo;
+    int mDispatchedSystemUiVisibility;
+    int mDispatchedSystemBarAppearance;
+    InputQueue.Callback mInputQueueCallback;
+    InputQueue mInputQueue;
+    @UnsupportedAppUsage
+    FallbackEventHandler mFallbackEventHandler;
+    final Choreographer mChoreographer;
+    protected final ViewFrameInfo mViewFrameInfo = new ViewFrameInfo();
+    private final InputEventAssigner mInputEventAssigner = new InputEventAssigner();
+
+    // Set to true if mSurfaceControl is used for Webview Overlay
+    private boolean mIsForWebviewOverlay;
+
+    /**
+     * Update the Choreographer's FrameInfo object with the timing information for the current
+     * ViewRootImpl instance. Erase the data in the current ViewFrameInfo to prepare for the next
+     * frame.
+     * @return the updated FrameInfo object
+     */
+    protected @NonNull FrameInfo getUpdatedFrameInfo() {
+        // Since Choreographer is a thread-local singleton while we can have multiple
+        // ViewRootImpl's, populate the frame information from the current viewRootImpl before
+        // starting the draw
+        FrameInfo frameInfo = mChoreographer.mFrameInfo;
+        mViewFrameInfo.populateFrameInfo(frameInfo);
+        mViewFrameInfo.reset();
+        mInputEventAssigner.notifyFrameProcessed();
+        return frameInfo;
+    }
+
+    // used in relayout to get SurfaceControl size
+    // for BLAST adapter surface setup
+    private final Point mSurfaceSize = new Point();
+    private final Point mLastSurfaceSize = new Point();
+
+    final Rect mTempRect; // used in the transaction to not thrash the heap.
+    final Rect mVisRect; // used to retrieve visible rect of focused view.
+    private final Rect mTempBoundsRect = new Rect(); // used to set the size of the bounds surface.
+
+    // This is used to reduce the race between window focus changes being dispatched from
+    // the window manager and input events coming through the input system.
+    @GuardedBy("this")
+    boolean mWindowFocusChanged;
+    @GuardedBy("this")
+    boolean mUpcomingWindowFocus;
+    @GuardedBy("this")
+    boolean mUpcomingInTouchMode;
+
+    public boolean mTraversalScheduled;
+    int mTraversalBarrier;
+    boolean mWillDrawSoon;
+    /** Set to true while in performTraversals for detecting when die(true) is called from internal
+     * callbacks such as onMeasure, onPreDraw, onDraw and deferring doDie() until later. */
+    boolean mIsInTraversal;
+    boolean mApplyInsetsRequested;
+    boolean mLayoutRequested;
+    boolean mFirst;
+
+    @Nullable
+    int mContentCaptureEnabled = CONTENT_CAPTURE_ENABLED_NOT_CHECKED;
+    boolean mPerformContentCapture;
+
+    boolean mReportNextDraw;
+    boolean mFullRedrawNeeded;
+    boolean mNewSurfaceNeeded;
+    boolean mForceNextWindowRelayout;
+    CountDownLatch mWindowDrawCountDown;
+
+    boolean mIsDrawing;
+    int mLastSystemUiVisibility;
+    int mClientWindowLayoutFlags;
+
+    // Pool of queued input events.
+    private static final int MAX_QUEUED_INPUT_EVENT_POOL_SIZE = 10;
+    private QueuedInputEvent mQueuedInputEventPool;
+    private int mQueuedInputEventPoolSize;
+
+    /* Input event queue.
+     * Pending input events are input events waiting to be delivered to the input stages
+     * and handled by the application.
+     */
+    QueuedInputEvent mPendingInputEventHead;
+    QueuedInputEvent mPendingInputEventTail;
+    int mPendingInputEventCount;
+    boolean mProcessInputEventsScheduled;
+    boolean mUnbufferedInputDispatch;
+    @InputSourceClass
+    int mUnbufferedInputSource = SOURCE_CLASS_NONE;
+
+    String mPendingInputEventQueueLengthCounterName = "pq";
+
+    InputStage mFirstInputStage;
+    InputStage mFirstPostImeInputStage;
+    InputStage mSyntheticInputStage;
+
+    private final UnhandledKeyManager mUnhandledKeyManager = new UnhandledKeyManager();
+
+    boolean mWindowAttributesChanged = false;
+
+    // These can be accessed by any thread, must be protected with a lock.
+    // Surface can never be reassigned or cleared (use Surface.clear()).
+    @UnsupportedAppUsage
+    public final Surface mSurface = new Surface();
+    private final SurfaceControl mSurfaceControl = new SurfaceControl();
+
+    private BLASTBufferQueue mBlastBufferQueue;
+
+    /**
+     * Transaction object that can be used to synchronize child SurfaceControl changes with
+     * ViewRootImpl SurfaceControl changes by the server. The object is passed along with
+     * the SurfaceChangedCallback.
+     */
+    private final Transaction mSurfaceChangedTransaction = new Transaction();
+    /**
+     * Child container layer of {@code mSurface} with the same bounds as its parent, and cropped to
+     * the surface insets. This surface is created only if a client requests it via {@link
+     * #getBoundsLayer()}. By parenting to this bounds surface, child surfaces can ensure they do
+     * not draw into the surface inset region set by the parent window.
+     */
+    private SurfaceControl mBoundsLayer;
+    private final SurfaceSession mSurfaceSession = new SurfaceSession();
+    private final Transaction mTransaction = new Transaction();
+
+    @UnsupportedAppUsage
+    boolean mAdded;
+    boolean mAddedTouchMode;
+
+    /**
+     * It usually keeps the latest layout result from {@link IWindow#resized} or
+     * {@link IWindowSession#relayout}.
+     */
+    private final ClientWindowFrames mTmpFrames = new ClientWindowFrames();
+
+    // These are accessed by multiple threads.
+    final Rect mWinFrame; // frame given by window manager.
+
+    final Rect mPendingBackDropFrame = new Rect();
+
+    private boolean mWillMove;
+    private boolean mWillResize;
+
+    boolean mPendingAlwaysConsumeSystemBars;
+    private final InsetsState mTempInsets = new InsetsState();
+    private final InsetsSourceControl[] mTempControls = new InsetsSourceControl[SIZE];
+    final ViewTreeObserver.InternalInsetsInfo mLastGivenInsets
+            = new ViewTreeObserver.InternalInsetsInfo();
+
+    private WindowInsets mLastWindowInsets;
+
+    // Insets types hidden by legacy window flags or system UI flags.
+    private @InsetsType int mTypesHiddenByFlags = 0;
+
+    /** Last applied configuration obtained from resources. */
+    private final Configuration mLastConfigurationFromResources = new Configuration();
+    /** Last configuration reported from WM or via {@link #MSG_UPDATE_CONFIGURATION}. */
+    private final MergedConfiguration mLastReportedMergedConfiguration = new MergedConfiguration();
+    /** Configurations waiting to be applied. */
+    private final MergedConfiguration mPendingMergedConfiguration = new MergedConfiguration();
+
+    boolean mScrollMayChange;
+    @SoftInputModeFlags
+    int mSoftInputMode;
+    @UnsupportedAppUsage
+    WeakReference<View> mLastScrolledFocus;
+    int mScrollY;
+    int mCurScrollY;
+    Scroller mScroller;
+    static final Interpolator mResizeInterpolator = new AccelerateDecelerateInterpolator();
+    private ArrayList<LayoutTransition> mPendingTransitions;
+
+    final ViewConfiguration mViewConfiguration;
+
+    /* Drag/drop */
+    ClipDescription mDragDescription;
+    View mCurrentDragView;
+    volatile Object mLocalDragState;
+    final PointF mDragPoint = new PointF();
+    final PointF mLastTouchPoint = new PointF();
+    int mLastTouchSource;
+
+    private boolean mProfileRendering;
+    private Choreographer.FrameCallback mRenderProfiler;
+    private boolean mRenderProfilingEnabled;
+
+    // Variables to track frames per second, enabled via DEBUG_FPS flag
+    private long mFpsStartTime = -1;
+    private long mFpsPrevTime = -1;
+    private int mFpsNumFrames;
+
+    private int mPointerIconType = PointerIcon.TYPE_NOT_SPECIFIED;
+    private PointerIcon mCustomPointerIcon = null;
+
+    /**
+     * see {@link #playSoundEffect(int)}
+     */
+    AudioManager mAudioManager;
+
+    final AccessibilityManager mAccessibilityManager;
+
+    AccessibilityInteractionController mAccessibilityInteractionController;
+
+    final AccessibilityInteractionConnectionManager mAccessibilityInteractionConnectionManager =
+            new AccessibilityInteractionConnectionManager();
+    final HighContrastTextManager mHighContrastTextManager;
+
+    SendWindowContentChangedAccessibilityEvent mSendWindowContentChangedAccessibilityEvent;
+
+    HashSet<View> mTempHashSet;
+
+    private final int mDensity;
+    private final int mNoncompatDensity;
+
+    private boolean mInLayout = false;
+    ArrayList<View> mLayoutRequesters = new ArrayList<View>();
+    boolean mHandlingLayoutInLayoutRequest = false;
+
+    private int mViewLayoutDirectionInitial;
+
+    /** Set to true once doDie() has been called. */
+    private boolean mRemoved;
+
+    private boolean mNeedsRendererSetup;
+
+    private final InputEventCompatProcessor mInputCompatProcessor;
+
+    /**
+     * Consistency verifier for debugging purposes.
+     */
+    protected final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
+            InputEventConsistencyVerifier.isInstrumentationEnabled() ?
+                    new InputEventConsistencyVerifier(this, 0) : null;
+
+    private final InsetsController mInsetsController;
+    private final ImeFocusController mImeFocusController;
+
+    private boolean mIsSurfaceOpaque;
+
+    private final BackgroundBlurDrawable.Aggregator mBlurRegionAggregator =
+            new BackgroundBlurDrawable.Aggregator(this);
+
+    /**
+     * @return {@link ImeFocusController} for this instance.
+     */
+    @NonNull
+    public ImeFocusController getImeFocusController() {
+        return mImeFocusController;
+    }
+
+    private final GestureExclusionTracker mGestureExclusionTracker = new GestureExclusionTracker();
+
+    private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
+
+    static final class SystemUiVisibilityInfo {
+        int globalVisibility;
+        int localValue;
+        int localChanges;
+    }
+
+    /**
+     * This is only used when the UI thread is paused due to {@link #mNextDrawUseBlastSync} being
+     * set. Specifically, it's only used when calling
+     * {@link BLASTBufferQueue#setNextTransaction(Transaction)} and then merged with
+     * {@link #mSurfaceChangedTransaction}. It doesn't need to be thread safe since it's only
+     * accessed when the UI thread is paused.
+     */
+    private final SurfaceControl.Transaction mRtBLASTSyncTransaction =
+            new SurfaceControl.Transaction();
+
+    /**
+     * Keeps track of whether the WM requested to use BLAST Sync when calling relayout. When set,
+     * we pause the UI thread to ensure we don't get overlapping requests. We then send a
+     * transaction to {@link BLASTBufferQueue#setNextTransaction(Transaction)}, which is then sent
+     * back to WM to synchronize.
+     *
+     * This flag is set to false only after the synchronized transaction that contains the buffer
+     * has been sent to SurfaceFlinger.
+     */
+    private boolean mNextDrawUseBlastSync = false;
+
+    /**
+     * Wait for the blast sync transaction complete callback before drawing and queuing up more
+     * frames. This will prevent out of order buffers submissions when WM has requested to
+     * synchronize with the client.
+     */
+    private boolean mWaitForBlastSyncComplete = false;
+
+    /**
+     * Keeps track of whether a traverse was triggered while the UI thread was paused. This can
+     * occur when the client is waiting on another process to submit the transaction that
+     * contains the buffer. The UI thread needs to wait on the callback before it can submit
+     * another buffer.
+     */
+    private boolean mRequestedTraverseWhilePaused = false;
+
+    private HashSet<ScrollCaptureCallback> mRootScrollCaptureCallbacks;
+
+    private long mScrollCaptureRequestTimeout = SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS;
+
+    /**
+     * Increment this value when the surface has been replaced.
+     */
+    private int mSurfaceSequenceId = 0;
+
+    private String mTag = TAG;
+
+    public ViewRootImpl(Context context, Display display) {
+        this(context, display, WindowManagerGlobal.getWindowSession(),
+                false /* useSfChoreographer */);
+    }
+
+    public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session) {
+        this(context, display, session, false /* useSfChoreographer */);
+    }
+
+    public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
+            boolean useSfChoreographer) {
+        mContext = context;
+        mWindowSession = session;
+        mDisplay = display;
+        mBasePackageName = context.getBasePackageName();
+        mThread = Thread.currentThread();
+        mLocation = new WindowLeaked(null);
+        mLocation.fillInStackTrace();
+        mWidth = -1;
+        mHeight = -1;
+        mDirty = new Rect();
+        mTempRect = new Rect();
+        mVisRect = new Rect();
+        mWinFrame = new Rect();
+        mWindow = new W(this);
+        mLeashToken = new Binder();
+        mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
+        mViewVisibility = View.GONE;
+        mTransparentRegion = new Region();
+        mPreviousTransparentRegion = new Region();
+        mFirst = true; // true for the first time the view is added
+        mPerformContentCapture = true; // also true for the first time the view is added
+        mAdded = false;
+        mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
+                context);
+        mCompatibleVisibilityInfo = new SystemUiVisibilityInfo();
+        mAccessibilityManager = AccessibilityManager.getInstance(context);
+        mAccessibilityManager.addAccessibilityStateChangeListener(
+                mAccessibilityInteractionConnectionManager, mHandler);
+        mHighContrastTextManager = new HighContrastTextManager();
+        mAccessibilityManager.addHighTextContrastStateChangeListener(
+                mHighContrastTextManager, mHandler);
+        mViewConfiguration = ViewConfiguration.get(context);
+        mDensity = context.getResources().getDisplayMetrics().densityDpi;
+        mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
+        mFallbackEventHandler = new PhoneFallbackEventHandler(context);
+        mChoreographer = useSfChoreographer
+                ? Choreographer.getSfInstance() : Choreographer.getInstance();
+        mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
+        mInsetsController = new InsetsController(new ViewRootInsetsControllerHost(this));
+
+        String processorOverrideName = context.getResources().getString(
+                                    R.string.config_inputEventCompatProcessorOverrideClassName);
+        if (processorOverrideName.isEmpty()) {
+            // No compatibility processor override, using default.
+            mInputCompatProcessor = new InputEventCompatProcessor(context);
+        } else {
+            InputEventCompatProcessor compatProcessor = null;
+            try {
+                final Class<? extends InputEventCompatProcessor> klass =
+                        (Class<? extends InputEventCompatProcessor>) Class.forName(
+                                processorOverrideName);
+                compatProcessor = klass.getConstructor(Context.class).newInstance(context);
+            } catch (Exception e) {
+                Log.e(TAG, "Unable to create the InputEventCompatProcessor. ", e);
+            } finally {
+                mInputCompatProcessor = compatProcessor;
+            }
+        }
+
+        if (!sCompatibilityDone) {
+            sAlwaysAssignFocus = mTargetSdkVersion < Build.VERSION_CODES.P;
+
+            sCompatibilityDone = true;
+        }
+
+        loadSystemProperties();
+        mImeFocusController = new ImeFocusController(this);
+        AudioManager audioManager = mContext.getSystemService(AudioManager.class);
+        mFastScrollSoundEffectsEnabled = audioManager.areNavigationRepeatSoundEffectsEnabled();
+
+        mScrollCaptureRequestTimeout = SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS;
+    }
+
+    public static void addFirstDrawHandler(Runnable callback) {
+        synchronized (sFirstDrawHandlers) {
+            if (!sFirstDrawComplete) {
+                sFirstDrawHandlers.add(callback);
+            }
+        }
+    }
+
+    /** Add static config callback to be notified about global config changes. */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public static void addConfigCallback(ConfigChangedCallback callback) {
+        synchronized (sConfigCallbacks) {
+            sConfigCallbacks.add(callback);
+        }
+    }
+
+    /** Add activity config callback to be notified about override config changes. */
+    public void setActivityConfigCallback(ActivityConfigCallback callback) {
+        mActivityConfigCallback = callback;
+    }
+
+    public void setOnContentApplyWindowInsetsListener(OnContentApplyWindowInsetsListener listener) {
+        mAttachInfo.mContentOnApplyWindowInsetsListener = listener;
+
+        // System windows will be fitted on first traversal, so no reason to request additional
+        // (possibly getting executed after the first traversal).
+        if (!mFirst) {
+            requestFitSystemWindows();
+        }
+    }
+
+    public void addWindowCallbacks(WindowCallbacks callback) {
+        synchronized (mWindowCallbacks) {
+            mWindowCallbacks.add(callback);
+        }
+    }
+
+    public void removeWindowCallbacks(WindowCallbacks callback) {
+        synchronized (mWindowCallbacks) {
+            mWindowCallbacks.remove(callback);
+        }
+    }
+
+    public void reportDrawFinish() {
+        if (mWindowDrawCountDown != null) {
+            mWindowDrawCountDown.countDown();
+        }
+    }
+
+    // FIXME for perf testing only
+    private boolean mProfile = false;
+
+    /**
+     * Call this to profile the next traversal call.
+     * FIXME for perf testing only. Remove eventually
+     */
+    public void profile() {
+        mProfile = true;
+    }
+
+    /**
+     * Indicates whether we are in touch mode. Calling this method triggers an IPC
+     * call and should be avoided whenever possible.
+     *
+     * @return True, if the device is in touch mode, false otherwise.
+     *
+     * @hide
+     */
+    static boolean isInTouchMode() {
+        IWindowSession windowSession = WindowManagerGlobal.peekWindowSession();
+        if (windowSession != null) {
+            try {
+                return windowSession.getInTouchMode();
+            } catch (RemoteException e) {
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Notifies us that our child has been rebuilt, following
+     * a window preservation operation. In these cases we
+     * keep the same DecorView, but the activity controlling it
+     * is a different instance, and we need to update our
+     * callbacks.
+     *
+     * @hide
+     */
+    public void notifyChildRebuilt() {
+        if (mView instanceof RootViewSurfaceTaker) {
+            if (mSurfaceHolderCallback != null) {
+                mSurfaceHolder.removeCallback(mSurfaceHolderCallback);
+            }
+
+            mSurfaceHolderCallback =
+                ((RootViewSurfaceTaker)mView).willYouTakeTheSurface();
+
+            if (mSurfaceHolderCallback != null) {
+                mSurfaceHolder = new TakenSurfaceHolder();
+                mSurfaceHolder.setFormat(PixelFormat.UNKNOWN);
+                mSurfaceHolder.addCallback(mSurfaceHolderCallback);
+            } else {
+                mSurfaceHolder = null;
+            }
+
+            mInputQueueCallback =
+                ((RootViewSurfaceTaker)mView).willYouTakeTheInputQueue();
+            if (mInputQueueCallback != null) {
+                mInputQueueCallback.onInputQueueCreated(mInputQueue);
+            }
+        }
+    }
+
+    // TODO(b/161810301): Make this private after window layout is moved to the client side.
+    public static void computeWindowBounds(WindowManager.LayoutParams attrs, InsetsState state,
+            Rect displayFrame, Rect outBounds) {
+        final @InsetsType int typesToFit = attrs.getFitInsetsTypes();
+        final @InsetsSide int sidesToFit = attrs.getFitInsetsSides();
+        final ArraySet<Integer> types = InsetsState.toInternalType(typesToFit);
+        final Rect df = displayFrame;
+        Insets insets = Insets.of(0, 0, 0, 0);
+        for (int i = types.size() - 1; i >= 0; i--) {
+            final InsetsSource source = state.peekSource(types.valueAt(i));
+            if (source == null) {
+                continue;
+            }
+            insets = Insets.max(insets, source.calculateInsets(
+                    df, attrs.isFitInsetsIgnoringVisibility()));
+        }
+        final int left = (sidesToFit & WindowInsets.Side.LEFT) != 0 ? insets.left : 0;
+        final int top = (sidesToFit & WindowInsets.Side.TOP) != 0 ? insets.top : 0;
+        final int right = (sidesToFit & WindowInsets.Side.RIGHT) != 0 ? insets.right : 0;
+        final int bottom = (sidesToFit & WindowInsets.Side.BOTTOM) != 0 ? insets.bottom : 0;
+        outBounds.set(df.left + left, df.top + top, df.right - right, df.bottom - bottom);
+    }
+
+    private Configuration getConfiguration() {
+        return mContext.getResources().getConfiguration();
+    }
+
+    /**
+     * We have one child
+     */
+    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
+        setView(view, attrs, panelParentView, UserHandle.myUserId());
+    }
+
+    /**
+     * We have one child
+     */
+    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
+            int userId) {
+        synchronized (this) {
+            if (mView == null) {
+                mView = view;
+
+                mAttachInfo.mDisplayState = mDisplay.getState();
+                mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
+
+                mViewLayoutDirectionInitial = mView.getRawLayoutDirection();
+                mFallbackEventHandler.setView(view);
+                mWindowAttributes.copyFrom(attrs);
+                if (mWindowAttributes.packageName == null) {
+                    mWindowAttributes.packageName = mBasePackageName;
+                }
+                mWindowAttributes.privateFlags |=
+                        WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
+
+                attrs = mWindowAttributes;
+                setTag();
+
+                if (DEBUG_KEEP_SCREEN_ON && (mClientWindowLayoutFlags
+                        & WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0
+                        && (attrs.flags&WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) == 0) {
+                    Slog.d(mTag, "setView: FLAG_KEEP_SCREEN_ON changed from true to false!");
+                }
+                // Keep track of the actual window flags supplied by the client.
+                mClientWindowLayoutFlags = attrs.flags;
+
+                setAccessibilityFocus(null, null);
+
+                if (view instanceof RootViewSurfaceTaker) {
+                    mSurfaceHolderCallback =
+                            ((RootViewSurfaceTaker)view).willYouTakeTheSurface();
+                    if (mSurfaceHolderCallback != null) {
+                        mSurfaceHolder = new TakenSurfaceHolder();
+                        mSurfaceHolder.setFormat(PixelFormat.UNKNOWN);
+                        mSurfaceHolder.addCallback(mSurfaceHolderCallback);
+                    }
+                }
+
+                // Compute surface insets required to draw at specified Z value.
+                // TODO: Use real shadow insets for a constant max Z.
+                if (!attrs.hasManualSurfaceInsets) {
+                    attrs.setSurfaceInsets(view, false /*manual*/, true /*preservePrevious*/);
+                }
+
+                CompatibilityInfo compatibilityInfo =
+                        mDisplay.getDisplayAdjustments().getCompatibilityInfo();
+                mTranslator = compatibilityInfo.getTranslator();
+
+                // If the application owns the surface, don't enable hardware acceleration
+                if (mSurfaceHolder == null) {
+                    // While this is supposed to enable only, it can effectively disable
+                    // the acceleration too.
+                    enableHardwareAcceleration(attrs);
+                    final boolean useMTRenderer = MT_RENDERER_AVAILABLE
+                            && mAttachInfo.mThreadedRenderer != null;
+                    if (mUseMTRenderer != useMTRenderer) {
+                        // Shouldn't be resizing, as it's done only in window setup,
+                        // but end just in case.
+                        endDragResizing();
+                        mUseMTRenderer = useMTRenderer;
+                    }
+                }
+
+                boolean restore = false;
+                if (mTranslator != null) {
+                    mSurface.setCompatibilityTranslator(mTranslator);
+                    restore = true;
+                    attrs.backup();
+                    mTranslator.translateWindowLayout(attrs);
+                }
+                if (DEBUG_LAYOUT) Log.d(mTag, "WindowLayout in setView:" + attrs);
+
+                if (!compatibilityInfo.supportsScreen()) {
+                    attrs.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
+                    mLastInCompatMode = true;
+                }
+
+                mSoftInputMode = attrs.softInputMode;
+                mWindowAttributesChanged = true;
+                mAttachInfo.mRootView = view;
+                mAttachInfo.mScalingRequired = mTranslator != null;
+                mAttachInfo.mApplicationScale =
+                        mTranslator == null ? 1.0f : mTranslator.applicationScale;
+                if (panelParentView != null) {
+                    mAttachInfo.mPanelParentWindowToken
+                            = panelParentView.getApplicationWindowToken();
+                }
+                mAdded = true;
+                int res; /* = WindowManagerImpl.ADD_OKAY; */
+
+                // Schedule the first layout -before- adding to the window
+                // manager, to make sure we do the relayout before receiving
+                // any other events from the system.
+                requestLayout();
+                InputChannel inputChannel = null;
+                if ((mWindowAttributes.inputFeatures
+                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
+                    inputChannel = new InputChannel();
+                }
+                mForceDecorViewVisibility = (mWindowAttributes.privateFlags
+                        & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
+
+                if (mView instanceof RootViewSurfaceTaker) {
+                    PendingInsetsController pendingInsetsController =
+                            ((RootViewSurfaceTaker) mView).providePendingInsetsController();
+                    if (pendingInsetsController != null) {
+                        pendingInsetsController.replayAndAttach(mInsetsController);
+                    }
+                }
+
+                try {
+                    mOrigWindowType = mWindowAttributes.type;
+                    mAttachInfo.mRecomputeGlobalAttributes = true;
+                    collectViewAttributes();
+                    adjustLayoutParamsForCompatibility(mWindowAttributes);
+                    controlInsetsForCompatibility(mWindowAttributes);
+                    res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
+                            getHostVisibility(), mDisplay.getDisplayId(), userId,
+                            mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,
+                            mTempControls);
+                    if (mTranslator != null) {
+                        mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
+                        mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
+                    }
+                } catch (RemoteException e) {
+                    mAdded = false;
+                    mView = null;
+                    mAttachInfo.mRootView = null;
+                    mFallbackEventHandler.setView(null);
+                    unscheduleTraversals();
+                    setAccessibilityFocus(null, null);
+                    throw new RuntimeException("Adding window failed", e);
+                } finally {
+                    if (restore) {
+                        attrs.restore();
+                    }
+                }
+
+                mAttachInfo.mAlwaysConsumeSystemBars =
+                        (res & WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS) != 0;
+                mPendingAlwaysConsumeSystemBars = mAttachInfo.mAlwaysConsumeSystemBars;
+                mInsetsController.onStateChanged(mTempInsets);
+                mInsetsController.onControlsChanged(mTempControls);
+                computeWindowBounds(mWindowAttributes, mInsetsController.getState(),
+                        getConfiguration().windowConfiguration.getBounds(), mTmpFrames.frame);
+                setFrame(mTmpFrames.frame);
+                if (DEBUG_LAYOUT) Log.v(mTag, "Added window " + mWindow);
+                if (res < WindowManagerGlobal.ADD_OKAY) {
+                    mAttachInfo.mRootView = null;
+                    mAdded = false;
+                    mFallbackEventHandler.setView(null);
+                    unscheduleTraversals();
+                    setAccessibilityFocus(null, null);
+                    switch (res) {
+                        case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
+                        case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
+                            throw new WindowManager.BadTokenException(
+                                    "Unable to add window -- token " + attrs.token
+                                    + " is not valid; is your activity running?");
+                        case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
+                            throw new WindowManager.BadTokenException(
+                                    "Unable to add window -- token " + attrs.token
+                                    + " is not for an application");
+                        case WindowManagerGlobal.ADD_APP_EXITING:
+                            throw new WindowManager.BadTokenException(
+                                    "Unable to add window -- app for token " + attrs.token
+                                    + " is exiting");
+                        case WindowManagerGlobal.ADD_DUPLICATE_ADD:
+                            throw new WindowManager.BadTokenException(
+                                    "Unable to add window -- window " + mWindow
+                                    + " has already been added");
+                        case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:
+                            // Silently ignore -- we would have just removed it
+                            // right away, anyway.
+                            return;
+                        case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:
+                            throw new WindowManager.BadTokenException("Unable to add window "
+                                    + mWindow + " -- another window of type "
+                                    + mWindowAttributes.type + " already exists");
+                        case WindowManagerGlobal.ADD_PERMISSION_DENIED:
+                            throw new WindowManager.BadTokenException("Unable to add window "
+                                    + mWindow + " -- permission denied for window type "
+                                    + mWindowAttributes.type);
+                        case WindowManagerGlobal.ADD_INVALID_DISPLAY:
+                            throw new WindowManager.InvalidDisplayException("Unable to add window "
+                                    + mWindow + " -- the specified display can not be found");
+                        case WindowManagerGlobal.ADD_INVALID_TYPE:
+                            throw new WindowManager.InvalidDisplayException("Unable to add window "
+                                    + mWindow + " -- the specified window type "
+                                    + mWindowAttributes.type + " is not valid");
+                        case WindowManagerGlobal.ADD_INVALID_USER:
+                            throw new WindowManager.BadTokenException("Unable to add Window "
+                                    + mWindow + " -- requested userId is not valid");
+                    }
+                    throw new RuntimeException(
+                            "Unable to add window -- unknown error code " + res);
+                }
+
+                if ((res & WindowManagerGlobal.ADD_FLAG_USE_BLAST) != 0) {
+                    mUseBLASTAdapter = true;
+                }
+
+                if (view instanceof RootViewSurfaceTaker) {
+                    mInputQueueCallback =
+                        ((RootViewSurfaceTaker)view).willYouTakeTheInputQueue();
+                }
+                if (inputChannel != null) {
+                    if (mInputQueueCallback != null) {
+                        mInputQueue = new InputQueue();
+                        mInputQueueCallback.onInputQueueCreated(mInputQueue);
+                    }
+                    mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
+                            Looper.myLooper());
+
+                    if (ENABLE_INPUT_LATENCY_TRACKING && mAttachInfo.mThreadedRenderer != null) {
+                        InputMetricsListener listener = new InputMetricsListener();
+                        mHardwareRendererObserver = new HardwareRendererObserver(
+                                listener, listener.data, mHandler, true /*waitForPresentTime*/);
+                        mAttachInfo.mThreadedRenderer.addObserver(mHardwareRendererObserver);
+                    }
+                }
+
+                view.assignParent(this);
+                mAddedTouchMode = (res & WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE) != 0;
+                mAppVisible = (res & WindowManagerGlobal.ADD_FLAG_APP_VISIBLE) != 0;
+
+                if (mAccessibilityManager.isEnabled()) {
+                    mAccessibilityInteractionConnectionManager.ensureConnection();
+                }
+
+                if (view.getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+                    view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+                }
+
+                // Set up the input pipeline.
+                CharSequence counterSuffix = attrs.getTitle();
+                mSyntheticInputStage = new SyntheticInputStage();
+                InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
+                InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
+                        "aq:native-post-ime:" + counterSuffix);
+                InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
+                InputStage imeStage = new ImeInputStage(earlyPostImeStage,
+                        "aq:ime:" + counterSuffix);
+                InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
+                InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
+                        "aq:native-pre-ime:" + counterSuffix);
+
+                mFirstInputStage = nativePreImeStage;
+                mFirstPostImeInputStage = earlyPostImeStage;
+                mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
+            }
+        }
+    }
+
+    private void setTag() {
+        final String[] split = mWindowAttributes.getTitle().toString().split("\\.");
+        if (split.length > 0) {
+            mTag = TAG + "[" + split[split.length - 1] + "]";
+        }
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public int getWindowFlags() {
+        return mWindowAttributes.flags;
+    }
+
+    public int getDisplayId() {
+        return mDisplay.getDisplayId();
+    }
+
+    public CharSequence getTitle() {
+        return mWindowAttributes.getTitle();
+    }
+
+    /**
+     * @return the width of the root view. Note that this will return {@code -1} until the first
+     *         layout traversal, when the width is set.
+     *
+     * @hide
+     */
+    public int getWidth() {
+        return mWidth;
+    }
+
+    /**
+     * @return the height of the root view. Note that this will return {@code -1} until the first
+     *         layout traversal, when the height is set.
+     *
+     * @hide
+     */
+    public int getHeight() {
+        return mHeight;
+    }
+
+    /**
+     * Destroys hardware rendering resources for this ViewRootImpl
+     *
+     * May be called on any thread
+     */
+    @AnyThread
+    void destroyHardwareResources() {
+        final ThreadedRenderer renderer = mAttachInfo.mThreadedRenderer;
+        if (renderer != null) {
+            // This is called by WindowManagerGlobal which may or may not be on the right thread
+            if (Looper.myLooper() != mAttachInfo.mHandler.getLooper()) {
+                mAttachInfo.mHandler.postAtFrontOfQueue(this::destroyHardwareResources);
+                return;
+            }
+            renderer.destroyHardwareResources(mView);
+            renderer.destroy();
+        }
+    }
+
+    /**
+     * Does nothing; Here only because of @UnsupportedAppUsage
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R,
+            publicAlternatives = "Use {@link android.webkit.WebView} instead")
+    public void detachFunctor(long functor) { }
+
+    /**
+     * Does nothing; Here only because of @UnsupportedAppUsage
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R,
+            publicAlternatives = "Use {@link android.webkit.WebView} instead")
+    public static void invokeFunctor(long functor, boolean waitForCompletion) { }
+
+    /**
+     * @param animator animator to register with the hardware renderer
+     */
+    public void registerAnimatingRenderNode(RenderNode animator) {
+        if (mAttachInfo.mThreadedRenderer != null) {
+            mAttachInfo.mThreadedRenderer.registerAnimatingRenderNode(animator);
+        } else {
+            if (mAttachInfo.mPendingAnimatingRenderNodes == null) {
+                mAttachInfo.mPendingAnimatingRenderNodes = new ArrayList<RenderNode>();
+            }
+            mAttachInfo.mPendingAnimatingRenderNodes.add(animator);
+        }
+    }
+
+    /**
+     * @param animator animator to register with the hardware renderer
+     */
+    public void registerVectorDrawableAnimator(NativeVectorDrawableAnimator animator) {
+        if (mAttachInfo.mThreadedRenderer != null) {
+            mAttachInfo.mThreadedRenderer.registerVectorDrawableAnimator(animator);
+        }
+    }
+
+    /**
+     * Registers a callback to be executed when the next frame is being drawn on RenderThread. This
+     * callback will be executed on a RenderThread worker thread, and only used for the next frame
+     * and thus it will only fire once.
+     *
+     * @param callback The callback to register.
+     */
+    public void registerRtFrameCallback(@NonNull FrameDrawingCallback callback) {
+        if (mAttachInfo.mThreadedRenderer != null) {
+            mAttachInfo.mThreadedRenderer.registerRtFrameCallback(frame -> {
+                try {
+                    callback.onFrameDraw(frame);
+                } catch (Exception e) {
+                    Log.e(TAG, "Exception while executing onFrameDraw", e);
+                }
+            });
+        }
+    }
+
+    /**
+     * Register a callback to be executed when Webview overlay needs to merge a transaction.
+     * This callback will be executed on RenderThread worker thread, and released inside native code
+     * when CanvasContext is destroyed.
+     */
+    private void addASurfaceTransactionCallback() {
+        HardwareRenderer.ASurfaceTransactionCallback callback = (nativeTransactionObj,
+                                                                 nativeSurfaceControlObj,
+                                                                 frameNr) -> {
+            if (mBlastBufferQueue == null) {
+                return false;
+            } else {
+                mBlastBufferQueue.mergeWithNextTransaction(nativeTransactionObj, frameNr);
+                return true;
+            }
+        };
+        mAttachInfo.mThreadedRenderer.setASurfaceTransactionCallback(callback);
+    }
+
+    /**
+     * Register a callback to be executed when Webview overlay needs a surface control.
+     * This callback will be executed on RenderThread worker thread, and released inside native code
+     * when CanvasContext is destroyed.
+     */
+    private void addPrepareSurfaceControlForWebviewCallback() {
+        HardwareRenderer.PrepareSurfaceControlForWebviewCallback callback = () -> {
+            // make mSurfaceControl transparent, so child surface controls are visible
+            if (mIsForWebviewOverlay) return;
+            synchronized (ViewRootImpl.this) {
+                mIsForWebviewOverlay = true;
+            }
+            mTransaction.setOpaque(mSurfaceControl, false).apply();
+        };
+        mAttachInfo.mThreadedRenderer.setPrepareSurfaceControlForWebviewCallback(callback);
+    }
+
+    @UnsupportedAppUsage
+    private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) {
+        mAttachInfo.mHardwareAccelerated = false;
+        mAttachInfo.mHardwareAccelerationRequested = false;
+
+        // Don't enable hardware acceleration when the application is in compatibility mode
+        if (mTranslator != null) return;
+
+        // Try to enable hardware acceleration if requested
+        final boolean hardwareAccelerated =
+                (attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0;
+
+        if (hardwareAccelerated) {
+            // Persistent processes (including the system) should not do
+            // accelerated rendering on low-end devices.  In that case,
+            // sRendererDisabled will be set.  In addition, the system process
+            // itself should never do accelerated rendering.  In that case, both
+            // sRendererDisabled and sSystemRendererDisabled are set.  When
+            // sSystemRendererDisabled is set, PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED
+            // can be used by code on the system process to escape that and enable
+            // HW accelerated drawing.  (This is basically for the lock screen.)
+
+            final boolean forceHwAccelerated = (attrs.privateFlags &
+                    WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED) != 0;
+
+            if (ThreadedRenderer.sRendererEnabled || forceHwAccelerated) {
+                if (mAttachInfo.mThreadedRenderer != null) {
+                    mAttachInfo.mThreadedRenderer.destroy();
+                }
+
+                final Rect insets = attrs.surfaceInsets;
+                final boolean hasSurfaceInsets = insets.left != 0 || insets.right != 0
+                        || insets.top != 0 || insets.bottom != 0;
+                final boolean translucent = attrs.format != PixelFormat.OPAQUE || hasSurfaceInsets;
+                mAttachInfo.mThreadedRenderer = ThreadedRenderer.create(mContext, translucent,
+                        attrs.getTitle().toString());
+                updateColorModeIfNeeded(attrs.getColorMode());
+                updateForceDarkMode();
+                if (mAttachInfo.mThreadedRenderer != null) {
+                    mAttachInfo.mHardwareAccelerated =
+                            mAttachInfo.mHardwareAccelerationRequested = true;
+                    if (mHardwareRendererObserver != null) {
+                        mAttachInfo.mThreadedRenderer.addObserver(mHardwareRendererObserver);
+                    }
+                    if (HardwareRenderer.isWebViewOverlaysEnabled()) {
+                        addPrepareSurfaceControlForWebviewCallback();
+                        addASurfaceTransactionCallback();
+                    }
+                    mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl);
+                }
+            }
+        }
+    }
+
+    private int getNightMode() {
+        return getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
+    }
+
+    private void updateForceDarkMode() {
+        if (mAttachInfo.mThreadedRenderer == null) return;
+
+        boolean useAutoDark = getNightMode() == Configuration.UI_MODE_NIGHT_YES;
+
+        if (useAutoDark) {
+            boolean forceDarkAllowedDefault =
+                    SystemProperties.getBoolean(ThreadedRenderer.DEBUG_FORCE_DARK, false);
+            TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme);
+            useAutoDark = a.getBoolean(R.styleable.Theme_isLightTheme, true)
+                    && a.getBoolean(R.styleable.Theme_forceDarkAllowed, forceDarkAllowedDefault);
+            a.recycle();
+        }
+
+        if (mAttachInfo.mThreadedRenderer.setForceDark(useAutoDark)) {
+            // TODO: Don't require regenerating all display lists to apply this setting
+            invalidateWorld(mView);
+        }
+    }
+
+    @UnsupportedAppUsage
+    public View getView() {
+        return mView;
+    }
+
+    final WindowLeaked getLocation() {
+        return mLocation;
+    }
+
+    @VisibleForTesting
+    public void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
+        synchronized (this) {
+            final int oldInsetLeft = mWindowAttributes.surfaceInsets.left;
+            final int oldInsetTop = mWindowAttributes.surfaceInsets.top;
+            final int oldInsetRight = mWindowAttributes.surfaceInsets.right;
+            final int oldInsetBottom = mWindowAttributes.surfaceInsets.bottom;
+            final int oldSoftInputMode = mWindowAttributes.softInputMode;
+            final boolean oldHasManualSurfaceInsets = mWindowAttributes.hasManualSurfaceInsets;
+
+            if (DEBUG_KEEP_SCREEN_ON && (mClientWindowLayoutFlags
+                    & WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0
+                    && (attrs.flags&WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) == 0) {
+                Slog.d(mTag, "setLayoutParams: FLAG_KEEP_SCREEN_ON from true to false!");
+            }
+
+            // Keep track of the actual window flags supplied by the client.
+            mClientWindowLayoutFlags = attrs.flags;
+
+            // Preserve compatible window flag if exists.
+            final int compatibleWindowFlag = mWindowAttributes.privateFlags
+                    & WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
+
+            // Preserve system UI visibility.
+            final int systemUiVisibility = mWindowAttributes.systemUiVisibility;
+            final int subtreeSystemUiVisibility = mWindowAttributes.subtreeSystemUiVisibility;
+
+            // Preserve appearance and behavior.
+            final int appearance = mWindowAttributes.insetsFlags.appearance;
+            final int behavior = mWindowAttributes.insetsFlags.behavior;
+            final int appearanceAndBehaviorPrivateFlags = mWindowAttributes.privateFlags
+                    & (PRIVATE_FLAG_APPEARANCE_CONTROLLED | PRIVATE_FLAG_BEHAVIOR_CONTROLLED);
+
+            final int changes = mWindowAttributes.copyFrom(attrs);
+            if ((changes & WindowManager.LayoutParams.TRANSLUCENT_FLAGS_CHANGED) != 0) {
+                // Recompute system ui visibility.
+                mAttachInfo.mRecomputeGlobalAttributes = true;
+            }
+            if ((changes & WindowManager.LayoutParams.LAYOUT_CHANGED) != 0) {
+                // Request to update light center.
+                mAttachInfo.mNeedsUpdateLightCenter = true;
+            }
+            if (mWindowAttributes.packageName == null) {
+                mWindowAttributes.packageName = mBasePackageName;
+            }
+
+            // Restore preserved flags.
+            mWindowAttributes.systemUiVisibility = systemUiVisibility;
+            mWindowAttributes.subtreeSystemUiVisibility = subtreeSystemUiVisibility;
+            mWindowAttributes.insetsFlags.appearance = appearance;
+            mWindowAttributes.insetsFlags.behavior = behavior;
+            mWindowAttributes.privateFlags |= compatibleWindowFlag
+                    | appearanceAndBehaviorPrivateFlags
+                    | WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
+
+            if (mWindowAttributes.preservePreviousSurfaceInsets) {
+                // Restore old surface insets.
+                mWindowAttributes.surfaceInsets.set(
+                        oldInsetLeft, oldInsetTop, oldInsetRight, oldInsetBottom);
+                mWindowAttributes.hasManualSurfaceInsets = oldHasManualSurfaceInsets;
+            } else if (mWindowAttributes.surfaceInsets.left != oldInsetLeft
+                    || mWindowAttributes.surfaceInsets.top != oldInsetTop
+                    || mWindowAttributes.surfaceInsets.right != oldInsetRight
+                    || mWindowAttributes.surfaceInsets.bottom != oldInsetBottom) {
+                mNeedsRendererSetup = true;
+            }
+
+            applyKeepScreenOnFlag(mWindowAttributes);
+
+            if (newView) {
+                mSoftInputMode = attrs.softInputMode;
+                requestLayout();
+            }
+
+            // Don't lose the mode we last auto-computed.
+            if ((attrs.softInputMode & SOFT_INPUT_MASK_ADJUST)
+                    == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
+                mWindowAttributes.softInputMode = (mWindowAttributes.softInputMode
+                        & ~SOFT_INPUT_MASK_ADJUST) | (oldSoftInputMode & SOFT_INPUT_MASK_ADJUST);
+            }
+
+            if (mWindowAttributes.softInputMode != oldSoftInputMode) {
+                requestFitSystemWindows();
+            }
+
+            mWindowAttributesChanged = true;
+            scheduleTraversals();
+        }
+    }
+
+    void handleAppVisibility(boolean visible) {
+        if (mAppVisible != visible) {
+            mAppVisible = visible;
+            mAppVisibilityChanged = true;
+            scheduleTraversals();
+            if (!mAppVisible) {
+                WindowManagerGlobal.trimForeground();
+            }
+        }
+    }
+
+    void handleGetNewSurface() {
+        mNewSurfaceNeeded = true;
+        mFullRedrawNeeded = true;
+        scheduleTraversals();
+    }
+
+    /** Handles messages {@link #MSG_RESIZED} and {@link #MSG_RESIZED_REPORT}. */
+    private void handleResized(int msg, SomeArgs args) {
+        if (!mAdded) {
+            return;
+        }
+
+        final ClientWindowFrames frames = (ClientWindowFrames) args.arg1;
+        final MergedConfiguration mergedConfiguration = (MergedConfiguration) args.arg2;
+        final boolean forceNextWindowRelayout = args.argi1 != 0;
+        final int displayId = args.argi3;
+        final Rect backdropFrame = frames.backdropFrame;
+
+        final boolean frameChanged = !mWinFrame.equals(frames.frame);
+        final boolean backdropFrameChanged = !mPendingBackDropFrame.equals(backdropFrame);
+        final boolean configChanged = !mLastReportedMergedConfiguration.equals(mergedConfiguration);
+        final boolean displayChanged = mDisplay.getDisplayId() != displayId;
+        if (msg == MSG_RESIZED && !frameChanged && !backdropFrameChanged && !configChanged
+                && !displayChanged && !forceNextWindowRelayout) {
+            return;
+        }
+
+        if (configChanged) {
+            // If configuration changed - notify about that and, maybe, about move to display.
+            performConfigurationChange(mergedConfiguration, false /* force */,
+                    displayChanged ? displayId : INVALID_DISPLAY /* same display */);
+        } else if (displayChanged) {
+            // Moved to display without config change - report last applied one.
+            onMovedToDisplay(displayId, mLastConfigurationFromResources);
+        }
+
+        setFrame(frames.frame);
+        mTmpFrames.displayFrame.set(frames.displayFrame);
+        mPendingBackDropFrame.set(backdropFrame);
+        mForceNextWindowRelayout = forceNextWindowRelayout;
+        mPendingAlwaysConsumeSystemBars = args.argi2 != 0;
+
+        if (msg == MSG_RESIZED_REPORT && !mNextDrawUseBlastSync) {
+            reportNextDraw();
+        }
+
+        if (mView != null && (frameChanged || configChanged)) {
+            forceLayout(mView);
+        }
+        requestLayout();
+    }
+
+    private final DisplayListener mDisplayListener = new DisplayListener() {
+        @Override
+        public void onDisplayChanged(int displayId) {
+            if (mView != null && mDisplay.getDisplayId() == displayId) {
+                final int oldDisplayState = mAttachInfo.mDisplayState;
+                final int newDisplayState = mDisplay.getState();
+                if (oldDisplayState != newDisplayState) {
+                    mAttachInfo.mDisplayState = newDisplayState;
+                    pokeDrawLockIfNeeded();
+                    if (oldDisplayState != Display.STATE_UNKNOWN) {
+                        final int oldScreenState = toViewScreenState(oldDisplayState);
+                        final int newScreenState = toViewScreenState(newDisplayState);
+                        if (oldScreenState != newScreenState) {
+                            mView.dispatchScreenStateChanged(newScreenState);
+                        }
+                        if (oldDisplayState == Display.STATE_OFF) {
+                            // Draw was suppressed so we need to for it to happen here.
+                            mFullRedrawNeeded = true;
+                            scheduleTraversals();
+                        }
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void onDisplayRemoved(int displayId) {
+        }
+
+        @Override
+        public void onDisplayAdded(int displayId) {
+        }
+
+        private int toViewScreenState(int displayState) {
+            return displayState == Display.STATE_OFF ?
+                    View.SCREEN_STATE_OFF : View.SCREEN_STATE_ON;
+        }
+    };
+
+    /**
+     * Notify about move to a different display.
+     * @param displayId The id of the display where this view root is moved to.
+     * @param config Configuration of the resources on new display after move.
+     *
+     * @hide
+     */
+    public void onMovedToDisplay(int displayId, Configuration config) {
+        if (mDisplay.getDisplayId() == displayId) {
+            return;
+        }
+
+        // Get new instance of display based on current display adjustments. It may be updated later
+        // if moving between the displays also involved a configuration change.
+        updateInternalDisplay(displayId, mView.getResources());
+        mImeFocusController.onMovedToDisplay();
+        mAttachInfo.mDisplayState = mDisplay.getState();
+        // Internal state updated, now notify the view hierarchy.
+        mView.dispatchMovedToDisplay(mDisplay, config);
+    }
+
+    /**
+     * Updates {@link #mDisplay} to the display object corresponding to {@param displayId}.
+     * Uses DEFAULT_DISPLAY if there isn't a display object in the system corresponding
+     * to {@param displayId}.
+     */
+    private void updateInternalDisplay(int displayId, Resources resources) {
+        final Display preferredDisplay =
+                ResourcesManager.getInstance().getAdjustedDisplay(displayId, resources);
+        if (preferredDisplay == null) {
+            // Fallback to use default display.
+            Slog.w(TAG, "Cannot get desired display with Id: " + displayId);
+            mDisplay = ResourcesManager.getInstance()
+                    .getAdjustedDisplay(DEFAULT_DISPLAY, resources);
+        } else {
+            mDisplay = preferredDisplay;
+        }
+        mContext.updateDisplay(mDisplay.getDisplayId());
+    }
+
+    void pokeDrawLockIfNeeded() {
+        final int displayState = mAttachInfo.mDisplayState;
+        if (mView != null && mAdded && mTraversalScheduled
+                && (displayState == Display.STATE_DOZE
+                        || displayState == Display.STATE_DOZE_SUSPEND)) {
+            try {
+                mWindowSession.pokeDrawLock(mWindow);
+            } catch (RemoteException ex) {
+                // System server died, oh well.
+            }
+        }
+    }
+
+    @Override
+    public void requestFitSystemWindows() {
+        checkThread();
+        mApplyInsetsRequested = true;
+        scheduleTraversals();
+    }
+
+    void notifyInsetsChanged() {
+        mApplyInsetsRequested = true;
+        if (mWillMove || mWillResize) {
+            // The window frame will be changed soon. The following logic will be executed then.
+            return;
+        }
+        requestLayout();
+
+        // See comment for View.sForceLayoutWhenInsetsChanged
+        if (View.sForceLayoutWhenInsetsChanged && mView != null
+                && (mWindowAttributes.softInputMode & SOFT_INPUT_MASK_ADJUST)
+                        == SOFT_INPUT_ADJUST_RESIZE) {
+            forceLayout(mView);
+        }
+
+        // If this changes during traversal, no need to schedule another one as it will dispatch it
+        // during the current traversal.
+        if (!mIsInTraversal) {
+            scheduleTraversals();
+        }
+    }
+
+    @Override
+    public void requestLayout() {
+        if (!mHandlingLayoutInLayoutRequest) {
+            checkThread();
+            mLayoutRequested = true;
+            scheduleTraversals();
+        }
+    }
+
+    @Override
+    public boolean isLayoutRequested() {
+        return mLayoutRequested;
+    }
+
+    @Override
+    public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) {
+        // TODO: Re-enable after camera is fixed or consider targetSdk checking this
+        // checkThread();
+        if ((descendant.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
+            mIsAnimating = true;
+        }
+        invalidate();
+    }
+
+    @UnsupportedAppUsage
+    void invalidate() {
+        mDirty.set(0, 0, mWidth, mHeight);
+        if (!mWillDrawSoon) {
+            scheduleTraversals();
+        }
+    }
+
+    void invalidateWorld(View view) {
+        view.invalidate();
+        if (view instanceof ViewGroup) {
+            ViewGroup parent = (ViewGroup) view;
+            for (int i = 0; i < parent.getChildCount(); i++) {
+                invalidateWorld(parent.getChildAt(i));
+            }
+        }
+    }
+
+    @Override
+    public void invalidateChild(View child, Rect dirty) {
+        invalidateChildInParent(null, dirty);
+    }
+
+    @Override
+    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
+        checkThread();
+        if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);
+
+        if (dirty == null) {
+            invalidate();
+            return null;
+        } else if (dirty.isEmpty() && !mIsAnimating) {
+            return null;
+        }
+
+        if (mCurScrollY != 0 || mTranslator != null) {
+            mTempRect.set(dirty);
+            dirty = mTempRect;
+            if (mCurScrollY != 0) {
+                dirty.offset(0, -mCurScrollY);
+            }
+            if (mTranslator != null) {
+                mTranslator.translateRectInAppWindowToScreen(dirty);
+            }
+            if (mAttachInfo.mScalingRequired) {
+                dirty.inset(-1, -1);
+            }
+        }
+
+        invalidateRectOnScreen(dirty);
+
+        return null;
+    }
+
+    private void invalidateRectOnScreen(Rect dirty) {
+        final Rect localDirty = mDirty;
+
+        // Add the new dirty rect to the current one
+        localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
+        // Intersect with the bounds of the window to skip
+        // updates that lie outside of the visible region
+        final float appScale = mAttachInfo.mApplicationScale;
+        final boolean intersected = localDirty.intersect(0, 0,
+                (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
+        if (!intersected) {
+            localDirty.setEmpty();
+        }
+        if (!mWillDrawSoon && (intersected || mIsAnimating)) {
+            scheduleTraversals();
+        }
+    }
+
+    public void setIsAmbientMode(boolean ambient) {
+        mIsAmbientMode = ambient;
+    }
+
+    void setWindowStopped(boolean stopped) {
+        checkThread();
+        if (mStopped != stopped) {
+            mStopped = stopped;
+            final ThreadedRenderer renderer = mAttachInfo.mThreadedRenderer;
+            if (renderer != null) {
+                if (DEBUG_DRAW) Log.d(mTag, "WindowStopped on " + getTitle() + " set to " + mStopped);
+                renderer.setStopped(mStopped);
+            }
+            if (!mStopped) {
+                mNewSurfaceNeeded = true;
+                scheduleTraversals();
+            } else {
+                if (renderer != null) {
+                    renderer.destroyHardwareResources(mView);
+                }
+
+                if (mSurface.isValid()) {
+                    if (mSurfaceHolder != null) {
+                        notifyHolderSurfaceDestroyed();
+                    }
+                    notifySurfaceDestroyed();
+                }
+                destroySurface();
+            }
+        }
+    }
+
+
+    /** Register callbacks to be notified when the ViewRootImpl surface changes. */
+    public interface SurfaceChangedCallback {
+        void surfaceCreated(Transaction t);
+        void surfaceReplaced(Transaction t);
+        void surfaceDestroyed();
+    }
+
+    private final ArrayList<SurfaceChangedCallback> mSurfaceChangedCallbacks = new ArrayList<>();
+    public void addSurfaceChangedCallback(SurfaceChangedCallback c) {
+        mSurfaceChangedCallbacks.add(c);
+    }
+
+    public void removeSurfaceChangedCallback(SurfaceChangedCallback c) {
+        mSurfaceChangedCallbacks.remove(c);
+    }
+
+    private void notifySurfaceCreated() {
+        for (int i = 0; i < mSurfaceChangedCallbacks.size(); i++) {
+            mSurfaceChangedCallbacks.get(i).surfaceCreated(mSurfaceChangedTransaction);
+        }
+    }
+
+    /**
+     * Notify listeners when the ViewRootImpl surface has been replaced. This callback will not be
+     * called if a new surface is created, only if the valid surface has been replaced with another
+     * valid surface.
+     */
+    private void notifySurfaceReplaced() {
+        for (int i = 0; i < mSurfaceChangedCallbacks.size(); i++) {
+            mSurfaceChangedCallbacks.get(i).surfaceReplaced(mSurfaceChangedTransaction);
+        }
+    }
+
+    private void notifySurfaceDestroyed() {
+        for (int i = 0; i < mSurfaceChangedCallbacks.size(); i++) {
+            mSurfaceChangedCallbacks.get(i).surfaceDestroyed();
+        }
+    }
+
+    /**
+     * @return child layer with the same bounds as its parent {@code mSurface} and cropped to the
+     * surface insets. If the layer does not exist, it is created.
+     *
+     * <p>Parenting to this layer will ensure that its children are cropped by the view's surface
+     * insets.
+     */
+    public SurfaceControl getBoundsLayer() {
+        if (mBoundsLayer == null) {
+            mBoundsLayer = new SurfaceControl.Builder(mSurfaceSession)
+                    .setContainerLayer()
+                    .setName("Bounds for - " + getTitle().toString())
+                    .setParent(getSurfaceControl())
+                    .setCallsite("ViewRootImpl.getBoundsLayer")
+                    .build();
+            setBoundsLayerCrop(mTransaction);
+            mTransaction.show(mBoundsLayer).apply();
+        }
+       return mBoundsLayer;
+    }
+
+    Surface getOrCreateBLASTSurface() {
+        if (!mSurfaceControl.isValid()) {
+            return null;
+        }
+
+        Surface ret = null;
+        if (mBlastBufferQueue == null) {
+            mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl,
+                mSurfaceSize.x, mSurfaceSize.y,
+                mWindowAttributes.format);
+            // We only return the Surface the first time, as otherwise
+            // it hasn't changed and there is no need to update.
+            ret = mBlastBufferQueue.createSurface();
+        } else {
+            mBlastBufferQueue.update(mSurfaceControl,
+                mSurfaceSize.x, mSurfaceSize.y,
+                mWindowAttributes.format);
+        }
+
+        return ret;
+    }
+
+    private void setBoundsLayerCrop(Transaction t) {
+        // Adjust of insets and update the bounds layer so child surfaces do not draw into
+        // the surface inset region.
+        mTempBoundsRect.set(0, 0, mSurfaceSize.x, mSurfaceSize.y);
+        mTempBoundsRect.inset(mWindowAttributes.surfaceInsets.left,
+                mWindowAttributes.surfaceInsets.top,
+                mWindowAttributes.surfaceInsets.right, mWindowAttributes.surfaceInsets.bottom);
+        t.setWindowCrop(mBoundsLayer, mTempBoundsRect);
+    }
+
+    /**
+     * Called after window layout to update the bounds surface. If the surface insets have changed
+     * or the surface has resized, update the bounds surface.
+     */
+    private boolean updateBoundsLayer(SurfaceControl.Transaction t) {
+        if (mBoundsLayer != null) {
+            setBoundsLayerCrop(t);
+            return true;
+        }
+        return false;
+    }
+
+    private void prepareSurfaces() {
+        final SurfaceControl.Transaction t = mTransaction;
+        final SurfaceControl sc = getSurfaceControl();
+        if (!sc.isValid()) return;
+
+        if (updateBoundsLayer(t)) {
+              mergeWithNextTransaction(t, mSurface.getNextFrameNumber());
+        }
+    }
+
+    private void destroySurface() {
+        if (mBoundsLayer != null) {
+            mBoundsLayer.release();
+            mBoundsLayer = null;
+        }
+        mSurface.release();
+        mSurfaceControl.release();
+
+        if (mBlastBufferQueue != null) {
+            mBlastBufferQueue.destroy();
+            mBlastBufferQueue = null;
+        }
+
+        if (mAttachInfo.mThreadedRenderer != null) {
+            mAttachInfo.mThreadedRenderer.setSurfaceControl(null);
+        }
+    }
+
+    /**
+     * Block the input events during an Activity Transition. The KEYCODE_BACK event is allowed
+     * through to allow quick reversal of the Activity Transition.
+     *
+     * @param paused true to pause, false to resume.
+     */
+    public void setPausedForTransition(boolean paused) {
+        mPausedForTransition = paused;
+    }
+
+    @Override
+    public ViewParent getParent() {
+        return null;
+    }
+
+    @Override
+    public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) {
+        if (child != mView) {
+            throw new RuntimeException("child is not mine, honest!");
+        }
+        // Note: don't apply scroll offset, because we want to know its
+        // visibility in the virtual canvas being given to the view hierarchy.
+        return r.intersect(0, 0, mWidth, mHeight);
+    }
+
+    @Override
+    public void bringChildToFront(View child) {
+    }
+
+    int getHostVisibility() {
+        return (mAppVisible || mForceDecorViewVisibility) ? mView.getVisibility() : View.GONE;
+    }
+
+    /**
+     * Add LayoutTransition to the list of transitions to be started in the next traversal.
+     * This list will be cleared after the transitions on the list are start()'ed. These
+     * transitionsa re added by LayoutTransition itself when it sets up animations. The setup
+     * happens during the layout phase of traversal, which we want to complete before any of the
+     * animations are started (because those animations may side-effect properties that layout
+     * depends upon, like the bounding rectangles of the affected views). So we add the transition
+     * to the list and it is started just prior to starting the drawing phase of traversal.
+     *
+     * @param transition The LayoutTransition to be started on the next traversal.
+     *
+     * @hide
+     */
+    public void requestTransitionStart(LayoutTransition transition) {
+        if (mPendingTransitions == null || !mPendingTransitions.contains(transition)) {
+            if (mPendingTransitions == null) {
+                 mPendingTransitions = new ArrayList<LayoutTransition>();
+            }
+            mPendingTransitions.add(transition);
+        }
+    }
+
+    /**
+     * Notifies the HardwareRenderer that a new frame will be coming soon.
+     * Currently only {@link ThreadedRenderer} cares about this, and uses
+     * this knowledge to adjust the scheduling of off-thread animations
+     */
+    void notifyRendererOfFramePending() {
+        if (mAttachInfo.mThreadedRenderer != null) {
+            mAttachInfo.mThreadedRenderer.notifyFramePending();
+        }
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    void scheduleTraversals() {
+        if (!mTraversalScheduled) {
+            mTraversalScheduled = true;
+            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
+            mChoreographer.postCallback(
+                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
+            notifyRendererOfFramePending();
+            pokeDrawLockIfNeeded();
+        }
+    }
+
+    void unscheduleTraversals() {
+        if (mTraversalScheduled) {
+            mTraversalScheduled = false;
+            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
+            mChoreographer.removeCallbacks(
+                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
+        }
+    }
+
+    void doTraversal() {
+        if (mTraversalScheduled) {
+            mTraversalScheduled = false;
+            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
+
+            if (mProfile) {
+                Debug.startMethodTracing("ViewAncestor");
+            }
+
+            performTraversals();
+
+            if (mProfile) {
+                Debug.stopMethodTracing();
+                mProfile = false;
+            }
+        }
+    }
+
+    private void applyKeepScreenOnFlag(WindowManager.LayoutParams params) {
+        // Update window's global keep screen on flag: if a view has requested
+        // that the screen be kept on, then it is always set; otherwise, it is
+        // set to whatever the client last requested for the global state.
+        if (mAttachInfo.mKeepScreenOn) {
+            params.flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
+        } else {
+            params.flags = (params.flags&~WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+                    | (mClientWindowLayoutFlags&WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        }
+    }
+
+    private boolean collectViewAttributes() {
+        if (mAttachInfo.mRecomputeGlobalAttributes) {
+            //Log.i(mTag, "Computing view hierarchy attributes!");
+            mAttachInfo.mRecomputeGlobalAttributes = false;
+            boolean oldScreenOn = mAttachInfo.mKeepScreenOn;
+            mAttachInfo.mKeepScreenOn = false;
+            mAttachInfo.mSystemUiVisibility = 0;
+            mAttachInfo.mHasSystemUiListeners = false;
+            mView.dispatchCollectViewAttributes(mAttachInfo, 0);
+            mAttachInfo.mSystemUiVisibility &= ~mAttachInfo.mDisabledSystemUiVisibility;
+            WindowManager.LayoutParams params = mWindowAttributes;
+            mAttachInfo.mSystemUiVisibility |= getImpliedSystemUiVisibility(params);
+            mCompatibleVisibilityInfo.globalVisibility =
+                    (mCompatibleVisibilityInfo.globalVisibility & ~View.SYSTEM_UI_FLAG_LOW_PROFILE)
+                            | (mAttachInfo.mSystemUiVisibility & View.SYSTEM_UI_FLAG_LOW_PROFILE);
+            dispatchDispatchSystemUiVisibilityChanged(mCompatibleVisibilityInfo);
+            if (mAttachInfo.mKeepScreenOn != oldScreenOn
+                    || mAttachInfo.mSystemUiVisibility != params.subtreeSystemUiVisibility
+                    || mAttachInfo.mHasSystemUiListeners != params.hasSystemUiListeners) {
+                applyKeepScreenOnFlag(params);
+                params.subtreeSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
+                params.hasSystemUiListeners = mAttachInfo.mHasSystemUiListeners;
+                mView.dispatchWindowSystemUiVisiblityChanged(mAttachInfo.mSystemUiVisibility);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private int getImpliedSystemUiVisibility(WindowManager.LayoutParams params) {
+        int vis = 0;
+        // Translucent decor window flags imply stable system ui visibility.
+        if ((params.flags & FLAG_TRANSLUCENT_STATUS) != 0) {
+            vis |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+        }
+        if ((params.flags & FLAG_TRANSLUCENT_NAVIGATION) != 0) {
+            vis |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+        }
+        return vis;
+    }
+
+    /**
+     * Update the compatible system UI visibility for dispatching it to the legacy app.
+     *
+     * @param type Indicates which type of the insets source we are handling.
+     * @param visible True if the insets source is visible.
+     * @param hasControl True if we can control the insets source.
+     */
+    void updateCompatSysUiVisibility(@InternalInsetsType int type, boolean visible,
+            boolean hasControl) {
+        if (type != ITYPE_STATUS_BAR && type != ITYPE_NAVIGATION_BAR) {
+            return;
+        }
+        final SystemUiVisibilityInfo info = mCompatibleVisibilityInfo;
+        final int systemUiFlag = type == ITYPE_STATUS_BAR
+                ? View.SYSTEM_UI_FLAG_FULLSCREEN
+                : View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+        final boolean wasVisible = (info.globalVisibility & systemUiFlag) == 0;
+        if (visible) {
+            info.globalVisibility &= ~systemUiFlag;
+            if (!wasVisible && hasControl) {
+                // The local system UI visibility can only be cleared while we have the control.
+                info.localChanges |= systemUiFlag;
+            }
+        } else {
+            info.globalVisibility |= systemUiFlag;
+            info.localChanges &= ~systemUiFlag;
+        }
+        dispatchDispatchSystemUiVisibilityChanged(info);
+    }
+
+    /**
+     * If the system is forcing showing any system bar, the legacy low profile flag should be
+     * cleared for compatibility.
+     *
+     * @param showTypes {@link InsetsType types} shown by the system.
+     * @param fromIme {@code true} if the invocation is from IME.
+     */
+    private void clearLowProfileModeIfNeeded(@InsetsType int showTypes, boolean fromIme) {
+        final SystemUiVisibilityInfo info = mCompatibleVisibilityInfo;
+        if ((showTypes & Type.systemBars()) != 0 && !fromIme
+                && (info.globalVisibility & SYSTEM_UI_FLAG_LOW_PROFILE) != 0) {
+            info.globalVisibility &= ~SYSTEM_UI_FLAG_LOW_PROFILE;
+            info.localChanges |= SYSTEM_UI_FLAG_LOW_PROFILE;
+            dispatchDispatchSystemUiVisibilityChanged(info);
+        }
+    }
+
+    private void dispatchDispatchSystemUiVisibilityChanged(SystemUiVisibilityInfo args) {
+        if (mDispatchedSystemUiVisibility != args.globalVisibility) {
+            mHandler.removeMessages(MSG_DISPATCH_SYSTEM_UI_VISIBILITY);
+            mHandler.sendMessage(mHandler.obtainMessage(MSG_DISPATCH_SYSTEM_UI_VISIBILITY, args));
+        }
+    }
+
+    private void handleDispatchSystemUiVisibilityChanged(SystemUiVisibilityInfo args) {
+        if (mView == null) return;
+        if (args.localChanges != 0) {
+            mView.updateLocalSystemUiVisibility(args.localValue, args.localChanges);
+            args.localChanges = 0;
+        }
+
+        final int visibility = args.globalVisibility & View.SYSTEM_UI_CLEARABLE_FLAGS;
+        if (mDispatchedSystemUiVisibility != visibility) {
+            mDispatchedSystemUiVisibility = visibility;
+            mView.dispatchSystemUiVisibilityChanged(visibility);
+        }
+    }
+
+    @VisibleForTesting
+    public static void adjustLayoutParamsForCompatibility(WindowManager.LayoutParams inOutParams) {
+        final int sysUiVis = inOutParams.systemUiVisibility | inOutParams.subtreeSystemUiVisibility;
+        final int flags = inOutParams.flags;
+        final int type = inOutParams.type;
+        final int adjust = inOutParams.softInputMode & SOFT_INPUT_MASK_ADJUST;
+
+        if ((inOutParams.privateFlags & PRIVATE_FLAG_APPEARANCE_CONTROLLED) == 0) {
+            inOutParams.insetsFlags.appearance = 0;
+            if ((sysUiVis & SYSTEM_UI_FLAG_LOW_PROFILE) != 0) {
+                inOutParams.insetsFlags.appearance |= APPEARANCE_LOW_PROFILE_BARS;
+            }
+            if ((sysUiVis & SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0) {
+                inOutParams.insetsFlags.appearance |= APPEARANCE_LIGHT_STATUS_BARS;
+            }
+            if ((sysUiVis & SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0) {
+                inOutParams.insetsFlags.appearance |= APPEARANCE_LIGHT_NAVIGATION_BARS;
+            }
+        }
+
+        if ((inOutParams.privateFlags & PRIVATE_FLAG_BEHAVIOR_CONTROLLED) == 0) {
+            if ((sysUiVis & SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0
+                    || (flags & FLAG_FULLSCREEN) != 0) {
+                inOutParams.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
+            } else {
+                inOutParams.insetsFlags.behavior = BEHAVIOR_DEFAULT;
+            }
+        }
+
+        inOutParams.privateFlags &= ~PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
+
+        if ((inOutParams.privateFlags & PRIVATE_FLAG_FIT_INSETS_CONTROLLED) != 0) {
+            return;
+        }
+
+        int types = inOutParams.getFitInsetsTypes();
+        boolean ignoreVis = inOutParams.isFitInsetsIgnoringVisibility();
+
+        if (((sysUiVis & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0
+                || (flags & FLAG_LAYOUT_IN_SCREEN) != 0)
+                || (flags & FLAG_TRANSLUCENT_STATUS) != 0) {
+            types &= ~Type.statusBars();
+        }
+        if ((sysUiVis & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
+                || (flags & FLAG_TRANSLUCENT_NAVIGATION) != 0) {
+            types &= ~Type.systemBars();
+        }
+        if (type == TYPE_TOAST || type == TYPE_SYSTEM_ALERT) {
+            ignoreVis = true;
+        } else if ((types & Type.systemBars()) == Type.systemBars()) {
+            if (adjust == SOFT_INPUT_ADJUST_RESIZE) {
+                types |= Type.ime();
+            } else {
+                inOutParams.privateFlags |= PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
+            }
+        }
+        inOutParams.setFitInsetsTypes(types);
+        inOutParams.setFitInsetsIgnoringVisibility(ignoreVis);
+
+        // The fitting of insets are not really controlled by the clients, so we remove the flag.
+        inOutParams.privateFlags &= ~PRIVATE_FLAG_FIT_INSETS_CONTROLLED;
+    }
+
+    private void controlInsetsForCompatibility(WindowManager.LayoutParams params) {
+        final int sysUiVis = params.systemUiVisibility | params.subtreeSystemUiVisibility;
+        final int flags = params.flags;
+        final boolean matchParent = params.width == MATCH_PARENT && params.height == MATCH_PARENT;
+        final boolean nonAttachedAppWindow = params.type >= FIRST_APPLICATION_WINDOW
+                && params.type <= LAST_APPLICATION_WINDOW;
+        final boolean statusWasHiddenByFlags = (mTypesHiddenByFlags & Type.statusBars()) != 0;
+        final boolean statusIsHiddenByFlags = (sysUiVis & SYSTEM_UI_FLAG_FULLSCREEN) != 0
+                || ((flags & FLAG_FULLSCREEN) != 0 && matchParent && nonAttachedAppWindow);
+        final boolean navWasHiddenByFlags = (mTypesHiddenByFlags & Type.navigationBars()) != 0;
+        final boolean navIsHiddenByFlags = (sysUiVis & SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0;
+
+        @InsetsType int typesToHide = 0;
+        @InsetsType int typesToShow = 0;
+        if (statusIsHiddenByFlags && !statusWasHiddenByFlags) {
+            typesToHide |= Type.statusBars();
+        } else if (!statusIsHiddenByFlags && statusWasHiddenByFlags) {
+            typesToShow |= Type.statusBars();
+        }
+        if (navIsHiddenByFlags && !navWasHiddenByFlags) {
+            typesToHide |= Type.navigationBars();
+        } else if (!navIsHiddenByFlags && navWasHiddenByFlags) {
+            typesToShow |= Type.navigationBars();
+        }
+        if (typesToHide != 0) {
+            getInsetsController().hide(typesToHide);
+        }
+        if (typesToShow != 0) {
+            getInsetsController().show(typesToShow);
+        }
+        mTypesHiddenByFlags |= typesToHide;
+        mTypesHiddenByFlags &= ~typesToShow;
+    }
+
+    private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
+            final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
+        int childWidthMeasureSpec;
+        int childHeightMeasureSpec;
+        boolean windowSizeMayChange = false;
+
+        if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(mTag,
+                "Measuring " + host + " in display " + desiredWindowWidth
+                + "x" + desiredWindowHeight + "...");
+
+        boolean goodMeasure = false;
+        if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
+            // On large screens, we don't want to allow dialogs to just
+            // stretch to fill the entire width of the screen to display
+            // one line of text.  First try doing the layout at a smaller
+            // size to see if it will fit.
+            final DisplayMetrics packageMetrics = res.getDisplayMetrics();
+            res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
+            int baseSize = 0;
+            if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
+                baseSize = (int)mTmpValue.getDimension(packageMetrics);
+            }
+            if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize
+                    + ", desiredWindowWidth=" + desiredWindowWidth);
+            if (baseSize != 0 && desiredWindowWidth > baseSize) {
+                childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
+                childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
+                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
+                if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
+                        + host.getMeasuredWidth() + "," + host.getMeasuredHeight()
+                        + ") from width spec: " + MeasureSpec.toString(childWidthMeasureSpec)
+                        + " and height spec: " + MeasureSpec.toString(childHeightMeasureSpec));
+                if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
+                    goodMeasure = true;
+                } else {
+                    // Didn't fit in that size... try expanding a bit.
+                    baseSize = (baseSize+desiredWindowWidth)/2;
+                    if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": next baseSize="
+                            + baseSize);
+                    childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
+                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
+                    if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
+                            + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
+                    if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
+                        if (DEBUG_DIALOG) Log.v(mTag, "Good!");
+                        goodMeasure = true;
+                    }
+                }
+            }
+        }
+
+        if (!goodMeasure) {
+            childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
+            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
+            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
+            if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
+                windowSizeMayChange = true;
+            }
+        }
+
+        if (DBG) {
+            System.out.println("======================================");
+            System.out.println("performTraversals -- after measure");
+            host.debug();
+        }
+
+        return windowSizeMayChange;
+    }
+
+    /**
+     * Modifies the input matrix such that it maps view-local coordinates to
+     * on-screen coordinates.
+     *
+     * @param m input matrix to modify
+     */
+    void transformMatrixToGlobal(Matrix m) {
+        m.preTranslate(mAttachInfo.mWindowLeft, mAttachInfo.mWindowTop);
+    }
+
+    /**
+     * Modifies the input matrix such that it maps on-screen coordinates to
+     * view-local coordinates.
+     *
+     * @param m input matrix to modify
+     */
+    void transformMatrixToLocal(Matrix m) {
+        m.postTranslate(-mAttachInfo.mWindowLeft, -mAttachInfo.mWindowTop);
+    }
+
+    /* package */ WindowInsets getWindowInsets(boolean forceConstruct) {
+        if (mLastWindowInsets == null || forceConstruct) {
+            final Configuration config = getConfiguration();
+            mLastWindowInsets = mInsetsController.calculateInsets(
+                    config.isScreenRound(), mAttachInfo.mAlwaysConsumeSystemBars,
+                    mWindowAttributes.type, config.windowConfiguration.getWindowingMode(),
+                    mWindowAttributes.softInputMode, mWindowAttributes.flags,
+                    (mWindowAttributes.systemUiVisibility
+                            | mWindowAttributes.subtreeSystemUiVisibility));
+
+            mAttachInfo.mContentInsets.set(mLastWindowInsets.getSystemWindowInsets().toRect());
+            mAttachInfo.mStableInsets.set(mLastWindowInsets.getStableInsets().toRect());
+            mAttachInfo.mVisibleInsets.set(mInsetsController.calculateVisibleInsets(
+                    mWindowAttributes.softInputMode));
+        }
+        return mLastWindowInsets;
+    }
+
+    public void dispatchApplyInsets(View host) {
+        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "dispatchApplyInsets");
+        mApplyInsetsRequested = false;
+        WindowInsets insets = getWindowInsets(true /* forceConstruct */);
+        if (!shouldDispatchCutout()) {
+            // Window is either not laid out in cutout or the status bar inset takes care of
+            // clearing the cutout, so we don't need to dispatch the cutout to the hierarchy.
+            insets = insets.consumeDisplayCutout();
+        }
+        host.dispatchApplyWindowInsets(insets);
+        mAttachInfo.delayNotifyContentCaptureInsetsEvent(insets.getInsets(Type.all()));
+        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+    }
+
+    private boolean updateCaptionInsets() {
+        if (!(mView instanceof DecorView)) return false;
+        final int captionInsetsHeight = ((DecorView) mView).getCaptionInsetsHeight();
+        final Rect captionFrame = new Rect();
+        if (captionInsetsHeight != 0) {
+            captionFrame.set(mWinFrame.left, mWinFrame.top, mWinFrame.right,
+                            mWinFrame.top + captionInsetsHeight);
+        }
+        if (mAttachInfo.mCaptionInsets.equals(captionFrame)) return false;
+        mAttachInfo.mCaptionInsets.set(captionFrame);
+        return true;
+    }
+
+    private boolean shouldDispatchCutout() {
+        return mWindowAttributes.layoutInDisplayCutoutMode
+                        == LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+                || mWindowAttributes.layoutInDisplayCutoutMode
+                        == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+    }
+
+    @VisibleForTesting
+    public InsetsController getInsetsController() {
+        return mInsetsController;
+    }
+
+    private static boolean shouldUseDisplaySize(final WindowManager.LayoutParams lp) {
+        return lp.type == TYPE_STATUS_BAR_ADDITIONAL
+                || lp.type == TYPE_INPUT_METHOD
+                || lp.type == TYPE_VOLUME_OVERLAY;
+    }
+
+    int dipToPx(int dip) {
+        final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
+        return (int) (displayMetrics.density * dip + 0.5f);
+    }
+
+    private void performTraversals() {
+        // cache mView since it is used so much below...
+        final View host = mView;
+
+        if (DBG) {
+            System.out.println("======================================");
+            System.out.println("performTraversals");
+            host.debug();
+        }
+
+        if (host == null || !mAdded) {
+            return;
+        }
+
+        // This is to ensure we don't end up queueing new frames while waiting on a previous frame
+        // to get latched. This can happen when there's been a sync request for this window. The
+        // frame could be in a transaction that's passed to different processes to ensure
+        // synchronization. It continues to block until ViewRootImpl receives a callback that the
+        // transaction containing the buffer has been sent to SurfaceFlinger. Once we receive, that
+        // signal, we know it's safe to start queuing new buffers.
+        //
+        // When the callback is invoked, it will trigger a traversal request if
+        // mRequestedTraverseWhilePaused is set so there's no need to attempt a retry here.
+        if (mWaitForBlastSyncComplete) {
+            if (DEBUG_BLAST) {
+                Log.w(mTag, "Can't perform draw while waiting for a transaction complete");
+            }
+            mRequestedTraverseWhilePaused = true;
+            return;
+        }
+
+        mIsInTraversal = true;
+        mWillDrawSoon = true;
+        boolean windowSizeMayChange = false;
+        WindowManager.LayoutParams lp = mWindowAttributes;
+
+        int desiredWindowWidth;
+        int desiredWindowHeight;
+
+        final int viewVisibility = getHostVisibility();
+        final boolean viewVisibilityChanged = !mFirst
+                && (mViewVisibility != viewVisibility || mNewSurfaceNeeded
+                // Also check for possible double visibility update, which will make current
+                // viewVisibility value equal to mViewVisibility and we may miss it.
+                || mAppVisibilityChanged);
+        mAppVisibilityChanged = false;
+        final boolean viewUserVisibilityChanged = !mFirst &&
+                ((mViewVisibility == View.VISIBLE) != (viewVisibility == View.VISIBLE));
+
+        WindowManager.LayoutParams params = null;
+        CompatibilityInfo compatibilityInfo =
+                mDisplay.getDisplayAdjustments().getCompatibilityInfo();
+        if (compatibilityInfo.supportsScreen() == mLastInCompatMode) {
+            params = lp;
+            mFullRedrawNeeded = true;
+            mLayoutRequested = true;
+            if (mLastInCompatMode) {
+                params.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
+                mLastInCompatMode = false;
+            } else {
+                params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
+                mLastInCompatMode = true;
+            }
+        }
+
+        Rect frame = mWinFrame;
+        if (mFirst) {
+            mFullRedrawNeeded = true;
+            mLayoutRequested = true;
+
+            final Configuration config = getConfiguration();
+            if (shouldUseDisplaySize(lp)) {
+                // NOTE -- system code, won't try to do compat mode.
+                Point size = new Point();
+                mDisplay.getRealSize(size);
+                desiredWindowWidth = size.x;
+                desiredWindowHeight = size.y;
+            } else if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
+                    || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
+                // For wrap content, we have to remeasure later on anyways. Use size consistent with
+                // below so we get best use of the measure cache.
+                desiredWindowWidth = dipToPx(config.screenWidthDp);
+                desiredWindowHeight = dipToPx(config.screenHeightDp);
+            } else {
+                // After addToDisplay, the frame contains the frameHint from window manager, which
+                // for most windows is going to be the same size as the result of relayoutWindow.
+                // Using this here allows us to avoid remeasuring after relayoutWindow
+                desiredWindowWidth = frame.width();
+                desiredWindowHeight = frame.height();
+            }
+
+            // We used to use the following condition to choose 32 bits drawing caches:
+            // PixelFormat.hasAlpha(lp.format) || lp.format == PixelFormat.RGBX_8888
+            // However, windows are now always 32 bits by default, so choose 32 bits
+            mAttachInfo.mUse32BitDrawingCache = true;
+            mAttachInfo.mWindowVisibility = viewVisibility;
+            mAttachInfo.mRecomputeGlobalAttributes = false;
+            mLastConfigurationFromResources.setTo(config);
+            mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
+            // Set the layout direction if it has not been set before (inherit is the default)
+            if (mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {
+                host.setLayoutDirection(config.getLayoutDirection());
+            }
+            host.dispatchAttachedToWindow(mAttachInfo, 0);
+            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
+            dispatchApplyInsets(host);
+        } else {
+            desiredWindowWidth = frame.width();
+            desiredWindowHeight = frame.height();
+            if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
+                if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame);
+                mFullRedrawNeeded = true;
+                mLayoutRequested = true;
+                windowSizeMayChange = true;
+            }
+        }
+
+        if (viewVisibilityChanged) {
+            mAttachInfo.mWindowVisibility = viewVisibility;
+            host.dispatchWindowVisibilityChanged(viewVisibility);
+            if (viewUserVisibilityChanged) {
+                host.dispatchVisibilityAggregated(viewVisibility == View.VISIBLE);
+            }
+            if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) {
+                endDragResizing();
+                destroyHardwareResources();
+            }
+        }
+
+        // Non-visible windows can't hold accessibility focus.
+        if (mAttachInfo.mWindowVisibility != View.VISIBLE) {
+            host.clearAccessibilityFocus();
+        }
+
+        // Execute enqueued actions on every traversal in case a detached view enqueued an action
+        getRunQueue().executeActions(mAttachInfo.mHandler);
+
+        boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
+        if (layoutRequested) {
+
+            final Resources res = mView.getContext().getResources();
+
+            if (mFirst) {
+                // make sure touch mode code executes by setting cached value
+                // to opposite of the added touch mode.
+                mAttachInfo.mInTouchMode = !mAddedTouchMode;
+                ensureTouchModeLocally(mAddedTouchMode);
+            } else {
+                if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
+                        || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
+                    windowSizeMayChange = true;
+
+                    if (shouldUseDisplaySize(lp)) {
+                        // NOTE -- system code, won't try to do compat mode.
+                        Point size = new Point();
+                        mDisplay.getRealSize(size);
+                        desiredWindowWidth = size.x;
+                        desiredWindowHeight = size.y;
+                    } else {
+                        Configuration config = res.getConfiguration();
+                        desiredWindowWidth = dipToPx(config.screenWidthDp);
+                        desiredWindowHeight = dipToPx(config.screenHeightDp);
+                    }
+                }
+            }
+
+            // Ask host how big it wants to be
+            windowSizeMayChange |= measureHierarchy(host, lp, res,
+                    desiredWindowWidth, desiredWindowHeight);
+        }
+
+        if (collectViewAttributes()) {
+            params = lp;
+        }
+        if (mAttachInfo.mForceReportNewAttributes) {
+            mAttachInfo.mForceReportNewAttributes = false;
+            params = lp;
+        }
+
+        if (mFirst || mAttachInfo.mViewVisibilityChanged) {
+            mAttachInfo.mViewVisibilityChanged = false;
+            int resizeMode = mSoftInputMode & SOFT_INPUT_MASK_ADJUST;
+            // If we are in auto resize mode, then we need to determine
+            // what mode to use now.
+            if (resizeMode == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
+                final int N = mAttachInfo.mScrollContainers.size();
+                for (int i=0; i<N; i++) {
+                    if (mAttachInfo.mScrollContainers.get(i).isShown()) {
+                        resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+                    }
+                }
+                if (resizeMode == 0) {
+                    resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
+                }
+                if ((lp.softInputMode & SOFT_INPUT_MASK_ADJUST) != resizeMode) {
+                    lp.softInputMode = (lp.softInputMode & ~SOFT_INPUT_MASK_ADJUST) | resizeMode;
+                    params = lp;
+                }
+            }
+        }
+
+        if (mApplyInsetsRequested && !(mWillMove || mWillResize)) {
+            dispatchApplyInsets(host);
+            if (mLayoutRequested) {
+                // Short-circuit catching a new layout request here, so
+                // we don't need to go through two layout passes when things
+                // change due to fitting system windows, which can happen a lot.
+                windowSizeMayChange |= measureHierarchy(host, lp,
+                        mView.getContext().getResources(),
+                        desiredWindowWidth, desiredWindowHeight);
+            }
+        }
+
+        if (layoutRequested) {
+            // Clear this now, so that if anything requests a layout in the
+            // rest of this function we will catch it and re-run a full
+            // layout pass.
+            mLayoutRequested = false;
+        }
+
+        boolean windowShouldResize = layoutRequested && windowSizeMayChange
+            && ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight())
+                || (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
+                        frame.width() < desiredWindowWidth && frame.width() != mWidth)
+                || (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
+                        frame.height() < desiredWindowHeight && frame.height() != mHeight));
+        windowShouldResize |= mDragResizing && mResizeMode == RESIZE_MODE_FREEFORM;
+
+        // If the activity was just relaunched, it might have unfrozen the task bounds (while
+        // relaunching), so we need to force a call into window manager to pick up the latest
+        // bounds.
+        windowShouldResize |= mActivityRelaunched;
+
+        // Determine whether to compute insets.
+        // If there are no inset listeners remaining then we may still need to compute
+        // insets in case the old insets were non-empty and must be reset.
+        final boolean computesInternalInsets =
+                mAttachInfo.mTreeObserver.hasComputeInternalInsetsListeners()
+                || mAttachInfo.mHasNonEmptyGivenInternalInsets;
+
+        boolean insetsPending = false;
+        int relayoutResult = 0;
+        boolean updatedConfiguration = false;
+
+        final int surfaceGenerationId = mSurface.getGenerationId();
+
+        final boolean isViewVisible = viewVisibility == View.VISIBLE;
+        final boolean windowRelayoutWasForced = mForceNextWindowRelayout;
+        boolean surfaceSizeChanged = false;
+        boolean surfaceCreated = false;
+        boolean surfaceDestroyed = false;
+        // True if surface generation id changes or relayout result is RELAYOUT_RES_SURFACE_CHANGED.
+        boolean surfaceReplaced = false;
+
+        final boolean windowAttributesChanged = mWindowAttributesChanged;
+        if (windowAttributesChanged) {
+            mWindowAttributesChanged = false;
+            params = lp;
+        }
+
+        if (params != null) {
+            if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0
+                    && !PixelFormat.formatHasAlpha(params.format)) {
+                params.format = PixelFormat.TRANSLUCENT;
+            }
+            adjustLayoutParamsForCompatibility(params);
+            controlInsetsForCompatibility(params);
+            if (mDispatchedSystemBarAppearance != params.insetsFlags.appearance) {
+                mDispatchedSystemBarAppearance = params.insetsFlags.appearance;
+                mView.onSystemBarAppearanceChanged(mDispatchedSystemBarAppearance);
+            }
+        }
+        final boolean wasReportNextDraw = mReportNextDraw;
+
+        if (mFirst || windowShouldResize || viewVisibilityChanged || params != null
+                || mForceNextWindowRelayout) {
+            mForceNextWindowRelayout = false;
+
+            // If this window is giving internal insets to the window manager, then we want to first
+            // make the provided insets unchanged during layout. This avoids it briefly causing
+            // other windows to resize/move based on the raw frame of the window, waiting until we
+            // can finish laying out this window and get back to the window manager with the
+            // ultimately computed insets.
+            insetsPending = computesInternalInsets;
+
+            if (mSurfaceHolder != null) {
+                mSurfaceHolder.mSurfaceLock.lock();
+                mDrawingAllowed = true;
+            }
+
+            boolean hwInitialized = false;
+            boolean dispatchApplyInsets = false;
+            boolean hadSurface = mSurface.isValid();
+
+            try {
+                if (DEBUG_LAYOUT) {
+                    Log.i(mTag, "host=w:" + host.getMeasuredWidth() + ", h:" +
+                            host.getMeasuredHeight() + ", params=" + params);
+                }
+
+                if (mAttachInfo.mThreadedRenderer != null) {
+                    // relayoutWindow may decide to destroy mSurface. As that decision
+                    // happens in WindowManager service, we need to be defensive here
+                    // and stop using the surface in case it gets destroyed.
+                    if (mAttachInfo.mThreadedRenderer.pause()) {
+                        // Animations were running so we need to push a frame
+                        // to resume them
+                        mDirty.set(0, 0, mWidth, mHeight);
+                    }
+                }
+                if (mFirst || viewVisibilityChanged) {
+                    mViewFrameInfo.flags |= FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED;
+                }
+                relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
+                final boolean freeformResizing = (relayoutResult
+                        & WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM) != 0;
+                final boolean dockedResizing = (relayoutResult
+                        & WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED) != 0;
+                final boolean dragResizing = freeformResizing || dockedResizing;
+                if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_BLAST_SYNC) != 0) {
+                    if (DEBUG_BLAST) {
+                        Log.d(mTag, "Relayout called with blastSync");
+                    }
+                    reportNextDraw();
+                    if (isHardwareEnabled()) {
+                        mNextDrawUseBlastSync = true;
+                    }
+                }
+
+                if (mSurfaceControl.isValid()) {
+                    updateOpacity(mWindowAttributes, dragResizing);
+                }
+
+                if (DEBUG_LAYOUT) Log.v(mTag, "relayout: frame=" + frame.toShortString()
+                        + " surface=" + mSurface);
+
+                // If the pending {@link MergedConfiguration} handed back from
+                // {@link #relayoutWindow} does not match the one last reported,
+                // WindowManagerService has reported back a frame from a configuration not yet
+                // handled by the client. In this case, we need to accept the configuration so we
+                // do not lay out and draw with the wrong configuration.
+                if (!mPendingMergedConfiguration.equals(mLastReportedMergedConfiguration)) {
+                    if (DEBUG_CONFIGURATION) Log.v(mTag, "Visible with new config: "
+                            + mPendingMergedConfiguration.getMergedConfiguration());
+                    performConfigurationChange(new MergedConfiguration(mPendingMergedConfiguration),
+                            !mFirst, INVALID_DISPLAY /* same display */);
+                    updatedConfiguration = true;
+                }
+
+                surfaceSizeChanged = false;
+                if (!mLastSurfaceSize.equals(mSurfaceSize)) {
+                    surfaceSizeChanged = true;
+                    mLastSurfaceSize.set(mSurfaceSize.x, mSurfaceSize.y);
+                }
+                final boolean alwaysConsumeSystemBarsChanged =
+                        mPendingAlwaysConsumeSystemBars != mAttachInfo.mAlwaysConsumeSystemBars;
+                updateColorModeIfNeeded(lp.getColorMode());
+                surfaceCreated = !hadSurface && mSurface.isValid();
+                surfaceDestroyed = hadSurface && !mSurface.isValid();
+
+                // When using Blast, the surface generation id may not change when there's a new
+                // SurfaceControl. In that case, we also check relayout flag
+                // RELAYOUT_RES_SURFACE_CHANGED since it should indicate that WMS created a new
+                // SurfaceControl.
+                surfaceReplaced = (surfaceGenerationId != mSurface.getGenerationId()
+                        || (relayoutResult & RELAYOUT_RES_SURFACE_CHANGED)
+                        == RELAYOUT_RES_SURFACE_CHANGED)
+                        && mSurface.isValid();
+                if (surfaceReplaced) {
+                    mSurfaceSequenceId++;
+                }
+
+                if (alwaysConsumeSystemBarsChanged) {
+                    mAttachInfo.mAlwaysConsumeSystemBars = mPendingAlwaysConsumeSystemBars;
+                    dispatchApplyInsets = true;
+                }
+                if (updateCaptionInsets()) {
+                    dispatchApplyInsets = true;
+                }
+                if (dispatchApplyInsets || mLastSystemUiVisibility !=
+                        mAttachInfo.mSystemUiVisibility || mApplyInsetsRequested) {
+                    mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
+                    dispatchApplyInsets(host);
+                    // We applied insets so force contentInsetsChanged to ensure the
+                    // hierarchy is measured below.
+                    dispatchApplyInsets = true;
+                }
+
+                if (surfaceCreated) {
+                    // If we are creating a new surface, then we need to
+                    // completely redraw it.
+                    mFullRedrawNeeded = true;
+                    mPreviousTransparentRegion.setEmpty();
+
+                    // Only initialize up-front if transparent regions are not
+                    // requested, otherwise defer to see if the entire window
+                    // will be transparent
+                    if (mAttachInfo.mThreadedRenderer != null) {
+                        try {
+                            hwInitialized = mAttachInfo.mThreadedRenderer.initialize(mSurface);
+                            if (hwInitialized && (host.mPrivateFlags
+                                            & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0) {
+                                // Don't pre-allocate if transparent regions
+                                // are requested as they may not be needed
+                                mAttachInfo.mThreadedRenderer.allocateBuffers();
+                            }
+                        } catch (OutOfResourcesException e) {
+                            handleOutOfResourcesException(e);
+                            return;
+                        }
+                    }
+                } else if (surfaceDestroyed) {
+                    // If the surface has been removed, then reset the scroll
+                    // positions.
+                    if (mLastScrolledFocus != null) {
+                        mLastScrolledFocus.clear();
+                    }
+                    mScrollY = mCurScrollY = 0;
+                    if (mView instanceof RootViewSurfaceTaker) {
+                        ((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
+                    }
+                    if (mScroller != null) {
+                        mScroller.abortAnimation();
+                    }
+                    // Our surface is gone
+                    if (isHardwareEnabled()) {
+                        mAttachInfo.mThreadedRenderer.destroy();
+                    }
+                } else if ((surfaceReplaced
+                        || surfaceSizeChanged || windowRelayoutWasForced)
+                        && mSurfaceHolder == null
+                        && mAttachInfo.mThreadedRenderer != null
+                        && mSurface.isValid()) {
+                    mFullRedrawNeeded = true;
+                    try {
+                        // Need to do updateSurface (which leads to CanvasContext::setSurface and
+                        // re-create the EGLSurface) if either the Surface changed (as indicated by
+                        // generation id), or WindowManager changed the surface size. The latter is
+                        // because on some chips, changing the consumer side's BufferQueue size may
+                        // not take effect immediately unless we create a new EGLSurface.
+                        // Note that frame size change doesn't always imply surface size change (eg.
+                        // drag resizing uses fullscreen surface), need to check surfaceSizeChanged
+                        // flag from WindowManager.
+                        mAttachInfo.mThreadedRenderer.updateSurface(mSurface);
+                    } catch (OutOfResourcesException e) {
+                        handleOutOfResourcesException(e);
+                        return;
+                    }
+                }
+
+                if (mDragResizing != dragResizing) {
+                    if (dragResizing) {
+                        mResizeMode = freeformResizing
+                                ? RESIZE_MODE_FREEFORM
+                                : RESIZE_MODE_DOCKED_DIVIDER;
+                        final boolean backdropSizeMatchesFrame =
+                                mWinFrame.width() == mPendingBackDropFrame.width()
+                                        && mWinFrame.height() == mPendingBackDropFrame.height();
+                        // TODO: Need cutout?
+                        startDragResizing(mPendingBackDropFrame, !backdropSizeMatchesFrame,
+                                mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mResizeMode);
+                    } else {
+                        // We shouldn't come here, but if we come we should end the resize.
+                        endDragResizing();
+                    }
+                }
+                if (!mUseMTRenderer) {
+                    if (dragResizing) {
+                        mCanvasOffsetX = mWinFrame.left;
+                        mCanvasOffsetY = mWinFrame.top;
+                    } else {
+                        mCanvasOffsetX = mCanvasOffsetY = 0;
+                    }
+                }
+            } catch (RemoteException e) {
+            }
+
+            if (DEBUG_ORIENTATION) Log.v(
+                    TAG, "Relayout returned: frame=" + frame + ", surface=" + mSurface);
+
+            mAttachInfo.mWindowLeft = frame.left;
+            mAttachInfo.mWindowTop = frame.top;
+
+            // !!FIXME!! This next section handles the case where we did not get the
+            // window size we asked for. We should avoid this by getting a maximum size from
+            // the window session beforehand.
+            if (mWidth != frame.width() || mHeight != frame.height()) {
+                mWidth = frame.width();
+                mHeight = frame.height();
+            }
+
+            if (mSurfaceHolder != null) {
+                // The app owns the surface; tell it about what is going on.
+                if (mSurface.isValid()) {
+                    // XXX .copyFrom() doesn't work!
+                    //mSurfaceHolder.mSurface.copyFrom(mSurface);
+                    mSurfaceHolder.mSurface = mSurface;
+                }
+                mSurfaceHolder.setSurfaceFrameSize(mWidth, mHeight);
+                mSurfaceHolder.mSurfaceLock.unlock();
+                if (surfaceCreated) {
+                    mSurfaceHolder.ungetCallbacks();
+
+                    mIsCreating = true;
+                    SurfaceHolder.Callback[] callbacks = mSurfaceHolder.getCallbacks();
+                    if (callbacks != null) {
+                        for (SurfaceHolder.Callback c : callbacks) {
+                            c.surfaceCreated(mSurfaceHolder);
+                        }
+                    }
+                }
+
+                if ((surfaceCreated || surfaceReplaced || surfaceSizeChanged
+                        || windowAttributesChanged) && mSurface.isValid()) {
+                    SurfaceHolder.Callback[] callbacks = mSurfaceHolder.getCallbacks();
+                    if (callbacks != null) {
+                        for (SurfaceHolder.Callback c : callbacks) {
+                            c.surfaceChanged(mSurfaceHolder, lp.format,
+                                    mWidth, mHeight);
+                        }
+                    }
+                    mIsCreating = false;
+                }
+
+                if (surfaceDestroyed) {
+                    notifyHolderSurfaceDestroyed();
+                    mSurfaceHolder.mSurfaceLock.lock();
+                    try {
+                        mSurfaceHolder.mSurface = new Surface();
+                    } finally {
+                        mSurfaceHolder.mSurfaceLock.unlock();
+                    }
+                }
+            }
+
+            final ThreadedRenderer threadedRenderer = mAttachInfo.mThreadedRenderer;
+            if (threadedRenderer != null && threadedRenderer.isEnabled()) {
+                if (hwInitialized
+                        || mWidth != threadedRenderer.getWidth()
+                        || mHeight != threadedRenderer.getHeight()
+                        || mNeedsRendererSetup) {
+                    threadedRenderer.setup(mWidth, mHeight, mAttachInfo,
+                            mWindowAttributes.surfaceInsets);
+                    mNeedsRendererSetup = false;
+                }
+            }
+
+            // TODO: In the CL "ViewRootImpl: Fix issue with early draw report in
+            // seamless rotation". We moved processing of RELAYOUT_RES_BLAST_SYNC
+            // earlier in the function, potentially triggering a call to
+            // reportNextDraw(). That same CL changed this and the next reference
+            // to wasReportNextDraw, such that this logic would remain undisturbed
+            // (it continues to operate as if the code was never moved). This was
+            // done to achieve a more hermetic fix for S, but it's entirely
+            // possible that checking the most recent value is actually more
+            // correct here.
+            if (!mStopped || wasReportNextDraw) {
+                boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
+                        (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
+                if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
+                        || mHeight != host.getMeasuredHeight() || dispatchApplyInsets ||
+                        updatedConfiguration) {
+                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
+                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
+
+                    if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed!  mWidth="
+                            + mWidth + " measuredWidth=" + host.getMeasuredWidth()
+                            + " mHeight=" + mHeight
+                            + " measuredHeight=" + host.getMeasuredHeight()
+                            + " dispatchApplyInsets=" + dispatchApplyInsets);
+
+                     // Ask host how big it wants to be
+                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
+
+                    // Implementation of weights from WindowManager.LayoutParams
+                    // We just grow the dimensions as needed and re-measure if
+                    // needs be
+                    int width = host.getMeasuredWidth();
+                    int height = host.getMeasuredHeight();
+                    boolean measureAgain = false;
+
+                    if (lp.horizontalWeight > 0.0f) {
+                        width += (int) ((mWidth - width) * lp.horizontalWeight);
+                        childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
+                                MeasureSpec.EXACTLY);
+                        measureAgain = true;
+                    }
+                    if (lp.verticalWeight > 0.0f) {
+                        height += (int) ((mHeight - height) * lp.verticalWeight);
+                        childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
+                                MeasureSpec.EXACTLY);
+                        measureAgain = true;
+                    }
+
+                    if (measureAgain) {
+                        if (DEBUG_LAYOUT) Log.v(mTag,
+                                "And hey let's measure once more: width=" + width
+                                + " height=" + height);
+                        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
+                    }
+
+                    layoutRequested = true;
+                }
+            }
+        } else {
+            // Not the first pass and no window/insets/visibility change but the window
+            // may have moved and we need check that and if so to update the left and right
+            // in the attach info. We translate only the window frame since on window move
+            // the window manager tells us only for the new frame but the insets are the
+            // same and we do not want to translate them more than once.
+            maybeHandleWindowMove(frame);
+        }
+
+        if (surfaceSizeChanged || surfaceReplaced || surfaceCreated || windowAttributesChanged) {
+            // If the surface has been replaced, there's a chance the bounds layer is not parented
+            // to the new layer. When updating bounds layer, also reparent to the main VRI
+            // SurfaceControl to ensure it's correctly placed in the hierarchy.
+            //
+            // This needs to be done on the client side since WMS won't reparent the children to the
+            // new surface if it thinks the app is closing. WMS gets the signal that the app is
+            // stopping, but on the client side it doesn't get stopped since it's restarted quick
+            // enough. WMS doesn't want to keep around old children since they will leak when the
+            // client creates new children.
+            prepareSurfaces();
+        }
+
+        final boolean didLayout = layoutRequested && (!mStopped || wasReportNextDraw);
+        boolean triggerGlobalLayoutListener = didLayout
+                || mAttachInfo.mRecomputeGlobalAttributes;
+        if (didLayout) {
+            performLayout(lp, mWidth, mHeight);
+
+            // By this point all views have been sized and positioned
+            // We can compute the transparent area
+
+            if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
+                // start out transparent
+                // TODO: AVOID THAT CALL BY CACHING THE RESULT?
+                host.getLocationInWindow(mTmpLocation);
+                mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1],
+                        mTmpLocation[0] + host.mRight - host.mLeft,
+                        mTmpLocation[1] + host.mBottom - host.mTop);
+
+                host.gatherTransparentRegion(mTransparentRegion);
+                if (mTranslator != null) {
+                    mTranslator.translateRegionInWindowToScreen(mTransparentRegion);
+                }
+
+                if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
+                    mPreviousTransparentRegion.set(mTransparentRegion);
+                    mFullRedrawNeeded = true;
+                    // TODO: Ideally we would do this in prepareSurfaces,
+                    // but prepareSurfaces is currently working under
+                    // the assumption that we paused the render thread
+                    // via the WM relayout code path. We probably eventually
+                    // want to synchronize transparent region hint changes
+                    // with draws.
+                    SurfaceControl sc = getSurfaceControl();
+                    if (sc.isValid()) {
+                        mTransaction.setTransparentRegionHint(sc, mTransparentRegion).apply();
+                    }
+                }
+            }
+
+            if (DBG) {
+                System.out.println("======================================");
+                System.out.println("performTraversals -- after setFrame");
+                host.debug();
+            }
+        }
+
+        // These callbacks will trigger SurfaceView SurfaceHolder.Callbacks and must be invoked
+        // after the measure pass. If its invoked before the measure pass and the app modifies
+        // the view hierarchy in the callbacks, we could leave the views in a broken state.
+        if (surfaceCreated) {
+            notifySurfaceCreated();
+        } else if (surfaceReplaced) {
+            notifySurfaceReplaced();
+        } else if (surfaceDestroyed)  {
+            notifySurfaceDestroyed();
+        }
+
+        if (triggerGlobalLayoutListener) {
+            mAttachInfo.mRecomputeGlobalAttributes = false;
+            mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
+        }
+
+        if (computesInternalInsets) {
+            // Clear the original insets.
+            final ViewTreeObserver.InternalInsetsInfo insets = mAttachInfo.mGivenInternalInsets;
+            insets.reset();
+
+            // Compute new insets in place.
+            mAttachInfo.mTreeObserver.dispatchOnComputeInternalInsets(insets);
+            mAttachInfo.mHasNonEmptyGivenInternalInsets = !insets.isEmpty();
+
+            // Tell the window manager.
+            if (insetsPending || !mLastGivenInsets.equals(insets)) {
+                mLastGivenInsets.set(insets);
+
+                // Translate insets to screen coordinates if needed.
+                final Rect contentInsets;
+                final Rect visibleInsets;
+                final Region touchableRegion;
+                if (mTranslator != null) {
+                    contentInsets = mTranslator.getTranslatedContentInsets(insets.contentInsets);
+                    visibleInsets = mTranslator.getTranslatedVisibleInsets(insets.visibleInsets);
+                    touchableRegion = mTranslator.getTranslatedTouchableArea(insets.touchableRegion);
+                } else {
+                    contentInsets = insets.contentInsets;
+                    visibleInsets = insets.visibleInsets;
+                    touchableRegion = insets.touchableRegion;
+                }
+
+                try {
+                    mWindowSession.setInsets(mWindow, insets.mTouchableInsets,
+                            contentInsets, visibleInsets, touchableRegion);
+                } catch (RemoteException e) {
+                }
+            }
+        }
+
+        if (mFirst) {
+            if (sAlwaysAssignFocus || !isInTouchMode()) {
+                // handle first focus request
+                if (DEBUG_INPUT_RESIZE) {
+                    Log.v(mTag, "First: mView.hasFocus()=" + mView.hasFocus());
+                }
+                if (mView != null) {
+                    if (!mView.hasFocus()) {
+                        mView.restoreDefaultFocus();
+                        if (DEBUG_INPUT_RESIZE) {
+                            Log.v(mTag, "First: requested focused view=" + mView.findFocus());
+                        }
+                    } else {
+                        if (DEBUG_INPUT_RESIZE) {
+                            Log.v(mTag, "First: existing focused view=" + mView.findFocus());
+                        }
+                    }
+                }
+            } else {
+                // Some views (like ScrollView) won't hand focus to descendants that aren't within
+                // their viewport. Before layout, there's a good change these views are size 0
+                // which means no children can get focus. After layout, this view now has size, but
+                // is not guaranteed to hand-off focus to a focusable child (specifically, the edge-
+                // case where the child has a size prior to layout and thus won't trigger
+                // focusableViewAvailable).
+                View focused = mView.findFocus();
+                if (focused instanceof ViewGroup
+                        && ((ViewGroup) focused).getDescendantFocusability()
+                                == ViewGroup.FOCUS_AFTER_DESCENDANTS) {
+                    focused.restoreDefaultFocus();
+                }
+            }
+        }
+
+        final boolean changedVisibility = (viewVisibilityChanged || mFirst) && isViewVisible;
+        final boolean hasWindowFocus = mAttachInfo.mHasWindowFocus && isViewVisible;
+        final boolean regainedFocus = hasWindowFocus && mLostWindowFocus;
+        if (regainedFocus) {
+            mLostWindowFocus = false;
+        } else if (!hasWindowFocus && mHadWindowFocus) {
+            mLostWindowFocus = true;
+        }
+
+        if (changedVisibility || regainedFocus) {
+            // Toasts are presented as notifications - don't present them as windows as well
+            boolean isToast = mWindowAttributes.type == TYPE_TOAST;
+            if (!isToast) {
+                host.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+            }
+        }
+
+        mFirst = false;
+        mWillDrawSoon = false;
+        mNewSurfaceNeeded = false;
+        mActivityRelaunched = false;
+        mViewVisibility = viewVisibility;
+        mHadWindowFocus = hasWindowFocus;
+
+        mImeFocusController.onTraversal(hasWindowFocus, mWindowAttributes);
+
+        // Remember if we must report the next draw.
+        if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
+            reportNextDraw();
+        }
+
+        boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
+
+        if (!cancelDraw) {
+            if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
+                for (int i = 0; i < mPendingTransitions.size(); ++i) {
+                    mPendingTransitions.get(i).startChangingAnimations();
+                }
+                mPendingTransitions.clear();
+            }
+            performDraw();
+        } else {
+            if (isViewVisible) {
+                // Try again
+                scheduleTraversals();
+            } else {
+                if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
+                    for (int i = 0; i < mPendingTransitions.size(); ++i) {
+                        mPendingTransitions.get(i).endChangingAnimations();
+                    }
+                    mPendingTransitions.clear();
+                }
+
+                // We may never draw since it's not visible. Report back that we're finished
+                // drawing.
+                if (!wasReportNextDraw && mReportNextDraw) {
+                    mReportNextDraw = false;
+                    pendingDrawFinished();
+                }
+            }
+        }
+
+        if (mAttachInfo.mContentCaptureEvents != null) {
+            notifyContentCatpureEvents();
+        }
+
+        mIsInTraversal = false;
+    }
+
+    private void notifyContentCatpureEvents() {
+        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "notifyContentCaptureEvents");
+        try {
+            MainContentCaptureSession mainSession = mAttachInfo.mContentCaptureManager
+                    .getMainContentCaptureSession();
+            for (int i = 0; i < mAttachInfo.mContentCaptureEvents.size(); i++) {
+                int sessionId = mAttachInfo.mContentCaptureEvents.keyAt(i);
+                mainSession.notifyViewTreeEvent(sessionId, /* started= */ true);
+                ArrayList<Object> events = mAttachInfo.mContentCaptureEvents
+                        .valueAt(i);
+                for_each_event: for (int j = 0; j < events.size(); j++) {
+                    Object event = events.get(j);
+                    if (event instanceof AutofillId) {
+                        mainSession.notifyViewDisappeared(sessionId, (AutofillId) event);
+                    } else if (event instanceof View) {
+                        View view = (View) event;
+                        ContentCaptureSession session = view.getContentCaptureSession();
+                        if (session == null) {
+                            Log.w(mTag, "no content capture session on view: " + view);
+                            continue for_each_event;
+                        }
+                        int actualId = session.getId();
+                        if (actualId != sessionId) {
+                            Log.w(mTag, "content capture session mismatch for view (" + view
+                                    + "): was " + sessionId + " before, it's " + actualId + " now");
+                            continue for_each_event;
+                        }
+                        ViewStructure structure = session.newViewStructure(view);
+                        view.onProvideContentCaptureStructure(structure, /* flags= */ 0);
+                        session.notifyViewAppeared(structure);
+                    } else if (event instanceof Insets) {
+                        mainSession.notifyViewInsetsChanged(sessionId, (Insets) event);
+                    } else {
+                        Log.w(mTag, "invalid content capture event: " + event);
+                    }
+                }
+                mainSession.notifyViewTreeEvent(sessionId, /* started= */ false);
+            }
+            mAttachInfo.mContentCaptureEvents = null;
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        }
+    }
+
+    private void notifyHolderSurfaceDestroyed() {
+        mSurfaceHolder.ungetCallbacks();
+        SurfaceHolder.Callback[] callbacks = mSurfaceHolder.getCallbacks();
+        if (callbacks != null) {
+            for (SurfaceHolder.Callback c : callbacks) {
+                c.surfaceDestroyed(mSurfaceHolder);
+            }
+        }
+    }
+
+    private void maybeHandleWindowMove(Rect frame) {
+        // TODO: Well, we are checking whether the frame has changed similarly
+        // to how this is done for the insets. This is however incorrect since
+        // the insets and the frame are translated. For example, the old frame
+        // was (1, 1 - 1, 1) and was translated to say (2, 2 - 2, 2), now the new
+        // reported frame is (2, 2 - 2, 2) which implies no change but this is not
+        // true since we are comparing a not translated value to a translated one.
+        // This scenario is rare but we may want to fix that.
+
+        final boolean windowMoved = mAttachInfo.mWindowLeft != frame.left
+                || mAttachInfo.mWindowTop != frame.top;
+        if (windowMoved) {
+            mAttachInfo.mWindowLeft = frame.left;
+            mAttachInfo.mWindowTop = frame.top;
+        }
+        if (windowMoved || mAttachInfo.mNeedsUpdateLightCenter) {
+            // Update the light position for the new offsets.
+            if (mAttachInfo.mThreadedRenderer != null) {
+                mAttachInfo.mThreadedRenderer.setLightCenter(mAttachInfo);
+            }
+            mAttachInfo.mNeedsUpdateLightCenter = false;
+        }
+    }
+
+    private void handleWindowFocusChanged() {
+        final boolean hasWindowFocus;
+        final boolean inTouchMode;
+        synchronized (this) {
+            if (!mWindowFocusChanged) {
+                return;
+            }
+            mWindowFocusChanged = false;
+            hasWindowFocus = mUpcomingWindowFocus;
+            inTouchMode = mUpcomingInTouchMode;
+        }
+        // TODO (b/131181940): Make sure this doesn't leak Activity with mActivityConfigCallback
+        // config changes.
+        if (hasWindowFocus) {
+            mInsetsController.onWindowFocusGained(
+                    getFocusedViewOrNull() != null /* hasViewFocused */);
+        } else {
+            mInsetsController.onWindowFocusLost();
+        }
+
+        if (mAdded) {
+            profileRendering(hasWindowFocus);
+
+            if (hasWindowFocus) {
+                ensureTouchModeLocally(inTouchMode);
+                if (mAttachInfo.mThreadedRenderer != null && mSurface.isValid()) {
+                    mFullRedrawNeeded = true;
+                    try {
+                        final Rect surfaceInsets = mWindowAttributes.surfaceInsets;
+                        mAttachInfo.mThreadedRenderer.initializeIfNeeded(
+                                mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
+                    } catch (OutOfResourcesException e) {
+                        Log.e(mTag, "OutOfResourcesException locking surface", e);
+                        try {
+                            if (!mWindowSession.outOfMemory(mWindow)) {
+                                Slog.w(mTag, "No processes killed for memory;"
+                                        + " killing self");
+                                Process.killProcess(Process.myPid());
+                            }
+                        } catch (RemoteException ex) {
+                        }
+                        // Retry in a bit.
+                        mHandler.sendMessageDelayed(mHandler.obtainMessage(
+                                MSG_WINDOW_FOCUS_CHANGED), 500);
+                        return;
+                    }
+                }
+            }
+
+            mAttachInfo.mHasWindowFocus = hasWindowFocus;
+            mImeFocusController.updateImeFocusable(mWindowAttributes, true /* force */);
+            mImeFocusController.onPreWindowFocus(hasWindowFocus, mWindowAttributes);
+
+            if (mView != null) {
+                mAttachInfo.mKeyDispatchState.reset();
+                mView.dispatchWindowFocusChanged(hasWindowFocus);
+                mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
+                if (mAttachInfo.mTooltipHost != null) {
+                    mAttachInfo.mTooltipHost.hideTooltip();
+                }
+            }
+
+            // Note: must be done after the focus change callbacks,
+            // so all of the view state is set up correctly.
+            mImeFocusController.onPostWindowFocus(
+                    getFocusedViewOrNull(), hasWindowFocus, mWindowAttributes);
+
+            if (hasWindowFocus) {
+                // Clear the forward bit.  We can just do this directly, since
+                // the window manager doesn't care about it.
+                mWindowAttributes.softInputMode &=
+                        ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
+                ((WindowManager.LayoutParams) mView.getLayoutParams())
+                        .softInputMode &=
+                        ~WindowManager.LayoutParams
+                                .SOFT_INPUT_IS_FORWARD_NAVIGATION;
+
+                // Refocusing a window that has a focused view should fire a
+                // focus event for the view since the global focused view changed.
+                fireAccessibilityFocusEventIfHasFocusedNode();
+            } else {
+                if (mPointerCapture) {
+                    handlePointerCaptureChanged(false);
+                }
+            }
+        }
+        mFirstInputStage.onWindowFocusChanged(hasWindowFocus);
+
+        // NOTE: there's no view visibility (appeared / disapparead) events when the windows focus
+        // is lost, so we don't need to to force a flush - there might be other events such as
+        // text changes, but these should be flushed independently.
+        if (hasWindowFocus) {
+            handleContentCaptureFlush();
+        }
+    }
+
+    private void fireAccessibilityFocusEventIfHasFocusedNode() {
+        if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
+            return;
+        }
+        final View focusedView = mView.findFocus();
+        if (focusedView == null) {
+            return;
+        }
+        final AccessibilityNodeProvider provider = focusedView.getAccessibilityNodeProvider();
+        if (provider == null) {
+            focusedView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+        } else {
+            final AccessibilityNodeInfo focusedNode = findFocusedVirtualNode(provider);
+            if (focusedNode != null) {
+                final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(
+                        focusedNode.getSourceNodeId());
+                // This is a best effort since clearing and setting the focus via the
+                // provider APIs could have side effects. We don't have a provider API
+                // similar to that on View to ask a given event to be fired.
+                final AccessibilityEvent event = AccessibilityEvent.obtain(
+                        AccessibilityEvent.TYPE_VIEW_FOCUSED);
+                event.setSource(focusedView, virtualId);
+                event.setPackageName(focusedNode.getPackageName());
+                event.setChecked(focusedNode.isChecked());
+                event.setContentDescription(focusedNode.getContentDescription());
+                event.setPassword(focusedNode.isPassword());
+                event.getText().add(focusedNode.getText());
+                event.setEnabled(focusedNode.isEnabled());
+                focusedView.getParent().requestSendAccessibilityEvent(focusedView, event);
+                focusedNode.recycle();
+            }
+        }
+    }
+
+    private AccessibilityNodeInfo findFocusedVirtualNode(AccessibilityNodeProvider provider) {
+        AccessibilityNodeInfo focusedNode = provider.findFocus(
+                AccessibilityNodeInfo.FOCUS_INPUT);
+        if (focusedNode != null) {
+            return focusedNode;
+        }
+
+        if (!mContext.isAutofillCompatibilityEnabled()) {
+            return null;
+        }
+
+        // Unfortunately some provider implementations don't properly
+        // implement AccessibilityNodeProvider#findFocus
+        AccessibilityNodeInfo current = provider.createAccessibilityNodeInfo(
+                AccessibilityNodeProvider.HOST_VIEW_ID);
+        if (current.isFocused()) {
+            return current;
+        }
+
+        final Queue<AccessibilityNodeInfo> fringe = new LinkedList<>();
+        fringe.offer(current);
+
+        while (!fringe.isEmpty()) {
+            current = fringe.poll();
+            final LongArray childNodeIds = current.getChildNodeIds();
+            if (childNodeIds== null || childNodeIds.size() <= 0) {
+                continue;
+            }
+            final int childCount = childNodeIds.size();
+            for (int i = 0; i < childCount; i++) {
+                final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(
+                        childNodeIds.get(i));
+                final AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(virtualId);
+                if (child != null) {
+                    if (child.isFocused()) {
+                        return child;
+                    }
+                    fringe.offer(child);
+                }
+            }
+            current.recycle();
+        }
+
+        return null;
+    }
+
+    private void handleOutOfResourcesException(Surface.OutOfResourcesException e) {
+        Log.e(mTag, "OutOfResourcesException initializing HW surface", e);
+        try {
+            if (!mWindowSession.outOfMemory(mWindow) &&
+                    Process.myUid() != Process.SYSTEM_UID) {
+                Slog.w(mTag, "No processes killed for memory; killing self");
+                Process.killProcess(Process.myPid());
+            }
+        } catch (RemoteException ex) {
+        }
+        mLayoutRequested = true;    // ask wm for a new surface next time.
+    }
+
+    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
+        if (mView == null) {
+            return;
+        }
+        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
+        try {
+            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        }
+    }
+
+    /**
+     * Called by {@link android.view.View#isInLayout()} to determine whether the view hierarchy
+     * is currently undergoing a layout pass.
+     *
+     * @return whether the view hierarchy is currently undergoing a layout pass
+     */
+    boolean isInLayout() {
+        return mInLayout;
+    }
+
+    /**
+     * Called by {@link android.view.View#requestLayout()} if the view hierarchy is currently
+     * undergoing a layout pass. requestLayout() should not generally be called during layout,
+     * unless the container hierarchy knows what it is doing (i.e., it is fine as long as
+     * all children in that container hierarchy are measured and laid out at the end of the layout
+     * pass for that container). If requestLayout() is called anyway, we handle it correctly
+     * by registering all requesters during a frame as it proceeds. At the end of the frame,
+     * we check all of those views to see if any still have pending layout requests, which
+     * indicates that they were not correctly handled by their container hierarchy. If that is
+     * the case, we clear all such flags in the tree, to remove the buggy flag state that leads
+     * to blank containers, and force a second request/measure/layout pass in this frame. If
+     * more requestLayout() calls are received during that second layout pass, we post those
+     * requests to the next frame to avoid possible infinite loops.
+     *
+     * <p>The return value from this method indicates whether the request should proceed
+     * (if it is a request during the first layout pass) or should be skipped and posted to the
+     * next frame (if it is a request during the second layout pass).</p>
+     *
+     * @param view the view that requested the layout.
+     *
+     * @return true if request should proceed, false otherwise.
+     */
+    boolean requestLayoutDuringLayout(final View view) {
+        if (view.mParent == null || view.mAttachInfo == null) {
+            // Would not normally trigger another layout, so just let it pass through as usual
+            return true;
+        }
+        if (!mLayoutRequesters.contains(view)) {
+            mLayoutRequesters.add(view);
+        }
+        if (!mHandlingLayoutInLayoutRequest) {
+            // Let the request proceed normally; it will be processed in a second layout pass
+            // if necessary
+            return true;
+        } else {
+            // Don't let the request proceed during the second layout pass.
+            // It will post to the next frame instead.
+            return false;
+        }
+    }
+
+    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
+            int desiredWindowHeight) {
+        mScrollMayChange = true;
+        mInLayout = true;
+
+        final View host = mView;
+        if (host == null) {
+            return;
+        }
+        if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
+            Log.v(mTag, "Laying out " + host + " to (" +
+                    host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
+        }
+
+        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
+        try {
+            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
+
+            mInLayout = false;
+            int numViewsRequestingLayout = mLayoutRequesters.size();
+            if (numViewsRequestingLayout > 0) {
+                // requestLayout() was called during layout.
+                // If no layout-request flags are set on the requesting views, there is no problem.
+                // If some requests are still pending, then we need to clear those flags and do
+                // a full request/measure/layout pass to handle this situation.
+                ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
+                        false);
+                if (validLayoutRequesters != null) {
+                    // Set this flag to indicate that any further requests are happening during
+                    // the second pass, which may result in posting those requests to the next
+                    // frame instead
+                    mHandlingLayoutInLayoutRequest = true;
+
+                    // Process fresh layout requests, then measure and layout
+                    int numValidRequests = validLayoutRequesters.size();
+                    for (int i = 0; i < numValidRequests; ++i) {
+                        final View view = validLayoutRequesters.get(i);
+                        Log.w("View", "requestLayout() improperly called by " + view +
+                                " during layout: running second layout pass");
+                        view.requestLayout();
+                    }
+                    measureHierarchy(host, lp, mView.getContext().getResources(),
+                            desiredWindowWidth, desiredWindowHeight);
+                    mInLayout = true;
+                    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
+
+                    mHandlingLayoutInLayoutRequest = false;
+
+                    // Check the valid requests again, this time without checking/clearing the
+                    // layout flags, since requests happening during the second pass get noop'd
+                    validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
+                    if (validLayoutRequesters != null) {
+                        final ArrayList<View> finalRequesters = validLayoutRequesters;
+                        // Post second-pass requests to the next frame
+                        getRunQueue().post(new Runnable() {
+                            @Override
+                            public void run() {
+                                int numValidRequests = finalRequesters.size();
+                                for (int i = 0; i < numValidRequests; ++i) {
+                                    final View view = finalRequesters.get(i);
+                                    Log.w("View", "requestLayout() improperly called by " + view +
+                                            " during second layout pass: posting in next frame");
+                                    view.requestLayout();
+                                }
+                            }
+                        });
+                    }
+                }
+
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        }
+        mInLayout = false;
+    }
+
+    /**
+     * This method is called during layout when there have been calls to requestLayout() during
+     * layout. It walks through the list of views that requested layout to determine which ones
+     * still need it, based on visibility in the hierarchy and whether they have already been
+     * handled (as is usually the case with ListView children).
+     *
+     * @param layoutRequesters The list of views that requested layout during layout
+     * @param secondLayoutRequests Whether the requests were issued during the second layout pass.
+     * If so, the FORCE_LAYOUT flag was not set on requesters.
+     * @return A list of the actual views that still need to be laid out.
+     */
+    private ArrayList<View> getValidLayoutRequesters(ArrayList<View> layoutRequesters,
+            boolean secondLayoutRequests) {
+
+        int numViewsRequestingLayout = layoutRequesters.size();
+        ArrayList<View> validLayoutRequesters = null;
+        for (int i = 0; i < numViewsRequestingLayout; ++i) {
+            View view = layoutRequesters.get(i);
+            if (view != null && view.mAttachInfo != null && view.mParent != null &&
+                    (secondLayoutRequests || (view.mPrivateFlags & View.PFLAG_FORCE_LAYOUT) ==
+                            View.PFLAG_FORCE_LAYOUT)) {
+                boolean gone = false;
+                View parent = view;
+                // Only trigger new requests for views in a non-GONE hierarchy
+                while (parent != null) {
+                    if ((parent.mViewFlags & View.VISIBILITY_MASK) == View.GONE) {
+                        gone = true;
+                        break;
+                    }
+                    if (parent.mParent instanceof View) {
+                        parent = (View) parent.mParent;
+                    } else {
+                        parent = null;
+                    }
+                }
+                if (!gone) {
+                    if (validLayoutRequesters == null) {
+                        validLayoutRequesters = new ArrayList<View>();
+                    }
+                    validLayoutRequesters.add(view);
+                }
+            }
+        }
+        if (!secondLayoutRequests) {
+            // If we're checking the layout flags, then we need to clean them up also
+            for (int i = 0; i < numViewsRequestingLayout; ++i) {
+                View view = layoutRequesters.get(i);
+                while (view != null &&
+                        (view.mPrivateFlags & View.PFLAG_FORCE_LAYOUT) != 0) {
+                    view.mPrivateFlags &= ~View.PFLAG_FORCE_LAYOUT;
+                    if (view.mParent instanceof View) {
+                        view = (View) view.mParent;
+                    } else {
+                        view = null;
+                    }
+                }
+            }
+        }
+        layoutRequesters.clear();
+        return validLayoutRequesters;
+    }
+
+    @Override
+    public void requestTransparentRegion(View child) {
+        // the test below should not fail unless someone is messing with us
+        checkThread();
+        if (mView != child) {
+            return;
+        }
+
+        if ((mView.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0) {
+            mView.mPrivateFlags |= View.PFLAG_REQUEST_TRANSPARENT_REGIONS;
+            // Need to make sure we re-evaluate the window attributes next
+            // time around, to ensure the window has the correct format.
+            mWindowAttributesChanged = true;
+        }
+
+        // Always request layout to apply the latest transparent region.
+        requestLayout();
+    }
+
+    /**
+     * Figures out the measure spec for the root view in a window based on it's
+     * layout params.
+     *
+     * @param windowSize
+     *            The available width or height of the window
+     *
+     * @param rootDimension
+     *            The layout params for one dimension (width or height) of the
+     *            window.
+     *
+     * @return The measure spec to use to measure the root view.
+     */
+    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
+        int measureSpec;
+        switch (rootDimension) {
+
+        case ViewGroup.LayoutParams.MATCH_PARENT:
+            // Window can't resize. Force root view to be windowSize.
+            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
+            break;
+        case ViewGroup.LayoutParams.WRAP_CONTENT:
+            // Window can resize. Set max size for root view.
+            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
+            break;
+        default:
+            // Window wants to be an exact size. Force root view to be that size.
+            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
+            break;
+        }
+        return measureSpec;
+    }
+
+    int mHardwareXOffset;
+    int mHardwareYOffset;
+
+    @Override
+    public void onPreDraw(RecordingCanvas canvas) {
+        // If mCurScrollY is not 0 then this influences the hardwareYOffset. The end result is we
+        // can apply offsets that are not handled by anything else, resulting in underdraw as
+        // the View is shifted (thus shifting the window background) exposing unpainted
+        // content. To handle this with minimal glitches we just clear to BLACK if the window
+        // is opaque. If it's not opaque then HWUI already internally does a glClear to
+        // transparent, so there's no risk of underdraw on non-opaque surfaces.
+        if (mCurScrollY != 0 && mHardwareYOffset != 0 && mAttachInfo.mThreadedRenderer.isOpaque()) {
+            canvas.drawColor(Color.BLACK);
+        }
+        canvas.translate(-mHardwareXOffset, -mHardwareYOffset);
+    }
+
+    @Override
+    public void onPostDraw(RecordingCanvas canvas) {
+        drawAccessibilityFocusedDrawableIfNeeded(canvas);
+        if (mUseMTRenderer) {
+            for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
+                mWindowCallbacks.get(i).onPostDraw(canvas);
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    void outputDisplayList(View view) {
+        view.mRenderNode.output();
+    }
+
+    /**
+     * @see #PROPERTY_PROFILE_RENDERING
+     */
+    private void profileRendering(boolean enabled) {
+        if (mProfileRendering) {
+            mRenderProfilingEnabled = enabled;
+
+            if (mRenderProfiler != null) {
+                mChoreographer.removeFrameCallback(mRenderProfiler);
+            }
+            if (mRenderProfilingEnabled) {
+                if (mRenderProfiler == null) {
+                    mRenderProfiler = new Choreographer.FrameCallback() {
+                        @Override
+                        public void doFrame(long frameTimeNanos) {
+                            mDirty.set(0, 0, mWidth, mHeight);
+                            scheduleTraversals();
+                            if (mRenderProfilingEnabled) {
+                                mChoreographer.postFrameCallback(mRenderProfiler);
+                            }
+                        }
+                    };
+                }
+                mChoreographer.postFrameCallback(mRenderProfiler);
+            } else {
+                mRenderProfiler = null;
+            }
+        }
+    }
+
+    /**
+     * Called from draw() when DEBUG_FPS is enabled
+     */
+    private void trackFPS() {
+        // Tracks frames per second drawn. First value in a series of draws may be bogus
+        // because it down not account for the intervening idle time
+        long nowTime = System.currentTimeMillis();
+        if (mFpsStartTime < 0) {
+            mFpsStartTime = mFpsPrevTime = nowTime;
+            mFpsNumFrames = 0;
+        } else {
+            ++mFpsNumFrames;
+            String thisHash = Integer.toHexString(System.identityHashCode(this));
+            long frameTime = nowTime - mFpsPrevTime;
+            long totalTime = nowTime - mFpsStartTime;
+            Log.v(mTag, "0x" + thisHash + "\tFrame time:\t" + frameTime);
+            mFpsPrevTime = nowTime;
+            if (totalTime > 1000) {
+                float fps = (float) mFpsNumFrames * 1000 / totalTime;
+                Log.v(mTag, "0x" + thisHash + "\tFPS:\t" + fps);
+                mFpsStartTime = nowTime;
+                mFpsNumFrames = 0;
+            }
+        }
+    }
+
+    /**
+     * A count of the number of calls to pendingDrawFinished we
+     * require to notify the WM drawing is complete.
+     */
+    int mDrawsNeededToReport = 0;
+
+    /**
+     * Delay notifying WM of draw finished until
+     * a balanced call to pendingDrawFinished.
+     */
+    void drawPending() {
+        mDrawsNeededToReport++;
+    }
+
+    void pendingDrawFinished() {
+        if (mDrawsNeededToReport == 0) {
+            throw new RuntimeException("Unbalanced drawPending/pendingDrawFinished calls");
+        }
+        mDrawsNeededToReport--;
+        if (mDrawsNeededToReport == 0) {
+            reportDrawFinished();
+        } else if (DEBUG_BLAST) {
+            Log.d(mTag, "pendingDrawFinished. Waiting on draw reported mDrawsNeededToReport="
+                    + mDrawsNeededToReport);
+        }
+    }
+
+    private void postDrawFinished() {
+        mHandler.sendEmptyMessage(MSG_DRAW_FINISHED);
+    }
+
+    private void reportDrawFinished() {
+        try {
+            if (DEBUG_BLAST) {
+                Log.d(mTag, "reportDrawFinished");
+            }
+            mDrawsNeededToReport = 0;
+            mWindowSession.finishDrawing(mWindow, mSurfaceChangedTransaction);
+        } catch (RemoteException e) {
+            Log.e(mTag, "Unable to report draw finished", e);
+            mSurfaceChangedTransaction.apply();
+        } finally {
+            mSurfaceChangedTransaction.clear();
+        }
+    }
+
+    /**
+     * The callback will run on the render thread.
+     */
+    private HardwareRenderer.FrameCompleteCallback createFrameCompleteCallback(Handler handler,
+            boolean reportNextDraw, ArrayList<Runnable> commitCallbacks) {
+        return frameNr -> {
+            if (DEBUG_BLAST) {
+                Log.d(mTag, "Received frameCompleteCallback frameNum=" + frameNr);
+            }
+
+            handler.postAtFrontOfQueue(() -> {
+                if (mNextDrawUseBlastSync) {
+                    // We don't need to synchronize mRtBLASTSyncTransaction here since we're
+                    // guaranteed that this is called after onFrameDraw and mNextDrawUseBlastSync
+                    // is only true when the UI thread is paused. Therefore, no one should be
+                    // modifying this object until the next vsync.
+                    mSurfaceChangedTransaction.merge(mRtBLASTSyncTransaction);
+                }
+
+                if (reportNextDraw) {
+                    // TODO: Use the frame number
+                    pendingDrawFinished();
+                }
+                if (commitCallbacks != null) {
+                    for (int i = 0; i < commitCallbacks.size(); i++) {
+                        commitCallbacks.get(i).run();
+                    }
+                }
+            });
+        };
+    }
+
+    /**
+     * @hide
+     */
+    public boolean isHardwareEnabled() {
+        return mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled();
+    }
+
+    private boolean addFrameCompleteCallbackIfNeeded() {
+        if (!isHardwareEnabled()) {
+            return false;
+        }
+
+        ArrayList<Runnable> commitCallbacks = mAttachInfo.mTreeObserver
+                .captureFrameCommitCallbacks();
+        final boolean needFrameCompleteCallback =
+                mNextDrawUseBlastSync || mReportNextDraw
+                        || (commitCallbacks != null && commitCallbacks.size() > 0);
+        if (needFrameCompleteCallback) {
+            if (DEBUG_BLAST) {
+                Log.d(mTag, "Creating frameCompleteCallback"
+                        + " mNextDrawUseBlastSync=" + mNextDrawUseBlastSync
+                        + " mReportNextDraw=" + mReportNextDraw
+                        + " commitCallbacks size="
+                        + (commitCallbacks == null ? 0 : commitCallbacks.size()));
+            }
+            mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(
+                    createFrameCompleteCallback(mAttachInfo.mHandler, mReportNextDraw,
+                            commitCallbacks));
+            return true;
+        }
+        return false;
+    }
+
+    private void addFrameCallbackIfNeeded() {
+        final boolean nextDrawUseBlastSync = mNextDrawUseBlastSync;
+        final boolean reportNextDraw = mReportNextDraw;
+        final boolean hasBlurUpdates = mBlurRegionAggregator.hasUpdates();
+        final boolean needsCallbackForBlur = hasBlurUpdates || mBlurRegionAggregator.hasRegions();
+
+        if (!nextDrawUseBlastSync && !reportNextDraw && !needsCallbackForBlur) {
+            return;
+        }
+
+        if (DEBUG_BLAST) {
+            Log.d(mTag, "Creating frameDrawingCallback"
+                    + " nextDrawUseBlastSync=" + nextDrawUseBlastSync
+                    + " reportNextDraw=" + reportNextDraw
+                    + " hasBlurUpdates=" + hasBlurUpdates);
+        }
+        mWaitForBlastSyncComplete = nextDrawUseBlastSync;
+        final BackgroundBlurDrawable.BlurRegion[] blurRegionsForFrame =
+                needsCallbackForBlur ?  mBlurRegionAggregator.getBlurRegionsCopyForRT() : null;
+
+        // The callback will run on the render thread.
+        HardwareRenderer.FrameDrawingCallback frameDrawingCallback = frame -> {
+            if (DEBUG_BLAST) {
+                Log.d(mTag, "Received frameDrawingCallback frameNum=" + frame + "."
+                        + " Creating transactionCompleteCallback=" + nextDrawUseBlastSync);
+            }
+
+            if (needsCallbackForBlur) {
+                mBlurRegionAggregator
+                    .dispatchBlurTransactionIfNeeded(frame, blurRegionsForFrame, hasBlurUpdates);
+            }
+
+            if (mBlastBufferQueue == null) {
+                return;
+            }
+
+            if (nextDrawUseBlastSync) {
+                // Frame callbacks will always occur after submitting draw requests and before
+                // the draw actually occurs. This will ensure that we set the next transaction
+                // for the frame that's about to get drawn and not on a previous frame that.
+
+                // We don't need to synchronize mRtBLASTSyncTransaction here since it's not
+                // being modified and only sent to BlastBufferQueue.
+                mBlastBufferQueue.setNextTransaction(mRtBLASTSyncTransaction);
+
+                mBlastBufferQueue.setTransactionCompleteCallback(frame, frameNumber -> {
+                    if (DEBUG_BLAST) {
+                        Log.d(mTag, "Received transactionCompleteCallback frameNum=" + frame);
+                    }
+                    mHandler.postAtFrontOfQueue(() -> {
+                        mNextDrawUseBlastSync = false;
+                        mWaitForBlastSyncComplete = false;
+                        if (DEBUG_BLAST) {
+                            Log.d(mTag, "Scheduling a traversal=" + mRequestedTraverseWhilePaused
+                                    + " due to a previous skipped traversal.");
+                        }
+                        if (mRequestedTraverseWhilePaused) {
+                            mRequestedTraverseWhilePaused = false;
+                            scheduleTraversals();
+                        }
+                    });
+                });
+            } else if (reportNextDraw) {
+                // If we need to report next draw, wait for adapter to flush its shadow queue
+                // by processing previously queued buffers so that we can submit the
+                // transaction a timely manner.
+                mBlastBufferQueue.flushShadowQueue();
+            }
+        };
+        registerRtFrameCallback(frameDrawingCallback);
+    }
+
+    private void performDraw() {
+        if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
+            return;
+        } else if (mView == null) {
+            return;
+        }
+
+        final boolean fullRedrawNeeded =
+                mFullRedrawNeeded || mReportNextDraw || mNextDrawUseBlastSync;
+        mFullRedrawNeeded = false;
+
+        mIsDrawing = true;
+        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
+
+        boolean usingAsyncReport = addFrameCompleteCallbackIfNeeded();
+        addFrameCallbackIfNeeded();
+
+        try {
+            boolean canUseAsync = draw(fullRedrawNeeded);
+            if (usingAsyncReport && !canUseAsync) {
+                mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
+                usingAsyncReport = false;
+            }
+        } finally {
+            mIsDrawing = false;
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        }
+
+        // For whatever reason we didn't create a HardwareRenderer, end any
+        // hardware animations that are now dangling
+        if (mAttachInfo.mPendingAnimatingRenderNodes != null) {
+            final int count = mAttachInfo.mPendingAnimatingRenderNodes.size();
+            for (int i = 0; i < count; i++) {
+                mAttachInfo.mPendingAnimatingRenderNodes.get(i).endAllAnimators();
+            }
+            mAttachInfo.mPendingAnimatingRenderNodes.clear();
+        }
+
+        if (mReportNextDraw) {
+            mReportNextDraw = false;
+
+            // if we're using multi-thread renderer, wait for the window frame draws
+            if (mWindowDrawCountDown != null) {
+                try {
+                    mWindowDrawCountDown.await();
+                } catch (InterruptedException e) {
+                    Log.e(mTag, "Window redraw count down interrupted!");
+                }
+                mWindowDrawCountDown = null;
+            }
+
+            if (mAttachInfo.mThreadedRenderer != null) {
+                mAttachInfo.mThreadedRenderer.setStopped(mStopped);
+            }
+
+            if (LOCAL_LOGV) {
+                Log.v(mTag, "FINISHED DRAWING: " + mWindowAttributes.getTitle());
+            }
+
+            if (mSurfaceHolder != null && mSurface.isValid()) {
+                SurfaceCallbackHelper sch = new SurfaceCallbackHelper(this::postDrawFinished);
+                SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
+
+                sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
+            } else if (!usingAsyncReport) {
+                if (mAttachInfo.mThreadedRenderer != null) {
+                    mAttachInfo.mThreadedRenderer.fence();
+                }
+                pendingDrawFinished();
+            }
+        }
+        if (mPerformContentCapture) {
+            performContentCaptureInitialReport();
+        }
+    }
+
+    /**
+     * Checks (and caches) if content capture is enabled for this context.
+     */
+    private boolean isContentCaptureEnabled() {
+        switch (mContentCaptureEnabled) {
+            case CONTENT_CAPTURE_ENABLED_TRUE:
+                return true;
+            case CONTENT_CAPTURE_ENABLED_FALSE:
+                return false;
+            case CONTENT_CAPTURE_ENABLED_NOT_CHECKED:
+                final boolean reallyEnabled = isContentCaptureReallyEnabled();
+                mContentCaptureEnabled = reallyEnabled ? CONTENT_CAPTURE_ENABLED_TRUE
+                        : CONTENT_CAPTURE_ENABLED_FALSE;
+                return reallyEnabled;
+            default:
+                Log.w(TAG, "isContentCaptureEnabled(): invalid state " + mContentCaptureEnabled);
+                return false;
+        }
+
+    }
+
+    /**
+     * Checks (without caching) if content capture is enabled for this context.
+     */
+    private boolean isContentCaptureReallyEnabled() {
+        // First check if context supports it, so it saves a service lookup when it doesn't
+        if (mContext.getContentCaptureOptions() == null) return false;
+
+        final ContentCaptureManager ccm = mAttachInfo.getContentCaptureManager(mContext);
+        // Then check if it's enabled in the contex itself.
+        if (ccm == null || !ccm.isContentCaptureEnabled()) return false;
+
+        return true;
+    }
+
+    private void performContentCaptureInitialReport() {
+        mPerformContentCapture = false; // One-time offer!
+        final View rootView = mView;
+        if (DEBUG_CONTENT_CAPTURE) {
+            Log.v(mTag, "performContentCaptureInitialReport() on " + rootView);
+        }
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "dispatchContentCapture() for "
+                    + getClass().getSimpleName());
+        }
+        try {
+            if (!isContentCaptureEnabled()) return;
+
+            // Content capture is a go!
+            rootView.dispatchInitialProvideContentCaptureStructure();
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        }
+    }
+
+    private void handleContentCaptureFlush() {
+        if (DEBUG_CONTENT_CAPTURE) {
+            Log.v(mTag, "handleContentCaptureFlush()");
+        }
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "flushContentCapture for "
+                    + getClass().getSimpleName());
+        }
+        try {
+            if (!isContentCaptureEnabled()) return;
+
+            final ContentCaptureManager ccm = mAttachInfo.mContentCaptureManager;
+            if (ccm == null) {
+                Log.w(TAG, "No ContentCapture on AttachInfo");
+                return;
+            }
+            ccm.flush(ContentCaptureSession.FLUSH_REASON_VIEW_ROOT_ENTERED);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        }
+    }
+
+    private boolean draw(boolean fullRedrawNeeded) {
+        Surface surface = mSurface;
+        if (!surface.isValid()) {
+            return false;
+        }
+
+        if (DEBUG_FPS) {
+            trackFPS();
+        }
+
+        if (!sFirstDrawComplete) {
+            synchronized (sFirstDrawHandlers) {
+                sFirstDrawComplete = true;
+                final int count = sFirstDrawHandlers.size();
+                for (int i = 0; i< count; i++) {
+                    mHandler.post(sFirstDrawHandlers.get(i));
+                }
+            }
+        }
+
+        scrollToRectOrFocus(null, false);
+
+        if (mAttachInfo.mViewScrollChanged) {
+            mAttachInfo.mViewScrollChanged = false;
+            mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
+        }
+
+        boolean animating = mScroller != null && mScroller.computeScrollOffset();
+        final int curScrollY;
+        if (animating) {
+            curScrollY = mScroller.getCurrY();
+        } else {
+            curScrollY = mScrollY;
+        }
+        if (mCurScrollY != curScrollY) {
+            mCurScrollY = curScrollY;
+            fullRedrawNeeded = true;
+            if (mView instanceof RootViewSurfaceTaker) {
+                ((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
+            }
+        }
+
+        final float appScale = mAttachInfo.mApplicationScale;
+        final boolean scalingRequired = mAttachInfo.mScalingRequired;
+
+        final Rect dirty = mDirty;
+        if (mSurfaceHolder != null) {
+            // The app owns the surface, we won't draw.
+            dirty.setEmpty();
+            if (animating && mScroller != null) {
+                mScroller.abortAnimation();
+            }
+            return false;
+        }
+
+        if (fullRedrawNeeded) {
+            dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
+        }
+
+        if (DEBUG_ORIENTATION || DEBUG_DRAW) {
+            Log.v(mTag, "Draw " + mView + "/"
+                    + mWindowAttributes.getTitle()
+                    + ": dirty={" + dirty.left + "," + dirty.top
+                    + "," + dirty.right + "," + dirty.bottom + "} surface="
+                    + surface + " surface.isValid()=" + surface.isValid() + ", appScale:" +
+                    appScale + ", width=" + mWidth + ", height=" + mHeight);
+        }
+
+        mAttachInfo.mTreeObserver.dispatchOnDraw();
+
+        int xOffset = -mCanvasOffsetX;
+        int yOffset = -mCanvasOffsetY + curScrollY;
+        final WindowManager.LayoutParams params = mWindowAttributes;
+        final Rect surfaceInsets = params != null ? params.surfaceInsets : null;
+        if (surfaceInsets != null) {
+            xOffset -= surfaceInsets.left;
+            yOffset -= surfaceInsets.top;
+
+            // Offset dirty rect for surface insets.
+            dirty.offset(surfaceInsets.left, surfaceInsets.top);
+        }
+
+        boolean accessibilityFocusDirty = false;
+        final Drawable drawable = mAttachInfo.mAccessibilityFocusDrawable;
+        if (drawable != null) {
+            final Rect bounds = mAttachInfo.mTmpInvalRect;
+            final boolean hasFocus = getAccessibilityFocusedRect(bounds);
+            if (!hasFocus) {
+                bounds.setEmpty();
+            }
+            if (!bounds.equals(drawable.getBounds())) {
+                accessibilityFocusDirty = true;
+            }
+        }
+
+        mAttachInfo.mDrawingTime =
+                mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
+
+        boolean useAsyncReport = false;
+        if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty || mNextDrawUseBlastSync) {
+            if (isHardwareEnabled()) {
+                // If accessibility focus moved, always invalidate the root.
+                boolean invalidateRoot = accessibilityFocusDirty || mInvalidateRootRequested;
+                mInvalidateRootRequested = false;
+
+                // Draw with hardware renderer.
+                mIsAnimating = false;
+
+                if (mHardwareYOffset != yOffset || mHardwareXOffset != xOffset) {
+                    mHardwareYOffset = yOffset;
+                    mHardwareXOffset = xOffset;
+                    invalidateRoot = true;
+                }
+
+                if (invalidateRoot) {
+                    mAttachInfo.mThreadedRenderer.invalidateRoot();
+                }
+
+                dirty.setEmpty();
+
+                // Stage the content drawn size now. It will be transferred to the renderer
+                // shortly before the draw commands get send to the renderer.
+                final boolean updated = updateContentDrawBounds();
+
+                if (mReportNextDraw) {
+                    // report next draw overrides setStopped()
+                    // This value is re-sync'd to the value of mStopped
+                    // in the handling of mReportNextDraw post-draw.
+                    mAttachInfo.mThreadedRenderer.setStopped(false);
+                }
+
+                if (updated) {
+                    requestDrawWindow();
+                }
+
+                useAsyncReport = true;
+
+                mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
+            } else {
+                // If we get here with a disabled & requested hardware renderer, something went
+                // wrong (an invalidate posted right before we destroyed the hardware surface
+                // for instance) so we should just bail out. Locking the surface with software
+                // rendering at this point would lock it forever and prevent hardware renderer
+                // from doing its job when it comes back.
+                // Before we request a new frame we must however attempt to reinitiliaze the
+                // hardware renderer if it's in requested state. This would happen after an
+                // eglTerminate() for instance.
+                if (mAttachInfo.mThreadedRenderer != null &&
+                        !mAttachInfo.mThreadedRenderer.isEnabled() &&
+                        mAttachInfo.mThreadedRenderer.isRequested() &&
+                        mSurface.isValid()) {
+
+                    try {
+                        mAttachInfo.mThreadedRenderer.initializeIfNeeded(
+                                mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
+                    } catch (OutOfResourcesException e) {
+                        handleOutOfResourcesException(e);
+                        return false;
+                    }
+
+                    mFullRedrawNeeded = true;
+                    scheduleTraversals();
+                    return false;
+                }
+
+                if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
+                        scalingRequired, dirty, surfaceInsets)) {
+                    return false;
+                }
+            }
+        }
+
+        if (animating) {
+            mFullRedrawNeeded = true;
+            scheduleTraversals();
+        }
+        return useAsyncReport;
+    }
+
+    /**
+     * @return true if drawing was successful, false if an error occurred
+     */
+    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
+            boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
+
+        // Draw with software renderer.
+        final Canvas canvas;
+
+        // We already have the offset of surfaceInsets in xoff, yoff and dirty region,
+        // therefore we need to add it back when moving the dirty region.
+        int dirtyXOffset = xoff;
+        int dirtyYOffset = yoff;
+        if (surfaceInsets != null) {
+            dirtyXOffset += surfaceInsets.left;
+            dirtyYOffset += surfaceInsets.top;
+        }
+
+        try {
+            dirty.offset(-dirtyXOffset, -dirtyYOffset);
+            final int left = dirty.left;
+            final int top = dirty.top;
+            final int right = dirty.right;
+            final int bottom = dirty.bottom;
+
+            canvas = mSurface.lockCanvas(dirty);
+
+            // TODO: Do this in native
+            canvas.setDensity(mDensity);
+        } catch (Surface.OutOfResourcesException e) {
+            handleOutOfResourcesException(e);
+            return false;
+        } catch (IllegalArgumentException e) {
+            Log.e(mTag, "Could not lock surface", e);
+            // Don't assume this is due to out of memory, it could be
+            // something else, and if it is something else then we could
+            // kill stuff (or ourself) for no reason.
+            mLayoutRequested = true;    // ask wm for a new surface next time.
+            return false;
+        } finally {
+            dirty.offset(dirtyXOffset, dirtyYOffset);  // Reset to the original value.
+        }
+
+        try {
+            if (DEBUG_ORIENTATION || DEBUG_DRAW) {
+                Log.v(mTag, "Surface " + surface + " drawing to bitmap w="
+                        + canvas.getWidth() + ", h=" + canvas.getHeight());
+                //canvas.drawARGB(255, 255, 0, 0);
+            }
+
+            // If this bitmap's format includes an alpha channel, we
+            // need to clear it before drawing so that the child will
+            // properly re-composite its drawing on a transparent
+            // background. This automatically respects the clip/dirty region
+            // or
+            // If we are applying an offset, we need to clear the area
+            // where the offset doesn't appear to avoid having garbage
+            // left in the blank areas.
+            if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
+                canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+            }
+
+            dirty.setEmpty();
+            mIsAnimating = false;
+            mView.mPrivateFlags |= View.PFLAG_DRAWN;
+
+            if (DEBUG_DRAW) {
+                Context cxt = mView.getContext();
+                Log.i(mTag, "Drawing: package:" + cxt.getPackageName() +
+                        ", metrics=" + cxt.getResources().getDisplayMetrics() +
+                        ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
+            }
+            canvas.translate(-xoff, -yoff);
+            if (mTranslator != null) {
+                mTranslator.translateCanvas(canvas);
+            }
+            canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
+
+            mView.draw(canvas);
+
+            drawAccessibilityFocusedDrawableIfNeeded(canvas);
+        } finally {
+            try {
+                surface.unlockCanvasAndPost(canvas);
+            } catch (IllegalArgumentException e) {
+                Log.e(mTag, "Could not unlock surface", e);
+                mLayoutRequested = true;    // ask wm for a new surface next time.
+                //noinspection ReturnInsideFinallyBlock
+                return false;
+            }
+
+            if (LOCAL_LOGV) {
+                Log.v(mTag, "Surface " + surface + " unlockCanvasAndPost");
+            }
+        }
+        return true;
+    }
+
+    /**
+     * We want to draw a highlight around the current accessibility focused.
+     * Since adding a style for all possible view is not a viable option we
+     * have this specialized drawing method.
+     *
+     * Note: We are doing this here to be able to draw the highlight for
+     *       virtual views in addition to real ones.
+     *
+     * @param canvas The canvas on which to draw.
+     */
+    private void drawAccessibilityFocusedDrawableIfNeeded(Canvas canvas) {
+        final Rect bounds = mAttachInfo.mTmpInvalRect;
+        if (getAccessibilityFocusedRect(bounds)) {
+            final Drawable drawable = getAccessibilityFocusedDrawable();
+            if (drawable != null) {
+                drawable.setBounds(bounds);
+                drawable.draw(canvas);
+            }
+        } else if (mAttachInfo.mAccessibilityFocusDrawable != null) {
+            mAttachInfo.mAccessibilityFocusDrawable.setBounds(0, 0, 0, 0);
+        }
+    }
+
+    private boolean getAccessibilityFocusedRect(Rect bounds) {
+        final AccessibilityManager manager = AccessibilityManager.getInstance(mView.mContext);
+        if (!manager.isEnabled() || !manager.isTouchExplorationEnabled()) {
+            return false;
+        }
+
+        final View host = mAccessibilityFocusedHost;
+        if (host == null || host.mAttachInfo == null) {
+            return false;
+        }
+
+        final AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
+        if (provider == null) {
+            host.getBoundsOnScreen(bounds, true);
+        } else if (mAccessibilityFocusedVirtualView != null) {
+            mAccessibilityFocusedVirtualView.getBoundsInScreen(bounds);
+        } else {
+            return false;
+        }
+
+        // Transform the rect into window-relative coordinates.
+        final AttachInfo attachInfo = mAttachInfo;
+        bounds.offset(0, attachInfo.mViewRootImpl.mScrollY);
+        bounds.offset(-attachInfo.mWindowLeft, -attachInfo.mWindowTop);
+        if (!bounds.intersect(0, 0, attachInfo.mViewRootImpl.mWidth,
+                attachInfo.mViewRootImpl.mHeight)) {
+            // If no intersection, set bounds to empty.
+            bounds.setEmpty();
+        }
+        return !bounds.isEmpty();
+    }
+
+    private Drawable getAccessibilityFocusedDrawable() {
+        // Lazily load the accessibility focus drawable.
+        if (mAttachInfo.mAccessibilityFocusDrawable == null) {
+            final TypedValue value = new TypedValue();
+            final boolean resolved = mView.mContext.getTheme().resolveAttribute(
+                    R.attr.accessibilityFocusedDrawable, value, true);
+            if (resolved) {
+                mAttachInfo.mAccessibilityFocusDrawable =
+                        mView.mContext.getDrawable(value.resourceId);
+            }
+        }
+        // Sets the focus appearance data into the accessibility focus drawable.
+        if (mAttachInfo.mAccessibilityFocusDrawable instanceof GradientDrawable) {
+            final GradientDrawable drawable =
+                    (GradientDrawable) mAttachInfo.mAccessibilityFocusDrawable;
+            drawable.setStroke(mAccessibilityManager.getAccessibilityFocusStrokeWidth(),
+                    mAccessibilityManager.getAccessibilityFocusColor());
+        }
+
+        return mAttachInfo.mAccessibilityFocusDrawable;
+    }
+
+    void updateSystemGestureExclusionRectsForView(View view) {
+        mGestureExclusionTracker.updateRectsForView(view);
+        mHandler.sendEmptyMessage(MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED);
+    }
+
+    void systemGestureExclusionChanged() {
+        final List<Rect> rectsForWindowManager = mGestureExclusionTracker.computeChangedRects();
+        if (rectsForWindowManager != null && mView != null) {
+            try {
+                mWindowSession.reportSystemGestureExclusionChanged(mWindow, rectsForWindowManager);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+            mAttachInfo.mTreeObserver
+                    .dispatchOnSystemGestureExclusionRectsChanged(rectsForWindowManager);
+        }
+    }
+
+    void updateLocationInParentDisplay(int x, int y) {
+        if (mAttachInfo != null
+                && !mAttachInfo.mLocationInParentDisplay.equals(x, y)) {
+            mAttachInfo.mLocationInParentDisplay.set(x, y);
+        }
+    }
+
+    /**
+     * Set the root-level system gesture exclusion rects. These are added to those provided by
+     * the root's view hierarchy.
+     */
+    public void setRootSystemGestureExclusionRects(@NonNull List<Rect> rects) {
+        mGestureExclusionTracker.setRootSystemGestureExclusionRects(rects);
+        mHandler.sendEmptyMessage(MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED);
+    }
+
+    /**
+     * Returns the root-level system gesture exclusion rects. These do not include those provided by
+     * the root's view hierarchy.
+     */
+    @NonNull
+    public List<Rect> getRootSystemGestureExclusionRects() {
+        return mGestureExclusionTracker.getRootSystemGestureExclusionRects();
+    }
+
+    /**
+     * Requests that the root render node is invalidated next time we perform a draw, such that
+     * {@link WindowCallbacks#onPostDraw} gets called.
+     */
+    public void requestInvalidateRootRenderNode() {
+        mInvalidateRootRequested = true;
+    }
+
+    boolean scrollToRectOrFocus(Rect rectangle, boolean immediate) {
+        final Rect ci = mAttachInfo.mContentInsets;
+        final Rect vi = mAttachInfo.mVisibleInsets;
+        int scrollY = 0;
+        boolean handled = false;
+
+        if (vi.left > ci.left || vi.top > ci.top
+                || vi.right > ci.right || vi.bottom > ci.bottom) {
+            // We'll assume that we aren't going to change the scroll
+            // offset, since we want to avoid that unless it is actually
+            // going to make the focus visible...  otherwise we scroll
+            // all over the place.
+            scrollY = mScrollY;
+            // We can be called for two different situations: during a draw,
+            // to update the scroll position if the focus has changed (in which
+            // case 'rectangle' is null), or in response to a
+            // requestChildRectangleOnScreen() call (in which case 'rectangle'
+            // is non-null and we just want to scroll to whatever that
+            // rectangle is).
+            final View focus = mView.findFocus();
+            if (focus == null) {
+                return false;
+            }
+            View lastScrolledFocus = (mLastScrolledFocus != null) ? mLastScrolledFocus.get() : null;
+            if (focus != lastScrolledFocus) {
+                // If the focus has changed, then ignore any requests to scroll
+                // to a rectangle; first we want to make sure the entire focus
+                // view is visible.
+                rectangle = null;
+            }
+            if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Eval scroll: focus=" + focus
+                    + " rectangle=" + rectangle + " ci=" + ci
+                    + " vi=" + vi);
+            if (focus == lastScrolledFocus && !mScrollMayChange && rectangle == null) {
+                // Optimization: if the focus hasn't changed since last
+                // time, and no layout has happened, then just leave things
+                // as they are.
+                if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Keeping scroll y="
+                        + mScrollY + " vi=" + vi.toShortString());
+            } else {
+                // We need to determine if the currently focused view is
+                // within the visible part of the window and, if not, apply
+                // a pan so it can be seen.
+                mLastScrolledFocus = new WeakReference<View>(focus);
+                mScrollMayChange = false;
+                if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Need to scroll?");
+                // Try to find the rectangle from the focus view.
+                if (focus.getGlobalVisibleRect(mVisRect, null)) {
+                    if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Root w="
+                            + mView.getWidth() + " h=" + mView.getHeight()
+                            + " ci=" + ci.toShortString()
+                            + " vi=" + vi.toShortString());
+                    if (rectangle == null) {
+                        focus.getFocusedRect(mTempRect);
+                        if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Focus " + focus
+                                + ": focusRect=" + mTempRect.toShortString());
+                        if (mView instanceof ViewGroup) {
+                            ((ViewGroup) mView).offsetDescendantRectToMyCoords(
+                                    focus, mTempRect);
+                        }
+                        if (DEBUG_INPUT_RESIZE) Log.v(mTag,
+                                "Focus in window: focusRect="
+                                + mTempRect.toShortString()
+                                + " visRect=" + mVisRect.toShortString());
+                    } else {
+                        mTempRect.set(rectangle);
+                        if (DEBUG_INPUT_RESIZE) Log.v(mTag,
+                                "Request scroll to rect: "
+                                + mTempRect.toShortString()
+                                + " visRect=" + mVisRect.toShortString());
+                    }
+                    if (mTempRect.intersect(mVisRect)) {
+                        if (DEBUG_INPUT_RESIZE) Log.v(mTag,
+                                "Focus window visible rect: "
+                                + mTempRect.toShortString());
+                        if (mTempRect.height() >
+                                (mView.getHeight()-vi.top-vi.bottom)) {
+                            // If the focus simply is not going to fit, then
+                            // best is probably just to leave things as-is.
+                            if (DEBUG_INPUT_RESIZE) Log.v(mTag,
+                                    "Too tall; leaving scrollY=" + scrollY);
+                        }
+                        // Next, check whether top or bottom is covered based on the non-scrolled
+                        // position, and calculate new scrollY (or set it to 0).
+                        // We can't keep using mScrollY here. For example mScrollY is non-zero
+                        // due to IME, then IME goes away. The current value of mScrollY leaves top
+                        // and bottom both visible, but we still need to scroll it back to 0.
+                        else if (mTempRect.top < vi.top) {
+                            scrollY = mTempRect.top - vi.top;
+                            if (DEBUG_INPUT_RESIZE) Log.v(mTag,
+                                    "Top covered; scrollY=" + scrollY);
+                        } else if (mTempRect.bottom > (mView.getHeight()-vi.bottom)) {
+                            scrollY = mTempRect.bottom - (mView.getHeight()-vi.bottom);
+                            if (DEBUG_INPUT_RESIZE) Log.v(mTag,
+                                    "Bottom covered; scrollY=" + scrollY);
+                        } else {
+                            scrollY = 0;
+                        }
+                        handled = true;
+                    }
+                }
+            }
+        }
+
+        if (scrollY != mScrollY) {
+            if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Pan scroll changed: old="
+                    + mScrollY + " , new=" + scrollY);
+            if (!immediate) {
+                if (mScroller == null) {
+                    mScroller = new Scroller(mView.getContext());
+                }
+                mScroller.startScroll(0, mScrollY, 0, scrollY-mScrollY);
+            } else if (mScroller != null) {
+                mScroller.abortAnimation();
+            }
+            mScrollY = scrollY;
+        }
+
+        return handled;
+    }
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public View getAccessibilityFocusedHost() {
+        return mAccessibilityFocusedHost;
+    }
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public AccessibilityNodeInfo getAccessibilityFocusedVirtualView() {
+        return mAccessibilityFocusedVirtualView;
+    }
+
+    void setAccessibilityFocus(View view, AccessibilityNodeInfo node) {
+        // If we have a virtual view with accessibility focus we need
+        // to clear the focus and invalidate the virtual view bounds.
+        if (mAccessibilityFocusedVirtualView != null) {
+
+            AccessibilityNodeInfo focusNode = mAccessibilityFocusedVirtualView;
+            View focusHost = mAccessibilityFocusedHost;
+
+            // Wipe the state of the current accessibility focus since
+            // the call into the provider to clear accessibility focus
+            // will fire an accessibility event which will end up calling
+            // this method and we want to have clean state when this
+            // invocation happens.
+            mAccessibilityFocusedHost = null;
+            mAccessibilityFocusedVirtualView = null;
+
+            // Clear accessibility focus on the host after clearing state since
+            // this method may be reentrant.
+            focusHost.clearAccessibilityFocusNoCallbacks(
+                    AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+
+            AccessibilityNodeProvider provider = focusHost.getAccessibilityNodeProvider();
+            if (provider != null) {
+                // Invalidate the area of the cleared accessibility focus.
+                focusNode.getBoundsInParent(mTempRect);
+                focusHost.invalidate(mTempRect);
+                // Clear accessibility focus in the virtual node.
+                final int virtualNodeId = AccessibilityNodeInfo.getVirtualDescendantId(
+                        focusNode.getSourceNodeId());
+                provider.performAction(virtualNodeId,
+                        AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
+            }
+            focusNode.recycle();
+        }
+        if ((mAccessibilityFocusedHost != null) && (mAccessibilityFocusedHost != view))  {
+            // Clear accessibility focus in the view.
+            mAccessibilityFocusedHost.clearAccessibilityFocusNoCallbacks(
+                    AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+        }
+
+        // Set the new focus host and node.
+        mAccessibilityFocusedHost = view;
+        mAccessibilityFocusedVirtualView = node;
+
+        if (mAttachInfo.mThreadedRenderer != null) {
+            mAttachInfo.mThreadedRenderer.invalidateRoot();
+        }
+    }
+
+    boolean hasPointerCapture() {
+        return mPointerCapture;
+    }
+
+    void requestPointerCapture(boolean enabled) {
+        if (mPointerCapture == enabled) {
+            return;
+        }
+        final IBinder inputToken = getInputToken();
+        if (inputToken == null) {
+            Log.e(mTag, "No input channel to request Pointer Capture.");
+            return;
+        }
+        InputManager.getInstance().requestPointerCapture(inputToken, enabled);
+    }
+
+    private void handlePointerCaptureChanged(boolean hasCapture) {
+        if (mPointerCapture == hasCapture) {
+            return;
+        }
+        mPointerCapture = hasCapture;
+        if (mView != null) {
+            mView.dispatchPointerCaptureChanged(hasCapture);
+        }
+    }
+
+    private void updateColorModeIfNeeded(int colorMode) {
+        if (mAttachInfo.mThreadedRenderer == null) {
+            return;
+        }
+        // TODO: Centralize this sanitization? Why do we let setting bad modes?
+        // Alternatively, can we just let HWUI figure it out? Do we need to care here?
+        if (!getConfiguration().isScreenWideColorGamut()) {
+            colorMode = ActivityInfo.COLOR_MODE_DEFAULT;
+        }
+        mAttachInfo.mThreadedRenderer.setColorMode(colorMode);
+    }
+
+    @Override
+    public void requestChildFocus(View child, View focused) {
+        if (DEBUG_INPUT_RESIZE) {
+            Log.v(mTag, "Request child focus: focus now " + focused);
+        }
+        checkThread();
+        scheduleTraversals();
+    }
+
+    @Override
+    public void clearChildFocus(View child) {
+        if (DEBUG_INPUT_RESIZE) {
+            Log.v(mTag, "Clearing child focus");
+        }
+        checkThread();
+        scheduleTraversals();
+    }
+
+    @Override
+    public ViewParent getParentForAccessibility() {
+        return null;
+    }
+
+    @Override
+    public void focusableViewAvailable(View v) {
+        checkThread();
+        if (mView != null) {
+            if (!mView.hasFocus()) {
+                if (sAlwaysAssignFocus || !mAttachInfo.mInTouchMode) {
+                    v.requestFocus();
+                }
+            } else {
+                // the one case where will transfer focus away from the current one
+                // is if the current view is a view group that prefers to give focus
+                // to its children first AND the view is a descendant of it.
+                View focused = mView.findFocus();
+                if (focused instanceof ViewGroup) {
+                    ViewGroup group = (ViewGroup) focused;
+                    if (group.getDescendantFocusability() == ViewGroup.FOCUS_AFTER_DESCENDANTS
+                            && isViewDescendantOf(v, focused)) {
+                        v.requestFocus();
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void recomputeViewAttributes(View child) {
+        checkThread();
+        if (mView == child) {
+            mAttachInfo.mRecomputeGlobalAttributes = true;
+            if (!mWillDrawSoon) {
+                scheduleTraversals();
+            }
+        }
+    }
+
+    void dispatchDetachedFromWindow() {
+        // Make sure we free-up insets resources if view never received onWindowFocusLost()
+        // because of a die-signal
+        mInsetsController.onWindowFocusLost();
+        mFirstInputStage.onDetachedFromWindow();
+        if (mView != null && mView.mAttachInfo != null) {
+            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
+            mView.dispatchDetachedFromWindow();
+        }
+
+        mAccessibilityInteractionConnectionManager.ensureNoConnection();
+        mAccessibilityManager.removeAccessibilityStateChangeListener(
+                mAccessibilityInteractionConnectionManager);
+        mAccessibilityManager.removeHighTextContrastStateChangeListener(
+                mHighContrastTextManager);
+        removeSendWindowContentChangedCallback();
+
+        destroyHardwareRenderer();
+
+        setAccessibilityFocus(null, null);
+
+        mInsetsController.cancelExistingAnimations();
+
+        mView.assignParent(null);
+        mView = null;
+        mAttachInfo.mRootView = null;
+
+        destroySurface();
+
+        if (mInputQueueCallback != null && mInputQueue != null) {
+            mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
+            mInputQueue.dispose();
+            mInputQueueCallback = null;
+            mInputQueue = null;
+        }
+        try {
+            mWindowSession.remove(mWindow);
+        } catch (RemoteException e) {
+        }
+        // Dispose receiver would dispose client InputChannel, too. That could send out a socket
+        // broken event, so we need to unregister the server InputChannel when removing window to
+        // prevent server side receive the event and prompt error.
+        if (mInputEventReceiver != null) {
+            mInputEventReceiver.dispose();
+            mInputEventReceiver = null;
+        }
+
+        mDisplayManager.unregisterDisplayListener(mDisplayListener);
+
+        unscheduleTraversals();
+    }
+
+    /**
+     * Notifies all callbacks that configuration and/or display has changed and updates internal
+     * state.
+     * @param mergedConfiguration New global and override config in {@link MergedConfiguration}
+     *                            container.
+     * @param force Flag indicating if we should force apply the config.
+     * @param newDisplayId Id of new display if moved, {@link Display#INVALID_DISPLAY} if not
+     *                     changed.
+     */
+    private void performConfigurationChange(MergedConfiguration mergedConfiguration, boolean force,
+            int newDisplayId) {
+        if (mergedConfiguration == null) {
+            throw new IllegalArgumentException("No merged config provided.");
+        }
+
+        Configuration globalConfig = mergedConfiguration.getGlobalConfiguration();
+        final Configuration overrideConfig = mergedConfiguration.getOverrideConfiguration();
+        if (DEBUG_CONFIGURATION) Log.v(mTag,
+                "Applying new config to window " + mWindowAttributes.getTitle()
+                        + ", globalConfig: " + globalConfig
+                        + ", overrideConfig: " + overrideConfig);
+
+        final CompatibilityInfo ci = mDisplay.getDisplayAdjustments().getCompatibilityInfo();
+        if (!ci.equals(CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO)) {
+            globalConfig = new Configuration(globalConfig);
+            ci.applyToConfiguration(mNoncompatDensity, globalConfig);
+        }
+
+        synchronized (sConfigCallbacks) {
+            for (int i=sConfigCallbacks.size()-1; i>=0; i--) {
+                sConfigCallbacks.get(i).onConfigurationChanged(globalConfig);
+            }
+        }
+
+        mLastReportedMergedConfiguration.setConfiguration(globalConfig, overrideConfig);
+
+        mForceNextConfigUpdate = force;
+        if (mActivityConfigCallback != null) {
+            // An activity callback is set - notify it about override configuration update.
+            // This basically initiates a round trip to ActivityThread and back, which will ensure
+            // that corresponding activity and resources are updated before updating inner state of
+            // ViewRootImpl. Eventually it will call #updateConfiguration().
+            mActivityConfigCallback.onConfigurationChanged(overrideConfig, newDisplayId);
+        } else {
+            // There is no activity callback - update the configuration right away.
+            updateConfiguration(newDisplayId);
+        }
+        mForceNextConfigUpdate = false;
+    }
+
+    /**
+     * Update display and views if last applied merged configuration changed.
+     * @param newDisplayId Id of new display if moved, {@link Display#INVALID_DISPLAY} otherwise.
+     */
+    public void updateConfiguration(int newDisplayId) {
+        if (mView == null) {
+            return;
+        }
+
+        // At this point the resources have been updated to
+        // have the most recent config, whatever that is.  Use
+        // the one in them which may be newer.
+        final Resources localResources = mView.getResources();
+        final Configuration config = localResources.getConfiguration();
+
+        // Handle move to display.
+        if (newDisplayId != INVALID_DISPLAY) {
+            onMovedToDisplay(newDisplayId, config);
+        }
+
+        // Handle configuration change.
+        if (mForceNextConfigUpdate || mLastConfigurationFromResources.diff(config) != 0) {
+            // Update the display with new DisplayAdjustments.
+            updateInternalDisplay(mDisplay.getDisplayId(), localResources);
+
+            final int lastLayoutDirection = mLastConfigurationFromResources.getLayoutDirection();
+            final int currentLayoutDirection = config.getLayoutDirection();
+            mLastConfigurationFromResources.setTo(config);
+            if (lastLayoutDirection != currentLayoutDirection
+                    && mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {
+                mView.setLayoutDirection(currentLayoutDirection);
+            }
+            mView.dispatchConfigurationChanged(config);
+
+            // We could have gotten this {@link Configuration} update after we called
+            // {@link #performTraversals} with an older {@link Configuration}. As a result, our
+            // window frame may be stale. We must ensure the next pass of {@link #performTraversals}
+            // catches this.
+            mForceNextWindowRelayout = true;
+            requestLayout();
+        }
+
+        updateForceDarkMode();
+    }
+
+    /**
+     * Return true if child is an ancestor of parent, (or equal to the parent).
+     */
+    public static boolean isViewDescendantOf(View child, View parent) {
+        if (child == parent) {
+            return true;
+        }
+
+        final ViewParent theParent = child.getParent();
+        return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent);
+    }
+
+    private static void forceLayout(View view) {
+        view.forceLayout();
+        if (view instanceof ViewGroup) {
+            ViewGroup group = (ViewGroup) view;
+            final int count = group.getChildCount();
+            for (int i = 0; i < count; i++) {
+                forceLayout(group.getChildAt(i));
+            }
+        }
+    }
+
+    private static final int MSG_INVALIDATE = 1;
+    private static final int MSG_INVALIDATE_RECT = 2;
+    private static final int MSG_DIE = 3;
+    private static final int MSG_RESIZED = 4;
+    private static final int MSG_RESIZED_REPORT = 5;
+    private static final int MSG_WINDOW_FOCUS_CHANGED = 6;
+    private static final int MSG_DISPATCH_INPUT_EVENT = 7;
+    private static final int MSG_DISPATCH_APP_VISIBILITY = 8;
+    private static final int MSG_DISPATCH_GET_NEW_SURFACE = 9;
+    private static final int MSG_DISPATCH_KEY_FROM_IME = 11;
+    private static final int MSG_DISPATCH_KEY_FROM_AUTOFILL = 12;
+    private static final int MSG_CHECK_FOCUS = 13;
+    private static final int MSG_CLOSE_SYSTEM_DIALOGS = 14;
+    private static final int MSG_DISPATCH_DRAG_EVENT = 15;
+    private static final int MSG_DISPATCH_DRAG_LOCATION_EVENT = 16;
+    private static final int MSG_DISPATCH_SYSTEM_UI_VISIBILITY = 17;
+    private static final int MSG_UPDATE_CONFIGURATION = 18;
+    private static final int MSG_PROCESS_INPUT_EVENTS = 19;
+    private static final int MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST = 21;
+    private static final int MSG_INVALIDATE_WORLD = 22;
+    private static final int MSG_WINDOW_MOVED = 23;
+    private static final int MSG_SYNTHESIZE_INPUT_EVENT = 24;
+    private static final int MSG_DISPATCH_WINDOW_SHOWN = 25;
+    private static final int MSG_REQUEST_KEYBOARD_SHORTCUTS = 26;
+    private static final int MSG_UPDATE_POINTER_ICON = 27;
+    private static final int MSG_POINTER_CAPTURE_CHANGED = 28;
+    private static final int MSG_DRAW_FINISHED = 29;
+    private static final int MSG_INSETS_CHANGED = 30;
+    private static final int MSG_INSETS_CONTROL_CHANGED = 31;
+    private static final int MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED = 32;
+    private static final int MSG_LOCATION_IN_PARENT_DISPLAY_CHANGED = 33;
+    private static final int MSG_SHOW_INSETS = 34;
+    private static final int MSG_HIDE_INSETS = 35;
+    private static final int MSG_REQUEST_SCROLL_CAPTURE = 36;
+
+
+    final class ViewRootHandler extends Handler {
+        @Override
+        public String getMessageName(Message message) {
+            switch (message.what) {
+                case MSG_INVALIDATE:
+                    return "MSG_INVALIDATE";
+                case MSG_INVALIDATE_RECT:
+                    return "MSG_INVALIDATE_RECT";
+                case MSG_DIE:
+                    return "MSG_DIE";
+                case MSG_RESIZED:
+                    return "MSG_RESIZED";
+                case MSG_RESIZED_REPORT:
+                    return "MSG_RESIZED_REPORT";
+                case MSG_WINDOW_FOCUS_CHANGED:
+                    return "MSG_WINDOW_FOCUS_CHANGED";
+                case MSG_DISPATCH_INPUT_EVENT:
+                    return "MSG_DISPATCH_INPUT_EVENT";
+                case MSG_DISPATCH_APP_VISIBILITY:
+                    return "MSG_DISPATCH_APP_VISIBILITY";
+                case MSG_DISPATCH_GET_NEW_SURFACE:
+                    return "MSG_DISPATCH_GET_NEW_SURFACE";
+                case MSG_DISPATCH_KEY_FROM_IME:
+                    return "MSG_DISPATCH_KEY_FROM_IME";
+                case MSG_DISPATCH_KEY_FROM_AUTOFILL:
+                    return "MSG_DISPATCH_KEY_FROM_AUTOFILL";
+                case MSG_CHECK_FOCUS:
+                    return "MSG_CHECK_FOCUS";
+                case MSG_CLOSE_SYSTEM_DIALOGS:
+                    return "MSG_CLOSE_SYSTEM_DIALOGS";
+                case MSG_DISPATCH_DRAG_EVENT:
+                    return "MSG_DISPATCH_DRAG_EVENT";
+                case MSG_DISPATCH_DRAG_LOCATION_EVENT:
+                    return "MSG_DISPATCH_DRAG_LOCATION_EVENT";
+                case MSG_DISPATCH_SYSTEM_UI_VISIBILITY:
+                    return "MSG_DISPATCH_SYSTEM_UI_VISIBILITY";
+                case MSG_UPDATE_CONFIGURATION:
+                    return "MSG_UPDATE_CONFIGURATION";
+                case MSG_PROCESS_INPUT_EVENTS:
+                    return "MSG_PROCESS_INPUT_EVENTS";
+                case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST:
+                    return "MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST";
+                case MSG_WINDOW_MOVED:
+                    return "MSG_WINDOW_MOVED";
+                case MSG_SYNTHESIZE_INPUT_EVENT:
+                    return "MSG_SYNTHESIZE_INPUT_EVENT";
+                case MSG_DISPATCH_WINDOW_SHOWN:
+                    return "MSG_DISPATCH_WINDOW_SHOWN";
+                case MSG_UPDATE_POINTER_ICON:
+                    return "MSG_UPDATE_POINTER_ICON";
+                case MSG_POINTER_CAPTURE_CHANGED:
+                    return "MSG_POINTER_CAPTURE_CHANGED";
+                case MSG_DRAW_FINISHED:
+                    return "MSG_DRAW_FINISHED";
+                case MSG_INSETS_CHANGED:
+                    return "MSG_INSETS_CHANGED";
+                case MSG_INSETS_CONTROL_CHANGED:
+                    return "MSG_INSETS_CONTROL_CHANGED";
+                case MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED:
+                    return "MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED";
+                case MSG_LOCATION_IN_PARENT_DISPLAY_CHANGED:
+                    return "MSG_LOCATION_IN_PARENT_DISPLAY_CHANGED";
+                case MSG_SHOW_INSETS:
+                    return "MSG_SHOW_INSETS";
+                case MSG_HIDE_INSETS:
+                    return "MSG_HIDE_INSETS";
+            }
+            return super.getMessageName(message);
+        }
+
+        @Override
+        public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
+            if (msg.what == MSG_REQUEST_KEYBOARD_SHORTCUTS && msg.obj == null) {
+                // Debugging for b/27963013
+                throw new NullPointerException(
+                        "Attempted to call MSG_REQUEST_KEYBOARD_SHORTCUTS with null receiver:");
+            }
+            return super.sendMessageAtTime(msg, uptimeMillis);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                Trace.traceBegin(Trace.TRACE_TAG_VIEW, getMessageName(msg));
+            }
+            try {
+                handleMessageImpl(msg);
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            }
+        }
+
+        private void handleMessageImpl(Message msg) {
+            switch (msg.what) {
+                case MSG_INVALIDATE:
+                    ((View) msg.obj).invalidate();
+                    break;
+                case MSG_INVALIDATE_RECT:
+                    final View.AttachInfo.InvalidateInfo info =
+                            (View.AttachInfo.InvalidateInfo) msg.obj;
+                    info.target.invalidate(info.left, info.top, info.right, info.bottom);
+                    info.recycle();
+                    break;
+                case MSG_PROCESS_INPUT_EVENTS:
+                    mProcessInputEventsScheduled = false;
+                    doProcessInputEvents();
+                    break;
+                case MSG_DISPATCH_APP_VISIBILITY:
+                    handleAppVisibility(msg.arg1 != 0);
+                    break;
+                case MSG_DISPATCH_GET_NEW_SURFACE:
+                    handleGetNewSurface();
+                    break;
+                case MSG_RESIZED:
+                case MSG_RESIZED_REPORT: {
+                    mWillMove = false;
+                    mWillResize = false;
+                    final SomeArgs args = (SomeArgs) msg.obj;
+                    handleResized(msg.what, args);
+                    args.recycle();
+                    break;
+                }
+                case MSG_INSETS_CHANGED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    mWillMove = args.argi1 == 1;
+                    mWillResize = args.argi2 == 1;
+                    mInsetsController.onStateChanged((InsetsState) args.arg1);
+                    args.recycle();
+                    break;
+                }
+                case MSG_INSETS_CONTROL_CHANGED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    mWillMove = args.argi1 == 1;
+                    mWillResize = args.argi2 == 1;
+
+                    // Deliver state change before control change, such that:
+                    // a) When gaining control, controller can compare with server state to evaluate
+                    // whether it needs to run animation.
+                    // b) When loosing control, controller can restore server state by taking last
+                    // dispatched state as truth.
+                    mInsetsController.onStateChanged((InsetsState) args.arg1);
+                    InsetsSourceControl[] controls = (InsetsSourceControl[]) args.arg2;
+                    if (mAdded) {
+                        mInsetsController.onControlsChanged(controls);
+                    } else if (controls != null) {
+                        for (InsetsSourceControl control : controls) {
+                            if (control != null) {
+                                control.release(SurfaceControl::release);
+                            }
+                        }
+                    }
+                    args.recycle();
+                    break;
+                }
+                case MSG_SHOW_INSETS: {
+                    if (mView == null) {
+                        Log.e(TAG,
+                                String.format("Calling showInsets(%d,%b) on window that no longer"
+                                        + " has views.", msg.arg1, msg.arg2 == 1));
+                    }
+                    clearLowProfileModeIfNeeded(msg.arg1, msg.arg2 == 1);
+                    mInsetsController.show(msg.arg1, msg.arg2 == 1);
+                    break;
+                }
+                case MSG_HIDE_INSETS: {
+                    mInsetsController.hide(msg.arg1, msg.arg2 == 1);
+                    break;
+                }
+                case MSG_WINDOW_MOVED:
+                    mWillMove = false;
+                    if (mAdded) {
+                        final int w = mWinFrame.width();
+                        final int h = mWinFrame.height();
+                        final int l = msg.arg1;
+                        final int t = msg.arg2;
+                        mTmpFrames.frame.left = l;
+                        mTmpFrames.frame.right = l + w;
+                        mTmpFrames.frame.top = t;
+                        mTmpFrames.frame.bottom = t + h;
+                        setFrame(mTmpFrames.frame);
+
+                        mPendingBackDropFrame.set(mWinFrame);
+                        maybeHandleWindowMove(mWinFrame);
+                    }
+                    break;
+                case MSG_WINDOW_FOCUS_CHANGED: {
+                    handleWindowFocusChanged();
+                } break;
+                case MSG_DIE:
+                    doDie();
+                    break;
+                case MSG_DISPATCH_INPUT_EVENT: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    InputEvent event = (InputEvent) args.arg1;
+                    InputEventReceiver receiver = (InputEventReceiver) args.arg2;
+                    enqueueInputEvent(event, receiver, 0, true);
+                    args.recycle();
+                } break;
+                case MSG_SYNTHESIZE_INPUT_EVENT: {
+                    InputEvent event = (InputEvent) msg.obj;
+                    enqueueInputEvent(event, null, QueuedInputEvent.FLAG_UNHANDLED, true);
+                } break;
+                case MSG_DISPATCH_KEY_FROM_IME: {
+                    if (LOCAL_LOGV) {
+                        Log.v(TAG, "Dispatching key " + msg.obj + " from IME to " + mView);
+                    }
+                    KeyEvent event = (KeyEvent) msg.obj;
+                    if ((event.getFlags() & KeyEvent.FLAG_FROM_SYSTEM) != 0) {
+                        // The IME is trying to say this event is from the
+                        // system!  Bad bad bad!
+                        //noinspection UnusedAssignment
+                        event = KeyEvent.changeFlags(event,
+                                event.getFlags() & ~KeyEvent.FLAG_FROM_SYSTEM);
+                    }
+                    enqueueInputEvent(event, null, QueuedInputEvent.FLAG_DELIVER_POST_IME, true);
+                } break;
+                case MSG_DISPATCH_KEY_FROM_AUTOFILL: {
+                    if (LOCAL_LOGV) {
+                        Log.v(TAG, "Dispatching key " + msg.obj + " from Autofill to " + mView);
+                    }
+                    KeyEvent event = (KeyEvent) msg.obj;
+                    enqueueInputEvent(event, null, 0, true);
+                } break;
+                case MSG_CHECK_FOCUS: {
+                    getImeFocusController().checkFocus(false, true);
+                } break;
+                case MSG_CLOSE_SYSTEM_DIALOGS: {
+                    if (mView != null) {
+                        mView.onCloseSystemDialogs((String) msg.obj);
+                    }
+                } break;
+                case MSG_DISPATCH_DRAG_EVENT: {
+                } // fall through
+                case MSG_DISPATCH_DRAG_LOCATION_EVENT: {
+                    DragEvent event = (DragEvent) msg.obj;
+                    // only present when this app called startDrag()
+                    event.mLocalState = mLocalDragState;
+                    handleDragEvent(event);
+                } break;
+                case MSG_DISPATCH_SYSTEM_UI_VISIBILITY: {
+                    handleDispatchSystemUiVisibilityChanged((SystemUiVisibilityInfo) msg.obj);
+                } break;
+                case MSG_UPDATE_CONFIGURATION: {
+                    Configuration config = (Configuration) msg.obj;
+                    if (config.isOtherSeqNewer(
+                            mLastReportedMergedConfiguration.getMergedConfiguration())) {
+                        // If we already have a newer merged config applied - use its global part.
+                        config = mLastReportedMergedConfiguration.getGlobalConfiguration();
+                    }
+
+                    // Use the newer global config and last reported override config.
+                    mPendingMergedConfiguration.setConfiguration(config,
+                            mLastReportedMergedConfiguration.getOverrideConfiguration());
+
+                    performConfigurationChange(new MergedConfiguration(mPendingMergedConfiguration),
+                            false /* force */, INVALID_DISPLAY /* same display */);
+                } break;
+                case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST: {
+                    setAccessibilityFocus(null, null);
+                } break;
+                case MSG_INVALIDATE_WORLD: {
+                    if (mView != null) {
+                        invalidateWorld(mView);
+                    }
+                } break;
+                case MSG_DISPATCH_WINDOW_SHOWN: {
+                    handleDispatchWindowShown();
+                } break;
+                case MSG_REQUEST_KEYBOARD_SHORTCUTS: {
+                    final IResultReceiver receiver = (IResultReceiver) msg.obj;
+                    final int deviceId = msg.arg1;
+                    handleRequestKeyboardShortcuts(receiver, deviceId);
+                } break;
+                case MSG_UPDATE_POINTER_ICON: {
+                    MotionEvent event = (MotionEvent) msg.obj;
+                    resetPointerIcon(event);
+                } break;
+                case MSG_POINTER_CAPTURE_CHANGED: {
+                    final boolean hasCapture = msg.arg1 != 0;
+                    handlePointerCaptureChanged(hasCapture);
+                } break;
+                case MSG_DRAW_FINISHED: {
+                    pendingDrawFinished();
+                } break;
+                case MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED: {
+                    systemGestureExclusionChanged();
+                } break;
+                case MSG_LOCATION_IN_PARENT_DISPLAY_CHANGED: {
+                    updateLocationInParentDisplay(msg.arg1, msg.arg2);
+                } break;
+                case MSG_REQUEST_SCROLL_CAPTURE:
+                    handleScrollCaptureRequest((IScrollCaptureResponseListener) msg.obj);
+                    break;
+            }
+        }
+    }
+
+    final ViewRootHandler mHandler = new ViewRootHandler();
+
+    /**
+     * Something in the current window tells us we need to change the touch mode.  For
+     * example, we are not in touch mode, and the user touches the screen.
+     *
+     * If the touch mode has changed, tell the window manager, and handle it locally.
+     *
+     * @param inTouchMode Whether we want to be in touch mode.
+     * @return True if the touch mode changed and focus changed was changed as a result
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    boolean ensureTouchMode(boolean inTouchMode) {
+        if (DBG) Log.d("touchmode", "ensureTouchMode(" + inTouchMode + "), current "
+                + "touch mode is " + mAttachInfo.mInTouchMode);
+        if (mAttachInfo.mInTouchMode == inTouchMode) return false;
+
+        // tell the window manager
+        try {
+            mWindowSession.setInTouchMode(inTouchMode);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+
+        // handle the change
+        return ensureTouchModeLocally(inTouchMode);
+    }
+
+    /**
+     * Ensure that the touch mode for this window is set, and if it is changing,
+     * take the appropriate action.
+     * @param inTouchMode Whether we want to be in touch mode.
+     * @return True if the touch mode changed and focus changed was changed as a result
+     */
+    private boolean ensureTouchModeLocally(boolean inTouchMode) {
+        if (DBG) Log.d("touchmode", "ensureTouchModeLocally(" + inTouchMode + "), current "
+                + "touch mode is " + mAttachInfo.mInTouchMode);
+
+        if (mAttachInfo.mInTouchMode == inTouchMode) return false;
+
+        mAttachInfo.mInTouchMode = inTouchMode;
+        mAttachInfo.mTreeObserver.dispatchOnTouchModeChanged(inTouchMode);
+
+        return (inTouchMode) ? enterTouchMode() : leaveTouchMode();
+    }
+
+    private boolean enterTouchMode() {
+        if (mView != null && mView.hasFocus()) {
+            // note: not relying on mFocusedView here because this could
+            // be when the window is first being added, and mFocused isn't
+            // set yet.
+            final View focused = mView.findFocus();
+            if (focused != null && !focused.isFocusableInTouchMode()) {
+                final ViewGroup ancestorToTakeFocus = findAncestorToTakeFocusInTouchMode(focused);
+                if (ancestorToTakeFocus != null) {
+                    // there is an ancestor that wants focus after its
+                    // descendants that is focusable in touch mode.. give it
+                    // focus
+                    return ancestorToTakeFocus.requestFocus();
+                } else {
+                    // There's nothing to focus. Clear and propagate through the
+                    // hierarchy, but don't attempt to place new focus.
+                    focused.clearFocusInternal(null, true, false);
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Find an ancestor of focused that wants focus after its descendants and is
+     * focusable in touch mode.
+     * @param focused The currently focused view.
+     * @return An appropriate view, or null if no such view exists.
+     */
+    private static ViewGroup findAncestorToTakeFocusInTouchMode(View focused) {
+        ViewParent parent = focused.getParent();
+        while (parent instanceof ViewGroup) {
+            final ViewGroup vgParent = (ViewGroup) parent;
+            if (vgParent.getDescendantFocusability() == ViewGroup.FOCUS_AFTER_DESCENDANTS
+                    && vgParent.isFocusableInTouchMode()) {
+                return vgParent;
+            }
+            if (vgParent.isRootNamespace()) {
+                return null;
+            } else {
+                parent = vgParent.getParent();
+            }
+        }
+        return null;
+    }
+
+    private boolean leaveTouchMode() {
+        if (mView != null) {
+            if (mView.hasFocus()) {
+                View focusedView = mView.findFocus();
+                if (!(focusedView instanceof ViewGroup)) {
+                    // some view has focus, let it keep it
+                    return false;
+                } else if (((ViewGroup) focusedView).getDescendantFocusability() !=
+                        ViewGroup.FOCUS_AFTER_DESCENDANTS) {
+                    // some view group has focus, and doesn't prefer its children
+                    // over itself for focus, so let them keep it.
+                    return false;
+                }
+            }
+
+            // find the best view to give focus to in this brave new non-touch-mode
+            // world
+            return mView.restoreDefaultFocus();
+        }
+        return false;
+    }
+
+    /**
+     * Base class for implementing a stage in the chain of responsibility
+     * for processing input events.
+     * <p>
+     * Events are delivered to the stage by the {@link #deliver} method.  The stage
+     * then has the choice of finishing the event or forwarding it to the next stage.
+     * </p>
+     */
+    abstract class InputStage {
+        private final InputStage mNext;
+
+        protected static final int FORWARD = 0;
+        protected static final int FINISH_HANDLED = 1;
+        protected static final int FINISH_NOT_HANDLED = 2;
+
+        private String mTracePrefix;
+
+        /**
+         * Creates an input stage.
+         * @param next The next stage to which events should be forwarded.
+         */
+        public InputStage(InputStage next) {
+            mNext = next;
+        }
+
+        /**
+         * Delivers an event to be processed.
+         */
+        public final void deliver(QueuedInputEvent q) {
+            if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
+                forward(q);
+            } else if (shouldDropInputEvent(q)) {
+                finish(q, false);
+            } else {
+                traceEvent(q, Trace.TRACE_TAG_VIEW);
+                final int result;
+                try {
+                    result = onProcess(q);
+                } finally {
+                    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+                }
+                apply(q, result);
+            }
+        }
+
+        /**
+         * Marks the the input event as finished then forwards it to the next stage.
+         */
+        protected void finish(QueuedInputEvent q, boolean handled) {
+            q.mFlags |= QueuedInputEvent.FLAG_FINISHED;
+            if (handled) {
+                q.mFlags |= QueuedInputEvent.FLAG_FINISHED_HANDLED;
+            }
+            forward(q);
+        }
+
+        /**
+         * Forwards the event to the next stage.
+         */
+        protected void forward(QueuedInputEvent q) {
+            onDeliverToNext(q);
+        }
+
+        /**
+         * Applies a result code from {@link #onProcess} to the specified event.
+         */
+        protected void apply(QueuedInputEvent q, int result) {
+            if (result == FORWARD) {
+                forward(q);
+            } else if (result == FINISH_HANDLED) {
+                finish(q, true);
+            } else if (result == FINISH_NOT_HANDLED) {
+                finish(q, false);
+            } else {
+                throw new IllegalArgumentException("Invalid result: " + result);
+            }
+        }
+
+        /**
+         * Called when an event is ready to be processed.
+         * @return A result code indicating how the event was handled.
+         */
+        protected int onProcess(QueuedInputEvent q) {
+            return FORWARD;
+        }
+
+        /**
+         * Called when an event is being delivered to the next stage.
+         */
+        protected void onDeliverToNext(QueuedInputEvent q) {
+            if (DEBUG_INPUT_STAGES) {
+                Log.v(mTag, "Done with " + getClass().getSimpleName() + ". " + q);
+            }
+            if (mNext != null) {
+                mNext.deliver(q);
+            } else {
+                finishInputEvent(q);
+            }
+        }
+
+        protected void onWindowFocusChanged(boolean hasWindowFocus) {
+            if (mNext != null) {
+                mNext.onWindowFocusChanged(hasWindowFocus);
+            }
+        }
+
+        protected void onDetachedFromWindow() {
+            if (mNext != null) {
+                mNext.onDetachedFromWindow();
+            }
+        }
+
+        protected boolean shouldDropInputEvent(QueuedInputEvent q) {
+            if (mView == null || !mAdded) {
+                Slog.w(mTag, "Dropping event due to root view being removed: " + q.mEvent);
+                return true;
+            }
+
+            // Find a reason for dropping or canceling the event.
+            final String reason;
+            if (!mAttachInfo.mHasWindowFocus
+                    && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)
+                    && !isAutofillUiShowing()) {
+                // This is a non-pointer event and the window doesn't currently have input focus
+                // This could be an event that came back from the previous stage
+                // but the window has lost focus or stopped in the meantime.
+                reason = "no window focus";
+            } else if (mStopped) {
+                reason = "window is stopped";
+            } else if (mIsAmbientMode
+                    && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_BUTTON)) {
+                reason = "non-button event in ambient mode";
+            } else if (mPausedForTransition && !isBack(q.mEvent)) {
+                reason = "paused for transition";
+            } else {
+                // Most common path: no reason to drop or cancel the event
+                return false;
+            }
+
+            if (isTerminalInputEvent(q.mEvent)) {
+                // Don't drop terminal input events, however mark them as canceled.
+                q.mEvent.cancel();
+                Slog.w(mTag, "Cancelling event (" + reason + "):" + q.mEvent);
+                return false;
+            }
+
+            // Drop non-terminal input events.
+            Slog.w(mTag, "Dropping event (" + reason + "):" + q.mEvent);
+            return true;
+        }
+
+        void dump(String prefix, PrintWriter writer) {
+            if (mNext != null) {
+                mNext.dump(prefix, writer);
+            }
+        }
+
+        private boolean isBack(InputEvent event) {
+            if (event instanceof KeyEvent) {
+                return ((KeyEvent) event).getKeyCode() == KeyEvent.KEYCODE_BACK;
+            } else {
+                return false;
+            }
+        }
+
+        private void traceEvent(QueuedInputEvent q, long traceTag) {
+            if (!Trace.isTagEnabled(traceTag)) {
+                return;
+            }
+
+            if (mTracePrefix == null) {
+                mTracePrefix = getClass().getSimpleName();
+            }
+            Trace.traceBegin(traceTag, mTracePrefix + " id=0x"
+                    + Integer.toHexString(q.mEvent.getId()));
+        }
+    }
+
+    /**
+     * Base class for implementing an input pipeline stage that supports
+     * asynchronous and out-of-order processing of input events.
+     * <p>
+     * In addition to what a normal input stage can do, an asynchronous
+     * input stage may also defer an input event that has been delivered to it
+     * and finish or forward it later.
+     * </p>
+     */
+    abstract class AsyncInputStage extends InputStage {
+        private final String mTraceCounter;
+
+        private QueuedInputEvent mQueueHead;
+        private QueuedInputEvent mQueueTail;
+        private int mQueueLength;
+
+        protected static final int DEFER = 3;
+
+        /**
+         * Creates an asynchronous input stage.
+         * @param next The next stage to which events should be forwarded.
+         * @param traceCounter The name of a counter to record the size of
+         * the queue of pending events.
+         */
+        public AsyncInputStage(InputStage next, String traceCounter) {
+            super(next);
+            mTraceCounter = traceCounter;
+        }
+
+        /**
+         * Marks the event as deferred, which is to say that it will be handled
+         * asynchronously.  The caller is responsible for calling {@link #forward}
+         * or {@link #finish} later when it is done handling the event.
+         */
+        protected void defer(QueuedInputEvent q) {
+            q.mFlags |= QueuedInputEvent.FLAG_DEFERRED;
+            enqueue(q);
+        }
+
+        @Override
+        protected void forward(QueuedInputEvent q) {
+            // Clear the deferred flag.
+            q.mFlags &= ~QueuedInputEvent.FLAG_DEFERRED;
+
+            // Fast path if the queue is empty.
+            QueuedInputEvent curr = mQueueHead;
+            if (curr == null) {
+                super.forward(q);
+                return;
+            }
+
+            // Determine whether the event must be serialized behind any others
+            // before it can be delivered to the next stage.  This is done because
+            // deferred events might be handled out of order by the stage.
+            final int deviceId = q.mEvent.getDeviceId();
+            QueuedInputEvent prev = null;
+            boolean blocked = false;
+            while (curr != null && curr != q) {
+                if (!blocked && deviceId == curr.mEvent.getDeviceId()) {
+                    blocked = true;
+                }
+                prev = curr;
+                curr = curr.mNext;
+            }
+
+            // If the event is blocked, then leave it in the queue to be delivered later.
+            // Note that the event might not yet be in the queue if it was not previously
+            // deferred so we will enqueue it if needed.
+            if (blocked) {
+                if (curr == null) {
+                    enqueue(q);
+                }
+                return;
+            }
+
+            // The event is not blocked.  Deliver it immediately.
+            if (curr != null) {
+                curr = curr.mNext;
+                dequeue(q, prev);
+            }
+            super.forward(q);
+
+            // Dequeuing this event may have unblocked successors.  Deliver them.
+            while (curr != null) {
+                if (deviceId == curr.mEvent.getDeviceId()) {
+                    if ((curr.mFlags & QueuedInputEvent.FLAG_DEFERRED) != 0) {
+                        break;
+                    }
+                    QueuedInputEvent next = curr.mNext;
+                    dequeue(curr, prev);
+                    super.forward(curr);
+                    curr = next;
+                } else {
+                    prev = curr;
+                    curr = curr.mNext;
+                }
+            }
+        }
+
+        @Override
+        protected void apply(QueuedInputEvent q, int result) {
+            if (result == DEFER) {
+                defer(q);
+            } else {
+                super.apply(q, result);
+            }
+        }
+
+        private void enqueue(QueuedInputEvent q) {
+            if (mQueueTail == null) {
+                mQueueHead = q;
+                mQueueTail = q;
+            } else {
+                mQueueTail.mNext = q;
+                mQueueTail = q;
+            }
+
+            mQueueLength += 1;
+            Trace.traceCounter(Trace.TRACE_TAG_INPUT, mTraceCounter, mQueueLength);
+        }
+
+        private void dequeue(QueuedInputEvent q, QueuedInputEvent prev) {
+            if (prev == null) {
+                mQueueHead = q.mNext;
+            } else {
+                prev.mNext = q.mNext;
+            }
+            if (mQueueTail == q) {
+                mQueueTail = prev;
+            }
+            q.mNext = null;
+
+            mQueueLength -= 1;
+            Trace.traceCounter(Trace.TRACE_TAG_INPUT, mTraceCounter, mQueueLength);
+        }
+
+        @Override
+        void dump(String prefix, PrintWriter writer) {
+            writer.print(prefix);
+            writer.print(getClass().getName());
+            writer.print(": mQueueLength=");
+            writer.println(mQueueLength);
+
+            super.dump(prefix, writer);
+        }
+    }
+
+    /**
+     * Delivers pre-ime input events to a native activity.
+     * Does not support pointer events.
+     */
+    final class NativePreImeInputStage extends AsyncInputStage
+            implements InputQueue.FinishedInputEventCallback {
+        public NativePreImeInputStage(InputStage next, String traceCounter) {
+            super(next, traceCounter);
+        }
+
+        @Override
+        protected int onProcess(QueuedInputEvent q) {
+            if (mInputQueue != null && q.mEvent instanceof KeyEvent) {
+                mInputQueue.sendInputEvent(q.mEvent, q, true, this);
+                return DEFER;
+            }
+            return FORWARD;
+        }
+
+        @Override
+        public void onFinishedInputEvent(Object token, boolean handled) {
+            QueuedInputEvent q = (QueuedInputEvent)token;
+            if (handled) {
+                finish(q, true);
+                return;
+            }
+            forward(q);
+        }
+    }
+
+    /**
+     * Delivers pre-ime input events to the view hierarchy.
+     * Does not support pointer events.
+     */
+    final class ViewPreImeInputStage extends InputStage {
+        public ViewPreImeInputStage(InputStage next) {
+            super(next);
+        }
+
+        @Override
+        protected int onProcess(QueuedInputEvent q) {
+            if (q.mEvent instanceof KeyEvent) {
+                return processKeyEvent(q);
+            }
+            return FORWARD;
+        }
+
+        private int processKeyEvent(QueuedInputEvent q) {
+            final KeyEvent event = (KeyEvent)q.mEvent;
+            if (mView.dispatchKeyEventPreIme(event)) {
+                return FINISH_HANDLED;
+            }
+            return FORWARD;
+        }
+    }
+
+    /**
+     * Delivers input events to the ime.
+     * Does not support pointer events.
+     */
+    final class ImeInputStage extends AsyncInputStage
+            implements InputMethodManager.FinishedInputEventCallback {
+        public ImeInputStage(InputStage next, String traceCounter) {
+            super(next, traceCounter);
+        }
+
+        @Override
+        protected int onProcess(QueuedInputEvent q) {
+            final int result = mImeFocusController.onProcessImeInputStage(
+                    q, q.mEvent, mWindowAttributes, this);
+            switch (result) {
+                case InputMethodManager.DISPATCH_IN_PROGRESS:
+                    // callback will be invoked later
+                    return DEFER;
+                case InputMethodManager.DISPATCH_NOT_HANDLED:
+                    // The IME could not handle it, so skip along to the next InputStage
+                    return FORWARD;
+                case InputMethodManager.DISPATCH_HANDLED:
+                    return FINISH_HANDLED;
+                default:
+                    throw new IllegalStateException("Unexpected result=" + result);
+            }
+        }
+
+        @Override
+        public void onFinishedInputEvent(Object token, boolean handled) {
+            QueuedInputEvent q = (QueuedInputEvent)token;
+            if (handled) {
+                finish(q, true);
+                return;
+            }
+            forward(q);
+        }
+    }
+
+    /**
+     * Performs early processing of post-ime input events.
+     */
+    final class EarlyPostImeInputStage extends InputStage {
+        public EarlyPostImeInputStage(InputStage next) {
+            super(next);
+        }
+
+        @Override
+        protected int onProcess(QueuedInputEvent q) {
+            if (q.mEvent instanceof KeyEvent) {
+                return processKeyEvent(q);
+            } else if (q.mEvent instanceof MotionEvent) {
+                return processMotionEvent(q);
+            }
+            return FORWARD;
+        }
+
+        private int processKeyEvent(QueuedInputEvent q) {
+            final KeyEvent event = (KeyEvent)q.mEvent;
+
+            if (mAttachInfo.mTooltipHost != null) {
+                mAttachInfo.mTooltipHost.handleTooltipKey(event);
+            }
+
+            // If the key's purpose is to exit touch mode then we consume it
+            // and consider it handled.
+            if (checkForLeavingTouchModeAndConsume(event)) {
+                return FINISH_HANDLED;
+            }
+
+            // Make sure the fallback event policy sees all keys that will be
+            // delivered to the view hierarchy.
+            mFallbackEventHandler.preDispatchKeyEvent(event);
+            return FORWARD;
+        }
+
+        private int processMotionEvent(QueuedInputEvent q) {
+            final MotionEvent event = (MotionEvent) q.mEvent;
+
+            if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
+                return processPointerEvent(q);
+            }
+
+            // If the motion event is from an absolute position device, exit touch mode
+            final int action = event.getActionMasked();
+            if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_SCROLL) {
+                if (event.isFromSource(InputDevice.SOURCE_CLASS_POSITION)) {
+                    ensureTouchMode(false);
+                }
+            }
+            return FORWARD;
+        }
+
+        private int processPointerEvent(QueuedInputEvent q) {
+            final MotionEvent event = (MotionEvent)q.mEvent;
+
+            // Translate the pointer event for compatibility, if needed.
+            if (mTranslator != null) {
+                mTranslator.translateEventInScreenToAppWindow(event);
+            }
+
+            // Enter touch mode on down or scroll from any type of a device.
+            final int action = event.getAction();
+            if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_SCROLL) {
+                ensureTouchMode(true);
+            }
+
+            if (action == MotionEvent.ACTION_DOWN) {
+                // Upon motion event within app window, close autofill ui.
+                AutofillManager afm = getAutofillManager();
+                if (afm != null) {
+                    afm.requestHideFillUi();
+                }
+            }
+
+            if (action == MotionEvent.ACTION_DOWN && mAttachInfo.mTooltipHost != null) {
+                mAttachInfo.mTooltipHost.hideTooltip();
+            }
+
+            // Offset the scroll position.
+            if (mCurScrollY != 0) {
+                event.offsetLocation(0, mCurScrollY);
+            }
+
+            // Remember the touch position for possible drag-initiation.
+            if (event.isTouchEvent()) {
+                mLastTouchPoint.x = event.getRawX();
+                mLastTouchPoint.y = event.getRawY();
+                mLastTouchSource = event.getSource();
+            }
+            return FORWARD;
+        }
+    }
+
+    /**
+     * Delivers post-ime input events to a native activity.
+     */
+    final class NativePostImeInputStage extends AsyncInputStage
+            implements InputQueue.FinishedInputEventCallback {
+        public NativePostImeInputStage(InputStage next, String traceCounter) {
+            super(next, traceCounter);
+        }
+
+        @Override
+        protected int onProcess(QueuedInputEvent q) {
+            if (mInputQueue != null) {
+                mInputQueue.sendInputEvent(q.mEvent, q, false, this);
+                return DEFER;
+            }
+            return FORWARD;
+        }
+
+        @Override
+        public void onFinishedInputEvent(Object token, boolean handled) {
+            QueuedInputEvent q = (QueuedInputEvent)token;
+            if (handled) {
+                finish(q, true);
+                return;
+            }
+            forward(q);
+        }
+    }
+
+    /**
+     * Delivers post-ime input events to the view hierarchy.
+     */
+    final class ViewPostImeInputStage extends InputStage {
+        public ViewPostImeInputStage(InputStage next) {
+            super(next);
+        }
+
+        @Override
+        protected int onProcess(QueuedInputEvent q) {
+            if (q.mEvent instanceof KeyEvent) {
+                return processKeyEvent(q);
+            } else {
+                final int source = q.mEvent.getSource();
+                if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+                    return processPointerEvent(q);
+                } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+                    return processTrackballEvent(q);
+                } else {
+                    return processGenericMotionEvent(q);
+                }
+            }
+        }
+
+        @Override
+        protected void onDeliverToNext(QueuedInputEvent q) {
+            if (mUnbufferedInputDispatch
+                    && q.mEvent instanceof MotionEvent
+                    && ((MotionEvent)q.mEvent).isTouchEvent()
+                    && isTerminalInputEvent(q.mEvent)) {
+                mUnbufferedInputDispatch = false;
+                scheduleConsumeBatchedInput();
+            }
+            super.onDeliverToNext(q);
+        }
+
+        private boolean performFocusNavigation(KeyEvent event) {
+            int direction = 0;
+            switch (event.getKeyCode()) {
+                case KeyEvent.KEYCODE_DPAD_LEFT:
+                    if (event.hasNoModifiers()) {
+                        direction = View.FOCUS_LEFT;
+                    }
+                    break;
+                case KeyEvent.KEYCODE_DPAD_RIGHT:
+                    if (event.hasNoModifiers()) {
+                        direction = View.FOCUS_RIGHT;
+                    }
+                    break;
+                case KeyEvent.KEYCODE_DPAD_UP:
+                    if (event.hasNoModifiers()) {
+                        direction = View.FOCUS_UP;
+                    }
+                    break;
+                case KeyEvent.KEYCODE_DPAD_DOWN:
+                    if (event.hasNoModifiers()) {
+                        direction = View.FOCUS_DOWN;
+                    }
+                    break;
+                case KeyEvent.KEYCODE_TAB:
+                    if (event.hasNoModifiers()) {
+                        direction = View.FOCUS_FORWARD;
+                    } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
+                        direction = View.FOCUS_BACKWARD;
+                    }
+                    break;
+            }
+            if (direction != 0) {
+                View focused = mView.findFocus();
+                if (focused != null) {
+                    View v = focused.focusSearch(direction);
+                    if (v != null && v != focused) {
+                        // do the math the get the interesting rect
+                        // of previous focused into the coord system of
+                        // newly focused view
+                        focused.getFocusedRect(mTempRect);
+                        if (mView instanceof ViewGroup) {
+                            ((ViewGroup) mView).offsetDescendantRectToMyCoords(
+                                    focused, mTempRect);
+                            ((ViewGroup) mView).offsetRectIntoDescendantCoords(
+                                    v, mTempRect);
+                        }
+                        if (v.requestFocus(direction, mTempRect)) {
+                            boolean isFastScrolling = event.getRepeatCount() > 0;
+                            playSoundEffect(
+                                    SoundEffectConstants.getConstantForFocusDirection(direction,
+                                            isFastScrolling));
+                            return true;
+                        }
+                    }
+
+                    // Give the focused view a last chance to handle the dpad key.
+                    if (mView.dispatchUnhandledMove(focused, direction)) {
+                        return true;
+                    }
+                } else {
+                    if (mView.restoreDefaultFocus()) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+
+        private boolean performKeyboardGroupNavigation(int direction) {
+            final View focused = mView.findFocus();
+            if (focused == null && mView.restoreDefaultFocus()) {
+                return true;
+            }
+            View cluster = focused == null ? keyboardNavigationClusterSearch(null, direction)
+                    : focused.keyboardNavigationClusterSearch(null, direction);
+
+            // Since requestFocus only takes "real" focus directions (and therefore also
+            // restoreFocusInCluster), convert forward/backward focus into FOCUS_DOWN.
+            int realDirection = direction;
+            if (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD) {
+                realDirection = View.FOCUS_DOWN;
+            }
+
+            if (cluster != null && cluster.isRootNamespace()) {
+                // the default cluster. Try to find a non-clustered view to focus.
+                if (cluster.restoreFocusNotInCluster()) {
+                    playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
+                    return true;
+                }
+                // otherwise skip to next actual cluster
+                cluster = keyboardNavigationClusterSearch(null, direction);
+            }
+
+            if (cluster != null && cluster.restoreFocusInCluster(realDirection)) {
+                playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
+                return true;
+            }
+
+            return false;
+        }
+
+        private int processKeyEvent(QueuedInputEvent q) {
+            final KeyEvent event = (KeyEvent)q.mEvent;
+
+            if (mUnhandledKeyManager.preViewDispatch(event)) {
+                return FINISH_HANDLED;
+            }
+
+            // Deliver the key to the view hierarchy.
+            if (mView.dispatchKeyEvent(event)) {
+                return FINISH_HANDLED;
+            }
+
+            if (shouldDropInputEvent(q)) {
+                return FINISH_NOT_HANDLED;
+            }
+
+            // This dispatch is for windows that don't have a Window.Callback. Otherwise,
+            // the Window.Callback usually will have already called this (see
+            // DecorView.superDispatchKeyEvent) leaving this call a no-op.
+            if (mUnhandledKeyManager.dispatch(mView, event)) {
+                return FINISH_HANDLED;
+            }
+
+            int groupNavigationDirection = 0;
+
+            if (event.getAction() == KeyEvent.ACTION_DOWN
+                    && event.getKeyCode() == KeyEvent.KEYCODE_TAB) {
+                if (KeyEvent.metaStateHasModifiers(event.getMetaState(), KeyEvent.META_META_ON)) {
+                    groupNavigationDirection = View.FOCUS_FORWARD;
+                } else if (KeyEvent.metaStateHasModifiers(event.getMetaState(),
+                        KeyEvent.META_META_ON | KeyEvent.META_SHIFT_ON)) {
+                    groupNavigationDirection = View.FOCUS_BACKWARD;
+                }
+            }
+
+            // If a modifier is held, try to interpret the key as a shortcut.
+            if (event.getAction() == KeyEvent.ACTION_DOWN
+                    && !KeyEvent.metaStateHasNoModifiers(event.getMetaState())
+                    && event.getRepeatCount() == 0
+                    && !KeyEvent.isModifierKey(event.getKeyCode())
+                    && groupNavigationDirection == 0) {
+                if (mView.dispatchKeyShortcutEvent(event)) {
+                    return FINISH_HANDLED;
+                }
+                if (shouldDropInputEvent(q)) {
+                    return FINISH_NOT_HANDLED;
+                }
+            }
+
+            // Apply the fallback event policy.
+            if (mFallbackEventHandler.dispatchKeyEvent(event)) {
+                return FINISH_HANDLED;
+            }
+            if (shouldDropInputEvent(q)) {
+                return FINISH_NOT_HANDLED;
+            }
+
+            // Handle automatic focus changes.
+            if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                if (groupNavigationDirection != 0) {
+                    if (performKeyboardGroupNavigation(groupNavigationDirection)) {
+                        return FINISH_HANDLED;
+                    }
+                } else {
+                    if (performFocusNavigation(event)) {
+                        return FINISH_HANDLED;
+                    }
+                }
+            }
+            return FORWARD;
+        }
+
+        private int processPointerEvent(QueuedInputEvent q) {
+            final MotionEvent event = (MotionEvent)q.mEvent;
+
+            mAttachInfo.mUnbufferedDispatchRequested = false;
+            mAttachInfo.mHandlingPointerEvent = true;
+            boolean handled = mView.dispatchPointerEvent(event);
+            maybeUpdatePointerIcon(event);
+            maybeUpdateTooltip(event);
+            mAttachInfo.mHandlingPointerEvent = false;
+            if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
+                mUnbufferedInputDispatch = true;
+                if (mConsumeBatchedInputScheduled) {
+                    scheduleConsumeBatchedInputImmediately();
+                }
+            }
+            return handled ? FINISH_HANDLED : FORWARD;
+        }
+
+        private void maybeUpdatePointerIcon(MotionEvent event) {
+            if (event.getPointerCount() == 1 && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+                if (event.getActionMasked() == MotionEvent.ACTION_HOVER_ENTER
+                        || event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) {
+                    // Other apps or the window manager may change the icon type outside of
+                    // this app, therefore the icon type has to be reset on enter/exit event.
+                    mPointerIconType = PointerIcon.TYPE_NOT_SPECIFIED;
+                }
+
+                if (event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT) {
+                    if (!updatePointerIcon(event) &&
+                            event.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE) {
+                        mPointerIconType = PointerIcon.TYPE_NOT_SPECIFIED;
+                    }
+                }
+            }
+        }
+
+        private int processTrackballEvent(QueuedInputEvent q) {
+            final MotionEvent event = (MotionEvent)q.mEvent;
+
+            if (event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE)) {
+                if (!hasPointerCapture() || mView.dispatchCapturedPointerEvent(event)) {
+                    return FINISH_HANDLED;
+                }
+            }
+
+            if (mView.dispatchTrackballEvent(event)) {
+                return FINISH_HANDLED;
+            }
+            return FORWARD;
+        }
+
+        private int processGenericMotionEvent(QueuedInputEvent q) {
+            final MotionEvent event = (MotionEvent)q.mEvent;
+
+            if (event.isFromSource(InputDevice.SOURCE_TOUCHPAD)) {
+                if (hasPointerCapture() && mView.dispatchCapturedPointerEvent(event)) {
+                    return FINISH_HANDLED;
+                }
+            }
+
+            // Deliver the event to the view.
+            if (mView.dispatchGenericMotionEvent(event)) {
+                return FINISH_HANDLED;
+            }
+            return FORWARD;
+        }
+    }
+
+    private void resetPointerIcon(MotionEvent event) {
+        mPointerIconType = PointerIcon.TYPE_NOT_SPECIFIED;
+        updatePointerIcon(event);
+    }
+
+    private boolean updatePointerIcon(MotionEvent event) {
+        final int pointerIndex = 0;
+        final float x = event.getX(pointerIndex);
+        final float y = event.getY(pointerIndex);
+        if (mView == null) {
+            // E.g. click outside a popup to dismiss it
+            Slog.d(mTag, "updatePointerIcon called after view was removed");
+            return false;
+        }
+        if (x < 0 || x >= mView.getWidth() || y < 0 || y >= mView.getHeight()) {
+            // E.g. when moving window divider with mouse
+            Slog.d(mTag, "updatePointerIcon called with position out of bounds");
+            return false;
+        }
+        final PointerIcon pointerIcon = mView.onResolvePointerIcon(event, pointerIndex);
+        final int pointerType = (pointerIcon != null) ?
+                pointerIcon.getType() : PointerIcon.TYPE_DEFAULT;
+
+        if (mPointerIconType != pointerType) {
+            mPointerIconType = pointerType;
+            mCustomPointerIcon = null;
+            if (mPointerIconType != PointerIcon.TYPE_CUSTOM) {
+                InputManager.getInstance().setPointerIconType(pointerType);
+                return true;
+            }
+        }
+        if (mPointerIconType == PointerIcon.TYPE_CUSTOM &&
+                !pointerIcon.equals(mCustomPointerIcon)) {
+            mCustomPointerIcon = pointerIcon;
+            InputManager.getInstance().setCustomPointerIcon(mCustomPointerIcon);
+        }
+        return true;
+    }
+
+    private void maybeUpdateTooltip(MotionEvent event) {
+        if (event.getPointerCount() != 1) {
+            return;
+        }
+        final int action = event.getActionMasked();
+        if (action != MotionEvent.ACTION_HOVER_ENTER
+                && action != MotionEvent.ACTION_HOVER_MOVE
+                && action != MotionEvent.ACTION_HOVER_EXIT) {
+            return;
+        }
+        AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
+        if (manager.isEnabled() && manager.isTouchExplorationEnabled()) {
+            return;
+        }
+        if (mView == null) {
+            Slog.d(mTag, "maybeUpdateTooltip called after view was removed");
+            return;
+        }
+        mView.dispatchTooltipHoverEvent(event);
+    }
+
+    @Nullable
+    private View getFocusedViewOrNull() {
+        return mView != null ? mView.findFocus() : null;
+    }
+
+    /**
+     * Performs synthesis of new input events from unhandled input events.
+     */
+    final class SyntheticInputStage extends InputStage {
+        private final SyntheticTrackballHandler mTrackball = new SyntheticTrackballHandler();
+        private final SyntheticJoystickHandler mJoystick = new SyntheticJoystickHandler();
+        private final SyntheticTouchNavigationHandler mTouchNavigation =
+                new SyntheticTouchNavigationHandler();
+        private final SyntheticKeyboardHandler mKeyboard = new SyntheticKeyboardHandler();
+
+        public SyntheticInputStage() {
+            super(null);
+        }
+
+        @Override
+        protected int onProcess(QueuedInputEvent q) {
+            q.mFlags |= QueuedInputEvent.FLAG_RESYNTHESIZED;
+            if (q.mEvent instanceof MotionEvent) {
+                final MotionEvent event = (MotionEvent)q.mEvent;
+                final int source = event.getSource();
+                if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+                    mTrackball.process(event);
+                    return FINISH_HANDLED;
+                } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
+                    mJoystick.process(event);
+                    return FINISH_HANDLED;
+                } else if ((source & InputDevice.SOURCE_TOUCH_NAVIGATION)
+                        == InputDevice.SOURCE_TOUCH_NAVIGATION) {
+                    mTouchNavigation.process(event);
+                    return FINISH_HANDLED;
+                }
+            } else if ((q.mFlags & QueuedInputEvent.FLAG_UNHANDLED) != 0) {
+                mKeyboard.process((KeyEvent)q.mEvent);
+                return FINISH_HANDLED;
+            }
+
+            return FORWARD;
+        }
+
+        @Override
+        protected void onDeliverToNext(QueuedInputEvent q) {
+            if ((q.mFlags & QueuedInputEvent.FLAG_RESYNTHESIZED) == 0) {
+                // Cancel related synthetic events if any prior stage has handled the event.
+                if (q.mEvent instanceof MotionEvent) {
+                    final MotionEvent event = (MotionEvent)q.mEvent;
+                    final int source = event.getSource();
+                    if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+                        mTrackball.cancel();
+                    } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
+                        mJoystick.cancel();
+                    } else if ((source & InputDevice.SOURCE_TOUCH_NAVIGATION)
+                            == InputDevice.SOURCE_TOUCH_NAVIGATION) {
+                        mTouchNavigation.cancel(event);
+                    }
+                }
+            }
+            super.onDeliverToNext(q);
+        }
+
+        @Override
+        protected void onWindowFocusChanged(boolean hasWindowFocus) {
+            if (!hasWindowFocus) {
+                mJoystick.cancel();
+            }
+        }
+
+        @Override
+        protected void onDetachedFromWindow() {
+            mJoystick.cancel();
+        }
+    }
+
+    /**
+     * Creates dpad events from unhandled trackball movements.
+     */
+    final class SyntheticTrackballHandler {
+        private final TrackballAxis mX = new TrackballAxis();
+        private final TrackballAxis mY = new TrackballAxis();
+        private long mLastTime;
+
+        public void process(MotionEvent event) {
+            // Translate the trackball event into DPAD keys and try to deliver those.
+            long curTime = SystemClock.uptimeMillis();
+            if ((mLastTime + MAX_TRACKBALL_DELAY) < curTime) {
+                // It has been too long since the last movement,
+                // so restart at the beginning.
+                mX.reset(0);
+                mY.reset(0);
+                mLastTime = curTime;
+            }
+
+            final int action = event.getAction();
+            final int metaState = event.getMetaState();
+            switch (action) {
+                case MotionEvent.ACTION_DOWN:
+                    mX.reset(2);
+                    mY.reset(2);
+                    enqueueInputEvent(new KeyEvent(curTime, curTime,
+                            KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER, 0, metaState,
+                            KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
+                            InputDevice.SOURCE_KEYBOARD));
+                    break;
+                case MotionEvent.ACTION_UP:
+                    mX.reset(2);
+                    mY.reset(2);
+                    enqueueInputEvent(new KeyEvent(curTime, curTime,
+                            KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER, 0, metaState,
+                            KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
+                            InputDevice.SOURCE_KEYBOARD));
+                    break;
+            }
+
+            if (DEBUG_TRACKBALL) Log.v(mTag, "TB X=" + mX.position + " step="
+                    + mX.step + " dir=" + mX.dir + " acc=" + mX.acceleration
+                    + " move=" + event.getX()
+                    + " / Y=" + mY.position + " step="
+                    + mY.step + " dir=" + mY.dir + " acc=" + mY.acceleration
+                    + " move=" + event.getY());
+            final float xOff = mX.collect(event.getX(), event.getEventTime(), "X");
+            final float yOff = mY.collect(event.getY(), event.getEventTime(), "Y");
+
+            // Generate DPAD events based on the trackball movement.
+            // We pick the axis that has moved the most as the direction of
+            // the DPAD.  When we generate DPAD events for one axis, then the
+            // other axis is reset -- we don't want to perform DPAD jumps due
+            // to slight movements in the trackball when making major movements
+            // along the other axis.
+            int keycode = 0;
+            int movement = 0;
+            float accel = 1;
+            if (xOff > yOff) {
+                movement = mX.generate();
+                if (movement != 0) {
+                    keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_RIGHT
+                            : KeyEvent.KEYCODE_DPAD_LEFT;
+                    accel = mX.acceleration;
+                    mY.reset(2);
+                }
+            } else if (yOff > 0) {
+                movement = mY.generate();
+                if (movement != 0) {
+                    keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_DOWN
+                            : KeyEvent.KEYCODE_DPAD_UP;
+                    accel = mY.acceleration;
+                    mX.reset(2);
+                }
+            }
+
+            if (keycode != 0) {
+                if (movement < 0) movement = -movement;
+                int accelMovement = (int)(movement * accel);
+                if (DEBUG_TRACKBALL) Log.v(mTag, "Move: movement=" + movement
+                        + " accelMovement=" + accelMovement
+                        + " accel=" + accel);
+                if (accelMovement > movement) {
+                    if (DEBUG_TRACKBALL) Log.v(mTag, "Delivering fake DPAD: "
+                            + keycode);
+                    movement--;
+                    int repeatCount = accelMovement - movement;
+                    enqueueInputEvent(new KeyEvent(curTime, curTime,
+                            KeyEvent.ACTION_MULTIPLE, keycode, repeatCount, metaState,
+                            KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
+                            InputDevice.SOURCE_KEYBOARD));
+                }
+                while (movement > 0) {
+                    if (DEBUG_TRACKBALL) Log.v(mTag, "Delivering fake DPAD: "
+                            + keycode);
+                    movement--;
+                    curTime = SystemClock.uptimeMillis();
+                    enqueueInputEvent(new KeyEvent(curTime, curTime,
+                            KeyEvent.ACTION_DOWN, keycode, 0, metaState,
+                            KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
+                            InputDevice.SOURCE_KEYBOARD));
+                    enqueueInputEvent(new KeyEvent(curTime, curTime,
+                            KeyEvent.ACTION_UP, keycode, 0, metaState,
+                            KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
+                            InputDevice.SOURCE_KEYBOARD));
+                }
+                mLastTime = curTime;
+            }
+        }
+
+        public void cancel() {
+            mLastTime = Integer.MIN_VALUE;
+
+            // If we reach this, we consumed a trackball event.
+            // Because we will not translate the trackball event into a key event,
+            // touch mode will not exit, so we exit touch mode here.
+            if (mView != null && mAdded) {
+                ensureTouchMode(false);
+            }
+        }
+    }
+
+    /**
+     * Maintains state information for a single trackball axis, generating
+     * discrete (DPAD) movements based on raw trackball motion.
+     */
+    static final class TrackballAxis {
+        /**
+         * The maximum amount of acceleration we will apply.
+         */
+        static final float MAX_ACCELERATION = 20;
+
+        /**
+         * The maximum amount of time (in milliseconds) between events in order
+         * for us to consider the user to be doing fast trackball movements,
+         * and thus apply an acceleration.
+         */
+        static final long FAST_MOVE_TIME = 150;
+
+        /**
+         * Scaling factor to the time (in milliseconds) between events to how
+         * much to multiple/divide the current acceleration.  When movement
+         * is < FAST_MOVE_TIME this multiplies the acceleration; when >
+         * FAST_MOVE_TIME it divides it.
+         */
+        static final float ACCEL_MOVE_SCALING_FACTOR = (1.0f/40);
+
+        static final float FIRST_MOVEMENT_THRESHOLD = 0.5f;
+        static final float SECOND_CUMULATIVE_MOVEMENT_THRESHOLD = 2.0f;
+        static final float SUBSEQUENT_INCREMENTAL_MOVEMENT_THRESHOLD = 1.0f;
+
+        float position;
+        float acceleration = 1;
+        long lastMoveTime = 0;
+        int step;
+        int dir;
+        int nonAccelMovement;
+
+        void reset(int _step) {
+            position = 0;
+            acceleration = 1;
+            lastMoveTime = 0;
+            step = _step;
+            dir = 0;
+        }
+
+        /**
+         * Add trackball movement into the state.  If the direction of movement
+         * has been reversed, the state is reset before adding the
+         * movement (so that you don't have to compensate for any previously
+         * collected movement before see the result of the movement in the
+         * new direction).
+         *
+         * @return Returns the absolute value of the amount of movement
+         * collected so far.
+         */
+        float collect(float off, long time, String axis) {
+            long normTime;
+            if (off > 0) {
+                normTime = (long)(off * FAST_MOVE_TIME);
+                if (dir < 0) {
+                    if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to positive!");
+                    position = 0;
+                    step = 0;
+                    acceleration = 1;
+                    lastMoveTime = 0;
+                }
+                dir = 1;
+            } else if (off < 0) {
+                normTime = (long)((-off) * FAST_MOVE_TIME);
+                if (dir > 0) {
+                    if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to negative!");
+                    position = 0;
+                    step = 0;
+                    acceleration = 1;
+                    lastMoveTime = 0;
+                }
+                dir = -1;
+            } else {
+                normTime = 0;
+            }
+
+            // The number of milliseconds between each movement that is
+            // considered "normal" and will not result in any acceleration
+            // or deceleration, scaled by the offset we have here.
+            if (normTime > 0) {
+                long delta = time - lastMoveTime;
+                lastMoveTime = time;
+                float acc = acceleration;
+                if (delta < normTime) {
+                    // The user is scrolling rapidly, so increase acceleration.
+                    float scale = (normTime-delta) * ACCEL_MOVE_SCALING_FACTOR;
+                    if (scale > 1) acc *= scale;
+                    if (DEBUG_TRACKBALL) Log.v(TAG, axis + " accelerate: off="
+                            + off + " normTime=" + normTime + " delta=" + delta
+                            + " scale=" + scale + " acc=" + acc);
+                    acceleration = acc < MAX_ACCELERATION ? acc : MAX_ACCELERATION;
+                } else {
+                    // The user is scrolling slowly, so decrease acceleration.
+                    float scale = (delta-normTime) * ACCEL_MOVE_SCALING_FACTOR;
+                    if (scale > 1) acc /= scale;
+                    if (DEBUG_TRACKBALL) Log.v(TAG, axis + " deccelerate: off="
+                            + off + " normTime=" + normTime + " delta=" + delta
+                            + " scale=" + scale + " acc=" + acc);
+                    acceleration = acc > 1 ? acc : 1;
+                }
+            }
+            position += off;
+            return Math.abs(position);
+        }
+
+        /**
+         * Generate the number of discrete movement events appropriate for
+         * the currently collected trackball movement.
+         *
+         * @return Returns the number of discrete movements, either positive
+         * or negative, or 0 if there is not enough trackball movement yet
+         * for a discrete movement.
+         */
+        int generate() {
+            int movement = 0;
+            nonAccelMovement = 0;
+            do {
+                final int dir = position >= 0 ? 1 : -1;
+                switch (step) {
+                    // If we are going to execute the first step, then we want
+                    // to do this as soon as possible instead of waiting for
+                    // a full movement, in order to make things look responsive.
+                    case 0:
+                        if (Math.abs(position) < FIRST_MOVEMENT_THRESHOLD) {
+                            return movement;
+                        }
+                        movement += dir;
+                        nonAccelMovement += dir;
+                        step = 1;
+                        break;
+                    // If we have generated the first movement, then we need
+                    // to wait for the second complete trackball motion before
+                    // generating the second discrete movement.
+                    case 1:
+                        if (Math.abs(position) < SECOND_CUMULATIVE_MOVEMENT_THRESHOLD) {
+                            return movement;
+                        }
+                        movement += dir;
+                        nonAccelMovement += dir;
+                        position -= SECOND_CUMULATIVE_MOVEMENT_THRESHOLD * dir;
+                        step = 2;
+                        break;
+                    // After the first two, we generate discrete movements
+                    // consistently with the trackball, applying an acceleration
+                    // if the trackball is moving quickly.  This is a simple
+                    // acceleration on top of what we already compute based
+                    // on how quickly the wheel is being turned, to apply
+                    // a longer increasing acceleration to continuous movement
+                    // in one direction.
+                    default:
+                        if (Math.abs(position) < SUBSEQUENT_INCREMENTAL_MOVEMENT_THRESHOLD) {
+                            return movement;
+                        }
+                        movement += dir;
+                        position -= dir * SUBSEQUENT_INCREMENTAL_MOVEMENT_THRESHOLD;
+                        float acc = acceleration;
+                        acc *= 1.1f;
+                        acceleration = acc < MAX_ACCELERATION ? acc : acceleration;
+                        break;
+                }
+            } while (true);
+        }
+    }
+
+    /**
+     * Creates dpad events from unhandled joystick movements.
+     */
+    final class SyntheticJoystickHandler extends Handler {
+        private final static int MSG_ENQUEUE_X_AXIS_KEY_REPEAT = 1;
+        private final static int MSG_ENQUEUE_Y_AXIS_KEY_REPEAT = 2;
+
+        private final JoystickAxesState mJoystickAxesState = new JoystickAxesState();
+        private final SparseArray<KeyEvent> mDeviceKeyEvents = new SparseArray<>();
+
+        public SyntheticJoystickHandler() {
+            super(true);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_ENQUEUE_X_AXIS_KEY_REPEAT:
+                case MSG_ENQUEUE_Y_AXIS_KEY_REPEAT: {
+                    if (mAttachInfo.mHasWindowFocus) {
+                        KeyEvent oldEvent = (KeyEvent) msg.obj;
+                        KeyEvent e = KeyEvent.changeTimeRepeat(oldEvent,
+                                SystemClock.uptimeMillis(), oldEvent.getRepeatCount() + 1);
+                        enqueueInputEvent(e);
+                        Message m = obtainMessage(msg.what, e);
+                        m.setAsynchronous(true);
+                        sendMessageDelayed(m, ViewConfiguration.getKeyRepeatDelay());
+                    }
+                } break;
+            }
+        }
+
+        public void process(MotionEvent event) {
+            switch(event.getActionMasked()) {
+                case MotionEvent.ACTION_CANCEL:
+                    cancel();
+                    break;
+                case MotionEvent.ACTION_MOVE:
+                    update(event);
+                    break;
+                default:
+                    Log.w(mTag, "Unexpected action: " + event.getActionMasked());
+            }
+        }
+
+        private void cancel() {
+            removeMessages(MSG_ENQUEUE_X_AXIS_KEY_REPEAT);
+            removeMessages(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT);
+            for (int i = 0; i < mDeviceKeyEvents.size(); i++) {
+                final KeyEvent keyEvent = mDeviceKeyEvents.valueAt(i);
+                if (keyEvent != null) {
+                    enqueueInputEvent(KeyEvent.changeTimeRepeat(keyEvent,
+                            SystemClock.uptimeMillis(), 0));
+                }
+            }
+            mDeviceKeyEvents.clear();
+            mJoystickAxesState.resetState();
+        }
+
+        private void update(MotionEvent event) {
+            final int historySize = event.getHistorySize();
+            for (int h = 0; h < historySize; h++) {
+                final long time = event.getHistoricalEventTime(h);
+                mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_X,
+                        event.getHistoricalAxisValue(MotionEvent.AXIS_X, 0, h));
+                mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_Y,
+                        event.getHistoricalAxisValue(MotionEvent.AXIS_Y, 0, h));
+                mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_HAT_X,
+                        event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_X, 0, h));
+                mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_HAT_Y,
+                        event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_Y, 0, h));
+            }
+            final long time = event.getEventTime();
+            mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_X,
+                    event.getAxisValue(MotionEvent.AXIS_X));
+            mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_Y,
+                    event.getAxisValue(MotionEvent.AXIS_Y));
+            mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_HAT_X,
+                    event.getAxisValue(MotionEvent.AXIS_HAT_X));
+            mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_HAT_Y,
+                    event.getAxisValue(MotionEvent.AXIS_HAT_Y));
+        }
+
+        final class JoystickAxesState {
+            // State machine: from neutral state (no button press) can go into
+            // button STATE_UP_OR_LEFT or STATE_DOWN_OR_RIGHT state, emitting an ACTION_DOWN event.
+            // From STATE_UP_OR_LEFT or STATE_DOWN_OR_RIGHT state can go into neutral state,
+            // emitting an ACTION_UP event.
+            private static final int STATE_UP_OR_LEFT = -1;
+            private static final int STATE_NEUTRAL = 0;
+            private static final int STATE_DOWN_OR_RIGHT = 1;
+
+            final int[] mAxisStatesHat = {STATE_NEUTRAL, STATE_NEUTRAL}; // {AXIS_HAT_X, AXIS_HAT_Y}
+            final int[] mAxisStatesStick = {STATE_NEUTRAL, STATE_NEUTRAL}; // {AXIS_X, AXIS_Y}
+
+            void resetState() {
+                mAxisStatesHat[0] = STATE_NEUTRAL;
+                mAxisStatesHat[1] = STATE_NEUTRAL;
+                mAxisStatesStick[0] = STATE_NEUTRAL;
+                mAxisStatesStick[1] = STATE_NEUTRAL;
+            }
+
+            void updateStateForAxis(MotionEvent event, long time, int axis, float value) {
+                // Emit KeyEvent if necessary
+                // axis can be AXIS_X, AXIS_Y, AXIS_HAT_X, AXIS_HAT_Y
+                final int axisStateIndex;
+                final int repeatMessage;
+                if (isXAxis(axis)) {
+                    axisStateIndex = 0;
+                    repeatMessage = MSG_ENQUEUE_X_AXIS_KEY_REPEAT;
+                } else if (isYAxis(axis)) {
+                    axisStateIndex = 1;
+                    repeatMessage = MSG_ENQUEUE_Y_AXIS_KEY_REPEAT;
+                } else {
+                    Log.e(mTag, "Unexpected axis " + axis + " in updateStateForAxis!");
+                    return;
+                }
+                final int newState = joystickAxisValueToState(value);
+
+                final int currentState;
+                if (axis == MotionEvent.AXIS_X || axis == MotionEvent.AXIS_Y) {
+                    currentState = mAxisStatesStick[axisStateIndex];
+                } else {
+                    currentState = mAxisStatesHat[axisStateIndex];
+                }
+
+                if (currentState == newState) {
+                    return;
+                }
+
+                final int metaState = event.getMetaState();
+                final int deviceId = event.getDeviceId();
+                final int source = event.getSource();
+
+                if (currentState == STATE_DOWN_OR_RIGHT || currentState == STATE_UP_OR_LEFT) {
+                    // send a button release event
+                    final int keyCode = joystickAxisAndStateToKeycode(axis, currentState);
+                    if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
+                        enqueueInputEvent(new KeyEvent(time, time, KeyEvent.ACTION_UP, keyCode,
+                                0, metaState, deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
+                        // remove the corresponding pending UP event if focus lost/view detached
+                        mDeviceKeyEvents.put(deviceId, null);
+                    }
+                    removeMessages(repeatMessage);
+                }
+
+                if (newState == STATE_DOWN_OR_RIGHT || newState == STATE_UP_OR_LEFT) {
+                    // send a button down event
+                    final int keyCode = joystickAxisAndStateToKeycode(axis, newState);
+                    if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
+                        KeyEvent keyEvent = new KeyEvent(time, time, KeyEvent.ACTION_DOWN, keyCode,
+                                0, metaState, deviceId, 0, KeyEvent.FLAG_FALLBACK, source);
+                        enqueueInputEvent(keyEvent);
+                        Message m = obtainMessage(repeatMessage, keyEvent);
+                        m.setAsynchronous(true);
+                        sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout());
+                        // store the corresponding ACTION_UP event so that it can be sent
+                        // if focus is lost or root view is removed
+                        mDeviceKeyEvents.put(deviceId,
+                                new KeyEvent(time, time, KeyEvent.ACTION_UP, keyCode,
+                                        0, metaState, deviceId, 0,
+                                        KeyEvent.FLAG_FALLBACK | KeyEvent.FLAG_CANCELED,
+                                        source));
+                    }
+                }
+                if (axis == MotionEvent.AXIS_X || axis == MotionEvent.AXIS_Y) {
+                    mAxisStatesStick[axisStateIndex] = newState;
+                } else {
+                    mAxisStatesHat[axisStateIndex] = newState;
+                }
+            }
+
+            private boolean isXAxis(int axis) {
+                return axis == MotionEvent.AXIS_X || axis == MotionEvent.AXIS_HAT_X;
+            }
+            private boolean isYAxis(int axis) {
+                return axis == MotionEvent.AXIS_Y || axis == MotionEvent.AXIS_HAT_Y;
+            }
+
+            private int joystickAxisAndStateToKeycode(int axis, int state) {
+                if (isXAxis(axis) && state == STATE_UP_OR_LEFT) {
+                    return KeyEvent.KEYCODE_DPAD_LEFT;
+                }
+                if (isXAxis(axis) && state == STATE_DOWN_OR_RIGHT) {
+                    return KeyEvent.KEYCODE_DPAD_RIGHT;
+                }
+                if (isYAxis(axis) && state == STATE_UP_OR_LEFT) {
+                    return KeyEvent.KEYCODE_DPAD_UP;
+                }
+                if (isYAxis(axis) && state == STATE_DOWN_OR_RIGHT) {
+                    return KeyEvent.KEYCODE_DPAD_DOWN;
+                }
+                Log.e(mTag, "Unknown axis " + axis + " or direction " + state);
+                return KeyEvent.KEYCODE_UNKNOWN; // should never happen
+            }
+
+            private int joystickAxisValueToState(float value) {
+                if (value >= 0.5f) {
+                    return STATE_DOWN_OR_RIGHT;
+                } else if (value <= -0.5f) {
+                    return STATE_UP_OR_LEFT;
+                } else {
+                    return STATE_NEUTRAL;
+                }
+            }
+        }
+    }
+
+    /**
+     * Creates dpad events from unhandled touch navigation movements.
+     */
+    final class SyntheticTouchNavigationHandler extends Handler {
+        private static final String LOCAL_TAG = "SyntheticTouchNavigationHandler";
+        private static final boolean LOCAL_DEBUG = false;
+
+        // Assumed nominal width and height in millimeters of a touch navigation pad,
+        // if no resolution information is available from the input system.
+        private static final float DEFAULT_WIDTH_MILLIMETERS = 48;
+        private static final float DEFAULT_HEIGHT_MILLIMETERS = 48;
+
+        /* TODO: These constants should eventually be moved to ViewConfiguration. */
+
+        // The nominal distance traveled to move by one unit.
+        private static final int TICK_DISTANCE_MILLIMETERS = 12;
+
+        // Minimum and maximum fling velocity in ticks per second.
+        // The minimum velocity should be set such that we perform enough ticks per
+        // second that the fling appears to be fluid.  For example, if we set the minimum
+        // to 2 ticks per second, then there may be up to half a second delay between the next
+        // to last and last ticks which is noticeably discrete and jerky.  This value should
+        // probably not be set to anything less than about 4.
+        // If fling accuracy is a problem then consider tuning the tick distance instead.
+        private static final float MIN_FLING_VELOCITY_TICKS_PER_SECOND = 6f;
+        private static final float MAX_FLING_VELOCITY_TICKS_PER_SECOND = 20f;
+
+        // Fling velocity decay factor applied after each new key is emitted.
+        // This parameter controls the deceleration and overall duration of the fling.
+        // The fling stops automatically when its velocity drops below the minimum
+        // fling velocity defined above.
+        private static final float FLING_TICK_DECAY = 0.8f;
+
+        /* The input device that we are tracking. */
+
+        private int mCurrentDeviceId = -1;
+        private int mCurrentSource;
+        private boolean mCurrentDeviceSupported;
+
+        /* Configuration for the current input device. */
+
+        // The scaled tick distance.  A movement of this amount should generally translate
+        // into a single dpad event in a given direction.
+        private float mConfigTickDistance;
+
+        // The minimum and maximum scaled fling velocity.
+        private float mConfigMinFlingVelocity;
+        private float mConfigMaxFlingVelocity;
+
+        /* Tracking state. */
+
+        // The velocity tracker for detecting flings.
+        private VelocityTracker mVelocityTracker;
+
+        // The active pointer id, or -1 if none.
+        private int mActivePointerId = -1;
+
+        // Location where tracking started.
+        private float mStartX;
+        private float mStartY;
+
+        // Most recently observed position.
+        private float mLastX;
+        private float mLastY;
+
+        // Accumulated movement delta since the last direction key was sent.
+        private float mAccumulatedX;
+        private float mAccumulatedY;
+
+        // Set to true if any movement was delivered to the app.
+        // Implies that tap slop was exceeded.
+        private boolean mConsumedMovement;
+
+        // The most recently sent key down event.
+        // The keycode remains set until the direction changes or a fling ends
+        // so that repeated key events may be generated as required.
+        private long mPendingKeyDownTime;
+        private int mPendingKeyCode = KeyEvent.KEYCODE_UNKNOWN;
+        private int mPendingKeyRepeatCount;
+        private int mPendingKeyMetaState;
+
+        // The current fling velocity while a fling is in progress.
+        private boolean mFlinging;
+        private float mFlingVelocity;
+
+        public SyntheticTouchNavigationHandler() {
+            super(true);
+        }
+
+        public void process(MotionEvent event) {
+            // Update the current device information.
+            final long time = event.getEventTime();
+            final int deviceId = event.getDeviceId();
+            final int source = event.getSource();
+            if (mCurrentDeviceId != deviceId || mCurrentSource != source) {
+                finishKeys(time);
+                finishTracking(time);
+                mCurrentDeviceId = deviceId;
+                mCurrentSource = source;
+                mCurrentDeviceSupported = false;
+                InputDevice device = event.getDevice();
+                if (device != null) {
+                    // In order to support an input device, we must know certain
+                    // characteristics about it, such as its size and resolution.
+                    InputDevice.MotionRange xRange = device.getMotionRange(MotionEvent.AXIS_X);
+                    InputDevice.MotionRange yRange = device.getMotionRange(MotionEvent.AXIS_Y);
+                    if (xRange != null && yRange != null) {
+                        mCurrentDeviceSupported = true;
+
+                        // Infer the resolution if it not actually known.
+                        float xRes = xRange.getResolution();
+                        if (xRes <= 0) {
+                            xRes = xRange.getRange() / DEFAULT_WIDTH_MILLIMETERS;
+                        }
+                        float yRes = yRange.getResolution();
+                        if (yRes <= 0) {
+                            yRes = yRange.getRange() / DEFAULT_HEIGHT_MILLIMETERS;
+                        }
+                        float nominalRes = (xRes + yRes) * 0.5f;
+
+                        // Precompute all of the configuration thresholds we will need.
+                        mConfigTickDistance = TICK_DISTANCE_MILLIMETERS * nominalRes;
+                        mConfigMinFlingVelocity =
+                                MIN_FLING_VELOCITY_TICKS_PER_SECOND * mConfigTickDistance;
+                        mConfigMaxFlingVelocity =
+                                MAX_FLING_VELOCITY_TICKS_PER_SECOND * mConfigTickDistance;
+
+                        if (LOCAL_DEBUG) {
+                            Log.d(LOCAL_TAG, "Configured device " + mCurrentDeviceId
+                                    + " (" + Integer.toHexString(mCurrentSource) + "): "
+                                    + ", mConfigTickDistance=" + mConfigTickDistance
+                                    + ", mConfigMinFlingVelocity=" + mConfigMinFlingVelocity
+                                    + ", mConfigMaxFlingVelocity=" + mConfigMaxFlingVelocity);
+                        }
+                    }
+                }
+            }
+            if (!mCurrentDeviceSupported) {
+                return;
+            }
+
+            // Handle the event.
+            final int action = event.getActionMasked();
+            switch (action) {
+                case MotionEvent.ACTION_DOWN: {
+                    boolean caughtFling = mFlinging;
+                    finishKeys(time);
+                    finishTracking(time);
+                    mActivePointerId = event.getPointerId(0);
+                    mVelocityTracker = VelocityTracker.obtain();
+                    mVelocityTracker.addMovement(event);
+                    mStartX = event.getX();
+                    mStartY = event.getY();
+                    mLastX = mStartX;
+                    mLastY = mStartY;
+                    mAccumulatedX = 0;
+                    mAccumulatedY = 0;
+
+                    // If we caught a fling, then pretend that the tap slop has already
+                    // been exceeded to suppress taps whose only purpose is to stop the fling.
+                    mConsumedMovement = caughtFling;
+                    break;
+                }
+
+                case MotionEvent.ACTION_MOVE:
+                case MotionEvent.ACTION_UP: {
+                    if (mActivePointerId < 0) {
+                        break;
+                    }
+                    final int index = event.findPointerIndex(mActivePointerId);
+                    if (index < 0) {
+                        finishKeys(time);
+                        finishTracking(time);
+                        break;
+                    }
+
+                    mVelocityTracker.addMovement(event);
+                    final float x = event.getX(index);
+                    final float y = event.getY(index);
+                    mAccumulatedX += x - mLastX;
+                    mAccumulatedY += y - mLastY;
+                    mLastX = x;
+                    mLastY = y;
+
+                    // Consume any accumulated movement so far.
+                    final int metaState = event.getMetaState();
+                    consumeAccumulatedMovement(time, metaState);
+
+                    // Detect taps and flings.
+                    if (action == MotionEvent.ACTION_UP) {
+                        if (mConsumedMovement && mPendingKeyCode != KeyEvent.KEYCODE_UNKNOWN) {
+                            // It might be a fling.
+                            mVelocityTracker.computeCurrentVelocity(1000, mConfigMaxFlingVelocity);
+                            final float vx = mVelocityTracker.getXVelocity(mActivePointerId);
+                            final float vy = mVelocityTracker.getYVelocity(mActivePointerId);
+                            if (!startFling(time, vx, vy)) {
+                                finishKeys(time);
+                            }
+                        }
+                        finishTracking(time);
+                    }
+                    break;
+                }
+
+                case MotionEvent.ACTION_CANCEL: {
+                    finishKeys(time);
+                    finishTracking(time);
+                    break;
+                }
+            }
+        }
+
+        public void cancel(MotionEvent event) {
+            if (mCurrentDeviceId == event.getDeviceId()
+                    && mCurrentSource == event.getSource()) {
+                final long time = event.getEventTime();
+                finishKeys(time);
+                finishTracking(time);
+            }
+        }
+
+        private void finishKeys(long time) {
+            cancelFling();
+            sendKeyUp(time);
+        }
+
+        private void finishTracking(long time) {
+            if (mActivePointerId >= 0) {
+                mActivePointerId = -1;
+                mVelocityTracker.recycle();
+                mVelocityTracker = null;
+            }
+        }
+
+        private void consumeAccumulatedMovement(long time, int metaState) {
+            final float absX = Math.abs(mAccumulatedX);
+            final float absY = Math.abs(mAccumulatedY);
+            if (absX >= absY) {
+                if (absX >= mConfigTickDistance) {
+                    mAccumulatedX = consumeAccumulatedMovement(time, metaState, mAccumulatedX,
+                            KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT);
+                    mAccumulatedY = 0;
+                    mConsumedMovement = true;
+                }
+            } else {
+                if (absY >= mConfigTickDistance) {
+                    mAccumulatedY = consumeAccumulatedMovement(time, metaState, mAccumulatedY,
+                            KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN);
+                    mAccumulatedX = 0;
+                    mConsumedMovement = true;
+                }
+            }
+        }
+
+        private float consumeAccumulatedMovement(long time, int metaState,
+                float accumulator, int negativeKeyCode, int positiveKeyCode) {
+            while (accumulator <= -mConfigTickDistance) {
+                sendKeyDownOrRepeat(time, negativeKeyCode, metaState);
+                accumulator += mConfigTickDistance;
+            }
+            while (accumulator >= mConfigTickDistance) {
+                sendKeyDownOrRepeat(time, positiveKeyCode, metaState);
+                accumulator -= mConfigTickDistance;
+            }
+            return accumulator;
+        }
+
+        private void sendKeyDownOrRepeat(long time, int keyCode, int metaState) {
+            if (mPendingKeyCode != keyCode) {
+                sendKeyUp(time);
+                mPendingKeyDownTime = time;
+                mPendingKeyCode = keyCode;
+                mPendingKeyRepeatCount = 0;
+            } else {
+                mPendingKeyRepeatCount += 1;
+            }
+            mPendingKeyMetaState = metaState;
+
+            // Note: Normally we would pass FLAG_LONG_PRESS when the repeat count is 1
+            // but it doesn't quite make sense when simulating the events in this way.
+            if (LOCAL_DEBUG) {
+                Log.d(LOCAL_TAG, "Sending key down: keyCode=" + mPendingKeyCode
+                        + ", repeatCount=" + mPendingKeyRepeatCount
+                        + ", metaState=" + Integer.toHexString(mPendingKeyMetaState));
+            }
+            enqueueInputEvent(new KeyEvent(mPendingKeyDownTime, time,
+                    KeyEvent.ACTION_DOWN, mPendingKeyCode, mPendingKeyRepeatCount,
+                    mPendingKeyMetaState, mCurrentDeviceId,
+                    KeyEvent.FLAG_FALLBACK, mCurrentSource));
+        }
+
+        private void sendKeyUp(long time) {
+            if (mPendingKeyCode != KeyEvent.KEYCODE_UNKNOWN) {
+                if (LOCAL_DEBUG) {
+                    Log.d(LOCAL_TAG, "Sending key up: keyCode=" + mPendingKeyCode
+                            + ", metaState=" + Integer.toHexString(mPendingKeyMetaState));
+                }
+                enqueueInputEvent(new KeyEvent(mPendingKeyDownTime, time,
+                        KeyEvent.ACTION_UP, mPendingKeyCode, 0, mPendingKeyMetaState,
+                        mCurrentDeviceId, 0, KeyEvent.FLAG_FALLBACK,
+                        mCurrentSource));
+                mPendingKeyCode = KeyEvent.KEYCODE_UNKNOWN;
+            }
+        }
+
+        private boolean startFling(long time, float vx, float vy) {
+            if (LOCAL_DEBUG) {
+                Log.d(LOCAL_TAG, "Considering fling: vx=" + vx + ", vy=" + vy
+                        + ", min=" + mConfigMinFlingVelocity);
+            }
+
+            // Flings must be oriented in the same direction as the preceding movements.
+            switch (mPendingKeyCode) {
+                case KeyEvent.KEYCODE_DPAD_LEFT:
+                    if (-vx >= mConfigMinFlingVelocity
+                            && Math.abs(vy) < mConfigMinFlingVelocity) {
+                        mFlingVelocity = -vx;
+                        break;
+                    }
+                    return false;
+
+                case KeyEvent.KEYCODE_DPAD_RIGHT:
+                    if (vx >= mConfigMinFlingVelocity
+                            && Math.abs(vy) < mConfigMinFlingVelocity) {
+                        mFlingVelocity = vx;
+                        break;
+                    }
+                    return false;
+
+                case KeyEvent.KEYCODE_DPAD_UP:
+                    if (-vy >= mConfigMinFlingVelocity
+                            && Math.abs(vx) < mConfigMinFlingVelocity) {
+                        mFlingVelocity = -vy;
+                        break;
+                    }
+                    return false;
+
+                case KeyEvent.KEYCODE_DPAD_DOWN:
+                    if (vy >= mConfigMinFlingVelocity
+                            && Math.abs(vx) < mConfigMinFlingVelocity) {
+                        mFlingVelocity = vy;
+                        break;
+                    }
+                    return false;
+            }
+
+            // Post the first fling event.
+            mFlinging = postFling(time);
+            return mFlinging;
+        }
+
+        private boolean postFling(long time) {
+            // The idea here is to estimate the time when the pointer would have
+            // traveled one tick distance unit given the current fling velocity.
+            // This effect creates continuity of motion.
+            if (mFlingVelocity >= mConfigMinFlingVelocity) {
+                long delay = (long)(mConfigTickDistance / mFlingVelocity * 1000);
+                postAtTime(mFlingRunnable, time + delay);
+                if (LOCAL_DEBUG) {
+                    Log.d(LOCAL_TAG, "Posted fling: velocity="
+                            + mFlingVelocity + ", delay=" + delay
+                            + ", keyCode=" + mPendingKeyCode);
+                }
+                return true;
+            }
+            return false;
+        }
+
+        private void cancelFling() {
+            if (mFlinging) {
+                removeCallbacks(mFlingRunnable);
+                mFlinging = false;
+            }
+        }
+
+        private final Runnable mFlingRunnable = new Runnable() {
+            @Override
+            public void run() {
+                final long time = SystemClock.uptimeMillis();
+                sendKeyDownOrRepeat(time, mPendingKeyCode, mPendingKeyMetaState);
+                mFlingVelocity *= FLING_TICK_DECAY;
+                if (!postFling(time)) {
+                    mFlinging = false;
+                    finishKeys(time);
+                }
+            }
+        };
+    }
+
+    final class SyntheticKeyboardHandler {
+        public void process(KeyEvent event) {
+            if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) != 0) {
+                return;
+            }
+
+            final KeyCharacterMap kcm = event.getKeyCharacterMap();
+            final int keyCode = event.getKeyCode();
+            final int metaState = event.getMetaState();
+
+            // Check for fallback actions specified by the key character map.
+            KeyCharacterMap.FallbackAction fallbackAction =
+                    kcm.getFallbackAction(keyCode, metaState);
+            if (fallbackAction != null) {
+                final int flags = event.getFlags() | KeyEvent.FLAG_FALLBACK;
+                KeyEvent fallbackEvent = KeyEvent.obtain(
+                        event.getDownTime(), event.getEventTime(),
+                        event.getAction(), fallbackAction.keyCode,
+                        event.getRepeatCount(), fallbackAction.metaState,
+                        event.getDeviceId(), event.getScanCode(),
+                        flags, event.getSource(), null);
+                fallbackAction.recycle();
+                enqueueInputEvent(fallbackEvent);
+            }
+        }
+    }
+
+    /**
+     * Returns true if the key is used for keyboard navigation.
+     * @param keyEvent The key event.
+     * @return True if the key is used for keyboard navigation.
+     */
+    private static boolean isNavigationKey(KeyEvent keyEvent) {
+        switch (keyEvent.getKeyCode()) {
+        case KeyEvent.KEYCODE_DPAD_LEFT:
+        case KeyEvent.KEYCODE_DPAD_RIGHT:
+        case KeyEvent.KEYCODE_DPAD_UP:
+        case KeyEvent.KEYCODE_DPAD_DOWN:
+        case KeyEvent.KEYCODE_DPAD_CENTER:
+        case KeyEvent.KEYCODE_PAGE_UP:
+        case KeyEvent.KEYCODE_PAGE_DOWN:
+        case KeyEvent.KEYCODE_MOVE_HOME:
+        case KeyEvent.KEYCODE_MOVE_END:
+        case KeyEvent.KEYCODE_TAB:
+        case KeyEvent.KEYCODE_SPACE:
+        case KeyEvent.KEYCODE_ENTER:
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if the key is used for typing.
+     * @param keyEvent The key event.
+     * @return True if the key is used for typing.
+     */
+    private static boolean isTypingKey(KeyEvent keyEvent) {
+        return keyEvent.getUnicodeChar() > 0;
+    }
+
+    /**
+     * See if the key event means we should leave touch mode (and leave touch mode if so).
+     * @param event The key event.
+     * @return Whether this key event should be consumed (meaning the act of
+     *   leaving touch mode alone is considered the event).
+     */
+    private boolean checkForLeavingTouchModeAndConsume(KeyEvent event) {
+        // Only relevant in touch mode.
+        if (!mAttachInfo.mInTouchMode) {
+            return false;
+        }
+
+        // Only consider leaving touch mode on DOWN or MULTIPLE actions, never on UP.
+        final int action = event.getAction();
+        if (action != KeyEvent.ACTION_DOWN && action != KeyEvent.ACTION_MULTIPLE) {
+            return false;
+        }
+
+        // Don't leave touch mode if the IME told us not to.
+        if ((event.getFlags() & KeyEvent.FLAG_KEEP_TOUCH_MODE) != 0) {
+            return false;
+        }
+
+        // If the key can be used for keyboard navigation then leave touch mode
+        // and select a focused view if needed (in ensureTouchMode).
+        // When a new focused view is selected, we consume the navigation key because
+        // navigation doesn't make much sense unless a view already has focus so
+        // the key's purpose is to set focus.
+        if (isNavigationKey(event)) {
+            return ensureTouchMode(false);
+        }
+
+        // If the key can be used for typing then leave touch mode
+        // and select a focused view if needed (in ensureTouchMode).
+        // Always allow the view to process the typing key.
+        if (isTypingKey(event)) {
+            ensureTouchMode(false);
+            return false;
+        }
+
+        return false;
+    }
+
+    /* drag/drop */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    void setLocalDragState(Object obj) {
+        mLocalDragState = obj;
+    }
+
+    private void handleDragEvent(DragEvent event) {
+        // From the root, only drag start/end/location are dispatched.  entered/exited
+        // are determined and dispatched by the viewgroup hierarchy, who then report
+        // that back here for ultimate reporting back to the framework.
+        if (mView != null && mAdded) {
+            final int what = event.mAction;
+
+            // Cache the drag description when the operation starts, then fill it in
+            // on subsequent calls as a convenience
+            if (what == DragEvent.ACTION_DRAG_STARTED) {
+                mCurrentDragView = null;    // Start the current-recipient tracking
+                mDragDescription = event.mClipDescription;
+            } else {
+                if (what == DragEvent.ACTION_DRAG_ENDED) {
+                    mDragDescription = null;
+                }
+                event.mClipDescription = mDragDescription;
+            }
+
+            if (what == DragEvent.ACTION_DRAG_EXITED) {
+                // A direct EXITED event means that the window manager knows we've just crossed
+                // a window boundary, so the current drag target within this one must have
+                // just been exited. Send the EXITED notification to the current drag view, if any.
+                if (View.sCascadedDragDrop) {
+                    mView.dispatchDragEnterExitInPreN(event);
+                }
+                setDragFocus(null, event);
+            } else {
+                // For events with a [screen] location, translate into window coordinates
+                if ((what == DragEvent.ACTION_DRAG_LOCATION) || (what == DragEvent.ACTION_DROP)) {
+                    mDragPoint.set(event.mX, event.mY);
+                    if (mTranslator != null) {
+                        mTranslator.translatePointInScreenToAppWindow(mDragPoint);
+                    }
+
+                    if (mCurScrollY != 0) {
+                        mDragPoint.offset(0, mCurScrollY);
+                    }
+
+                    event.mX = mDragPoint.x;
+                    event.mY = mDragPoint.y;
+                }
+
+                // Remember who the current drag target is pre-dispatch
+                final View prevDragView = mCurrentDragView;
+
+                if (what == DragEvent.ACTION_DROP && event.mClipData != null) {
+                    event.mClipData.prepareToEnterProcess(
+                            mView.getContext().getAttributionSource());
+                }
+
+                // Now dispatch the drag/drop event
+                boolean result = mView.dispatchDragEvent(event);
+
+                if (what == DragEvent.ACTION_DRAG_LOCATION && !event.mEventHandlerWasCalled) {
+                    // If the LOCATION event wasn't delivered to any handler, no view now has a drag
+                    // focus.
+                    setDragFocus(null, event);
+                }
+
+                // If we changed apparent drag target, tell the OS about it
+                if (prevDragView != mCurrentDragView) {
+                    try {
+                        if (prevDragView != null) {
+                            mWindowSession.dragRecipientExited(mWindow);
+                        }
+                        if (mCurrentDragView != null) {
+                            mWindowSession.dragRecipientEntered(mWindow);
+                        }
+                    } catch (RemoteException e) {
+                        Slog.e(mTag, "Unable to note drag target change");
+                    }
+                }
+
+                // Report the drop result when we're done
+                if (what == DragEvent.ACTION_DROP) {
+                    try {
+                        Log.i(mTag, "Reporting drop result: " + result);
+                        mWindowSession.reportDropResult(mWindow, result);
+                    } catch (RemoteException e) {
+                        Log.e(mTag, "Unable to report drop result");
+                    }
+                }
+
+                // When the drag operation ends, reset drag-related state
+                if (what == DragEvent.ACTION_DRAG_ENDED) {
+                    mCurrentDragView = null;
+                    setLocalDragState(null);
+                    mAttachInfo.mDragToken = null;
+                    if (mAttachInfo.mDragSurface != null) {
+                        mAttachInfo.mDragSurface.release();
+                        mAttachInfo.mDragSurface = null;
+                    }
+                }
+            }
+        }
+        event.recycle();
+    }
+
+    /**
+     * Notify that the window title changed
+     */
+    public void onWindowTitleChanged() {
+        mAttachInfo.mForceReportNewAttributes = true;
+    }
+
+    public void handleDispatchWindowShown() {
+        mAttachInfo.mTreeObserver.dispatchOnWindowShown();
+    }
+
+    public void handleRequestKeyboardShortcuts(IResultReceiver receiver, int deviceId) {
+        Bundle data = new Bundle();
+        ArrayList<KeyboardShortcutGroup> list = new ArrayList<>();
+        if (mView != null) {
+            mView.requestKeyboardShortcuts(list, deviceId);
+        }
+        data.putParcelableArrayList(WindowManager.PARCEL_KEY_SHORTCUTS_ARRAY, list);
+        try {
+            receiver.send(0, data);
+        } catch (RemoteException e) {
+        }
+    }
+
+    @UnsupportedAppUsage
+    public void getLastTouchPoint(Point outLocation) {
+        outLocation.x = (int) mLastTouchPoint.x;
+        outLocation.y = (int) mLastTouchPoint.y;
+    }
+
+    public int getLastTouchSource() {
+        return mLastTouchSource;
+    }
+
+    public void setDragFocus(View newDragTarget, DragEvent event) {
+        if (mCurrentDragView != newDragTarget && !View.sCascadedDragDrop) {
+            // Send EXITED and ENTERED notifications to the old and new drag focus views.
+
+            final float tx = event.mX;
+            final float ty = event.mY;
+            final int action = event.mAction;
+            final ClipData td = event.mClipData;
+            // Position should not be available for ACTION_DRAG_ENTERED and ACTION_DRAG_EXITED.
+            event.mX = 0;
+            event.mY = 0;
+            event.mClipData = null;
+
+            if (mCurrentDragView != null) {
+                event.mAction = DragEvent.ACTION_DRAG_EXITED;
+                mCurrentDragView.callDragEventHandler(event);
+            }
+
+            if (newDragTarget != null) {
+                event.mAction = DragEvent.ACTION_DRAG_ENTERED;
+                newDragTarget.callDragEventHandler(event);
+            }
+
+            event.mAction = action;
+            event.mX = tx;
+            event.mY = ty;
+            event.mClipData = td;
+        }
+
+        mCurrentDragView = newDragTarget;
+    }
+
+    private AudioManager getAudioManager() {
+        if (mView == null) {
+            throw new IllegalStateException("getAudioManager called when there is no mView");
+        }
+        if (mAudioManager == null) {
+            mAudioManager = (AudioManager) mView.getContext().getSystemService(Context.AUDIO_SERVICE);
+        }
+        return mAudioManager;
+    }
+
+    private @Nullable AutofillManager getAutofillManager() {
+        if (mView instanceof ViewGroup) {
+            ViewGroup decorView = (ViewGroup) mView;
+            if (decorView.getChildCount() > 0) {
+                // We cannot use decorView's Context for querying AutofillManager: DecorView's
+                // context is based on Application Context, it would allocate a different
+                // AutofillManager instance.
+                return decorView.getChildAt(0).getContext()
+                        .getSystemService(AutofillManager.class);
+            }
+        }
+        return null;
+    }
+
+    private boolean isAutofillUiShowing() {
+        AutofillManager afm = getAutofillManager();
+        if (afm == null) {
+            return false;
+        }
+        return afm.isAutofillUiShowing();
+    }
+
+    public AccessibilityInteractionController getAccessibilityInteractionController() {
+        if (mView == null) {
+            throw new IllegalStateException("getAccessibilityInteractionController"
+                    + " called when there is no mView");
+        }
+        if (mAccessibilityInteractionController == null) {
+            mAccessibilityInteractionController = new AccessibilityInteractionController(this);
+        }
+        return mAccessibilityInteractionController;
+    }
+
+    private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
+            boolean insetsPending) throws RemoteException {
+
+        float appScale = mAttachInfo.mApplicationScale;
+        boolean restore = false;
+        if (params != null && mTranslator != null) {
+            restore = true;
+            params.backup();
+            mTranslator.translateWindowLayout(params);
+        }
+
+        if (params != null) {
+            if (DBG) Log.d(mTag, "WindowLayout in layoutWindow:" + params);
+
+            if (mOrigWindowType != params.type) {
+                // For compatibility with old apps, don't crash here.
+                if (mTargetSdkVersion < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+                    Slog.w(mTag, "Window type can not be changed after "
+                            + "the window is added; ignoring change of " + mView);
+                    params.type = mOrigWindowType;
+                }
+            }
+        }
+
+        long frameNumber = -1;
+        if (mSurface.isValid()) {
+            frameNumber = mSurface.getNextFrameNumber();
+        }
+
+        int relayoutResult = mWindowSession.relayout(mWindow, params,
+                (int) (mView.getMeasuredWidth() * appScale + 0.5f),
+                (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
+                insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
+                mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
+                mTempControls, mSurfaceSize);
+        mPendingBackDropFrame.set(mTmpFrames.backdropFrame);
+        if (mSurfaceControl.isValid()) {
+            if (!useBLAST()) {
+                mSurface.copyFrom(mSurfaceControl);
+            } else {
+                final Surface blastSurface = getOrCreateBLASTSurface();
+                // If blastSurface == null that means it hasn't changed since the last time we
+                // called. In this situation, avoid calling transferFrom as we would then
+                // inc the generation ID and cause EGL resources to be recreated.
+                if (blastSurface != null) {
+                    mSurface.transferFrom(blastSurface);
+                }
+            }
+            if (mAttachInfo.mThreadedRenderer != null) {
+                if (HardwareRenderer.isWebViewOverlaysEnabled()) {
+                    addPrepareSurfaceControlForWebviewCallback();
+                    addASurfaceTransactionCallback();
+                }
+                mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl);
+            }
+        } else {
+            destroySurface();
+        }
+
+        mPendingAlwaysConsumeSystemBars =
+                (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0;
+
+        if (restore) {
+            params.restore();
+        }
+
+        if (mTranslator != null) {
+            mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame);
+            mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
+            mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
+        }
+        setFrame(mTmpFrames.frame);
+        mWillMove = false;
+        mWillResize = false;
+        mInsetsController.onStateChanged(mTempInsets);
+        mInsetsController.onControlsChanged(mTempControls);
+        return relayoutResult;
+    }
+
+    private void updateOpacity(WindowManager.LayoutParams params, boolean dragResizing) {
+        boolean opaque = false;
+
+        if (!PixelFormat.formatHasAlpha(params.format)
+                // Don't make surface with surfaceInsets opaque as they display a
+                // translucent shadow.
+                && params.surfaceInsets.left == 0
+                && params.surfaceInsets.top == 0
+                && params.surfaceInsets.right == 0
+                && params.surfaceInsets.bottom == 0
+                // Don't make surface opaque when resizing to reduce the amount of
+                // artifacts shown in areas the app isn't drawing content to.
+                && !dragResizing) {
+            opaque = true;
+        }
+
+        if (mIsSurfaceOpaque == opaque) {
+            return;
+        }
+
+        synchronized (this) {
+            if (mIsForWebviewOverlay) {
+                mIsSurfaceOpaque = false;
+                return;
+            }
+            mTransaction.setOpaque(mSurfaceControl, opaque).apply();
+        }
+
+        mIsSurfaceOpaque = opaque;
+    }
+
+    private void setFrame(Rect frame) {
+        mWinFrame.set(frame);
+        mInsetsController.onFrameChanged(frame);
+    }
+
+    /**
+     * Gets the current display size in which the window is being laid out, accounting for screen
+     * decorations around it.
+     */
+    void getDisplayFrame(Rect outFrame) {
+        outFrame.set(mTmpFrames.displayFrame);
+    }
+
+    /**
+     * Gets the current display size in which the window is being laid out, accounting for screen
+     * decorations around it.
+     */
+    void getWindowVisibleDisplayFrame(Rect outFrame) {
+        outFrame.set(mTmpFrames.displayFrame);
+        // XXX This is really broken, and probably all needs to be done
+        // in the window manager, and we need to know more about whether
+        // we want the area behind or in front of the IME.
+        final Rect insets = mAttachInfo.mVisibleInsets;
+        outFrame.left += insets.left;
+        outFrame.top += insets.top;
+        outFrame.right -= insets.right;
+        outFrame.bottom -= insets.bottom;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void playSoundEffect(@SoundEffectConstants.SoundEffect int effectId) {
+        checkThread();
+
+        try {
+            final AudioManager audioManager = getAudioManager();
+
+            if (mFastScrollSoundEffectsEnabled
+                    && SoundEffectConstants.isNavigationRepeat(effectId)) {
+                audioManager.playSoundEffect(
+                        SoundEffectConstants.nextNavigationRepeatSoundEffectId());
+                return;
+            }
+
+            switch (effectId) {
+                case SoundEffectConstants.CLICK:
+                    audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
+                    return;
+                case SoundEffectConstants.NAVIGATION_DOWN:
+                case SoundEffectConstants.NAVIGATION_REPEAT_DOWN:
+                    audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN);
+                    return;
+                case SoundEffectConstants.NAVIGATION_LEFT:
+                case SoundEffectConstants.NAVIGATION_REPEAT_LEFT:
+                    audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT);
+                    return;
+                case SoundEffectConstants.NAVIGATION_RIGHT:
+                case SoundEffectConstants.NAVIGATION_REPEAT_RIGHT:
+                    audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT);
+                    return;
+                case SoundEffectConstants.NAVIGATION_UP:
+                case SoundEffectConstants.NAVIGATION_REPEAT_UP:
+                    audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP);
+                    return;
+                default:
+                    throw new IllegalArgumentException("unknown effect id " + effectId +
+                            " not defined in " + SoundEffectConstants.class.getCanonicalName());
+            }
+        } catch (IllegalStateException e) {
+            // Exception thrown by getAudioManager() when mView is null
+            Log.e(mTag, "FATAL EXCEPTION when attempting to play sound effect: " + e);
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean performHapticFeedback(int effectId, boolean always) {
+        try {
+            return mWindowSession.performHapticFeedback(effectId, always);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public View focusSearch(View focused, int direction) {
+        checkThread();
+        if (!(mView instanceof ViewGroup)) {
+            return null;
+        }
+        return FocusFinder.getInstance().findNextFocus((ViewGroup) mView, focused, direction);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public View keyboardNavigationClusterSearch(View currentCluster,
+            @FocusDirection int direction) {
+        checkThread();
+        return FocusFinder.getInstance().findNextKeyboardNavigationCluster(
+                mView, currentCluster, direction);
+    }
+
+    public void debug() {
+        mView.debug();
+    }
+
+    /**
+     * Export the state of {@link ViewRootImpl} and other relevant classes into a protocol buffer
+     * output stream.
+     *
+     * @param proto Stream to write the state to
+     * @param fieldId FieldId of ViewRootImpl as defined in the parent message
+     */
+    @GuardedBy("this")
+    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(VIEW, Objects.toString(mView));
+        proto.write(DISPLAY_ID, mDisplay.getDisplayId());
+        proto.write(APP_VISIBLE, mAppVisible);
+        proto.write(HEIGHT, mHeight);
+        proto.write(WIDTH, mWidth);
+        proto.write(IS_ANIMATING, mIsAnimating);
+        mVisRect.dumpDebug(proto, VISIBLE_RECT);
+        proto.write(IS_DRAWING, mIsDrawing);
+        proto.write(ADDED, mAdded);
+        mWinFrame.dumpDebug(proto, WIN_FRAME);
+        proto.write(LAST_WINDOW_INSETS, Objects.toString(mLastWindowInsets));
+        proto.write(SOFT_INPUT_MODE, InputMethodDebug.softInputModeToString(mSoftInputMode));
+        proto.write(SCROLL_Y, mScrollY);
+        proto.write(CUR_SCROLL_Y, mCurScrollY);
+        proto.write(REMOVED, mRemoved);
+        mWindowAttributes.dumpDebug(proto, WINDOW_ATTRIBUTES);
+        proto.end(token);
+        mInsetsController.dumpDebug(proto, INSETS_CONTROLLER);
+        mImeFocusController.dumpDebug(proto, IME_FOCUS_CONTROLLER);
+    }
+
+    /**
+     * Dump information about this ViewRootImpl
+     * @param prefix the prefix that will be prepended to each line of the produced output
+     * @param writer the writer that will receive the resulting text
+     */
+    public void dump(String prefix, PrintWriter writer) {
+        String innerPrefix = prefix + "  ";
+        writer.println(prefix + "ViewRoot:");
+        writer.println(innerPrefix + "mAdded=" + mAdded);
+        writer.println(innerPrefix + "mRemoved=" + mRemoved);
+        writer.println(innerPrefix + "mStopped=" + mStopped);
+        writer.println(innerPrefix + "mPausedForTransition=" + mPausedForTransition);
+        writer.println(innerPrefix + "mConsumeBatchedInputScheduled="
+                + mConsumeBatchedInputScheduled);
+        writer.println(innerPrefix + "mConsumeBatchedInputImmediatelyScheduled="
+                + mConsumeBatchedInputImmediatelyScheduled);
+        writer.println(innerPrefix + "mPendingInputEventCount=" + mPendingInputEventCount);
+        writer.println(innerPrefix + "mProcessInputEventsScheduled="
+                + mProcessInputEventsScheduled);
+        writer.println(innerPrefix + "mTraversalScheduled=" + mTraversalScheduled);
+        if (mTraversalScheduled) {
+            writer.println(innerPrefix + " (barrier=" + mTraversalBarrier + ")");
+        }
+        writer.println(innerPrefix + "mIsAmbientMode="  + mIsAmbientMode);
+        writer.println(innerPrefix + "mUnbufferedInputSource="
+                + Integer.toHexString(mUnbufferedInputSource));
+        if (mAttachInfo != null) {
+            writer.print(innerPrefix + "mAttachInfo= ");
+            mAttachInfo.dump(innerPrefix, writer);
+        } else {
+            writer.println(innerPrefix + "mAttachInfo=<null>");
+        }
+
+        mFirstInputStage.dump(innerPrefix, writer);
+
+        if (mInputEventReceiver != null) {
+            mInputEventReceiver.dump(innerPrefix, writer);
+        }
+
+        mChoreographer.dump(prefix, writer);
+
+        mInsetsController.dump(prefix, writer);
+
+        writer.println(prefix + "View Hierarchy:");
+        dumpViewHierarchy(innerPrefix, writer, mView);
+    }
+
+    private void dumpViewHierarchy(String prefix, PrintWriter writer, View view) {
+        writer.print(prefix);
+        if (view == null) {
+            writer.println("null");
+            return;
+        }
+        writer.println(view.toString());
+        if (!(view instanceof ViewGroup)) {
+            return;
+        }
+        ViewGroup grp = (ViewGroup)view;
+        final int N = grp.getChildCount();
+        if (N <= 0) {
+            return;
+        }
+        prefix = prefix + "  ";
+        for (int i=0; i<N; i++) {
+            dumpViewHierarchy(prefix, writer, grp.getChildAt(i));
+        }
+    }
+
+    static final class GfxInfo {
+        public int viewCount;
+        public long renderNodeMemoryUsage;
+        public long renderNodeMemoryAllocated;
+
+        void add(GfxInfo other) {
+            viewCount += other.viewCount;
+            renderNodeMemoryUsage += other.renderNodeMemoryUsage;
+            renderNodeMemoryAllocated += other.renderNodeMemoryAllocated;
+        }
+    }
+
+    GfxInfo getGfxInfo() {
+        GfxInfo info = new GfxInfo();
+        if (mView != null) {
+            appendGfxInfo(mView, info);
+        }
+        return info;
+    }
+
+    private static void computeRenderNodeUsage(RenderNode node, GfxInfo info) {
+        if (node == null) return;
+        info.renderNodeMemoryUsage += node.computeApproximateMemoryUsage();
+        info.renderNodeMemoryAllocated += node.computeApproximateMemoryAllocated();
+    }
+
+    private static void appendGfxInfo(View view, GfxInfo info) {
+        info.viewCount++;
+        computeRenderNodeUsage(view.mRenderNode, info);
+        computeRenderNodeUsage(view.mBackgroundRenderNode, info);
+        if (view instanceof ViewGroup) {
+            ViewGroup group = (ViewGroup) view;
+
+            int count = group.getChildCount();
+            for (int i = 0; i < count; i++) {
+                appendGfxInfo(group.getChildAt(i), info);
+            }
+        }
+    }
+
+    /**
+     * @param immediate True, do now if not in traversal. False, put on queue and do later.
+     * @return True, request has been queued. False, request has been completed.
+     */
+    boolean die(boolean immediate) {
+        // Make sure we do execute immediately if we are in the middle of a traversal or the damage
+        // done by dispatchDetachedFromWindow will cause havoc on return.
+        if (immediate && !mIsInTraversal) {
+            doDie();
+            return false;
+        }
+
+        if (!mIsDrawing) {
+            destroyHardwareRenderer();
+        } else {
+            Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
+                    "  window=" + this + ", title=" + mWindowAttributes.getTitle());
+        }
+        mHandler.sendEmptyMessage(MSG_DIE);
+        return true;
+    }
+
+    void doDie() {
+        checkThread();
+        if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
+        synchronized (this) {
+            if (mRemoved) {
+                return;
+            }
+            mRemoved = true;
+            if (mAdded) {
+                dispatchDetachedFromWindow();
+            }
+
+            if (mAdded && !mFirst) {
+                destroyHardwareRenderer();
+
+                if (mView != null) {
+                    int viewVisibility = mView.getVisibility();
+                    boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
+                    if (mWindowAttributesChanged || viewVisibilityChanged) {
+                        // If layout params have been changed, first give them
+                        // to the window manager to make sure it has the correct
+                        // animation info.
+                        try {
+                            if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
+                                    & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
+                                mWindowSession.finishDrawing(
+                                        mWindow, null /* postDrawTransaction */);
+                            }
+                        } catch (RemoteException e) {
+                        }
+                    }
+
+                    destroySurface();
+                }
+            }
+
+            // If our window is removed, we might not get notified about losing control.
+            // Invoking this can release the leashes as soon as possible instead of relying on GC.
+            mInsetsController.onControlsChanged(null);
+
+            mAdded = false;
+        }
+        WindowManagerGlobal.getInstance().doRemoveView(this);
+    }
+
+    public void requestUpdateConfiguration(Configuration config) {
+        Message msg = mHandler.obtainMessage(MSG_UPDATE_CONFIGURATION, config);
+        mHandler.sendMessage(msg);
+    }
+
+    public void loadSystemProperties() {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                // Profiling
+                mProfileRendering = SystemProperties.getBoolean(PROPERTY_PROFILE_RENDERING, false);
+                profileRendering(mAttachInfo.mHasWindowFocus);
+
+                // Hardware rendering
+                if (mAttachInfo.mThreadedRenderer != null) {
+                    if (mAttachInfo.mThreadedRenderer.loadSystemProperties()) {
+                        invalidate();
+                    }
+                }
+
+                // Layout debugging
+                boolean layout = DisplayProperties.debug_layout().orElse(false);
+                if (layout != mAttachInfo.mDebugLayout) {
+                    mAttachInfo.mDebugLayout = layout;
+                    if (!mHandler.hasMessages(MSG_INVALIDATE_WORLD)) {
+                        mHandler.sendEmptyMessageDelayed(MSG_INVALIDATE_WORLD, 200);
+                    }
+                }
+            }
+        });
+    }
+
+    private void destroyHardwareRenderer() {
+        ThreadedRenderer hardwareRenderer = mAttachInfo.mThreadedRenderer;
+
+        if (hardwareRenderer != null) {
+            if (mHardwareRendererObserver != null) {
+                hardwareRenderer.removeObserver(mHardwareRendererObserver);
+            }
+            if (mView != null) {
+                hardwareRenderer.destroyHardwareResources(mView);
+            }
+            hardwareRenderer.destroy();
+            hardwareRenderer.setRequested(false);
+
+            mAttachInfo.mThreadedRenderer = null;
+            mAttachInfo.mHardwareAccelerated = false;
+        }
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private void dispatchResized(ClientWindowFrames frames, boolean reportDraw,
+            MergedConfiguration mergedConfiguration, boolean forceLayout,
+            boolean alwaysConsumeSystemBars, int displayId) {
+        final Rect frame = frames.frame;
+        final Rect backDropFrame = frames.backdropFrame;
+        if (DEBUG_LAYOUT) Log.v(mTag, "Resizing " + this + ": frame=" + frame.toShortString()
+                + " reportDraw=" + reportDraw
+                + " backDropFrame=" + backDropFrame);
+
+        // Tell all listeners that we are resizing the window so that the chrome can get
+        // updated as fast as possible on a separate thread,
+        if (mDragResizing && mUseMTRenderer) {
+            boolean fullscreen = frame.equals(backDropFrame);
+            synchronized (mWindowCallbacks) {
+                for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
+                    mWindowCallbacks.get(i).onWindowSizeIsChanging(backDropFrame, fullscreen,
+                            mAttachInfo.mVisibleInsets, mAttachInfo.mStableInsets);
+                }
+            }
+        }
+
+        Message msg = mHandler.obtainMessage(reportDraw ? MSG_RESIZED_REPORT : MSG_RESIZED);
+        if (mTranslator != null) {
+            mTranslator.translateRectInScreenToAppWindow(frame);
+        }
+        SomeArgs args = SomeArgs.obtain();
+        final boolean sameProcessCall = (Binder.getCallingPid() == android.os.Process.myPid());
+        args.arg1 = sameProcessCall ? new ClientWindowFrames(frames) : frames;
+        args.arg2 = sameProcessCall && mergedConfiguration != null
+                ? new MergedConfiguration(mergedConfiguration) : mergedConfiguration;
+        args.argi1 = forceLayout ? 1 : 0;
+        args.argi2 = alwaysConsumeSystemBars ? 1 : 0;
+        args.argi3 = displayId;
+        msg.obj = args;
+        mHandler.sendMessage(msg);
+    }
+
+    private void dispatchInsetsChanged(InsetsState insetsState, boolean willMove,
+            boolean willResize) {
+        if (Binder.getCallingPid() == android.os.Process.myPid()) {
+            insetsState = new InsetsState(insetsState, true /* copySource */);
+        }
+        if (mTranslator != null) {
+            mTranslator.translateInsetsStateInScreenToAppWindow(insetsState);
+        }
+        if (insetsState != null && insetsState.getSource(ITYPE_IME).isVisible()) {
+            ImeTracing.getInstance().triggerClientDump("ViewRootImpl#dispatchInsetsChanged",
+                    getInsetsController().getHost().getInputMethodManager(), null /* icProto */);
+        }
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = insetsState;
+        args.argi1 = willMove ? 1 : 0;
+        args.argi2 = willResize ? 1 : 0;
+        mHandler.obtainMessage(MSG_INSETS_CHANGED, args).sendToTarget();
+    }
+
+    private void dispatchInsetsControlChanged(InsetsState insetsState,
+            InsetsSourceControl[] activeControls, boolean willMove, boolean willResize) {
+        if (Binder.getCallingPid() == android.os.Process.myPid()) {
+            insetsState = new InsetsState(insetsState, true /* copySource */);
+            if (activeControls != null) {
+                for (int i = activeControls.length - 1; i >= 0; i--) {
+                    activeControls[i] = new InsetsSourceControl(activeControls[i]);
+                }
+            }
+        }
+        if (mTranslator != null) {
+            mTranslator.translateInsetsStateInScreenToAppWindow(insetsState);
+            mTranslator.translateSourceControlsInScreenToAppWindow(activeControls);
+        }
+        if (insetsState != null && insetsState.getSource(ITYPE_IME).isVisible()) {
+            ImeTracing.getInstance().triggerClientDump("ViewRootImpl#dispatchInsetsControlChanged",
+                    getInsetsController().getHost().getInputMethodManager(), null /* icProto */);
+        }
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = insetsState;
+        args.arg2 = activeControls;
+        args.argi1 = willMove ? 1 : 0;
+        args.argi2 = willResize ? 1 : 0;
+        mHandler.obtainMessage(MSG_INSETS_CONTROL_CHANGED, args).sendToTarget();
+    }
+
+    private void showInsets(@InsetsType int types, boolean fromIme) {
+        mHandler.obtainMessage(MSG_SHOW_INSETS, types, fromIme ? 1 : 0).sendToTarget();
+    }
+
+    private void hideInsets(@InsetsType int types, boolean fromIme) {
+        mHandler.obtainMessage(MSG_HIDE_INSETS, types, fromIme ? 1 : 0).sendToTarget();
+    }
+
+    public void dispatchMoved(int newX, int newY) {
+        if (DEBUG_LAYOUT) Log.v(mTag, "Window moved " + this + ": newX=" + newX + " newY=" + newY);
+        if (mTranslator != null) {
+            PointF point = new PointF(newX, newY);
+            mTranslator.translatePointInScreenToAppWindow(point);
+            newX = (int) (point.x + 0.5);
+            newY = (int) (point.y + 0.5);
+        }
+        Message msg = mHandler.obtainMessage(MSG_WINDOW_MOVED, newX, newY);
+        mHandler.sendMessage(msg);
+    }
+
+    /**
+     * Represents a pending input event that is waiting in a queue.
+     *
+     * Input events are processed in serial order by the timestamp specified by
+     * {@link InputEvent#getEventTimeNano()}.  In general, the input dispatcher delivers
+     * one input event to the application at a time and waits for the application
+     * to finish handling it before delivering the next one.
+     *
+     * However, because the application or IME can synthesize and inject multiple
+     * key events at a time without going through the input dispatcher, we end up
+     * needing a queue on the application's side.
+     */
+    private static final class QueuedInputEvent {
+        public static final int FLAG_DELIVER_POST_IME = 1 << 0;
+        public static final int FLAG_DEFERRED = 1 << 1;
+        public static final int FLAG_FINISHED = 1 << 2;
+        public static final int FLAG_FINISHED_HANDLED = 1 << 3;
+        public static final int FLAG_RESYNTHESIZED = 1 << 4;
+        public static final int FLAG_UNHANDLED = 1 << 5;
+        public static final int FLAG_MODIFIED_FOR_COMPATIBILITY = 1 << 6;
+
+        public QueuedInputEvent mNext;
+
+        public InputEvent mEvent;
+        public InputEventReceiver mReceiver;
+        public int mFlags;
+
+        public boolean shouldSkipIme() {
+            if ((mFlags & FLAG_DELIVER_POST_IME) != 0) {
+                return true;
+            }
+            return mEvent instanceof MotionEvent
+                    && (mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)
+                        || mEvent.isFromSource(InputDevice.SOURCE_ROTARY_ENCODER));
+        }
+
+        public boolean shouldSendToSynthesizer() {
+            if ((mFlags & FLAG_UNHANDLED) != 0) {
+                return true;
+            }
+
+            return false;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder("QueuedInputEvent{flags=");
+            boolean hasPrevious = false;
+            hasPrevious = flagToString("DELIVER_POST_IME", FLAG_DELIVER_POST_IME, hasPrevious, sb);
+            hasPrevious = flagToString("DEFERRED", FLAG_DEFERRED, hasPrevious, sb);
+            hasPrevious = flagToString("FINISHED", FLAG_FINISHED, hasPrevious, sb);
+            hasPrevious = flagToString("FINISHED_HANDLED", FLAG_FINISHED_HANDLED, hasPrevious, sb);
+            hasPrevious = flagToString("RESYNTHESIZED", FLAG_RESYNTHESIZED, hasPrevious, sb);
+            hasPrevious = flagToString("UNHANDLED", FLAG_UNHANDLED, hasPrevious, sb);
+            if (!hasPrevious) {
+                sb.append("0");
+            }
+            sb.append(", hasNextQueuedEvent=" + (mEvent != null ? "true" : "false"));
+            sb.append(", hasInputEventReceiver=" + (mReceiver != null ? "true" : "false"));
+            sb.append(", mEvent=" + mEvent + "}");
+            return sb.toString();
+        }
+
+        private boolean flagToString(String name, int flag,
+                boolean hasPrevious, StringBuilder sb) {
+            if ((mFlags & flag) != 0) {
+                if (hasPrevious) {
+                    sb.append("|");
+                }
+                sb.append(name);
+                return true;
+            }
+            return hasPrevious;
+        }
+    }
+
+    private QueuedInputEvent obtainQueuedInputEvent(InputEvent event,
+            InputEventReceiver receiver, int flags) {
+        QueuedInputEvent q = mQueuedInputEventPool;
+        if (q != null) {
+            mQueuedInputEventPoolSize -= 1;
+            mQueuedInputEventPool = q.mNext;
+            q.mNext = null;
+        } else {
+            q = new QueuedInputEvent();
+        }
+
+        q.mEvent = event;
+        q.mReceiver = receiver;
+        q.mFlags = flags;
+        return q;
+    }
+
+    private void recycleQueuedInputEvent(QueuedInputEvent q) {
+        q.mEvent = null;
+        q.mReceiver = null;
+
+        if (mQueuedInputEventPoolSize < MAX_QUEUED_INPUT_EVENT_POOL_SIZE) {
+            mQueuedInputEventPoolSize += 1;
+            q.mNext = mQueuedInputEventPool;
+            mQueuedInputEventPool = q;
+        }
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    void enqueueInputEvent(InputEvent event) {
+        enqueueInputEvent(event, null, 0, false);
+    }
+
+    @UnsupportedAppUsage
+    void enqueueInputEvent(InputEvent event,
+            InputEventReceiver receiver, int flags, boolean processImmediately) {
+        QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
+
+        if (event instanceof MotionEvent) {
+            MotionEvent me = (MotionEvent) event;
+            if (me.getAction() == MotionEvent.ACTION_CANCEL) {
+                EventLog.writeEvent(EventLogTags.VIEW_ENQUEUE_INPUT_EVENT, "Motion - Cancel",
+                        getTitle());
+            }
+        } else if (event instanceof KeyEvent) {
+            KeyEvent ke = (KeyEvent) event;
+            if (ke.isCanceled()) {
+                EventLog.writeEvent(EventLogTags.VIEW_ENQUEUE_INPUT_EVENT, "Key - Cancel",
+                        getTitle());
+            }
+        }
+        // Always enqueue the input event in order, regardless of its time stamp.
+        // We do this because the application or the IME may inject key events
+        // in response to touch events and we want to ensure that the injected keys
+        // are processed in the order they were received and we cannot trust that
+        // the time stamp of injected events are monotonic.
+        QueuedInputEvent last = mPendingInputEventTail;
+        if (last == null) {
+            mPendingInputEventHead = q;
+            mPendingInputEventTail = q;
+        } else {
+            last.mNext = q;
+            mPendingInputEventTail = q;
+        }
+        mPendingInputEventCount += 1;
+        Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
+                mPendingInputEventCount);
+
+        if (processImmediately) {
+            doProcessInputEvents();
+        } else {
+            scheduleProcessInputEvents();
+        }
+    }
+
+    private void scheduleProcessInputEvents() {
+        if (!mProcessInputEventsScheduled) {
+            mProcessInputEventsScheduled = true;
+            Message msg = mHandler.obtainMessage(MSG_PROCESS_INPUT_EVENTS);
+            msg.setAsynchronous(true);
+            mHandler.sendMessage(msg);
+        }
+    }
+
+    void doProcessInputEvents() {
+        // Deliver all pending input events in the queue.
+        while (mPendingInputEventHead != null) {
+            QueuedInputEvent q = mPendingInputEventHead;
+            mPendingInputEventHead = q.mNext;
+            if (mPendingInputEventHead == null) {
+                mPendingInputEventTail = null;
+            }
+            q.mNext = null;
+
+            mPendingInputEventCount -= 1;
+            Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
+                    mPendingInputEventCount);
+
+            mViewFrameInfo.setInputEvent(mInputEventAssigner.processEvent(q.mEvent));
+
+            deliverInputEvent(q);
+        }
+
+        // We are done processing all input events that we can process right now
+        // so we can clear the pending flag immediately.
+        if (mProcessInputEventsScheduled) {
+            mProcessInputEventsScheduled = false;
+            mHandler.removeMessages(MSG_PROCESS_INPUT_EVENTS);
+        }
+    }
+
+    private void deliverInputEvent(QueuedInputEvent q) {
+        Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
+                q.mEvent.getId());
+
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent src=0x"
+                    + Integer.toHexString(q.mEvent.getSource()) + " eventTimeNano="
+                    + q.mEvent.getEventTimeNano() + " id=0x"
+                    + Integer.toHexString(q.mEvent.getId()));
+        }
+        try {
+            if (mInputEventConsistencyVerifier != null) {
+                Trace.traceBegin(Trace.TRACE_TAG_VIEW, "verifyEventConsistency");
+                try {
+                    mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
+                } finally {
+                    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+                }
+            }
+
+            InputStage stage;
+            if (q.shouldSendToSynthesizer()) {
+                stage = mSyntheticInputStage;
+            } else {
+                stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
+            }
+
+            if (q.mEvent instanceof KeyEvent) {
+                Trace.traceBegin(Trace.TRACE_TAG_VIEW, "preDispatchToUnhandledKeyManager");
+                try {
+                    mUnhandledKeyManager.preDispatch((KeyEvent) q.mEvent);
+                } finally {
+                    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+                }
+            }
+
+            if (stage != null) {
+                handleWindowFocusChanged();
+                stage.deliver(q);
+            } else {
+                finishInputEvent(q);
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        }
+    }
+
+    private void finishInputEvent(QueuedInputEvent q) {
+        Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
+                q.mEvent.getId());
+
+        if (q.mReceiver != null) {
+            boolean handled = (q.mFlags & QueuedInputEvent.FLAG_FINISHED_HANDLED) != 0;
+            boolean modified = (q.mFlags & QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY) != 0;
+            if (modified) {
+                Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventBeforeFinish");
+                InputEvent processedEvent;
+                try {
+                    processedEvent =
+                            mInputCompatProcessor.processInputEventBeforeFinish(q.mEvent);
+                } finally {
+                    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+                }
+                if (processedEvent != null) {
+                    q.mReceiver.finishInputEvent(processedEvent, handled);
+                }
+            } else {
+                q.mReceiver.finishInputEvent(q.mEvent, handled);
+            }
+        } else {
+            q.mEvent.recycleIfNeededAfterDispatch();
+        }
+
+        recycleQueuedInputEvent(q);
+    }
+
+    static boolean isTerminalInputEvent(InputEvent event) {
+        if (event instanceof KeyEvent) {
+            final KeyEvent keyEvent = (KeyEvent)event;
+            return keyEvent.getAction() == KeyEvent.ACTION_UP;
+        } else {
+            final MotionEvent motionEvent = (MotionEvent)event;
+            final int action = motionEvent.getAction();
+            return action == MotionEvent.ACTION_UP
+                    || action == MotionEvent.ACTION_CANCEL
+                    || action == MotionEvent.ACTION_HOVER_EXIT;
+        }
+    }
+
+    void scheduleConsumeBatchedInput() {
+        // If anything is currently scheduled to consume batched input then there's no point in
+        // scheduling it again.
+        if (!mConsumeBatchedInputScheduled && !mConsumeBatchedInputImmediatelyScheduled) {
+            mConsumeBatchedInputScheduled = true;
+            mChoreographer.postCallback(Choreographer.CALLBACK_INPUT,
+                    mConsumedBatchedInputRunnable, null);
+        }
+    }
+
+    void unscheduleConsumeBatchedInput() {
+        if (mConsumeBatchedInputScheduled) {
+            mConsumeBatchedInputScheduled = false;
+            mChoreographer.removeCallbacks(Choreographer.CALLBACK_INPUT,
+                    mConsumedBatchedInputRunnable, null);
+        }
+    }
+
+    void scheduleConsumeBatchedInputImmediately() {
+        if (!mConsumeBatchedInputImmediatelyScheduled) {
+            unscheduleConsumeBatchedInput();
+            mConsumeBatchedInputImmediatelyScheduled = true;
+            mHandler.post(mConsumeBatchedInputImmediatelyRunnable);
+        }
+    }
+
+    boolean doConsumeBatchedInput(long frameTimeNanos) {
+        final boolean consumedBatches;
+        if (mInputEventReceiver != null) {
+            consumedBatches = mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos);
+        } else {
+            consumedBatches = false;
+        }
+        doProcessInputEvents();
+        return consumedBatches;
+    }
+
+    final class TraversalRunnable implements Runnable {
+        @Override
+        public void run() {
+            doTraversal();
+        }
+    }
+    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
+
+    final class WindowInputEventReceiver extends InputEventReceiver {
+        public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
+            super(inputChannel, looper);
+        }
+
+        @Override
+        public void onInputEvent(InputEvent event) {
+            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventForCompatibility");
+            List<InputEvent> processedEvents;
+            try {
+                processedEvents =
+                    mInputCompatProcessor.processInputEventForCompatibility(event);
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            }
+            if (processedEvents != null) {
+                if (processedEvents.isEmpty()) {
+                    // InputEvent consumed by mInputCompatProcessor
+                    finishInputEvent(event, true);
+                } else {
+                    for (int i = 0; i < processedEvents.size(); i++) {
+                        enqueueInputEvent(
+                                processedEvents.get(i), this,
+                                QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true);
+                    }
+                }
+            } else {
+                enqueueInputEvent(event, this, 0, true);
+            }
+        }
+
+        @Override
+        public void onBatchedInputEventPending(int source) {
+            final boolean unbuffered = mUnbufferedInputDispatch
+                    || (source & mUnbufferedInputSource) != SOURCE_CLASS_NONE;
+            if (unbuffered) {
+                if (mConsumeBatchedInputScheduled) {
+                    unscheduleConsumeBatchedInput();
+                }
+                // Consume event immediately if unbuffered input dispatch has been requested.
+                consumeBatchedInputEvents(-1);
+                return;
+            }
+            scheduleConsumeBatchedInput();
+        }
+
+        @Override
+        public void onFocusEvent(boolean hasFocus, boolean inTouchMode) {
+            windowFocusChanged(hasFocus, inTouchMode);
+        }
+
+        @Override
+        public void onPointerCaptureEvent(boolean pointerCaptureEnabled) {
+            dispatchPointerCaptureChanged(pointerCaptureEnabled);
+        }
+
+        @Override
+        public void onDragEvent(boolean isExiting, float x, float y) {
+            // force DRAG_EXITED_EVENT if appropriate
+            DragEvent event = DragEvent.obtain(
+                    isExiting ? DragEvent.ACTION_DRAG_EXITED : DragEvent.ACTION_DRAG_LOCATION,
+                    x, y, 0 /* offsetX */, 0 /* offsetY */, null/* localState */,
+                    null/* description */, null /* data */, null /* dragSurface */,
+                    null /* dragAndDropPermissions */, false /* result */);
+            dispatchDragEvent(event);
+        }
+
+        @Override
+        public void dispose() {
+            unscheduleConsumeBatchedInput();
+            super.dispose();
+        }
+    }
+    private WindowInputEventReceiver mInputEventReceiver;
+
+    final class InputMetricsListener
+            implements HardwareRendererObserver.OnFrameMetricsAvailableListener {
+        public long[] data = new long[FrameMetrics.Index.FRAME_STATS_COUNT];
+
+        @Override
+        public void onFrameMetricsAvailable(int dropCountSinceLastInvocation) {
+            final int inputEventId = (int) data[FrameMetrics.Index.INPUT_EVENT_ID];
+            if (inputEventId == INVALID_INPUT_EVENT_ID) {
+                return;
+            }
+            final long presentTime = data[FrameMetrics.Index.DISPLAY_PRESENT_TIME];
+            if (presentTime <= 0) {
+                // Present time is not available for this frame. If the present time is not
+                // available, we cannot compute end-to-end input latency metrics.
+                return;
+            }
+            final long gpuCompletedTime = data[FrameMetrics.Index.GPU_COMPLETED];
+            if (mInputEventReceiver == null) {
+                return;
+            }
+            if (gpuCompletedTime >= presentTime) {
+                final double discrepancyMs = (gpuCompletedTime - presentTime) * 1E-6;
+                final long vsyncId = data[FrameMetrics.Index.FRAME_TIMELINE_VSYNC_ID];
+                Log.w(TAG, "Not reporting timeline because gpuCompletedTime is " + discrepancyMs
+                        + "ms ahead of presentTime. FRAME_TIMELINE_VSYNC_ID=" + vsyncId
+                        + ", INPUT_EVENT_ID=" + inputEventId);
+                // TODO(b/186664409): figure out why this sometimes happens
+                return;
+            }
+            mInputEventReceiver.reportTimeline(inputEventId, gpuCompletedTime, presentTime);
+        }
+    }
+    HardwareRendererObserver mHardwareRendererObserver;
+
+    final class ConsumeBatchedInputRunnable implements Runnable {
+        @Override
+        public void run() {
+            mConsumeBatchedInputScheduled = false;
+            if (doConsumeBatchedInput(mChoreographer.getFrameTimeNanos())) {
+                // If we consumed a batch here, we want to go ahead and schedule the
+                // consumption of batched input events on the next frame. Otherwise, we would
+                // wait until we have more input events pending and might get starved by other
+                // things occurring in the process.
+                scheduleConsumeBatchedInput();
+            }
+        }
+    }
+    final ConsumeBatchedInputRunnable mConsumedBatchedInputRunnable =
+            new ConsumeBatchedInputRunnable();
+    boolean mConsumeBatchedInputScheduled;
+
+    final class ConsumeBatchedInputImmediatelyRunnable implements Runnable {
+        @Override
+        public void run() {
+            mConsumeBatchedInputImmediatelyScheduled = false;
+            doConsumeBatchedInput(-1);
+        }
+    }
+    final ConsumeBatchedInputImmediatelyRunnable mConsumeBatchedInputImmediatelyRunnable =
+            new ConsumeBatchedInputImmediatelyRunnable();
+    boolean mConsumeBatchedInputImmediatelyScheduled;
+
+    final class InvalidateOnAnimationRunnable implements Runnable {
+        private boolean mPosted;
+        private final ArrayList<View> mViews = new ArrayList<View>();
+        private final ArrayList<AttachInfo.InvalidateInfo> mViewRects =
+                new ArrayList<AttachInfo.InvalidateInfo>();
+        private View[] mTempViews;
+        private AttachInfo.InvalidateInfo[] mTempViewRects;
+
+        public void addView(View view) {
+            synchronized (this) {
+                mViews.add(view);
+                postIfNeededLocked();
+            }
+        }
+
+        public void addViewRect(AttachInfo.InvalidateInfo info) {
+            synchronized (this) {
+                mViewRects.add(info);
+                postIfNeededLocked();
+            }
+        }
+
+        public void removeView(View view) {
+            synchronized (this) {
+                mViews.remove(view);
+
+                for (int i = mViewRects.size(); i-- > 0; ) {
+                    AttachInfo.InvalidateInfo info = mViewRects.get(i);
+                    if (info.target == view) {
+                        mViewRects.remove(i);
+                        info.recycle();
+                    }
+                }
+
+                if (mPosted && mViews.isEmpty() && mViewRects.isEmpty()) {
+                    mChoreographer.removeCallbacks(Choreographer.CALLBACK_ANIMATION, this, null);
+                    mPosted = false;
+                }
+            }
+        }
+
+        @Override
+        public void run() {
+            final int viewCount;
+            final int viewRectCount;
+            synchronized (this) {
+                mPosted = false;
+
+                viewCount = mViews.size();
+                if (viewCount != 0) {
+                    mTempViews = mViews.toArray(mTempViews != null
+                            ? mTempViews : new View[viewCount]);
+                    mViews.clear();
+                }
+
+                viewRectCount = mViewRects.size();
+                if (viewRectCount != 0) {
+                    mTempViewRects = mViewRects.toArray(mTempViewRects != null
+                            ? mTempViewRects : new AttachInfo.InvalidateInfo[viewRectCount]);
+                    mViewRects.clear();
+                }
+            }
+
+            for (int i = 0; i < viewCount; i++) {
+                mTempViews[i].invalidate();
+                mTempViews[i] = null;
+            }
+
+            for (int i = 0; i < viewRectCount; i++) {
+                final View.AttachInfo.InvalidateInfo info = mTempViewRects[i];
+                info.target.invalidate(info.left, info.top, info.right, info.bottom);
+                info.recycle();
+            }
+        }
+
+        private void postIfNeededLocked() {
+            if (!mPosted) {
+                mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
+                mPosted = true;
+            }
+        }
+    }
+    final InvalidateOnAnimationRunnable mInvalidateOnAnimationRunnable =
+            new InvalidateOnAnimationRunnable();
+
+    public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
+        Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
+        mHandler.sendMessageDelayed(msg, delayMilliseconds);
+    }
+
+    public void dispatchInvalidateRectDelayed(AttachInfo.InvalidateInfo info,
+            long delayMilliseconds) {
+        final Message msg = mHandler.obtainMessage(MSG_INVALIDATE_RECT, info);
+        mHandler.sendMessageDelayed(msg, delayMilliseconds);
+    }
+
+    public void dispatchInvalidateOnAnimation(View view) {
+        mInvalidateOnAnimationRunnable.addView(view);
+    }
+
+    public void dispatchInvalidateRectOnAnimation(AttachInfo.InvalidateInfo info) {
+        mInvalidateOnAnimationRunnable.addViewRect(info);
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public void cancelInvalidate(View view) {
+        mHandler.removeMessages(MSG_INVALIDATE, view);
+        // fixme: might leak the AttachInfo.InvalidateInfo objects instead of returning
+        // them to the pool
+        mHandler.removeMessages(MSG_INVALIDATE_RECT, view);
+        mInvalidateOnAnimationRunnable.removeView(view);
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public void dispatchInputEvent(InputEvent event) {
+        dispatchInputEvent(event, null);
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public void dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = event;
+        args.arg2 = receiver;
+        Message msg = mHandler.obtainMessage(MSG_DISPATCH_INPUT_EVENT, args);
+        msg.setAsynchronous(true);
+        mHandler.sendMessage(msg);
+    }
+
+    public void synthesizeInputEvent(InputEvent event) {
+        Message msg = mHandler.obtainMessage(MSG_SYNTHESIZE_INPUT_EVENT, event);
+        msg.setAsynchronous(true);
+        mHandler.sendMessage(msg);
+    }
+
+    @UnsupportedAppUsage
+    public void dispatchKeyFromIme(KeyEvent event) {
+        Message msg = mHandler.obtainMessage(MSG_DISPATCH_KEY_FROM_IME, event);
+        msg.setAsynchronous(true);
+        mHandler.sendMessage(msg);
+    }
+
+    public void dispatchKeyFromAutofill(KeyEvent event) {
+        Message msg = mHandler.obtainMessage(MSG_DISPATCH_KEY_FROM_AUTOFILL, event);
+        msg.setAsynchronous(true);
+        mHandler.sendMessage(msg);
+    }
+
+    /**
+     * Reinject unhandled {@link InputEvent}s in order to synthesize fallbacks events.
+     *
+     * Note that it is the responsibility of the caller of this API to recycle the InputEvent it
+     * passes in.
+     */
+    @UnsupportedAppUsage
+    public void dispatchUnhandledInputEvent(InputEvent event) {
+        if (event instanceof MotionEvent) {
+            event = MotionEvent.obtain((MotionEvent) event);
+        }
+        synthesizeInputEvent(event);
+    }
+
+    public void dispatchAppVisibility(boolean visible) {
+        Message msg = mHandler.obtainMessage(MSG_DISPATCH_APP_VISIBILITY);
+        msg.arg1 = visible ? 1 : 0;
+        mHandler.sendMessage(msg);
+    }
+
+    public void dispatchGetNewSurface() {
+        Message msg = mHandler.obtainMessage(MSG_DISPATCH_GET_NEW_SURFACE);
+        mHandler.sendMessage(msg);
+    }
+
+    /**
+     * Dispatch the offset changed.
+     *
+     * @param offset the offset of this view in the parent window.
+     */
+    public void dispatchLocationInParentDisplayChanged(Point offset) {
+        Message msg =
+                mHandler.obtainMessage(MSG_LOCATION_IN_PARENT_DISPLAY_CHANGED, offset.x, offset.y);
+        mHandler.sendMessage(msg);
+    }
+
+    public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
+        synchronized (this) {
+            mWindowFocusChanged = true;
+            mUpcomingWindowFocus = hasFocus;
+            mUpcomingInTouchMode = inTouchMode;
+        }
+        Message msg = Message.obtain();
+        msg.what = MSG_WINDOW_FOCUS_CHANGED;
+        mHandler.sendMessage(msg);
+    }
+
+    public void dispatchWindowShown() {
+        mHandler.sendEmptyMessage(MSG_DISPATCH_WINDOW_SHOWN);
+    }
+
+    public void dispatchCloseSystemDialogs(String reason) {
+        Message msg = Message.obtain();
+        msg.what = MSG_CLOSE_SYSTEM_DIALOGS;
+        msg.obj = reason;
+        mHandler.sendMessage(msg);
+    }
+
+    public void dispatchDragEvent(DragEvent event) {
+        final int what;
+        if (event.getAction() == DragEvent.ACTION_DRAG_LOCATION) {
+            what = MSG_DISPATCH_DRAG_LOCATION_EVENT;
+            mHandler.removeMessages(what);
+        } else {
+            what = MSG_DISPATCH_DRAG_EVENT;
+        }
+        Message msg = mHandler.obtainMessage(what, event);
+        mHandler.sendMessage(msg);
+    }
+
+    public void updatePointerIcon(float x, float y) {
+        final int what = MSG_UPDATE_POINTER_ICON;
+        mHandler.removeMessages(what);
+        final long now = SystemClock.uptimeMillis();
+        final MotionEvent event = MotionEvent.obtain(
+                0, now, MotionEvent.ACTION_HOVER_MOVE, x, y, 0);
+        Message msg = mHandler.obtainMessage(what, event);
+        mHandler.sendMessage(msg);
+    }
+
+    public void dispatchCheckFocus() {
+        if (!mHandler.hasMessages(MSG_CHECK_FOCUS)) {
+            // This will result in a call to checkFocus() below.
+            mHandler.sendEmptyMessage(MSG_CHECK_FOCUS);
+        }
+    }
+
+    public void dispatchRequestKeyboardShortcuts(IResultReceiver receiver, int deviceId) {
+        mHandler.obtainMessage(
+                MSG_REQUEST_KEYBOARD_SHORTCUTS, deviceId, 0, receiver).sendToTarget();
+    }
+
+    private void dispatchPointerCaptureChanged(boolean on) {
+        final int what = MSG_POINTER_CAPTURE_CHANGED;
+        mHandler.removeMessages(what);
+        Message msg = mHandler.obtainMessage(what);
+        msg.arg1 = on ? 1 : 0;
+        mHandler.sendMessage(msg);
+    }
+
+    /**
+     * Post a callback to send a
+     * {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event.
+     * This event is send at most once every
+     * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}.
+     */
+    private void postSendWindowContentChangedCallback(View source, int changeType) {
+        if (mSendWindowContentChangedAccessibilityEvent == null) {
+            mSendWindowContentChangedAccessibilityEvent =
+                new SendWindowContentChangedAccessibilityEvent();
+        }
+        mSendWindowContentChangedAccessibilityEvent.runOrPost(source, changeType);
+    }
+
+    /**
+     * Remove a posted callback to send a
+     * {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event.
+     */
+    private void removeSendWindowContentChangedCallback() {
+        if (mSendWindowContentChangedAccessibilityEvent != null) {
+            mHandler.removeCallbacks(mSendWindowContentChangedAccessibilityEvent);
+        }
+    }
+
+    @Override
+    public boolean showContextMenuForChild(View originalView) {
+        return false;
+    }
+
+    @Override
+    public boolean showContextMenuForChild(View originalView, float x, float y) {
+        return false;
+    }
+
+    @Override
+    public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback) {
+        return null;
+    }
+
+    @Override
+    public ActionMode startActionModeForChild(
+            View originalView, ActionMode.Callback callback, int type) {
+        return null;
+    }
+
+    @Override
+    public void createContextMenu(ContextMenu menu) {
+    }
+
+    @Override
+    public void childDrawableStateChanged(View child) {
+    }
+
+    @Override
+    public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+        if (mView == null || mStopped || mPausedForTransition) {
+            return false;
+        }
+
+        // Immediately flush pending content changed event (if any) to preserve event order
+        if (event.getEventType() != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
+                && mSendWindowContentChangedAccessibilityEvent != null
+                && mSendWindowContentChangedAccessibilityEvent.mSource != null) {
+            mSendWindowContentChangedAccessibilityEvent.removeCallbacksAndRun();
+        }
+
+        // Intercept accessibility focus events fired by virtual nodes to keep
+        // track of accessibility focus position in such nodes.
+        final int eventType = event.getEventType();
+        final View source = getSourceForAccessibilityEvent(event);
+        switch (eventType) {
+            case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
+                if (source != null) {
+                    AccessibilityNodeProvider provider = source.getAccessibilityNodeProvider();
+                    if (provider != null) {
+                        final int virtualNodeId = AccessibilityNodeInfo.getVirtualDescendantId(
+                                event.getSourceNodeId());
+                        final AccessibilityNodeInfo node;
+                        node = provider.createAccessibilityNodeInfo(virtualNodeId);
+                        setAccessibilityFocus(source, node);
+                    }
+                }
+            } break;
+            case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
+                if (source != null && source.getAccessibilityNodeProvider() != null) {
+                    setAccessibilityFocus(null, null);
+                }
+            } break;
+
+
+            case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: {
+                handleWindowContentChangedEvent(event);
+            } break;
+        }
+        mAccessibilityManager.sendAccessibilityEvent(event);
+        return true;
+    }
+
+    private View getSourceForAccessibilityEvent(AccessibilityEvent event) {
+        final long sourceNodeId = event.getSourceNodeId();
+        final int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
+                sourceNodeId);
+        return AccessibilityNodeIdManager.getInstance().findView(accessibilityViewId);
+    }
+
+    /**
+     * Updates the focused virtual view, when necessary, in response to a
+     * content changed event.
+     * <p>
+     * This is necessary to get updated bounds after a position change.
+     *
+     * @param event an accessibility event of type
+     *              {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED}
+     */
+    private void handleWindowContentChangedEvent(AccessibilityEvent event) {
+        final View focusedHost = mAccessibilityFocusedHost;
+        if (focusedHost == null || mAccessibilityFocusedVirtualView == null) {
+            // No virtual view focused, nothing to do here.
+            return;
+        }
+
+        final AccessibilityNodeProvider provider = focusedHost.getAccessibilityNodeProvider();
+        if (provider == null) {
+            // Error state: virtual view with no provider. Clear focus.
+            mAccessibilityFocusedHost = null;
+            mAccessibilityFocusedVirtualView = null;
+            focusedHost.clearAccessibilityFocusNoCallbacks(0);
+            return;
+        }
+
+        // We only care about change types that may affect the bounds of the
+        // focused virtual view.
+        final int changes = event.getContentChangeTypes();
+        if ((changes & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) == 0
+                && changes != AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED) {
+            return;
+        }
+
+        final long eventSourceNodeId = event.getSourceNodeId();
+        final int changedViewId = AccessibilityNodeInfo.getAccessibilityViewId(eventSourceNodeId);
+
+        // Search up the tree for subtree containment.
+        boolean hostInSubtree = false;
+        View root = mAccessibilityFocusedHost;
+        while (root != null && !hostInSubtree) {
+            if (changedViewId == root.getAccessibilityViewId()) {
+                hostInSubtree = true;
+            } else {
+                final ViewParent parent = root.getParent();
+                if (parent instanceof View) {
+                    root = (View) parent;
+                } else {
+                    root = null;
+                }
+            }
+        }
+
+        // We care only about changes in subtrees containing the host view.
+        if (!hostInSubtree) {
+            return;
+        }
+
+        final long focusedSourceNodeId = mAccessibilityFocusedVirtualView.getSourceNodeId();
+        int focusedChildId = AccessibilityNodeInfo.getVirtualDescendantId(focusedSourceNodeId);
+
+        // Refresh the node for the focused virtual view.
+        final Rect oldBounds = mTempRect;
+        mAccessibilityFocusedVirtualView.getBoundsInScreen(oldBounds);
+        mAccessibilityFocusedVirtualView = provider.createAccessibilityNodeInfo(focusedChildId);
+        if (mAccessibilityFocusedVirtualView == null) {
+            // Error state: The node no longer exists. Clear focus.
+            mAccessibilityFocusedHost = null;
+            focusedHost.clearAccessibilityFocusNoCallbacks(0);
+
+            // This will probably fail, but try to keep the provider's internal
+            // state consistent by clearing focus.
+            provider.performAction(focusedChildId,
+                    AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS.getId(), null);
+            invalidateRectOnScreen(oldBounds);
+        } else {
+            // The node was refreshed, invalidate bounds if necessary.
+            final Rect newBounds = mAccessibilityFocusedVirtualView.getBoundsInScreen();
+            if (!oldBounds.equals(newBounds)) {
+                oldBounds.union(newBounds);
+                invalidateRectOnScreen(oldBounds);
+            }
+        }
+    }
+
+    @Override
+    public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) {
+        postSendWindowContentChangedCallback(Preconditions.checkNotNull(source), changeType);
+    }
+
+    @Override
+    public boolean canResolveLayoutDirection() {
+        return true;
+    }
+
+    @Override
+    public boolean isLayoutDirectionResolved() {
+        return true;
+    }
+
+    @Override
+    public int getLayoutDirection() {
+        return View.LAYOUT_DIRECTION_RESOLVED_DEFAULT;
+    }
+
+    @Override
+    public boolean canResolveTextDirection() {
+        return true;
+    }
+
+    @Override
+    public boolean isTextDirectionResolved() {
+        return true;
+    }
+
+    @Override
+    public int getTextDirection() {
+        return View.TEXT_DIRECTION_RESOLVED_DEFAULT;
+    }
+
+    @Override
+    public boolean canResolveTextAlignment() {
+        return true;
+    }
+
+    @Override
+    public boolean isTextAlignmentResolved() {
+        return true;
+    }
+
+    @Override
+    public int getTextAlignment() {
+        return View.TEXT_ALIGNMENT_RESOLVED_DEFAULT;
+    }
+
+    private View getCommonPredecessor(View first, View second) {
+        if (mTempHashSet == null) {
+            mTempHashSet = new HashSet<View>();
+        }
+        HashSet<View> seen = mTempHashSet;
+        seen.clear();
+        View firstCurrent = first;
+        while (firstCurrent != null) {
+            seen.add(firstCurrent);
+            ViewParent firstCurrentParent = firstCurrent.mParent;
+            if (firstCurrentParent instanceof View) {
+                firstCurrent = (View) firstCurrentParent;
+            } else {
+                firstCurrent = null;
+            }
+        }
+        View secondCurrent = second;
+        while (secondCurrent != null) {
+            if (seen.contains(secondCurrent)) {
+                seen.clear();
+                return secondCurrent;
+            }
+            ViewParent secondCurrentParent = secondCurrent.mParent;
+            if (secondCurrentParent instanceof View) {
+                secondCurrent = (View) secondCurrentParent;
+            } else {
+                secondCurrent = null;
+            }
+        }
+        seen.clear();
+        return null;
+    }
+
+    void checkThread() {
+        if (mThread != Thread.currentThread()) {
+            throw new CalledFromWrongThreadException(
+                    "Only the original thread that created a view hierarchy can touch its views.");
+        }
+    }
+
+    @Override
+    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+        // ViewAncestor never intercepts touch event, so this can be a no-op
+    }
+
+    @Override
+    public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
+        if (rectangle == null) {
+            return scrollToRectOrFocus(null, immediate);
+        }
+        rectangle.offset(child.getLeft() - child.getScrollX(),
+                child.getTop() - child.getScrollY());
+        final boolean scrolled = scrollToRectOrFocus(rectangle, immediate);
+        mTempRect.set(rectangle);
+        mTempRect.offset(0, -mCurScrollY);
+        mTempRect.offset(mAttachInfo.mWindowLeft, mAttachInfo.mWindowTop);
+        try {
+            mWindowSession.onRectangleOnScreenRequested(mWindow, mTempRect);
+        } catch (RemoteException re) {
+            /* ignore */
+        }
+        return scrolled;
+    }
+
+    @Override
+    public void childHasTransientStateChanged(View child, boolean hasTransientState) {
+        // Do nothing.
+    }
+
+    @Override
+    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
+        return false;
+    }
+
+    @Override
+    public void onStopNestedScroll(View target) {
+    }
+
+    @Override
+    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
+    }
+
+    @Override
+    public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
+            int dxUnconsumed, int dyUnconsumed) {
+    }
+
+    @Override
+    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
+    }
+
+    @Override
+    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
+        return false;
+    }
+
+    @Override
+    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
+        return false;
+    }
+
+    @Override
+    public boolean onNestedPrePerformAccessibilityAction(View target, int action, Bundle args) {
+        return false;
+    }
+
+    /**
+     * Adds a scroll capture callback to this window.
+     *
+     * @param callback the callback to add
+     */
+    public void addScrollCaptureCallback(ScrollCaptureCallback callback) {
+        if (mRootScrollCaptureCallbacks == null) {
+            mRootScrollCaptureCallbacks = new HashSet<>();
+        }
+        mRootScrollCaptureCallbacks.add(callback);
+    }
+
+    /**
+     * Removes a scroll capture callback from this window.
+     *
+     * @param callback the callback to remove
+     */
+    public void removeScrollCaptureCallback(ScrollCaptureCallback callback) {
+        if (mRootScrollCaptureCallbacks != null) {
+            mRootScrollCaptureCallbacks.remove(callback);
+            if (mRootScrollCaptureCallbacks.isEmpty()) {
+                mRootScrollCaptureCallbacks = null;
+            }
+        }
+    }
+
+    /**
+     * Dispatches a scroll capture request to the view hierarchy on the ui thread.
+     *
+     * @param listener for the response
+     */
+    public void dispatchScrollCaptureRequest(@NonNull IScrollCaptureResponseListener listener) {
+        mHandler.obtainMessage(MSG_REQUEST_SCROLL_CAPTURE, listener).sendToTarget();
+    }
+
+    /**
+     * Collect and include any ScrollCaptureCallback instances registered with the window.
+     *
+     * @see #addScrollCaptureCallback(ScrollCaptureCallback)
+     * @param results an object to collect the results of the search
+     */
+    private void collectRootScrollCaptureTargets(ScrollCaptureSearchResults results) {
+        if (mRootScrollCaptureCallbacks == null) {
+            return;
+        }
+        for (ScrollCaptureCallback cb : mRootScrollCaptureCallbacks) {
+            // Add to the list for consideration
+            Point offset = new Point(mView.getLeft(), mView.getTop());
+            Rect rect = new Rect(0, 0, mView.getWidth(), mView.getHeight());
+            results.addTarget(new ScrollCaptureTarget(mView, rect, offset, cb));
+        }
+    }
+
+    /**
+     * Update the timeout for scroll capture requests. Only affects this view root.
+     * The default value is {@link #SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS}.
+     *
+     * @param timeMillis the new timeout in milliseconds
+     */
+    public void setScrollCaptureRequestTimeout(int timeMillis) {
+        mScrollCaptureRequestTimeout = timeMillis;
+    }
+
+    /**
+     * Get the current timeout for scroll capture requests.
+     *
+     * @return the timeout in milliseconds
+     */
+    public long getScrollCaptureRequestTimeout() {
+        return mScrollCaptureRequestTimeout;
+    }
+
+    /**
+     * Handles an inbound request for scroll capture from the system. A search will be
+     * dispatched through the view tree to locate scrolling content.
+     * <p>
+     * A call to
+     * {@link IScrollCaptureResponseListener#onScrollCaptureResponse} will follow.
+     *
+     * @param listener to receive responses
+     * @see ScrollCaptureSearchResults
+     */
+    public void handleScrollCaptureRequest(@NonNull IScrollCaptureResponseListener listener) {
+        ScrollCaptureSearchResults results =
+                new ScrollCaptureSearchResults(mContext.getMainExecutor());
+
+        // Window (root) level callbacks
+        collectRootScrollCaptureTargets(results);
+
+        // Search through View-tree
+        View rootView = getView();
+        if (rootView != null) {
+            Point point = new Point();
+            Rect rect = new Rect(0, 0, rootView.getWidth(), rootView.getHeight());
+            getChildVisibleRect(rootView, rect, point);
+            rootView.dispatchScrollCaptureSearch(rect, point, results::addTarget);
+        }
+        Runnable onComplete = () -> dispatchScrollCaptureSearchResponse(listener, results);
+        results.setOnCompleteListener(onComplete);
+        if (!results.isComplete()) {
+            mHandler.postDelayed(results::finish, getScrollCaptureRequestTimeout());
+        }
+    }
+
+    /** Called by {@link #handleScrollCaptureRequest} when a result is returned */
+    private void dispatchScrollCaptureSearchResponse(
+            @NonNull IScrollCaptureResponseListener listener,
+            @NonNull ScrollCaptureSearchResults results) {
+
+        ScrollCaptureTarget selectedTarget = results.getTopResult();
+
+        ScrollCaptureResponse.Builder response = new ScrollCaptureResponse.Builder();
+        response.setWindowTitle(getTitle().toString());
+
+        StringWriter writer =  new StringWriter();
+        IndentingPrintWriter pw = new IndentingPrintWriter(writer);
+        results.dump(pw);
+        pw.flush();
+        response.addMessage(writer.toString());
+
+        if (selectedTarget == null) {
+            response.setDescription("No scrollable targets found in window");
+            try {
+                listener.onScrollCaptureResponse(response.build());
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to send scroll capture search result", e);
+            }
+            return;
+        }
+
+        response.setDescription("Connected");
+
+        // Compute area covered by scrolling content within window
+        Rect boundsInWindow = new Rect();
+        View containingView = selectedTarget.getContainingView();
+        containingView.getLocationInWindow(mAttachInfo.mTmpLocation);
+        boundsInWindow.set(selectedTarget.getScrollBounds());
+        boundsInWindow.offset(mAttachInfo.mTmpLocation[0], mAttachInfo.mTmpLocation[1]);
+        response.setBoundsInWindow(boundsInWindow);
+
+        // Compute the area on screen covered by the window
+        Rect boundsOnScreen = new Rect();
+        mView.getLocationOnScreen(mAttachInfo.mTmpLocation);
+        boundsOnScreen.set(0, 0, mView.getWidth(), mView.getHeight());
+        boundsOnScreen.offset(mAttachInfo.mTmpLocation[0], mAttachInfo.mTmpLocation[1]);
+        response.setWindowBounds(boundsOnScreen);
+
+        // Create a connection and return it to the caller
+        ScrollCaptureConnection connection = new ScrollCaptureConnection(
+                mView.getContext().getMainExecutor(), selectedTarget);
+        response.setConnection(connection);
+
+        try {
+            listener.onScrollCaptureResponse(response.build());
+        } catch (RemoteException e) {
+            if (DEBUG_SCROLL_CAPTURE) {
+                Log.w(TAG, "Failed to send scroll capture search response.", e);
+            }
+            connection.close();
+        }
+    }
+
+    private void reportNextDraw() {
+        if (mReportNextDraw == false) {
+            drawPending();
+        }
+        mReportNextDraw = true;
+    }
+
+    /**
+     * Force the window to report its next draw.
+     * <p>
+     * This method is only supposed to be used to speed up the interaction from SystemUI and window
+     * manager when waiting for the first frame to be drawn when turning on the screen. DO NOT USE
+     * unless you fully understand this interaction.
+     * @hide
+     */
+    public void setReportNextDraw() {
+        reportNextDraw();
+        invalidate();
+    }
+
+    void changeCanvasOpacity(boolean opaque) {
+        Log.d(mTag, "changeCanvasOpacity: opaque=" + opaque);
+        opaque = opaque & ((mView.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0);
+        if (mAttachInfo.mThreadedRenderer != null) {
+            mAttachInfo.mThreadedRenderer.setOpaque(opaque);
+        }
+    }
+
+    /**
+     * Dispatches a KeyEvent to all registered key fallback handlers.
+     *
+     * @param event
+     * @return {@code true} if the event was handled, {@code false} otherwise.
+     */
+    public boolean dispatchUnhandledKeyEvent(KeyEvent event) {
+        return mUnhandledKeyManager.dispatch(mView, event);
+    }
+
+    class TakenSurfaceHolder extends BaseSurfaceHolder {
+        @Override
+        public boolean onAllowLockCanvas() {
+            return mDrawingAllowed;
+        }
+
+        @Override
+        public void onRelayoutContainer() {
+            // Not currently interesting -- from changing between fixed and layout size.
+        }
+
+        @Override
+        public void setFormat(int format) {
+            ((RootViewSurfaceTaker)mView).setSurfaceFormat(format);
+        }
+
+        @Override
+        public void setType(int type) {
+            ((RootViewSurfaceTaker)mView).setSurfaceType(type);
+        }
+
+        @Override
+        public void onUpdateSurface() {
+            // We take care of format and type changes on our own.
+            throw new IllegalStateException("Shouldn't be here");
+        }
+
+        @Override
+        public boolean isCreating() {
+            return mIsCreating;
+        }
+
+        @Override
+        public void setFixedSize(int width, int height) {
+            throw new UnsupportedOperationException(
+                    "Currently only support sizing from layout");
+        }
+
+        @Override
+        public void setKeepScreenOn(boolean screenOn) {
+            ((RootViewSurfaceTaker)mView).setSurfaceKeepScreenOn(screenOn);
+        }
+    }
+
+    static class W extends IWindow.Stub {
+        private final WeakReference<ViewRootImpl> mViewAncestor;
+        private final IWindowSession mWindowSession;
+
+        W(ViewRootImpl viewAncestor) {
+            mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor);
+            mWindowSession = viewAncestor.mWindowSession;
+        }
+
+        @Override
+        public void resized(ClientWindowFrames frames, boolean reportDraw,
+                MergedConfiguration mergedConfiguration, boolean forceLayout,
+                boolean alwaysConsumeSystemBars, int displayId) {
+            final ViewRootImpl viewAncestor = mViewAncestor.get();
+            if (viewAncestor != null) {
+                viewAncestor.dispatchResized(frames, reportDraw, mergedConfiguration, forceLayout,
+                        alwaysConsumeSystemBars, displayId);
+            }
+        }
+
+        @Override
+        public void locationInParentDisplayChanged(Point offset) {
+            final ViewRootImpl viewAncestor = mViewAncestor.get();
+            if (viewAncestor != null) {
+                viewAncestor.dispatchLocationInParentDisplayChanged(offset);
+            }
+        }
+
+        @Override
+        public void insetsChanged(InsetsState insetsState, boolean willMove, boolean willResize) {
+            final ViewRootImpl viewAncestor = mViewAncestor.get();
+            if (viewAncestor != null) {
+                viewAncestor.dispatchInsetsChanged(insetsState, willMove, willResize);
+            }
+        }
+
+        @Override
+        public void insetsControlChanged(InsetsState insetsState,
+                InsetsSourceControl[] activeControls, boolean willMove, boolean willResize) {
+            final ViewRootImpl viewAncestor = mViewAncestor.get();
+            if (viewAncestor != null) {
+                viewAncestor.dispatchInsetsControlChanged(
+                        insetsState, activeControls, willMove, willResize);
+            }
+        }
+
+        @Override
+        public void showInsets(@InsetsType int types, boolean fromIme) {
+            final ViewRootImpl viewAncestor = mViewAncestor.get();
+            if (fromIme) {
+                ImeTracing.getInstance().triggerClientDump("ViewRootImpl.W#showInsets",
+                        viewAncestor.getInsetsController().getHost().getInputMethodManager(),
+                        null /* icProto */);
+            }
+            if (viewAncestor != null) {
+                viewAncestor.showInsets(types, fromIme);
+            }
+        }
+
+        @Override
+        public void hideInsets(@InsetsType int types, boolean fromIme) {
+
+            final ViewRootImpl viewAncestor = mViewAncestor.get();
+            if (fromIme) {
+                ImeTracing.getInstance().triggerClientDump("ViewRootImpl.W#hideInsets",
+                        viewAncestor.getInsetsController().getHost().getInputMethodManager(),
+                        null /* icProto */);
+            }
+            if (viewAncestor != null) {
+                viewAncestor.hideInsets(types, fromIme);
+            }
+        }
+
+        @Override
+        public void moved(int newX, int newY) {
+            final ViewRootImpl viewAncestor = mViewAncestor.get();
+            if (viewAncestor != null) {
+                viewAncestor.dispatchMoved(newX, newY);
+            }
+        }
+
+        @Override
+        public void dispatchAppVisibility(boolean visible) {
+            final ViewRootImpl viewAncestor = mViewAncestor.get();
+            if (viewAncestor != null) {
+                viewAncestor.dispatchAppVisibility(visible);
+            }
+        }
+
+        @Override
+        public void dispatchGetNewSurface() {
+            final ViewRootImpl viewAncestor = mViewAncestor.get();
+            if (viewAncestor != null) {
+                viewAncestor.dispatchGetNewSurface();
+            }
+        }
+
+        @Override
+        public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
+            final ViewRootImpl viewAncestor = mViewAncestor.get();
+            if (viewAncestor != null) {
+                viewAncestor.windowFocusChanged(hasFocus, inTouchMode);
+            }
+        }
+
+        private static int checkCallingPermission(String permission) {
+            try {
+                return ActivityManager.getService().checkPermission(
+                        permission, Binder.getCallingPid(), Binder.getCallingUid());
+            } catch (RemoteException e) {
+                return PackageManager.PERMISSION_DENIED;
+            }
+        }
+
+        @Override
+        public void executeCommand(String command, String parameters, ParcelFileDescriptor out) {
+            final ViewRootImpl viewAncestor = mViewAncestor.get();
+            if (viewAncestor != null) {
+                final View view = viewAncestor.mView;
+                if (view != null) {
+                    if (checkCallingPermission(Manifest.permission.DUMP) !=
+                            PackageManager.PERMISSION_GRANTED) {
+                        throw new SecurityException("Insufficient permissions to invoke"
+                                + " executeCommand() from pid=" + Binder.getCallingPid()
+                                + ", uid=" + Binder.getCallingUid());
+                    }
+
+                    OutputStream clientStream = null;
+                    try {
+                        clientStream = new ParcelFileDescriptor.AutoCloseOutputStream(out);
+                        ViewDebug.dispatchCommand(view, command, parameters, clientStream);
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    } finally {
+                        if (clientStream != null) {
+                            try {
+                                clientStream.close();
+                            } catch (IOException e) {
+                                e.printStackTrace();
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void closeSystemDialogs(String reason) {
+            final ViewRootImpl viewAncestor = mViewAncestor.get();
+            if (viewAncestor != null) {
+                viewAncestor.dispatchCloseSystemDialogs(reason);
+            }
+        }
+
+        @Override
+        public void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep,
+                float zoom, boolean sync) {
+            if (sync) {
+                try {
+                    mWindowSession.wallpaperOffsetsComplete(asBinder());
+                } catch (RemoteException e) {
+                }
+            }
+        }
+
+        @Override
+        public void dispatchWallpaperCommand(String action, int x, int y,
+                int z, Bundle extras, boolean sync) {
+            if (sync) {
+                try {
+                    mWindowSession.wallpaperCommandComplete(asBinder(), null);
+                } catch (RemoteException e) {
+                }
+            }
+        }
+
+        /* Drag/drop */
+        @Override
+        public void dispatchDragEvent(DragEvent event) {
+            final ViewRootImpl viewAncestor = mViewAncestor.get();
+            if (viewAncestor != null) {
+                viewAncestor.dispatchDragEvent(event);
+            }
+        }
+
+        @Override
+        public void updatePointerIcon(float x, float y) {
+            final ViewRootImpl viewAncestor = mViewAncestor.get();
+            if (viewAncestor != null) {
+                viewAncestor.updatePointerIcon(x, y);
+            }
+        }
+
+        @Override
+        public void dispatchWindowShown() {
+            final ViewRootImpl viewAncestor = mViewAncestor.get();
+            if (viewAncestor != null) {
+                viewAncestor.dispatchWindowShown();
+            }
+        }
+
+        @Override
+        public void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId) {
+            ViewRootImpl viewAncestor = mViewAncestor.get();
+            if (viewAncestor != null) {
+                viewAncestor.dispatchRequestKeyboardShortcuts(receiver, deviceId);
+            }
+        }
+
+        @Override
+        public void requestScrollCapture(IScrollCaptureResponseListener listener) {
+            final ViewRootImpl viewAncestor = mViewAncestor.get();
+            if (viewAncestor != null) {
+                viewAncestor.dispatchScrollCaptureRequest(listener);
+            }
+        }
+    }
+
+    public static final class CalledFromWrongThreadException extends AndroidRuntimeException {
+        @UnsupportedAppUsage
+        public CalledFromWrongThreadException(String msg) {
+            super(msg);
+        }
+    }
+
+    static HandlerActionQueue getRunQueue() {
+        HandlerActionQueue rq = sRunQueues.get();
+        if (rq != null) {
+            return rq;
+        }
+        rq = new HandlerActionQueue();
+        sRunQueues.set(rq);
+        return rq;
+    }
+
+    /**
+     * Start a drag resizing which will inform all listeners that a window resize is taking place.
+     */
+    private void startDragResizing(Rect initialBounds, boolean fullscreen, Rect systemInsets,
+            Rect stableInsets, int resizeMode) {
+        if (!mDragResizing) {
+            mDragResizing = true;
+            if (mUseMTRenderer) {
+                for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
+                    mWindowCallbacks.get(i).onWindowDragResizeStart(
+                            initialBounds, fullscreen, systemInsets, stableInsets, resizeMode);
+                }
+            }
+            mFullRedrawNeeded = true;
+        }
+    }
+
+    /**
+     * End a drag resize which will inform all listeners that a window resize has ended.
+     */
+    private void endDragResizing() {
+        if (mDragResizing) {
+            mDragResizing = false;
+            if (mUseMTRenderer) {
+                for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
+                    mWindowCallbacks.get(i).onWindowDragResizeEnd();
+                }
+            }
+            mFullRedrawNeeded = true;
+        }
+    }
+
+    private boolean updateContentDrawBounds() {
+        boolean updated = false;
+        if (mUseMTRenderer) {
+            for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
+                updated |=
+                        mWindowCallbacks.get(i).onContentDrawn(mWindowAttributes.surfaceInsets.left,
+                                mWindowAttributes.surfaceInsets.top, mWidth, mHeight);
+            }
+        }
+        return updated | (mDragResizing && mReportNextDraw);
+    }
+
+    private void requestDrawWindow() {
+        if (!mUseMTRenderer) {
+            return;
+        }
+        mWindowDrawCountDown = new CountDownLatch(mWindowCallbacks.size());
+        for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
+            mWindowCallbacks.get(i).onRequestDraw(mReportNextDraw);
+        }
+    }
+
+    /**
+     * Tells this instance that its corresponding activity has just relaunched. In this case, we
+     * need to force a relayout of the window to make sure we get the correct bounds from window
+     * manager.
+     */
+    public void reportActivityRelaunched() {
+        mActivityRelaunched = true;
+    }
+
+    public SurfaceControl getSurfaceControl() {
+        return mSurfaceControl;
+    }
+
+    /**
+     * @return Returns a token used to identify the windows input channel.
+     */
+    public IBinder getInputToken() {
+        if (mInputEventReceiver == null) {
+            return null;
+        }
+        return mInputEventReceiver.getToken();
+    }
+
+    @NonNull
+    public IBinder getWindowToken() {
+        return mAttachInfo.mWindowToken;
+    }
+
+    /**
+     * Class for managing the accessibility interaction connection
+     * based on the global accessibility state.
+     */
+    final class AccessibilityInteractionConnectionManager
+            implements AccessibilityStateChangeListener {
+        @Override
+        public void onAccessibilityStateChanged(boolean enabled) {
+            if (enabled) {
+                ensureConnection();
+                if (mAttachInfo.mHasWindowFocus && (mView != null)) {
+                    mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+                    View focusedView = mView.findFocus();
+                    if (focusedView != null && focusedView != mView) {
+                        focusedView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+                    }
+                }
+                if (mAttachInfo.mLeashedParentToken != null) {
+                    mAccessibilityManager.associateEmbeddedHierarchy(
+                            mAttachInfo.mLeashedParentToken, mLeashToken);
+                }
+            } else {
+                ensureNoConnection();
+                mHandler.obtainMessage(MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST).sendToTarget();
+            }
+        }
+
+        public void ensureConnection() {
+            final boolean registered = mAttachInfo.mAccessibilityWindowId
+                    != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+            if (!registered) {
+                mAttachInfo.mAccessibilityWindowId =
+                        mAccessibilityManager.addAccessibilityInteractionConnection(mWindow,
+                                mLeashToken,
+                                mContext.getPackageName(),
+                                new AccessibilityInteractionConnection(ViewRootImpl.this));
+            }
+        }
+
+        public void ensureNoConnection() {
+            final boolean registered = mAttachInfo.mAccessibilityWindowId
+                    != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+            if (registered) {
+                mAttachInfo.mAccessibilityWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+                mAccessibilityManager.removeAccessibilityInteractionConnection(mWindow);
+            }
+        }
+    }
+
+    final class HighContrastTextManager implements HighTextContrastChangeListener {
+        HighContrastTextManager() {
+            ThreadedRenderer.setHighContrastText(mAccessibilityManager.isHighTextContrastEnabled());
+        }
+        @Override
+        public void onHighTextContrastStateChanged(boolean enabled) {
+            ThreadedRenderer.setHighContrastText(enabled);
+
+            // Destroy Displaylists so they can be recreated with high contrast recordings
+            destroyHardwareResources();
+
+            // Schedule redraw, which will rerecord + redraw all text
+            invalidate();
+        }
+    }
+
+    /**
+     * This class is an interface this ViewAncestor provides to the
+     * AccessibilityManagerService to the latter can interact with
+     * the view hierarchy in this ViewAncestor.
+     */
+    static final class AccessibilityInteractionConnection
+            extends IAccessibilityInteractionConnection.Stub {
+        private final WeakReference<ViewRootImpl> mViewRootImpl;
+
+        AccessibilityInteractionConnection(ViewRootImpl viewRootImpl) {
+            mViewRootImpl = new WeakReference<ViewRootImpl>(viewRootImpl);
+        }
+
+        @Override
+        public void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId,
+                Region interactiveRegion, int interactionId,
+                IAccessibilityInteractionConnectionCallback callback, int flags,
+                int interrogatingPid, long interrogatingTid, MagnificationSpec spec, Bundle args) {
+            ViewRootImpl viewRootImpl = mViewRootImpl.get();
+            if (viewRootImpl != null && viewRootImpl.mView != null) {
+                viewRootImpl.getAccessibilityInteractionController()
+                    .findAccessibilityNodeInfoByAccessibilityIdClientThread(accessibilityNodeId,
+                            interactiveRegion, interactionId, callback, flags, interrogatingPid,
+                            interrogatingTid, spec, args);
+            } else {
+                // We cannot make the call and notify the caller so it does not wait.
+                try {
+                    callback.setFindAccessibilityNodeInfosResult(null, interactionId);
+                } catch (RemoteException re) {
+                    /* best effort - ignore */
+                }
+            }
+        }
+
+        @Override
+        public void performAccessibilityAction(long accessibilityNodeId, int action,
+                Bundle arguments, int interactionId,
+                IAccessibilityInteractionConnectionCallback callback, int flags,
+                int interrogatingPid, long interrogatingTid) {
+            ViewRootImpl viewRootImpl = mViewRootImpl.get();
+            if (viewRootImpl != null && viewRootImpl.mView != null) {
+                viewRootImpl.getAccessibilityInteractionController()
+                    .performAccessibilityActionClientThread(accessibilityNodeId, action, arguments,
+                            interactionId, callback, flags, interrogatingPid, interrogatingTid);
+            } else {
+                // We cannot make the call and notify the caller so it does not wait.
+                try {
+                    callback.setPerformAccessibilityActionResult(false, interactionId);
+                } catch (RemoteException re) {
+                    /* best effort - ignore */
+                }
+            }
+        }
+
+        @Override
+        public void findAccessibilityNodeInfosByViewId(long accessibilityNodeId,
+                String viewId, Region interactiveRegion, int interactionId,
+                IAccessibilityInteractionConnectionCallback callback, int flags,
+                int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
+            ViewRootImpl viewRootImpl = mViewRootImpl.get();
+            if (viewRootImpl != null && viewRootImpl.mView != null) {
+                viewRootImpl.getAccessibilityInteractionController()
+                    .findAccessibilityNodeInfosByViewIdClientThread(accessibilityNodeId,
+                            viewId, interactiveRegion, interactionId, callback, flags,
+                            interrogatingPid, interrogatingTid, spec);
+            } else {
+                // We cannot make the call and notify the caller so it does not wait.
+                try {
+                    callback.setFindAccessibilityNodeInfoResult(null, interactionId);
+                } catch (RemoteException re) {
+                    /* best effort - ignore */
+                }
+            }
+        }
+
+        @Override
+        public void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text,
+                Region interactiveRegion, int interactionId,
+                IAccessibilityInteractionConnectionCallback callback, int flags,
+                int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
+            ViewRootImpl viewRootImpl = mViewRootImpl.get();
+            if (viewRootImpl != null && viewRootImpl.mView != null) {
+                viewRootImpl.getAccessibilityInteractionController()
+                    .findAccessibilityNodeInfosByTextClientThread(accessibilityNodeId, text,
+                            interactiveRegion, interactionId, callback, flags, interrogatingPid,
+                            interrogatingTid, spec);
+            } else {
+                // We cannot make the call and notify the caller so it does not wait.
+                try {
+                    callback.setFindAccessibilityNodeInfosResult(null, interactionId);
+                } catch (RemoteException re) {
+                    /* best effort - ignore */
+                }
+            }
+        }
+
+        @Override
+        public void findFocus(long accessibilityNodeId, int focusType, Region interactiveRegion,
+                int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
+                int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
+            ViewRootImpl viewRootImpl = mViewRootImpl.get();
+            if (viewRootImpl != null && viewRootImpl.mView != null) {
+                viewRootImpl.getAccessibilityInteractionController()
+                    .findFocusClientThread(accessibilityNodeId, focusType, interactiveRegion,
+                            interactionId, callback, flags, interrogatingPid, interrogatingTid,
+                            spec);
+            } else {
+                // We cannot make the call and notify the caller so it does not wait.
+                try {
+                    callback.setFindAccessibilityNodeInfoResult(null, interactionId);
+                } catch (RemoteException re) {
+                    /* best effort - ignore */
+                }
+            }
+        }
+
+        @Override
+        public void focusSearch(long accessibilityNodeId, int direction, Region interactiveRegion,
+                int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
+                int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
+            ViewRootImpl viewRootImpl = mViewRootImpl.get();
+            if (viewRootImpl != null && viewRootImpl.mView != null) {
+                viewRootImpl.getAccessibilityInteractionController()
+                    .focusSearchClientThread(accessibilityNodeId, direction, interactiveRegion,
+                            interactionId, callback, flags, interrogatingPid, interrogatingTid,
+                            spec);
+            } else {
+                // We cannot make the call and notify the caller so it does not wait.
+                try {
+                    callback.setFindAccessibilityNodeInfoResult(null, interactionId);
+                } catch (RemoteException re) {
+                    /* best effort - ignore */
+                }
+            }
+        }
+
+        @Override
+        public void clearAccessibilityFocus() {
+            ViewRootImpl viewRootImpl = mViewRootImpl.get();
+            if (viewRootImpl != null && viewRootImpl.mView != null) {
+                viewRootImpl.getAccessibilityInteractionController()
+                        .clearAccessibilityFocusClientThread();
+            }
+        }
+
+        @Override
+        public void notifyOutsideTouch() {
+            ViewRootImpl viewRootImpl = mViewRootImpl.get();
+            if (viewRootImpl != null && viewRootImpl.mView != null) {
+                viewRootImpl.getAccessibilityInteractionController()
+                        .notifyOutsideTouchClientThread();
+            }
+        }
+    }
+
+    /**
+     * Gets an accessibility embedded connection interface for this ViewRootImpl.
+     * @hide
+     */
+    public IAccessibilityEmbeddedConnection getAccessibilityEmbeddedConnection() {
+        if (mAccessibilityEmbeddedConnection == null) {
+            mAccessibilityEmbeddedConnection = new AccessibilityEmbeddedConnection(
+                    ViewRootImpl.this);
+        }
+        return mAccessibilityEmbeddedConnection;
+    }
+
+    private class SendWindowContentChangedAccessibilityEvent implements Runnable {
+        private int mChangeTypes = 0;
+
+        public View mSource;
+        public long mLastEventTimeMillis;
+        /**
+         * Override for {@link AccessibilityEvent#originStackTrace} to provide the stack trace
+         * of the original {@link #runOrPost} call instead of one for sending the delayed event
+         * from a looper.
+         */
+        public StackTraceElement[] mOrigin;
+
+        @Override
+        public void run() {
+            // Protect against re-entrant code and attempt to do the right thing in the case that
+            // we're multithreaded.
+            View source = mSource;
+            mSource = null;
+            if (source == null) {
+                Log.e(TAG, "Accessibility content change has no source");
+                return;
+            }
+            // The accessibility may be turned off while we were waiting so check again.
+            if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+                mLastEventTimeMillis = SystemClock.uptimeMillis();
+                AccessibilityEvent event = AccessibilityEvent.obtain();
+                event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+                event.setContentChangeTypes(mChangeTypes);
+                if (AccessibilityEvent.DEBUG_ORIGIN) event.originStackTrace = mOrigin;
+                source.sendAccessibilityEventUnchecked(event);
+            } else {
+                mLastEventTimeMillis = 0;
+            }
+            // In any case reset to initial state.
+            source.resetSubtreeAccessibilityStateChanged();
+            mChangeTypes = 0;
+            if (AccessibilityEvent.DEBUG_ORIGIN) mOrigin = null;
+        }
+
+        public void runOrPost(View source, int changeType) {
+            if (mHandler.getLooper() != Looper.myLooper()) {
+                CalledFromWrongThreadException e = new CalledFromWrongThreadException("Only the "
+                        + "original thread that created a view hierarchy can touch its views.");
+                // TODO: Throw the exception
+                Log.e(TAG, "Accessibility content change on non-UI thread. Future Android "
+                        + "versions will throw an exception.", e);
+                // Attempt to recover. This code does not eliminate the thread safety issue, but
+                // it should force any issues to happen near the above log.
+                mHandler.removeCallbacks(this);
+                if (mSource != null) {
+                    // Dispatch whatever was pending. It's still possible that the runnable started
+                    // just before we removed the callbacks, and bad things will happen, but at
+                    // least they should happen very close to the logged error.
+                    run();
+                }
+            }
+            if (mSource != null) {
+                // If there is no common predecessor, then mSource points to
+                // a removed view, hence in this case always prefer the source.
+                View predecessor = getCommonPredecessor(mSource, source);
+                if (predecessor != null) {
+                    predecessor = predecessor.getSelfOrParentImportantForA11y();
+                }
+                mSource = (predecessor != null) ? predecessor : source;
+                mChangeTypes |= changeType;
+                return;
+            }
+            mSource = source;
+            mChangeTypes = changeType;
+            if (AccessibilityEvent.DEBUG_ORIGIN) {
+                mOrigin = Thread.currentThread().getStackTrace();
+            }
+            final long timeSinceLastMillis = SystemClock.uptimeMillis() - mLastEventTimeMillis;
+            final long minEventIntevalMillis =
+                    ViewConfiguration.getSendRecurringAccessibilityEventsInterval();
+            if (timeSinceLastMillis >= minEventIntevalMillis) {
+                removeCallbacksAndRun();
+            } else {
+                mHandler.postDelayed(this, minEventIntevalMillis - timeSinceLastMillis);
+            }
+        }
+
+        public void removeCallbacksAndRun() {
+            mHandler.removeCallbacks(this);
+            run();
+        }
+    }
+
+    private static class UnhandledKeyManager {
+        // This is used to ensure that unhandled events are only dispatched once. We attempt
+        // to dispatch more than once in order to achieve a certain order. Specifically, if we
+        // are in an Activity or Dialog (and have a Window.Callback), the unhandled events should
+        // be dispatched after the view hierarchy, but before the Callback. However, if we aren't
+        // in an activity, we still want unhandled keys to be dispatched.
+        private boolean mDispatched = true;
+
+        // Keeps track of which Views have unhandled key focus for which keys. This doesn't
+        // include modifiers.
+        private final SparseArray<WeakReference<View>> mCapturedKeys = new SparseArray<>();
+
+        // The current receiver. This value is transient and used between the pre-dispatch and
+        // pre-view phase to ensure that other input-stages don't interfere with tracking.
+        private WeakReference<View> mCurrentReceiver = null;
+
+        boolean dispatch(View root, KeyEvent event) {
+            if (mDispatched) {
+                return false;
+            }
+            View consumer;
+            try {
+                Trace.traceBegin(Trace.TRACE_TAG_VIEW, "UnhandledKeyEvent dispatch");
+                mDispatched = true;
+
+                consumer = root.dispatchUnhandledKeyEvent(event);
+
+                // If an unhandled listener handles one, then keep track of it so that the
+                // consuming view is first to receive its repeats and release as well.
+                if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                    int keycode = event.getKeyCode();
+                    if (consumer != null && !KeyEvent.isModifierKey(keycode)) {
+                        mCapturedKeys.put(keycode, new WeakReference<>(consumer));
+                    }
+                }
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            }
+            return consumer != null;
+        }
+
+        /**
+         * Called before the event gets dispatched to anything
+         */
+        void preDispatch(KeyEvent event) {
+            // Always clean-up 'up' events since it's possible for earlier dispatch stages to
+            // consume them without consuming the corresponding 'down' event.
+            mCurrentReceiver = null;
+            if (event.getAction() == KeyEvent.ACTION_UP) {
+                int idx = mCapturedKeys.indexOfKey(event.getKeyCode());
+                if (idx >= 0) {
+                    mCurrentReceiver = mCapturedKeys.valueAt(idx);
+                    mCapturedKeys.removeAt(idx);
+                }
+            }
+        }
+
+        /**
+         * Called before the event gets dispatched to the view hierarchy
+         * @return {@code true} if an unhandled handler has focus and consumed the event
+         */
+        boolean preViewDispatch(KeyEvent event) {
+            mDispatched = false;
+            if (mCurrentReceiver == null) {
+                mCurrentReceiver = mCapturedKeys.get(event.getKeyCode());
+            }
+            if (mCurrentReceiver != null) {
+                View target = mCurrentReceiver.get();
+                if (event.getAction() == KeyEvent.ACTION_UP) {
+                    mCurrentReceiver = null;
+                }
+                if (target != null && target.isAttachedToWindow()) {
+                    target.onUnhandledKeyEvent(event);
+                }
+                // consume anyways so that we don't feed uncaptured key events to other views
+                return true;
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Sends a list of blur regions to SurfaceFlinger, tagged with a frame.
+     *
+     * @param regionCopy List of regions
+     * @param frameNumber Frame where it should be applied (or current when using BLAST)
+     */
+    public void dispatchBlurRegions(float[][] regionCopy, long frameNumber) {
+        final SurfaceControl surfaceControl = getSurfaceControl();
+        if (!surfaceControl.isValid()) {
+            return;
+        }
+
+        SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+        transaction.setBlurRegions(surfaceControl, regionCopy);
+
+        if (useBLAST() && mBlastBufferQueue != null) {
+            mBlastBufferQueue.mergeWithNextTransaction(transaction, frameNumber);
+        }
+    }
+
+    /**
+     * Creates a background blur drawable for the backing {@link Surface}.
+     */
+    public BackgroundBlurDrawable createBackgroundBlurDrawable() {
+        return mBlurRegionAggregator.createBackgroundBlurDrawable(mContext);
+    }
+
+    @Override
+    public void onDescendantUnbufferedRequested() {
+        mUnbufferedInputSource = mView.mUnbufferedInputSource;
+    }
+
+    /**
+     * Force disabling use of the BLAST adapter regardless of the system
+     * flag. Needs to be called before addView.
+     */
+    void forceDisableBLAST() {
+        mForceDisableBLAST = true;
+    }
+
+    boolean useBLAST() {
+        return mUseBLASTAdapter && !mForceDisableBLAST;
+    }
+
+    int getSurfaceSequenceId() {
+        return mSurfaceSequenceId;
+    }
+
+    /**
+     * Merges the transaction passed in with the next transaction in BLASTBufferQueue. This ensures
+     * you can add transactions to the upcoming frame.
+     */
+    public void mergeWithNextTransaction(Transaction t, long frameNumber) {
+        if (mBlastBufferQueue != null) {
+            mBlastBufferQueue.mergeWithNextTransaction(t, frameNumber);
+        } else {
+            t.apply();
+        }
+    }
+
+    @Override
+    @Nullable public SurfaceControl.Transaction buildReparentTransaction(
+        @NonNull SurfaceControl child) {
+        if (mSurfaceControl.isValid()) {
+            return new SurfaceControl.Transaction().reparent(child, mSurfaceControl);
+        }
+        return null;
+    }
+
+    @Override
+    public boolean applyTransactionOnDraw(@NonNull SurfaceControl.Transaction t) {
+        registerRtFrameCallback(frame -> {
+            mergeWithNextTransaction(t, frame);
+        });
+        return true;
+    }
+
+    int getSurfaceTransformHint() {
+        return mSurfaceControl.getTransformHint();
+    }
+}
diff --git a/android/view/ViewRootImpl_Accessor.java b/android/view/ViewRootImpl_Accessor.java
new file mode 100644
index 0000000..0e15b97
--- /dev/null
+++ b/android/view/ViewRootImpl_Accessor.java
@@ -0,0 +1,26 @@
+/*
+ * 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.view;
+
+/**
+ * Accessor to allow layoutlib to call {@link ViewRootImpl#dispatchApplyInsets} directly.
+ */
+public class ViewRootImpl_Accessor {
+    public static void dispatchApplyInsets(ViewRootImpl viewRoot, View host) {
+        viewRoot.dispatchApplyInsets(host);
+    }
+}
diff --git a/android/view/ViewRootImpl_Delegate.java b/android/view/ViewRootImpl_Delegate.java
new file mode 100644
index 0000000..14b84ef
--- /dev/null
+++ b/android/view/ViewRootImpl_Delegate.java
@@ -0,0 +1,34 @@
+/*
+ * 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.view;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link ViewRootImpl}
+ *
+ * Through the layoutlib_create tool, the original  methods of ViewRootImpl have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class ViewRootImpl_Delegate {
+
+    @LayoutlibDelegate
+    /*package*/ static boolean isInTouchMode() {
+        return false; // this allows displaying selection.
+    }
+}
diff --git a/android/view/ViewRootInsetsControllerHost.java b/android/view/ViewRootInsetsControllerHost.java
new file mode 100644
index 0000000..ce882da
--- /dev/null
+++ b/android/view/ViewRootInsetsControllerHost.java
@@ -0,0 +1,271 @@
+/*
+ * 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.view;
+
+import static android.view.InsetsController.DEBUG;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_APPEARANCE_CONTROLLED;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
+
+import android.annotation.NonNull;
+import android.content.res.CompatibilityInfo;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.inputmethod.InputMethodManager;
+
+import java.util.List;
+
+/**
+ * Implements {@link InsetsController.Host} for {@link ViewRootImpl}s.
+ * @hide
+ */
+public class ViewRootInsetsControllerHost implements InsetsController.Host {
+
+    private final String TAG = "VRInsetsControllerHost";
+
+    private final ViewRootImpl mViewRoot;
+    private SyncRtSurfaceTransactionApplier mApplier;
+
+    public ViewRootInsetsControllerHost(ViewRootImpl viewRoot) {
+        mViewRoot = viewRoot;
+    }
+
+    @Override
+    public Handler getHandler() {
+        return mViewRoot.mHandler;
+    }
+
+    @Override
+    public void notifyInsetsChanged() {
+        mViewRoot.notifyInsetsChanged();
+    }
+
+    @Override
+    public void addOnPreDrawRunnable(Runnable r) {
+        if (mViewRoot.mView == null) {
+            return;
+        }
+        mViewRoot.mView.getViewTreeObserver().addOnPreDrawListener(
+                new ViewTreeObserver.OnPreDrawListener() {
+                    @Override
+                    public boolean onPreDraw() {
+                        mViewRoot.mView.getViewTreeObserver().removeOnPreDrawListener(this);
+                        r.run();
+                        return true;
+                    }
+                });
+        mViewRoot.mView.invalidate();
+    }
+
+    @Override
+    public void dispatchWindowInsetsAnimationPrepare(@NonNull WindowInsetsAnimation animation) {
+        if (mViewRoot.mView == null) {
+            return;
+        }
+        mViewRoot.mView.dispatchWindowInsetsAnimationPrepare(animation);
+    }
+
+    @Override
+    public WindowInsetsAnimation.Bounds dispatchWindowInsetsAnimationStart(
+            @NonNull WindowInsetsAnimation animation,
+            @NonNull WindowInsetsAnimation.Bounds bounds) {
+        if (mViewRoot.mView == null) {
+            return null;
+        }
+        if (DEBUG) Log.d(TAG, "windowInsetsAnimation started");
+        return mViewRoot.mView.dispatchWindowInsetsAnimationStart(animation, bounds);
+    }
+
+    @Override
+    public WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull WindowInsets insets,
+            @NonNull List<WindowInsetsAnimation> runningAnimations) {
+        if (mViewRoot.mView == null) {
+            // The view has already detached from window.
+            return null;
+        }
+        if (DEBUG) {
+            for (WindowInsetsAnimation anim : runningAnimations) {
+                Log.d(TAG, "windowInsetsAnimation progress: "
+                        + anim.getInterpolatedFraction());
+            }
+        }
+        return mViewRoot.mView.dispatchWindowInsetsAnimationProgress(insets, runningAnimations);
+    }
+
+    @Override
+    public void dispatchWindowInsetsAnimationEnd(@NonNull WindowInsetsAnimation animation) {
+        if (DEBUG) Log.d(TAG, "windowInsetsAnimation ended");
+        if (mViewRoot.mView == null) {
+            // The view has already detached from window.
+            return;
+        }
+        mViewRoot.mView.dispatchWindowInsetsAnimationEnd(animation);
+    }
+
+    @Override
+    public void applySurfaceParams(SyncRtSurfaceTransactionApplier.SurfaceParams... params) {
+        if (mViewRoot.mView == null) {
+            throw new IllegalStateException("View of the ViewRootImpl is not initiated.");
+        }
+        if (mApplier == null) {
+            mApplier = new SyncRtSurfaceTransactionApplier(mViewRoot.mView);
+        }
+        if (mViewRoot.mView.isHardwareAccelerated()) {
+            mApplier.scheduleApply(params);
+        } else {
+            // Window doesn't support hardware acceleration, no synchronization for now.
+            // TODO(b/149342281): use mViewRoot.mSurface.getNextFrameNumber() to sync on every
+            //  frame instead.
+            final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            mApplier.applyParams(t, params);
+            mApplier.applyTransaction(t, -1);
+        }
+    }
+
+    @Override
+    public void postInsetsAnimationCallback(Runnable r) {
+        mViewRoot.mChoreographer.postCallback(Choreographer.CALLBACK_INSETS_ANIMATION, r,
+                null /* token */);
+    }
+
+    @Override
+    public void updateCompatSysUiVisibility(int type, boolean visible, boolean hasControl) {
+        mViewRoot.updateCompatSysUiVisibility(type, visible, hasControl);
+    }
+
+    @Override
+    public void onInsetsModified(InsetsState insetsState) {
+        try {
+            if (mViewRoot.mAdded) {
+                mViewRoot.mWindowSession.insetsModified(mViewRoot.mWindow, insetsState);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to call insetsModified", e);
+        }
+    }
+
+    @Override
+    public boolean hasAnimationCallbacks() {
+        if (mViewRoot.mView == null) {
+            return false;
+        }
+        return mViewRoot.mView.hasWindowInsetsAnimationCallback();
+    }
+
+    @Override
+    public void setSystemBarsAppearance(int appearance, int mask) {
+        mViewRoot.mWindowAttributes.privateFlags |= PRIVATE_FLAG_APPEARANCE_CONTROLLED;
+        final InsetsFlags insetsFlags = mViewRoot.mWindowAttributes.insetsFlags;
+        if (insetsFlags.appearance != appearance) {
+            insetsFlags.appearance = (insetsFlags.appearance & ~mask) | (appearance & mask);
+            mViewRoot.mWindowAttributesChanged = true;
+            mViewRoot.scheduleTraversals();
+        }
+    }
+
+    @Override
+    public int getSystemBarsAppearance() {
+        return mViewRoot.mWindowAttributes.insetsFlags.appearance;
+    }
+
+    @Override
+    public boolean isSystemBarsAppearanceControlled() {
+        return (mViewRoot.mWindowAttributes.privateFlags & PRIVATE_FLAG_APPEARANCE_CONTROLLED) != 0;
+    }
+
+    @Override
+    public void setSystemBarsBehavior(int behavior) {
+        mViewRoot.mWindowAttributes.privateFlags |= PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
+        if (mViewRoot.mWindowAttributes.insetsFlags.behavior != behavior) {
+            mViewRoot.mWindowAttributes.insetsFlags.behavior = behavior;
+            mViewRoot.mWindowAttributesChanged = true;
+            mViewRoot.scheduleTraversals();
+        }
+    }
+
+    @Override
+    public int getSystemBarsBehavior() {
+        return mViewRoot.mWindowAttributes.insetsFlags.behavior;
+    }
+
+    @Override
+    public boolean isSystemBarsBehaviorControlled() {
+        return (mViewRoot.mWindowAttributes.privateFlags & PRIVATE_FLAG_BEHAVIOR_CONTROLLED) != 0;
+    }
+
+    @Override
+    public void releaseSurfaceControlFromRt(SurfaceControl surfaceControl) {
+
+         // At the time we receive new leashes (e.g. InsetsSourceConsumer is processing
+         // setControl) we need to release the old leash. But we may have already scheduled
+         // a SyncRtSurfaceTransaction applier to use it from the RenderThread. To avoid
+         // synchronization issues we also release from the RenderThread so this release
+         // happens after any existing items on the work queue.
+
+        if (mViewRoot.mView != null && mViewRoot.mView.isHardwareAccelerated()) {
+            mViewRoot.registerRtFrameCallback(frame -> {
+                surfaceControl.release();
+            });
+            // Make sure a frame gets scheduled.
+            mViewRoot.mView.invalidate();
+        } else {
+            surfaceControl.release();
+        }
+    }
+
+    @Override
+    public InputMethodManager getInputMethodManager() {
+        return mViewRoot.mContext.getSystemService(InputMethodManager.class);
+    }
+
+    @Override
+    public String getRootViewTitle() {
+        if (mViewRoot == null) {
+            return null;
+        }
+        return mViewRoot.getTitle().toString();
+    }
+
+    @Override
+    public int dipToPx(int dips) {
+        if (mViewRoot != null) {
+            return mViewRoot.dipToPx(dips);
+        }
+        return 0;
+    }
+
+    @Override
+    public IBinder getWindowToken() {
+        if (mViewRoot == null) {
+            return null;
+        }
+        final View view = mViewRoot.getView();
+        if (view == null) {
+            return null;
+        }
+        return view.getWindowToken();
+    }
+
+    @Override
+    public CompatibilityInfo.Translator getTranslator() {
+        if (mViewRoot != null) {
+            return mViewRoot.mTranslator;
+        }
+        return null;
+    }
+}
diff --git a/android/view/ViewShowHidePerfTest.java b/android/view/ViewShowHidePerfTest.java
new file mode 100644
index 0000000..a69d3ff
--- /dev/null
+++ b/android/view/ViewShowHidePerfTest.java
@@ -0,0 +1,255 @@
+/*
+ * 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.view;
+
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.perftests.utils.PerfTestActivity;
+import android.view.View.MeasureSpec;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import androidx.benchmark.BenchmarkState;
+import androidx.benchmark.junit4.BenchmarkRule;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(Parameterized.class)
+@LargeTest
+public class ViewShowHidePerfTest {
+
+    @Rule
+    public ActivityTestRule<PerfTestActivity> mActivityRule =
+            new ActivityTestRule<>(PerfTestActivity.class);
+
+    @Rule
+    public final BenchmarkRule mBenchmarkRule = new BenchmarkRule();
+
+    public Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    static abstract class SubTreeFactory {
+        String mName;
+        SubTreeFactory(String name) { mName = name; }
+
+        abstract View create(Context context, int depth);
+
+        @Override
+        public String toString() {
+            return mName;
+        }
+    }
+
+    private static SubTreeFactory[] sSubTreeFactories = new SubTreeFactory[] {
+            new SubTreeFactory("NestedLinearLayoutTree") {
+                private int mColorToggle = 0;
+
+                private void createNestedLinearLayoutTree(Context context, LinearLayout parent,
+                        int remainingDepth) {
+                    if (remainingDepth <= 0) {
+                        mColorToggle = (mColorToggle + 1) % 4;
+                        parent.setBackgroundColor((mColorToggle < 2) ? Color.RED : Color.BLUE);
+                        return;
+                    }
+
+                    boolean vertical = remainingDepth % 2 == 0;
+                    parent.setOrientation(vertical ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL);
+
+                    for (int i = 0; i < 2; i++) {
+                        LinearLayout child = new LinearLayout(context);
+                        // vertical: match parent in x axis, horizontal: y axis.
+                        parent.addView(child, new LinearLayout.LayoutParams(
+                                (vertical ? ViewGroup.LayoutParams.MATCH_PARENT : 0),
+                                (vertical ? 0 : ViewGroup.LayoutParams.MATCH_PARENT),
+                                1.0f));
+
+                        createNestedLinearLayoutTree(context, child, remainingDepth - 1);
+                    }
+                }
+
+                @Override
+                public View create(Context context, int depth) {
+                    LinearLayout root = new LinearLayout(context);
+                    createNestedLinearLayoutTree(context, root, depth - 1);
+                    return root;
+                }
+            },
+            new SubTreeFactory("ImageViewList") {
+                @Override
+                public View create(Context context, int depth) {
+                    LinearLayout root = new LinearLayout(context);
+                    root.setOrientation(LinearLayout.HORIZONTAL);
+                    int childCount = (int) Math.pow(2, depth);
+                    for (int i = 0; i < childCount; i++) {
+                        ImageView imageView = new ImageView(context);
+                        root.addView(imageView, new LinearLayout.LayoutParams(
+                                0, ViewGroup.LayoutParams.MATCH_PARENT, 1.0f));
+                        imageView.setImageDrawable(new ColorDrawable(Color.RED));
+                    }
+                    return root;
+                }
+            },
+    };
+
+
+    @Parameterized.Parameters(name = "Factory:{0},depth:{1}")
+    public static Iterable<Object[]> params() {
+        List<Object[]> params = new ArrayList<>();
+        for (int depth : new int[] { 6 }) {
+            for (SubTreeFactory subTreeFactory : sSubTreeFactories) {
+                params.add(new Object[]{ subTreeFactory, depth });
+            }
+        }
+        return params;
+    }
+
+    private final View mChild;
+
+    public ViewShowHidePerfTest(SubTreeFactory subTreeFactory, int depth) {
+        mChild = subTreeFactory.create(getContext(), depth);
+    }
+
+    interface TestCallback {
+        void run(BenchmarkState state, int width, int height, ViewGroup parent, View child);
+    }
+
+    private void testParentWithChild(TestCallback callback) throws Throwable {
+        mActivityRule.runOnUiThread(() -> {
+            final BenchmarkState state = mBenchmarkRule.getState();
+
+            FrameLayout parent = new FrameLayout(getContext());
+            mActivityRule.getActivity().setContentView(parent);
+
+            final int width = 1000;
+            final int height = 1000;
+            layout(width, height, parent);
+
+            callback.run(state, width, height, parent, mChild);
+        });
+    }
+
+    private void updateAndValidateDisplayList(View view) {
+        boolean hasDisplayList = view.updateDisplayListIfDirty().hasDisplayList();
+        assertTrue(hasDisplayList);
+    }
+
+    private void layout(int width, int height, View view) {
+        view.measure(
+                MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
+        view.layout(0, 0, height, width);
+    }
+
+    @Test
+    public void testRemove() throws Throwable {
+        testParentWithChild((state, width, height, parent, child) -> {
+            while (state.keepRunning()) {
+                state.pauseTiming();
+                updateAndValidateDisplayList(parent); // Note, done to be safe, likely not needed
+                parent.addView(child);
+                layout(width, height, child);
+                updateAndValidateDisplayList(parent);
+                state.resumeTiming();
+
+                parent.removeAllViews();
+            }
+        });
+    }
+
+    @Test
+    public void testAdd() throws Throwable {
+        testParentWithChild((state, width, height, parent, child) -> {
+            while (state.keepRunning()) {
+                state.pauseTiming();
+                layout(width, height, child); // Note, done to be safe, likely not needed
+                updateAndValidateDisplayList(parent); // Note, done to be safe, likely not needed
+                parent.removeAllViews();
+                updateAndValidateDisplayList(parent);
+                state.resumeTiming();
+
+                parent.addView(child);
+            }
+        });
+    }
+
+    @Test
+    public void testRecordAfterAdd() throws Throwable {
+        testParentWithChild((state, width, height, parent, child) -> {
+            while (state.keepRunning()) {
+                state.pauseTiming();
+                parent.removeAllViews();
+                updateAndValidateDisplayList(parent); // Note, done to be safe, likely not needed
+                parent.addView(child);
+                layout(width, height, child);
+                state.resumeTiming();
+
+                updateAndValidateDisplayList(parent);
+            }
+        });
+    }
+
+    private void testVisibility(int fromVisibility, int toVisibility) throws Throwable {
+        testParentWithChild((state, width, height, parent, child) -> {
+            parent.addView(child);
+
+            while (state.keepRunning()) {
+                state.pauseTiming();
+                layout(width, height, parent);
+                updateAndValidateDisplayList(parent);
+                child.setVisibility(fromVisibility);
+                layout(width, height, parent);
+                updateAndValidateDisplayList(parent);
+                state.resumeTiming();
+
+                child.setVisibility(toVisibility);
+            }
+        });
+    }
+
+    @Test
+    public void testInvisibleToVisible() throws Throwable {
+        testVisibility(View.INVISIBLE, View.VISIBLE);
+    }
+
+    @Test
+    public void testVisibleToInvisible() throws Throwable {
+        testVisibility(View.VISIBLE, View.INVISIBLE);
+    }
+    @Test
+    public void testGoneToVisible() throws Throwable {
+        testVisibility(View.GONE, View.VISIBLE);
+    }
+
+    @Test
+    public void testVisibleToGone() throws Throwable {
+        testVisibility(View.VISIBLE, View.GONE);
+    }
+}
diff --git a/android/view/ViewStructure.java b/android/view/ViewStructure.java
new file mode 100644
index 0000000..e246634
--- /dev/null
+++ b/android/view/ViewStructure.java
@@ -0,0 +1,521 @@
+/*
+ * 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.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.LocaleList;
+import android.util.Pair;
+import android.view.View.AutofillImportance;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+
+/**
+ * <p><code>ViewStructure</code> is a container for storing additional
+ * per-view data generated by {@link View#onProvideStructure
+ * View.onProvideStructure} and {@link View#onProvideAutofillStructure
+ * View.onProvideAutofillStructure}.
+ *
+ * <p>To learn more about using Autofill in your app, read the
+ * <a href="/guide/topics/text/autofill">Autofill Framework</a> guides.
+ *
+ */
+public abstract class ViewStructure {
+
+    /**
+     * Set the identifier for this view.
+     *
+     * @param id The view's identifier, as per {@link View#getId View.getId()}.
+     * @param packageName The package name of the view's identifier, or null if there is none.
+     * @param typeName The type name of the view's identifier, or null if there is none.
+     * @param entryName The entry name of the view's identifier, or null if there is none.
+     */
+    public abstract void setId(int id, String packageName, String typeName, String entryName);
+
+    /**
+     * Set the basic dimensions of this view.
+     *
+     * @param left The view's left position, in pixels relative to its parent's left edge.
+     * @param top The view's top position, in pixels relative to its parent's top edge.
+     * @param scrollX How much the view's x coordinate space has been scrolled, in pixels.
+     * @param scrollY How much the view's y coordinate space has been scrolled, in pixels.
+     * @param width The view's visible width, in pixels.  This is the width visible on screen,
+     * not the total data width of a scrollable view.
+     * @param height The view's visible height, in pixels.  This is the height visible on
+     * screen, not the total data height of a scrollable view.
+     */
+    public abstract void setDimens(int left, int top, int scrollX, int scrollY, int width,
+            int height);
+
+    /**
+     * Set the transformation matrix associated with this view, as per
+     * {@link View#getMatrix View.getMatrix()}, or null if there is none.
+     */
+    public abstract void setTransformation(Matrix matrix);
+
+    /**
+     * Set the visual elevation (shadow) of the view, as per
+     * {@link View#getZ View.getZ()}.  Note this is <em>not</em> related
+     * to the physical Z-ordering of this view relative to its other siblings (that is how
+     * they overlap when drawing), it is only the visual representation for shadowing.
+     */
+    public abstract void setElevation(float elevation);
+
+    /**
+     * Set an alpha transformation that is applied to this view, as per
+     * {@link View#getAlpha View.getAlpha()}.  Value ranges from 0
+     * (completely transparent) to 1 (completely opaque); the default is 1, which means
+     * no transformation.
+     */
+    public abstract void setAlpha(float alpha);
+
+    /**
+     * Set the visibility state of this view, as per
+     * {@link View#getVisibility View.getVisibility()}.
+     */
+    public abstract void setVisibility(int visibility);
+
+    /** @hide */
+    @SuppressWarnings("HiddenAbstractMethod")
+    public abstract void setAssistBlocked(boolean state);
+
+    /**
+     * Set the enabled state of this view, as per {@link View#isEnabled View.isEnabled()}.
+     */
+    public abstract void setEnabled(boolean state);
+
+    /**
+     * Set the clickable state of this view, as per {@link View#isClickable View.isClickable()}.
+     */
+    public abstract void setClickable(boolean state);
+
+    /**
+     * Set the long clickable state of this view, as per
+     * {@link View#isLongClickable View.isLongClickable()}.
+     */
+    public abstract void setLongClickable(boolean state);
+
+    /**
+     * Set the context clickable state of this view, as per
+     * {@link View#isContextClickable View.isContextClickable()}.
+     */
+    public abstract void setContextClickable(boolean state);
+
+    /**
+     * Set the focusable state of this view, as per {@link View#isFocusable View.isFocusable()}.
+     */
+    public abstract void setFocusable(boolean state);
+
+    /**
+     * Set the focused state of this view, as per {@link View#isFocused View.isFocused()}.
+     */
+    public abstract void setFocused(boolean state);
+
+    /**
+     * Set the accessibility focused state of this view, as per
+     * {@link View#isAccessibilityFocused View.isAccessibilityFocused()}.
+     */
+    public abstract void setAccessibilityFocused(boolean state);
+
+    /**
+     * Set the checkable state of this view, such as whether it implements the
+     * {@link android.widget.Checkable} interface.
+     */
+    public abstract void setCheckable(boolean state);
+
+    /**
+     * Set the checked state of this view, such as
+     * {@link android.widget.Checkable#isChecked Checkable.isChecked()}.
+     */
+    public abstract void setChecked(boolean state);
+
+    /**
+     * Set the selected state of this view, as per {@link View#isSelected View.isSelected()}.
+     */
+    public abstract void setSelected(boolean state);
+
+    /**
+     * Set the activated state of this view, as per {@link View#isActivated View.isActivated()}.
+     */
+    public abstract void setActivated(boolean state);
+
+    /**
+     * Set the opaque state of this view, as per {@link View#isOpaque View.isOpaque()}.
+     */
+    public abstract void setOpaque(boolean opaque);
+
+    /**
+     * Set the class name of the view, as per
+     * {@link View#getAccessibilityClassName View.getAccessibilityClassName()}.
+     */
+    public abstract void setClassName(String className);
+
+    /**
+     * Set the content description of the view, as per
+     * {@link View#getContentDescription View.getContentDescription()}.
+     */
+    public abstract void setContentDescription(CharSequence contentDescription);
+
+    /**
+     * Set the text that is associated with this view.  There is no selection
+     * associated with the text.  The text may have style spans to supply additional
+     * display and semantic information.
+     */
+    public abstract void setText(CharSequence text);
+
+    /**
+     * Like {@link #setText(CharSequence)} but with an active selection
+     * extending from <var>selectionStart</var> through <var>selectionEnd</var>.
+     */
+    public abstract void setText(CharSequence text, int selectionStart, int selectionEnd);
+
+    /**
+     * Explicitly set default global style information for text that was previously set with
+     * {@link #setText}.
+     *
+     * @param size The size, in pixels, of the text.
+     * @param fgColor The foreground color, packed as 0xAARRGGBB.
+     * @param bgColor The background color, packed as 0xAARRGGBB.
+     * @param style Style flags, as defined by {@link android.app.assist.AssistStructure.ViewNode}.
+     */
+    public abstract void setTextStyle(float size, int fgColor, int bgColor, int style);
+
+    /**
+     * Set line information for test that was previously supplied through
+     * {@link #setText(CharSequence)}.  This provides the line breaking of the text as it
+     * is shown on screen.  This function takes ownership of the provided arrays; you should
+     * not make further modification to them.
+     *
+     * @param charOffsets The offset in to {@link #setText} where a line starts.
+     * @param baselines The baseline where the line is drawn on screen.
+     */
+    public abstract void setTextLines(int[] charOffsets, int[] baselines);
+
+    /**
+     * Sets the identifier used to set the text associated with this view.
+     *
+     * <p>Should only be set when the node is used for autofill purposes - it will be ignored
+     * when used for Assist.
+     */
+    public void setTextIdEntry(@NonNull String entryName) {
+        Preconditions.checkNotNull(entryName);
+    }
+
+    /**
+     * Set optional hint text associated with this view; this is for example the text that is
+     * shown by an EditText when it is empty to indicate to the user the kind of text to input.
+     */
+    public abstract void setHint(CharSequence hint);
+
+    /**
+     * Sets the identifier used to set the hint associated with this view.
+     *
+     * <p>Used as metadata for fingerprinting view nodes/structures.
+     *
+     * <p>Should only be set when the node is used for autofill purposes - it will be ignored
+     * when used for Assist.
+     */
+    public void setHintIdEntry(@NonNull String entryName) {
+        Preconditions.checkNotNull(entryName);
+    }
+
+    /**
+     * Retrieve the last {@link #setText(CharSequence)}.
+     */
+    public abstract CharSequence getText();
+
+    /**
+     * Retrieve the last selection start set by {@link #setText(CharSequence, int, int)}.
+     */
+    public abstract int getTextSelectionStart();
+
+    /**
+     * Retrieve the last selection end set by {@link #setText(CharSequence, int, int)}.
+     */
+    public abstract int getTextSelectionEnd();
+
+    /**
+     * Retrieve the last hint set by {@link #setHint}.
+     */
+    public abstract CharSequence getHint();
+
+    /**
+     * Get extra data associated with this view structure; the returned Bundle is mutable,
+     * allowing you to view and modify its contents.  Keys placed in the Bundle should use
+     * an appropriate namespace prefix (such as com.google.MY_KEY) to avoid conflicts.
+     */
+    public abstract Bundle getExtras();
+
+    /**
+     * Returns true if {@link #getExtras} has been used to create extra content.
+     */
+    public abstract boolean hasExtras();
+
+    /**
+     * Set the number of children of this view, which defines the range of indices you can
+     * use with {@link #newChild} and {@link #asyncNewChild}.  Calling this method again
+     * resets all of the child state of the view, removing any children that had previously
+     * been added.
+     */
+    public abstract void setChildCount(int num);
+
+    /**
+     * Add to this view's child count.  This increases the current child count by
+     * <var>num</var> children beyond what was last set by {@link #setChildCount}
+     * or {@link #addChildCount}.  The index at which the new child starts in the child
+     * array is returned.
+     *
+     * @param num The number of new children to add.
+     * @return Returns the index in the child array at which the new children start.
+     */
+    public abstract int addChildCount(int num);
+
+    /**
+     * Return the child count as set by {@link #setChildCount}.
+     */
+    public abstract int getChildCount();
+
+    /**
+     * Create a new child {@link ViewStructure} in this view, putting into the list of
+     * children at <var>index</var>.
+     *
+     * <p><b>NOTE: </b>you must pre-allocate space for the child first, by calling either
+     * {@link #addChildCount(int)} or {@link #setChildCount(int)}.
+     *
+     * @return Returns an fresh {@link ViewStructure} ready to be filled in.
+     */
+    public abstract ViewStructure newChild(int index);
+
+    /**
+     * Like {@link #newChild}, but allows the caller to asynchronously populate the returned
+     * child.  It can transfer the returned {@link ViewStructure} to another thread for it
+     * to build its content (and children etc).  Once done, some thread must call
+     * {@link #asyncCommit} to tell the containing {@link ViewStructure} that the async
+     * population is done.
+     *
+     * <p><b>NOTE: </b>you must pre-allocate space for the child first, by calling either
+     * {@link #addChildCount(int)} or {@link #setChildCount(int)}.
+     *
+     * @return Returns an fresh {@link ViewStructure} ready to be filled in.
+     */
+    public abstract ViewStructure asyncNewChild(int index);
+
+    /**
+     * Gets the {@link AutofillId} associated with this node.
+     */
+    @Nullable
+    public abstract AutofillId getAutofillId();
+
+    /**
+     * Sets the {@link AutofillId} associated with this node.
+     */
+    public abstract void setAutofillId(@NonNull AutofillId id);
+
+    /**
+     * Sets the {@link AutofillId} for this virtual node.
+     *
+     * @param parentId id of the parent node.
+     * @param virtualId an opaque ID to the Android System; it's the same id used on
+     *            {@link View#autofill(android.util.SparseArray)}.
+     */
+    public abstract void setAutofillId(@NonNull AutofillId parentId, int virtualId);
+
+    /**
+     * Sets the {@link View#getAutofillType()} that can be used to autofill this node.
+     */
+    public abstract void setAutofillType(@View.AutofillType int type);
+
+    /**
+     * Sets the a hints that helps the autofill service to select the appropriate data to fill the
+     * view.
+     */
+    public abstract void setAutofillHints(@Nullable String[] hint);
+
+    /**
+     * Sets the {@link AutofillValue} representing the current value of this node.
+     */
+    public abstract void setAutofillValue(AutofillValue value);
+
+    /**
+     * Sets the options that can be used to autofill this node.
+     *
+     * <p>Typically used by nodes whose {@link View#getAutofillType()} is a list to indicate the
+     * meaning of each possible value in the list.
+     */
+    public abstract void setAutofillOptions(CharSequence[] options);
+
+    /**
+     * Sets the {@link View#setImportantForAutofill(int) importantForAutofill mode} of the
+     * view associated with this node.
+     */
+    public void setImportantForAutofill(@AutofillImportance int mode) {}
+
+    /**
+     * Sets the MIME types accepted by this view. See {@link View#getReceiveContentMimeTypes()}.
+     *
+     * <p>Should only be set when the node is used for Autofill or Content Capture purposes - it
+     * will be ignored when used for Assist.
+     */
+    public void setReceiveContentMimeTypes(
+            @SuppressLint("NullableCollection") @Nullable String[] mimeTypes) {}
+
+    /**
+     * Sets the {@link android.text.InputType} bits of this node.
+     *
+     * @param inputType inputType bits as defined by {@link android.text.InputType}.
+     */
+    public abstract void setInputType(int inputType);
+
+    /**
+     * Sets whether the data on this node is sensitive; if it is, then its content (text, autofill
+     * value, etc..) is striped before calls to {@link
+     * android.service.autofill.AutofillService#onFillRequest(android.service.autofill.FillRequest,
+     * android.os.CancellationSignal, android.service.autofill.FillCallback)}.
+     *
+     * <p>By default, all nodes are assumed to be sensitive, and only nodes that does not have PII
+     * (Personally Identifiable Information - sensitive data such as email addresses, credit card
+     * numbers, passwords, etc...) should be marked as non-sensitive; a good rule of thumb is to
+     * mark as non-sensitive nodes whose value were statically set from resources.
+     *
+     * <p>Notice that the content of even sensitive nodes are sent to the service (through the
+     * {@link
+     * android.service.autofill.AutofillService#onSaveRequest(android.service.autofill.SaveRequest,
+     * android.service.autofill.SaveCallback)} call) when the user consented to save
+     * thedata, so it is important to set the content of sensitive nodes as well, but mark them as
+     * sensitive.
+     *
+     * <p>Should only be set when the node is used for autofill purposes - it will be ignored
+     * when used for Assist.
+     */
+    public abstract void setDataIsSensitive(boolean sensitive);
+
+    /**
+     * Sets the minimum width in ems of the text associated with this view, when supported.
+     *
+     * <p>Should only be set when the node is used for autofill purposes - it will be ignored
+     * when used for Assist.
+     */
+    public void setMinTextEms(@SuppressWarnings("unused") int minEms) {}
+
+    /**
+     * Sets the maximum width in ems of the text associated with this view, when supported.
+     *
+     * <p>Should only be set when the node is used for autofill purposes - it will be ignored
+     * when used for Assist.
+     */
+    public void setMaxTextEms(@SuppressWarnings("unused") int maxEms) {}
+
+    /**
+     * Sets the maximum length of the text associated with this view, when supported.
+     *
+     * <p>Should only be set when the node is used for autofill purposes - it will be ignored
+     * when used for Assist.
+     */
+    public void setMaxTextLength(@SuppressWarnings("unused") int maxLength) {}
+
+    /**
+     * Call when done populating a {@link ViewStructure} returned by
+     * {@link #asyncNewChild}.
+     */
+    public abstract void asyncCommit();
+
+    /** @hide */
+    @SuppressWarnings("HiddenAbstractMethod")
+    public abstract Rect getTempRect();
+
+    /**
+     * Sets the Web domain represented by this node.
+     *
+     * <p>Typically used when the view is a container for an HTML document.
+     *
+     * @param domain RFC 2396-compliant URI representing the domain.
+     */
+    public abstract void setWebDomain(@Nullable String domain);
+
+    /**
+     * Sets the the list of locales associated with this node.
+     */
+    public abstract void setLocaleList(LocaleList localeList);
+
+    /**
+     * Creates a new {@link HtmlInfo.Builder} for the given HTML tag.
+     *
+     * @param tagName name of the HTML tag.
+     * @return a new builder.
+     */
+    public abstract HtmlInfo.Builder newHtmlInfoBuilder(@NonNull String tagName);
+
+    /**
+     * Sets the HTML properties of this node when it represents an HTML element.
+     *
+     * <p>Should only be set when the node is used for autofill purposes - it will be ignored
+     * when used for assist.
+     *
+     * @param htmlInfo HTML properties.
+     */
+    public abstract void setHtmlInfo(@NonNull HtmlInfo htmlInfo);
+
+    /**
+     * Simplified representation of the HTML properties of a node that represents an HTML element.
+     */
+    public abstract static class HtmlInfo {
+
+        /**
+         * Gets the HTML tag.
+         */
+        @NonNull
+        public abstract String getTag();
+
+        /**
+         * Gets the list of HTML attributes.
+         *
+         * @return list of key/value pairs; could contain pairs with the same keys.
+         */
+        @Nullable
+        public abstract List<Pair<String, String>> getAttributes();
+
+        /**
+         * Builder for {@link HtmlInfo} objects.
+         */
+        public abstract static class Builder {
+
+            /**
+             * Adds an HTML attribute.
+             *
+             * @param name name of the attribute.
+             * @param value value of the attribute.
+             * @return same builder, for chaining.
+             */
+            public abstract Builder addAttribute(@NonNull String name, @NonNull String value);
+
+            /**
+             * Builds the {@link HtmlInfo} object.
+             *
+             * @return a new {@link HtmlInfo} instance.
+             */
+            public abstract HtmlInfo build();
+        }
+    }
+}
diff --git a/android/view/ViewStub.java b/android/view/ViewStub.java
new file mode 100644
index 0000000..9ca1632
--- /dev/null
+++ b/android/view/ViewStub.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.IdRes;
+import android.annotation.LayoutRes;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.widget.RemoteViews.RemoteView;
+
+import com.android.internal.R;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * A ViewStub is an invisible, zero-sized View that can be used to lazily inflate
+ * layout resources at runtime.
+ *
+ * When a ViewStub is made visible, or when {@link #inflate()}  is invoked, the layout resource 
+ * is inflated. The ViewStub then replaces itself in its parent with the inflated View or Views.
+ * Therefore, the ViewStub exists in the view hierarchy until {@link #setVisibility(int)} or
+ * {@link #inflate()} is invoked.
+ *
+ * The inflated View is added to the ViewStub's parent with the ViewStub's layout
+ * parameters. Similarly, you can define/override the inflate View's id by using the
+ * ViewStub's inflatedId property. For instance:
+ *
+ * <pre>
+ *     &lt;ViewStub android:id="@+id/stub"
+ *               android:inflatedId="@+id/subTree"
+ *               android:layout="@layout/mySubTree"
+ *               android:layout_width="120dip"
+ *               android:layout_height="40dip" /&gt;
+ * </pre>
+ *
+ * The ViewStub thus defined can be found using the id "stub." After inflation of
+ * the layout resource "mySubTree," the ViewStub is removed from its parent. The
+ * View created by inflating the layout resource "mySubTree" can be found using the
+ * id "subTree," specified by the inflatedId property. The inflated View is finally
+ * assigned a width of 120dip and a height of 40dip.
+ *
+ * The preferred way to perform the inflation of the layout resource is the following:
+ *
+ * <pre>
+ *     ViewStub stub = findViewById(R.id.stub);
+ *     View inflated = stub.inflate();
+ * </pre>
+ *
+ * When {@link #inflate()} is invoked, the ViewStub is replaced by the inflated View
+ * and the inflated View is returned. This lets applications get a reference to the
+ * inflated View without executing an extra findViewById().
+ *
+ * @attr ref android.R.styleable#ViewStub_inflatedId
+ * @attr ref android.R.styleable#ViewStub_layout
+ */
+@RemoteView
+public final class ViewStub extends View {
+    private int mInflatedId;
+    private int mLayoutResource;
+
+    private WeakReference<View> mInflatedViewRef;
+
+    private LayoutInflater mInflater;
+    private OnInflateListener mInflateListener;
+
+    public ViewStub(Context context) {
+        this(context, 0);
+    }
+
+    /**
+     * Creates a new ViewStub with the specified layout resource.
+     *
+     * @param context The application's environment.
+     * @param layoutResource The reference to a layout resource that will be inflated.
+     */
+    public ViewStub(Context context, @LayoutRes int layoutResource) {
+        this(context, null);
+
+        mLayoutResource = layoutResource;
+    }
+
+    public ViewStub(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ViewStub(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context);
+
+        final TypedArray a = context.obtainStyledAttributes(attrs,
+                R.styleable.ViewStub, defStyleAttr, defStyleRes);
+        saveAttributeDataForStyleable(context, R.styleable.ViewStub, attrs, a, defStyleAttr,
+                defStyleRes);
+
+        mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
+        mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
+        mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
+        a.recycle();
+
+        setVisibility(GONE);
+        setWillNotDraw(true);
+    }
+
+    /**
+     * Returns the id taken by the inflated view. If the inflated id is
+     * {@link View#NO_ID}, the inflated view keeps its original id.
+     *
+     * @return A positive integer used to identify the inflated view or
+     *         {@link #NO_ID} if the inflated view should keep its id.
+     *
+     * @see #setInflatedId(int)
+     * @attr ref android.R.styleable#ViewStub_inflatedId
+     */
+    @IdRes
+    public int getInflatedId() {
+        return mInflatedId;
+    }
+
+    /**
+     * Defines the id taken by the inflated view. If the inflated id is
+     * {@link View#NO_ID}, the inflated view keeps its original id.
+     *
+     * @param inflatedId A positive integer used to identify the inflated view or
+     *                   {@link #NO_ID} if the inflated view should keep its id.
+     *
+     * @see #getInflatedId()
+     * @attr ref android.R.styleable#ViewStub_inflatedId
+     */
+    @android.view.RemotableViewMethod(asyncImpl = "setInflatedIdAsync")
+    public void setInflatedId(@IdRes int inflatedId) {
+        mInflatedId = inflatedId;
+    }
+
+    /** @hide **/
+    public Runnable setInflatedIdAsync(@IdRes int inflatedId) {
+        mInflatedId = inflatedId;
+        return null;
+    }
+
+    /**
+     * Returns the layout resource that will be used by {@link #setVisibility(int)} or
+     * {@link #inflate()} to replace this StubbedView
+     * in its parent by another view.
+     *
+     * @return The layout resource identifier used to inflate the new View.
+     *
+     * @see #setLayoutResource(int)
+     * @see #setVisibility(int)
+     * @see #inflate()
+     * @attr ref android.R.styleable#ViewStub_layout
+     */
+    @LayoutRes
+    public int getLayoutResource() {
+        return mLayoutResource;
+    }
+
+    /**
+     * Specifies the layout resource to inflate when this StubbedView becomes visible or invisible
+     * or when {@link #inflate()} is invoked. The View created by inflating the layout resource is
+     * used to replace this StubbedView in its parent.
+     * 
+     * @param layoutResource A valid layout resource identifier (different from 0.)
+     * 
+     * @see #getLayoutResource()
+     * @see #setVisibility(int)
+     * @see #inflate()
+     * @attr ref android.R.styleable#ViewStub_layout
+     */
+    @android.view.RemotableViewMethod(asyncImpl = "setLayoutResourceAsync")
+    public void setLayoutResource(@LayoutRes int layoutResource) {
+        mLayoutResource = layoutResource;
+    }
+
+    /** @hide **/
+    public Runnable setLayoutResourceAsync(@LayoutRes int layoutResource) {
+        mLayoutResource = layoutResource;
+        return null;
+    }
+
+    /**
+     * Set {@link LayoutInflater} to use in {@link #inflate()}, or {@code null}
+     * to use the default.
+     */
+    public void setLayoutInflater(LayoutInflater inflater) {
+        mInflater = inflater;
+    }
+
+    /**
+     * Get current {@link LayoutInflater} used in {@link #inflate()}.
+     */
+    public LayoutInflater getLayoutInflater() {
+        return mInflater;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        setMeasuredDimension(0, 0);
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+    }
+
+    /**
+     * When visibility is set to {@link #VISIBLE} or {@link #INVISIBLE},
+     * {@link #inflate()} is invoked and this StubbedView is replaced in its parent
+     * by the inflated layout resource. After that calls to this function are passed
+     * through to the inflated view.
+     *
+     * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
+     *
+     * @see #inflate() 
+     */
+    @Override
+    @android.view.RemotableViewMethod(asyncImpl = "setVisibilityAsync")
+    public void setVisibility(int visibility) {
+        if (mInflatedViewRef != null) {
+            View view = mInflatedViewRef.get();
+            if (view != null) {
+                view.setVisibility(visibility);
+            } else {
+                throw new IllegalStateException("setVisibility called on un-referenced view");
+            }
+        } else {
+            super.setVisibility(visibility);
+            if (visibility == VISIBLE || visibility == INVISIBLE) {
+                inflate();
+            }
+        }
+    }
+
+    /** @hide **/
+    public Runnable setVisibilityAsync(int visibility) {
+        if (visibility == VISIBLE || visibility == INVISIBLE) {
+            ViewGroup parent = (ViewGroup) getParent();
+            return new ViewReplaceRunnable(inflateViewNoAdd(parent));
+        } else {
+            return null;
+        }
+    }
+
+    private View inflateViewNoAdd(ViewGroup parent) {
+        final LayoutInflater factory;
+        if (mInflater != null) {
+            factory = mInflater;
+        } else {
+            factory = LayoutInflater.from(mContext);
+        }
+        final View view = factory.inflate(mLayoutResource, parent, false);
+
+        if (mInflatedId != NO_ID) {
+            view.setId(mInflatedId);
+        }
+        return view;
+    }
+
+    private void replaceSelfWithView(View view, ViewGroup parent) {
+        final int index = parent.indexOfChild(this);
+        parent.removeViewInLayout(this);
+
+        final ViewGroup.LayoutParams layoutParams = getLayoutParams();
+        if (layoutParams != null) {
+            parent.addView(view, index, layoutParams);
+        } else {
+            parent.addView(view, index);
+        }
+    }
+
+    /**
+     * Inflates the layout resource identified by {@link #getLayoutResource()}
+     * and replaces this StubbedView in its parent by the inflated layout resource.
+     *
+     * @return The inflated layout resource.
+     *
+     */
+    public View inflate() {
+        final ViewParent viewParent = getParent();
+
+        if (viewParent != null && viewParent instanceof ViewGroup) {
+            if (mLayoutResource != 0) {
+                final ViewGroup parent = (ViewGroup) viewParent;
+                final View view = inflateViewNoAdd(parent);
+                replaceSelfWithView(view, parent);
+
+                mInflatedViewRef = new WeakReference<>(view);
+                if (mInflateListener != null) {
+                    mInflateListener.onInflate(this, view);
+                }
+
+                return view;
+            } else {
+                throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
+            }
+        } else {
+            throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
+        }
+    }
+
+    /**
+     * Specifies the inflate listener to be notified after this ViewStub successfully
+     * inflated its layout resource.
+     *
+     * @param inflateListener The OnInflateListener to notify of successful inflation.
+     *
+     * @see android.view.ViewStub.OnInflateListener
+     */
+    public void setOnInflateListener(OnInflateListener inflateListener) {
+        mInflateListener = inflateListener;
+    }
+
+    /**
+     * Listener used to receive a notification after a ViewStub has successfully
+     * inflated its layout resource.
+     *
+     * @see android.view.ViewStub#setOnInflateListener(android.view.ViewStub.OnInflateListener) 
+     */
+    public static interface OnInflateListener {
+        /**
+         * Invoked after a ViewStub successfully inflated its layout resource.
+         * This method is invoked after the inflated view was added to the
+         * hierarchy but before the layout pass.
+         *
+         * @param stub The ViewStub that initiated the inflation.
+         * @param inflated The inflated View.
+         */
+        void onInflate(ViewStub stub, View inflated);
+    }
+
+    /** @hide **/
+    public class ViewReplaceRunnable implements Runnable {
+        public final View view;
+
+        ViewReplaceRunnable(View view) {
+            this.view = view;
+        }
+
+        @Override
+        public void run() {
+            replaceSelfWithView(view, (ViewGroup) getParent());
+        }
+    }
+}
diff --git a/android/view/ViewTreeObserver.java b/android/view/ViewTreeObserver.java
new file mode 100644
index 0000000..5a99ab2
--- /dev/null
+++ b/android/view/ViewTreeObserver.java
@@ -0,0 +1,1331 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.Build;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Consumer;
+
+/**
+ * A view tree observer is used to register listeners that can be notified of global
+ * changes in the view tree. Such global events include, but are not limited to,
+ * layout of the whole tree, beginning of the drawing pass, touch mode change....
+ *
+ * A ViewTreeObserver should never be instantiated by applications as it is provided
+ * by the views hierarchy. Refer to {@link android.view.View#getViewTreeObserver()}
+ * for more information.
+ */
+public final class ViewTreeObserver {
+    // Recursive listeners use CopyOnWriteArrayList
+    private CopyOnWriteArrayList<OnWindowFocusChangeListener> mOnWindowFocusListeners;
+    private CopyOnWriteArrayList<OnWindowAttachListener> mOnWindowAttachListeners;
+    private CopyOnWriteArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners;
+    @UnsupportedAppUsage
+    private CopyOnWriteArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners;
+    private CopyOnWriteArrayList<OnEnterAnimationCompleteListener>
+            mOnEnterAnimationCompleteListeners;
+
+    // Non-recursive listeners use CopyOnWriteArray
+    // Any listener invoked from ViewRootImpl.performTraversals() should not be recursive
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private CopyOnWriteArray<OnGlobalLayoutListener> mOnGlobalLayoutListeners;
+    @UnsupportedAppUsage
+    private CopyOnWriteArray<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners;
+    @UnsupportedAppUsage
+    private CopyOnWriteArray<OnScrollChangedListener> mOnScrollChangedListeners;
+    private CopyOnWriteArray<OnPreDrawListener> mOnPreDrawListeners;
+    private CopyOnWriteArray<OnWindowShownListener> mOnWindowShownListeners;
+    private CopyOnWriteArray<Consumer<List<Rect>>> mGestureExclusionListeners;
+
+    // These listeners cannot be mutated during dispatch
+    private boolean mInDispatchOnDraw;
+    private ArrayList<OnDrawListener> mOnDrawListeners;
+    private static boolean sIllegalOnDrawModificationIsFatal;
+
+    // These listeners are one-shot
+    private ArrayList<Runnable> mOnFrameCommitListeners;
+
+    /** Remains false until #dispatchOnWindowShown() is called. If a listener registers after
+     * that the listener will be immediately called. */
+    private boolean mWindowShown;
+
+    private boolean mAlive = true;
+
+    /**
+     * Interface definition for a callback to be invoked when the view hierarchy is
+     * attached to and detached from its window.
+     */
+    public interface OnWindowAttachListener {
+        /**
+         * Callback method to be invoked when the view hierarchy is attached to a window
+         */
+        public void onWindowAttached();
+
+        /**
+         * Callback method to be invoked when the view hierarchy is detached from a window
+         */
+        public void onWindowDetached();
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when the view hierarchy's window
+     * focus state changes.
+     */
+    public interface OnWindowFocusChangeListener {
+        /**
+         * Callback method to be invoked when the window focus changes in the view tree.
+         *
+         * @param hasFocus Set to true if the window is gaining focus, false if it is
+         * losing focus.
+         */
+        public void onWindowFocusChanged(boolean hasFocus);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when the focus state within
+     * the view tree changes.
+     */
+    public interface OnGlobalFocusChangeListener {
+        /**
+         * Callback method to be invoked when the focus changes in the view tree. When
+         * the view tree transitions from touch mode to non-touch mode, oldFocus is null.
+         * When the view tree transitions from non-touch mode to touch mode, newFocus is
+         * null. When focus changes in non-touch mode (without transition from or to
+         * touch mode) either oldFocus or newFocus can be null.
+         *
+         * @param oldFocus The previously focused view, if any.
+         * @param newFocus The newly focused View, if any.
+         */
+        public void onGlobalFocusChanged(View oldFocus, View newFocus);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when the global layout state
+     * or the visibility of views within the view tree changes.
+     */
+    public interface OnGlobalLayoutListener {
+        /**
+         * Callback method to be invoked when the global layout state or the visibility of views
+         * within the view tree changes
+         */
+        public void onGlobalLayout();
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when the view tree is about to be drawn.
+     */
+    public interface OnPreDrawListener {
+        /**
+         * Callback method to be invoked when the view tree is about to be drawn. At this point, all
+         * views in the tree have been measured and given a frame. Clients can use this to adjust
+         * their scroll bounds or even to request a new layout before drawing occurs.
+         *
+         * @return Return true to proceed with the current drawing pass, or false to cancel.
+         *
+         * @see android.view.View#onMeasure
+         * @see android.view.View#onLayout
+         * @see android.view.View#onDraw
+         */
+        public boolean onPreDraw();
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when the view tree is about to be drawn.
+     */
+    public interface OnDrawListener {
+        /**
+         * <p>Callback method to be invoked when the view tree is about to be drawn. At this point,
+         * views cannot be modified in any way.</p>
+         * 
+         * <p>Unlike with {@link OnPreDrawListener}, this method cannot be used to cancel the
+         * current drawing pass.</p>
+         * 
+         * <p>An {@link OnDrawListener} listener <strong>cannot be added or removed</strong>
+         * from this method.</p>
+         *
+         * @see android.view.View#onMeasure
+         * @see android.view.View#onLayout
+         * @see android.view.View#onDraw
+         */
+        public void onDraw();
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when the touch mode changes.
+     */
+    public interface OnTouchModeChangeListener {
+        /**
+         * Callback method to be invoked when the touch mode changes.
+         *
+         * @param isInTouchMode True if the view hierarchy is now in touch mode, false  otherwise.
+         */
+        public void onTouchModeChanged(boolean isInTouchMode);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when
+     * something in the view tree has been scrolled.
+     */
+    public interface OnScrollChangedListener {
+        /**
+         * Callback method to be invoked when something in the view tree
+         * has been scrolled.
+         */
+        public void onScrollChanged();
+    }
+
+    /**
+     * Interface definition for a callback noting when a system window has been displayed.
+     * This is only used for non-Activity windows. Activity windows can use
+     * Activity.onEnterAnimationComplete() to get the same signal.
+     * @hide
+     */
+    public interface OnWindowShownListener {
+        /**
+         * Callback method to be invoked when a non-activity window is fully shown.
+         */
+        void onWindowShown();
+    }
+
+    /**
+     * Parameters used with OnComputeInternalInsetsListener.
+     * 
+     * We are not yet ready to commit to this API and support it, so
+     * @hide
+     */
+    public final static class InternalInsetsInfo {
+
+        @UnsupportedAppUsage
+        public InternalInsetsInfo() {
+        }
+
+        /**
+         * Offsets from the frame of the window at which the content of
+         * windows behind it should be placed.
+         */
+        @UnsupportedAppUsage
+        public final Rect contentInsets = new Rect();
+
+        /**
+         * Offsets from the frame of the window at which windows behind it
+         * are visible.
+         */
+        @UnsupportedAppUsage
+        public final Rect visibleInsets = new Rect();
+
+        /**
+         * Touchable region defined relative to the origin of the frame of the window.
+         * Only used when {@link #setTouchableInsets(int)} is called with
+         * the option {@link #TOUCHABLE_INSETS_REGION}.
+         */
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        public final Region touchableRegion = new Region();
+
+        /**
+         * Option for {@link #setTouchableInsets(int)}: the entire window frame
+         * can be touched.
+         */
+        public static final int TOUCHABLE_INSETS_FRAME = 0;
+
+        /**
+         * Option for {@link #setTouchableInsets(int)}: the area inside of
+         * the content insets can be touched.
+         */
+        public static final int TOUCHABLE_INSETS_CONTENT = 1;
+
+        /**
+         * Option for {@link #setTouchableInsets(int)}: the area inside of
+         * the visible insets can be touched.
+         */
+        public static final int TOUCHABLE_INSETS_VISIBLE = 2;
+
+        /**
+         * Option for {@link #setTouchableInsets(int)}: the area inside of
+         * the provided touchable region in {@link #touchableRegion} can be touched.
+         */
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        public static final int TOUCHABLE_INSETS_REGION = 3;
+
+        /**
+         * Set which parts of the window can be touched: either
+         * {@link #TOUCHABLE_INSETS_FRAME}, {@link #TOUCHABLE_INSETS_CONTENT},
+         * {@link #TOUCHABLE_INSETS_VISIBLE}, or {@link #TOUCHABLE_INSETS_REGION}.
+         */
+        @UnsupportedAppUsage
+        public void setTouchableInsets(int val) {
+            mTouchableInsets = val;
+        }
+
+        @UnsupportedAppUsage
+        int mTouchableInsets;
+
+        void reset() {
+            contentInsets.setEmpty();
+            visibleInsets.setEmpty();
+            touchableRegion.setEmpty();
+            mTouchableInsets = TOUCHABLE_INSETS_FRAME;
+        }
+
+        boolean isEmpty() {
+            return contentInsets.isEmpty()
+                    && visibleInsets.isEmpty()
+                    && touchableRegion.isEmpty()
+                    && mTouchableInsets == TOUCHABLE_INSETS_FRAME;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = contentInsets.hashCode();
+            result = 31 * result + visibleInsets.hashCode();
+            result = 31 * result + touchableRegion.hashCode();
+            result = 31 * result + mTouchableInsets;
+            return result;
+        }
+
+        @Override
+        public boolean equals(@Nullable Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            InternalInsetsInfo other = (InternalInsetsInfo)o;
+            return mTouchableInsets == other.mTouchableInsets &&
+                    contentInsets.equals(other.contentInsets) &&
+                    visibleInsets.equals(other.visibleInsets) &&
+                    touchableRegion.equals(other.touchableRegion);
+        }
+
+        @UnsupportedAppUsage
+        void set(InternalInsetsInfo other) {
+            contentInsets.set(other.contentInsets);
+            visibleInsets.set(other.visibleInsets);
+            touchableRegion.set(other.touchableRegion);
+            mTouchableInsets = other.mTouchableInsets;
+        }
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when layout has
+     * completed and the client can compute its interior insets.
+     * 
+     * We are not yet ready to commit to this API and support it, so
+     * @hide
+     */
+    public interface OnComputeInternalInsetsListener {
+        /**
+         * Callback method to be invoked when layout has completed and the
+         * client can compute its interior insets.
+         *
+         * @param inoutInfo Should be filled in by the implementation with
+         * the information about the insets of the window.  This is called
+         * with whatever values the previous OnComputeInternalInsetsListener
+         * returned, if there are multiple such listeners in the window.
+         */
+        public void onComputeInternalInsets(InternalInsetsInfo inoutInfo);
+    }
+
+    /**
+     * @hide
+     */
+    public interface OnEnterAnimationCompleteListener {
+        public void onEnterAnimationComplete();
+    }
+
+    /**
+     * Creates a new ViewTreeObserver. This constructor should not be called
+     */
+    ViewTreeObserver(Context context) {
+        sIllegalOnDrawModificationIsFatal =
+                context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O;
+    }
+
+    /**
+     * Merges all the listeners registered on the specified observer with the listeners
+     * registered on this object. After this method is invoked, the specified observer
+     * will return false in {@link #isAlive()} and should not be used anymore.
+     *
+     * @param observer The ViewTreeObserver whose listeners must be added to this observer
+     */
+    void merge(ViewTreeObserver observer) {
+        if (observer.mOnWindowAttachListeners != null) {
+            if (mOnWindowAttachListeners != null) {
+                mOnWindowAttachListeners.addAll(observer.mOnWindowAttachListeners);
+            } else {
+                mOnWindowAttachListeners = observer.mOnWindowAttachListeners;
+            }
+        }
+
+        if (observer.mOnWindowFocusListeners != null) {
+            if (mOnWindowFocusListeners != null) {
+                mOnWindowFocusListeners.addAll(observer.mOnWindowFocusListeners);
+            } else {
+                mOnWindowFocusListeners = observer.mOnWindowFocusListeners;
+            }
+        }
+
+        if (observer.mOnGlobalFocusListeners != null) {
+            if (mOnGlobalFocusListeners != null) {
+                mOnGlobalFocusListeners.addAll(observer.mOnGlobalFocusListeners);
+            } else {
+                mOnGlobalFocusListeners = observer.mOnGlobalFocusListeners;
+            }
+        }
+
+        if (observer.mOnGlobalLayoutListeners != null) {
+            if (mOnGlobalLayoutListeners != null) {
+                mOnGlobalLayoutListeners.addAll(observer.mOnGlobalLayoutListeners);
+            } else {
+                mOnGlobalLayoutListeners = observer.mOnGlobalLayoutListeners;
+            }
+        }
+
+        if (observer.mOnPreDrawListeners != null) {
+            if (mOnPreDrawListeners != null) {
+                mOnPreDrawListeners.addAll(observer.mOnPreDrawListeners);
+            } else {
+                mOnPreDrawListeners = observer.mOnPreDrawListeners;
+            }
+        }
+
+        if (observer.mOnDrawListeners != null) {
+            if (mOnDrawListeners != null) {
+                mOnDrawListeners.addAll(observer.mOnDrawListeners);
+            } else {
+                mOnDrawListeners = observer.mOnDrawListeners;
+            }
+        }
+
+        if (observer.mOnFrameCommitListeners != null) {
+            if (mOnFrameCommitListeners != null) {
+                mOnFrameCommitListeners.addAll(observer.captureFrameCommitCallbacks());
+            } else {
+                mOnFrameCommitListeners = observer.captureFrameCommitCallbacks();
+            }
+        }
+
+        if (observer.mOnTouchModeChangeListeners != null) {
+            if (mOnTouchModeChangeListeners != null) {
+                mOnTouchModeChangeListeners.addAll(observer.mOnTouchModeChangeListeners);
+            } else {
+                mOnTouchModeChangeListeners = observer.mOnTouchModeChangeListeners;
+            }
+        }
+
+        if (observer.mOnComputeInternalInsetsListeners != null) {
+            if (mOnComputeInternalInsetsListeners != null) {
+                mOnComputeInternalInsetsListeners.addAll(observer.mOnComputeInternalInsetsListeners);
+            } else {
+                mOnComputeInternalInsetsListeners = observer.mOnComputeInternalInsetsListeners;
+            }
+        }
+
+        if (observer.mOnScrollChangedListeners != null) {
+            if (mOnScrollChangedListeners != null) {
+                mOnScrollChangedListeners.addAll(observer.mOnScrollChangedListeners);
+            } else {
+                mOnScrollChangedListeners = observer.mOnScrollChangedListeners;
+            }
+        }
+
+        if (observer.mOnWindowShownListeners != null) {
+            if (mOnWindowShownListeners != null) {
+                mOnWindowShownListeners.addAll(observer.mOnWindowShownListeners);
+            } else {
+                mOnWindowShownListeners = observer.mOnWindowShownListeners;
+            }
+        }
+
+        if (observer.mGestureExclusionListeners != null) {
+            if (mGestureExclusionListeners != null) {
+                mGestureExclusionListeners.addAll(observer.mGestureExclusionListeners);
+            } else {
+                mGestureExclusionListeners = observer.mGestureExclusionListeners;
+            }
+        }
+
+        observer.kill();
+    }
+
+    /**
+     * Register a callback to be invoked when the view hierarchy is attached to a window.
+     *
+     * @param listener The callback to add
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     */
+    public void addOnWindowAttachListener(OnWindowAttachListener listener) {
+        checkIsAlive();
+
+        if (mOnWindowAttachListeners == null) {
+            mOnWindowAttachListeners
+                    = new CopyOnWriteArrayList<OnWindowAttachListener>();
+        }
+
+        mOnWindowAttachListeners.add(listener);
+    }
+
+    /**
+     * Remove a previously installed window attach callback.
+     *
+     * @param victim The callback to remove
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     *
+     * @see #addOnWindowAttachListener(android.view.ViewTreeObserver.OnWindowAttachListener)
+     */
+    public void removeOnWindowAttachListener(OnWindowAttachListener victim) {
+        checkIsAlive();
+        if (mOnWindowAttachListeners == null) {
+            return;
+        }
+        mOnWindowAttachListeners.remove(victim);
+    }
+
+    /**
+     * Register a callback to be invoked when the window focus state within the view tree changes.
+     *
+     * @param listener The callback to add
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     */
+    public void addOnWindowFocusChangeListener(OnWindowFocusChangeListener listener) {
+        checkIsAlive();
+
+        if (mOnWindowFocusListeners == null) {
+            mOnWindowFocusListeners
+                    = new CopyOnWriteArrayList<OnWindowFocusChangeListener>();
+        }
+
+        mOnWindowFocusListeners.add(listener);
+    }
+
+    /**
+     * Remove a previously installed window focus change callback.
+     *
+     * @param victim The callback to remove
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     *
+     * @see #addOnWindowFocusChangeListener(android.view.ViewTreeObserver.OnWindowFocusChangeListener)
+     */
+    public void removeOnWindowFocusChangeListener(OnWindowFocusChangeListener victim) {
+        checkIsAlive();
+        if (mOnWindowFocusListeners == null) {
+            return;
+        }
+        mOnWindowFocusListeners.remove(victim);
+    }
+
+    /**
+     * Register a callback to be invoked when the focus state within the view tree changes.
+     *
+     * @param listener The callback to add
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     */
+    public void addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener listener) {
+        checkIsAlive();
+
+        if (mOnGlobalFocusListeners == null) {
+            mOnGlobalFocusListeners = new CopyOnWriteArrayList<OnGlobalFocusChangeListener>();
+        }
+
+        mOnGlobalFocusListeners.add(listener);
+    }
+
+    /**
+     * Remove a previously installed focus change callback.
+     *
+     * @param victim The callback to remove
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     *
+     * @see #addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener)
+     */
+    public void removeOnGlobalFocusChangeListener(OnGlobalFocusChangeListener victim) {
+        checkIsAlive();
+        if (mOnGlobalFocusListeners == null) {
+            return;
+        }
+        mOnGlobalFocusListeners.remove(victim);
+    }
+
+    /**
+     * Register a callback to be invoked when the global layout state or the visibility of views
+     * within the view tree changes
+     *
+     * @param listener The callback to add
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     */
+    public void addOnGlobalLayoutListener(OnGlobalLayoutListener listener) {
+        checkIsAlive();
+
+        if (mOnGlobalLayoutListeners == null) {
+            mOnGlobalLayoutListeners = new CopyOnWriteArray<OnGlobalLayoutListener>();
+        }
+
+        mOnGlobalLayoutListeners.add(listener);
+    }
+
+    /**
+     * Remove a previously installed global layout callback
+     *
+     * @param victim The callback to remove
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     * 
+     * @deprecated Use #removeOnGlobalLayoutListener instead
+     *
+     * @see #addOnGlobalLayoutListener(OnGlobalLayoutListener)
+     */
+    @Deprecated
+    public void removeGlobalOnLayoutListener(OnGlobalLayoutListener victim) {
+        removeOnGlobalLayoutListener(victim);
+    }
+
+    /**
+     * Remove a previously installed global layout callback
+     *
+     * @param victim The callback to remove
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     * 
+     * @see #addOnGlobalLayoutListener(OnGlobalLayoutListener)
+     */
+    public void removeOnGlobalLayoutListener(OnGlobalLayoutListener victim) {
+        checkIsAlive();
+        if (mOnGlobalLayoutListeners == null) {
+            return;
+        }
+        mOnGlobalLayoutListeners.remove(victim);
+    }
+
+    /**
+     * Register a callback to be invoked when the view tree is about to be drawn
+     *
+     * @param listener The callback to add
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     */
+    public void addOnPreDrawListener(OnPreDrawListener listener) {
+        checkIsAlive();
+
+        if (mOnPreDrawListeners == null) {
+            mOnPreDrawListeners = new CopyOnWriteArray<OnPreDrawListener>();
+        }
+
+        mOnPreDrawListeners.add(listener);
+    }
+
+    /**
+     * Remove a previously installed pre-draw callback
+     *
+     * @param victim The callback to remove
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     *
+     * @see #addOnPreDrawListener(OnPreDrawListener)
+     */
+    public void removeOnPreDrawListener(OnPreDrawListener victim) {
+        checkIsAlive();
+        if (mOnPreDrawListeners == null) {
+            return;
+        }
+        mOnPreDrawListeners.remove(victim);
+    }
+
+    /**
+     * Register a callback to be invoked when the view tree window has been shown
+     *
+     * @param listener The callback to add
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     * @hide
+     */
+    public void addOnWindowShownListener(OnWindowShownListener listener) {
+        checkIsAlive();
+
+        if (mOnWindowShownListeners == null) {
+            mOnWindowShownListeners = new CopyOnWriteArray<OnWindowShownListener>();
+        }
+
+        mOnWindowShownListeners.add(listener);
+        if (mWindowShown) {
+            listener.onWindowShown();
+        }
+    }
+
+    /**
+     * Remove a previously installed window shown callback
+     *
+     * @param victim The callback to remove
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     *
+     * @see #addOnWindowShownListener(OnWindowShownListener)
+     * @hide
+     */
+    public void removeOnWindowShownListener(OnWindowShownListener victim) {
+        checkIsAlive();
+        if (mOnWindowShownListeners == null) {
+            return;
+        }
+        mOnWindowShownListeners.remove(victim);
+    }
+
+    /**
+     * <p>Register a callback to be invoked when the view tree is about to be drawn.</p>
+     * <p><strong>Note:</strong> this method <strong>cannot</strong> be invoked from
+     * {@link android.view.ViewTreeObserver.OnDrawListener#onDraw()}.</p>
+     *
+     * @param listener The callback to add
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     */
+    public void addOnDrawListener(OnDrawListener listener) {
+        checkIsAlive();
+
+        if (mOnDrawListeners == null) {
+            mOnDrawListeners = new ArrayList<OnDrawListener>();
+        }
+
+        if (mInDispatchOnDraw) {
+            IllegalStateException ex = new IllegalStateException(
+                    "Cannot call addOnDrawListener inside of onDraw");
+            if (sIllegalOnDrawModificationIsFatal) {
+                throw ex;
+            } else {
+                Log.e("ViewTreeObserver", ex.getMessage(), ex);
+            }
+        }
+        mOnDrawListeners.add(listener);
+    }
+
+    /**
+     * <p>Remove a previously installed pre-draw callback.</p>
+     * <p><strong>Note:</strong> this method <strong>cannot</strong> be invoked from
+     * {@link android.view.ViewTreeObserver.OnDrawListener#onDraw()}.</p>
+     *
+     * @param victim The callback to remove
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     *
+     * @see #addOnDrawListener(OnDrawListener)
+     */
+    public void removeOnDrawListener(OnDrawListener victim) {
+        checkIsAlive();
+        if (mOnDrawListeners == null) {
+            return;
+        }
+        if (mInDispatchOnDraw) {
+            IllegalStateException ex = new IllegalStateException(
+                    "Cannot call removeOnDrawListener inside of onDraw");
+            if (sIllegalOnDrawModificationIsFatal) {
+                throw ex;
+            } else {
+                Log.e("ViewTreeObserver", ex.getMessage(), ex);
+            }
+        }
+        mOnDrawListeners.remove(victim);
+    }
+
+    /**
+     * Adds a frame commit callback. This callback will be invoked when the current rendering
+     * content has been rendered into a frame and submitted to the swap chain. The frame may
+     * not currently be visible on the display when this is invoked, but it has been submitted.
+     * This callback is useful in combination with {@link PixelCopy} to capture the current
+     * rendered content of the UI reliably.
+     *
+     * Note: Only works with hardware rendering. Does nothing otherwise.
+     *
+     * @param callback The callback to invoke when the frame is committed.
+     */
+    public void registerFrameCommitCallback(@NonNull Runnable callback) {
+        checkIsAlive();
+        if (mOnFrameCommitListeners == null) {
+            mOnFrameCommitListeners = new ArrayList<>();
+        }
+        mOnFrameCommitListeners.add(callback);
+    }
+
+    @Nullable ArrayList<Runnable> captureFrameCommitCallbacks() {
+        ArrayList<Runnable> ret = mOnFrameCommitListeners;
+        mOnFrameCommitListeners = null;
+        return ret;
+    }
+
+    /**
+     * Attempts to remove the given callback from the list of pending frame complete callbacks.
+     *
+     * @param callback The callback to remove
+     * @return Whether or not the callback was removed. If this returns true the callback will
+     *         not be invoked. If false is returned then the callback was either never added
+     *         or may already be pending execution and was unable to be removed
+     */
+    public boolean unregisterFrameCommitCallback(@NonNull Runnable callback) {
+        checkIsAlive();
+        if (mOnFrameCommitListeners == null) {
+            return false;
+        }
+        return mOnFrameCommitListeners.remove(callback);
+    }
+
+    /**
+     * Register a callback to be invoked when a view has been scrolled.
+     *
+     * @param listener The callback to add
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     */
+    public void addOnScrollChangedListener(OnScrollChangedListener listener) {
+        checkIsAlive();
+
+        if (mOnScrollChangedListeners == null) {
+            mOnScrollChangedListeners = new CopyOnWriteArray<OnScrollChangedListener>();
+        }
+
+        mOnScrollChangedListeners.add(listener);
+    }
+
+    /**
+     * Remove a previously installed scroll-changed callback
+     *
+     * @param victim The callback to remove
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     *
+     * @see #addOnScrollChangedListener(OnScrollChangedListener)
+     */
+    public void removeOnScrollChangedListener(OnScrollChangedListener victim) {
+        checkIsAlive();
+        if (mOnScrollChangedListeners == null) {
+            return;
+        }
+        mOnScrollChangedListeners.remove(victim);
+    }
+
+    /**
+     * Register a callback to be invoked when the invoked when the touch mode changes.
+     *
+     * @param listener The callback to add
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     */
+    public void addOnTouchModeChangeListener(OnTouchModeChangeListener listener) {
+        checkIsAlive();
+
+        if (mOnTouchModeChangeListeners == null) {
+            mOnTouchModeChangeListeners = new CopyOnWriteArrayList<OnTouchModeChangeListener>();
+        }
+
+        mOnTouchModeChangeListeners.add(listener);
+    }
+
+    /**
+     * Remove a previously installed touch mode change callback
+     *
+     * @param victim The callback to remove
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     *
+     * @see #addOnTouchModeChangeListener(OnTouchModeChangeListener)
+     */
+    public void removeOnTouchModeChangeListener(OnTouchModeChangeListener victim) {
+        checkIsAlive();
+        if (mOnTouchModeChangeListeners == null) {
+            return;
+        }
+        mOnTouchModeChangeListeners.remove(victim);
+    }
+
+    /**
+     * Register a callback to be invoked when the invoked when it is time to
+     * compute the window's internal insets.
+     *
+     * @param listener The callback to add
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     * 
+     * We are not yet ready to commit to this API and support it, so
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener listener) {
+        checkIsAlive();
+
+        if (mOnComputeInternalInsetsListeners == null) {
+            mOnComputeInternalInsetsListeners =
+                    new CopyOnWriteArray<OnComputeInternalInsetsListener>();
+        }
+
+        mOnComputeInternalInsetsListeners.add(listener);
+    }
+
+    /**
+     * Remove a previously installed internal insets computation callback
+     *
+     * @param victim The callback to remove
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     *
+     * @see #addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener)
+     * 
+     * We are not yet ready to commit to this API and support it, so
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void removeOnComputeInternalInsetsListener(OnComputeInternalInsetsListener victim) {
+        checkIsAlive();
+        if (mOnComputeInternalInsetsListeners == null) {
+            return;
+        }
+        mOnComputeInternalInsetsListeners.remove(victim);
+    }
+
+    /**
+     * @hide
+     */
+    public void addOnEnterAnimationCompleteListener(OnEnterAnimationCompleteListener listener) {
+        checkIsAlive();
+        if (mOnEnterAnimationCompleteListeners == null) {
+            mOnEnterAnimationCompleteListeners =
+                    new CopyOnWriteArrayList<OnEnterAnimationCompleteListener>();
+        }
+        mOnEnterAnimationCompleteListeners.add(listener);
+    }
+
+    /**
+     * @hide
+     */
+    public void removeOnEnterAnimationCompleteListener(OnEnterAnimationCompleteListener listener) {
+        checkIsAlive();
+        if (mOnEnterAnimationCompleteListeners == null) {
+            return;
+        }
+        mOnEnterAnimationCompleteListeners.remove(listener);
+    }
+
+    /**
+     * Add a listener to be notified when the tree's <em>transformed</em> gesture exclusion rects
+     * change. This could be the result of an animation or other layout change, or a view calling
+     * {@link View#setSystemGestureExclusionRects(List)}.
+     *
+     * @param listener listener to add
+     * @see View#setSystemGestureExclusionRects(List)
+     */
+    public void addOnSystemGestureExclusionRectsChangedListener(
+            @NonNull Consumer<List<Rect>> listener) {
+        checkIsAlive();
+        if (mGestureExclusionListeners == null) {
+            mGestureExclusionListeners = new CopyOnWriteArray<>();
+        }
+        mGestureExclusionListeners.add(listener);
+    }
+
+    /**
+     * Unsubscribe the given listener from gesture exclusion rect changes.
+     * @see #addOnSystemGestureExclusionRectsChangedListener(Consumer)
+     * @see View#setSystemGestureExclusionRects(List)
+     */
+    public void removeOnSystemGestureExclusionRectsChangedListener(
+            @NonNull Consumer<List<Rect>> listener) {
+        checkIsAlive();
+        if (mGestureExclusionListeners == null) {
+            return;
+        }
+        mGestureExclusionListeners.remove(listener);
+    }
+
+    private void checkIsAlive() {
+        if (!mAlive) {
+            throw new IllegalStateException("This ViewTreeObserver is not alive, call "
+                    + "getViewTreeObserver() again");
+        }
+    }
+
+    /**
+     * Indicates whether this ViewTreeObserver is alive. When an observer is not alive,
+     * any call to a method (except this one) will throw an exception.
+     *
+     * If an application keeps a long-lived reference to this ViewTreeObserver, it should
+     * always check for the result of this method before calling any other method.
+     *
+     * @return True if this object is alive and be used, false otherwise.
+     */
+    public boolean isAlive() {
+        return mAlive;
+    }
+
+    /**
+     * Marks this ViewTreeObserver as not alive. After invoking this method, invoking
+     * any other method but {@link #isAlive()} and {@link #kill()} will throw an Exception.
+     *
+     * @hide
+     */
+    private void kill() {
+        mAlive = false;
+    }
+
+    /**
+     * Notifies registered listeners that window has been attached/detached.
+     */
+    final void dispatchOnWindowAttachedChange(boolean attached) {
+        // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
+        // perform the dispatching. The iterator is a safe guard against listeners that
+        // could mutate the list by calling the various add/remove methods. This prevents
+        // the array from being modified while we iterate it.
+        final CopyOnWriteArrayList<OnWindowAttachListener> listeners
+                = mOnWindowAttachListeners;
+        if (listeners != null && listeners.size() > 0) {
+            for (OnWindowAttachListener listener : listeners) {
+                if (attached) listener.onWindowAttached();
+                else listener.onWindowDetached();
+            }
+        }
+    }
+
+    /**
+     * Notifies registered listeners that window focus has changed.
+     */
+    final void dispatchOnWindowFocusChange(boolean hasFocus) {
+        // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
+        // perform the dispatching. The iterator is a safe guard against listeners that
+        // could mutate the list by calling the various add/remove methods. This prevents
+        // the array from being modified while we iterate it.
+        final CopyOnWriteArrayList<OnWindowFocusChangeListener> listeners
+                = mOnWindowFocusListeners;
+        if (listeners != null && listeners.size() > 0) {
+            for (OnWindowFocusChangeListener listener : listeners) {
+                listener.onWindowFocusChanged(hasFocus);
+            }
+        }
+    }
+
+    /**
+     * Notifies registered listeners that focus has changed.
+     */
+    @UnsupportedAppUsage
+    final void dispatchOnGlobalFocusChange(View oldFocus, View newFocus) {
+        // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
+        // perform the dispatching. The iterator is a safe guard against listeners that
+        // could mutate the list by calling the various add/remove methods. This prevents
+        // the array from being modified while we iterate it.
+        final CopyOnWriteArrayList<OnGlobalFocusChangeListener> listeners = mOnGlobalFocusListeners;
+        if (listeners != null && listeners.size() > 0) {
+            for (OnGlobalFocusChangeListener listener : listeners) {
+                listener.onGlobalFocusChanged(oldFocus, newFocus);
+            }
+        }
+    }
+
+    /**
+     * Notifies registered listeners that a global layout happened. This can be called
+     * manually if you are forcing a layout on a View or a hierarchy of Views that are
+     * not attached to a Window or in the GONE state.
+     */
+    public final void dispatchOnGlobalLayout() {
+        // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
+        // perform the dispatching. The iterator is a safe guard against listeners that
+        // could mutate the list by calling the various add/remove methods. This prevents
+        // the array from being modified while we iterate it.
+        final CopyOnWriteArray<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners;
+        if (listeners != null && listeners.size() > 0) {
+            CopyOnWriteArray.Access<OnGlobalLayoutListener> access = listeners.start();
+            try {
+                int count = access.size();
+                for (int i = 0; i < count; i++) {
+                    access.get(i).onGlobalLayout();
+                }
+            } finally {
+                listeners.end();
+            }
+        }
+    }
+
+    /**
+     * Returns whether there are listeners for on pre-draw events.
+     */
+    final boolean hasOnPreDrawListeners() {
+        return mOnPreDrawListeners != null && mOnPreDrawListeners.size() > 0;
+    }
+
+    /**
+     * Notifies registered listeners that the drawing pass is about to start. If a
+     * listener returns true, then the drawing pass is canceled and rescheduled. This can
+     * be called manually if you are forcing the drawing on a View or a hierarchy of Views
+     * that are not attached to a Window or in the GONE state.
+     *
+     * @return True if the current draw should be canceled and rescheduled, false otherwise.
+     */
+    @SuppressWarnings("unchecked")
+    public final boolean dispatchOnPreDraw() {
+        boolean cancelDraw = false;
+        final CopyOnWriteArray<OnPreDrawListener> listeners = mOnPreDrawListeners;
+        if (listeners != null && listeners.size() > 0) {
+            CopyOnWriteArray.Access<OnPreDrawListener> access = listeners.start();
+            try {
+                int count = access.size();
+                for (int i = 0; i < count; i++) {
+                    cancelDraw |= !(access.get(i).onPreDraw());
+                }
+            } finally {
+                listeners.end();
+            }
+        }
+        return cancelDraw;
+    }
+
+    /**
+     * Notifies registered listeners that the window is now shown
+     * @hide
+     */
+    @SuppressWarnings("unchecked")
+    public final void dispatchOnWindowShown() {
+        mWindowShown = true;
+        final CopyOnWriteArray<OnWindowShownListener> listeners = mOnWindowShownListeners;
+        if (listeners != null && listeners.size() > 0) {
+            CopyOnWriteArray.Access<OnWindowShownListener> access = listeners.start();
+            try {
+                int count = access.size();
+                for (int i = 0; i < count; i++) {
+                    access.get(i).onWindowShown();
+                }
+            } finally {
+                listeners.end();
+            }
+        }
+    }
+
+    /**
+     * Notifies registered listeners that the drawing pass is about to start.
+     */
+    public final void dispatchOnDraw() {
+        if (mOnDrawListeners != null) {
+            mInDispatchOnDraw = true;
+            final ArrayList<OnDrawListener> listeners = mOnDrawListeners;
+            int numListeners = listeners.size();
+            for (int i = 0; i < numListeners; ++i) {
+                listeners.get(i).onDraw();
+            }
+            mInDispatchOnDraw = false;
+        }
+    }
+
+    /**
+     * Notifies registered listeners that the touch mode has changed.
+     *
+     * @param inTouchMode True if the touch mode is now enabled, false otherwise.
+     */
+    @UnsupportedAppUsage
+    final void dispatchOnTouchModeChanged(boolean inTouchMode) {
+        final CopyOnWriteArrayList<OnTouchModeChangeListener> listeners =
+                mOnTouchModeChangeListeners;
+        if (listeners != null && listeners.size() > 0) {
+            for (OnTouchModeChangeListener listener : listeners) {
+                listener.onTouchModeChanged(inTouchMode);
+            }
+        }
+    }
+
+    /**
+     * Notifies registered listeners that something has scrolled.
+     */
+    @UnsupportedAppUsage
+    final void dispatchOnScrollChanged() {
+        // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
+        // perform the dispatching. The iterator is a safe guard against listeners that
+        // could mutate the list by calling the various add/remove methods. This prevents
+        // the array from being modified while we iterate it.
+        final CopyOnWriteArray<OnScrollChangedListener> listeners = mOnScrollChangedListeners;
+        if (listeners != null && listeners.size() > 0) {
+            CopyOnWriteArray.Access<OnScrollChangedListener> access = listeners.start();
+            try {
+                int count = access.size();
+                for (int i = 0; i < count; i++) {
+                    access.get(i).onScrollChanged();
+                }
+            } finally {
+                listeners.end();
+            }
+        }
+    }
+
+    /**
+     * Returns whether there are listeners for computing internal insets.
+     */
+    @UnsupportedAppUsage
+    final boolean hasComputeInternalInsetsListeners() {
+        final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners =
+                mOnComputeInternalInsetsListeners;
+        return (listeners != null && listeners.size() > 0);
+    }
+
+    /**
+     * Calls all listeners to compute the current insets.
+     */
+    @UnsupportedAppUsage
+    final void dispatchOnComputeInternalInsets(InternalInsetsInfo inoutInfo) {
+        // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
+        // perform the dispatching. The iterator is a safe guard against listeners that
+        // could mutate the list by calling the various add/remove methods. This prevents
+        // the array from being modified while we iterate it.
+        final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners =
+                mOnComputeInternalInsetsListeners;
+        if (listeners != null && listeners.size() > 0) {
+            CopyOnWriteArray.Access<OnComputeInternalInsetsListener> access = listeners.start();
+            try {
+                int count = access.size();
+                for (int i = 0; i < count; i++) {
+                    access.get(i).onComputeInternalInsets(inoutInfo);
+                }
+            } finally {
+                listeners.end();
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public final void dispatchOnEnterAnimationComplete() {
+        // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
+        // perform the dispatching. The iterator is a safe guard against listeners that
+        // could mutate the list by calling the various add/remove methods. This prevents
+        // the array from being modified while we iterate it.
+        final CopyOnWriteArrayList<OnEnterAnimationCompleteListener> listeners =
+                mOnEnterAnimationCompleteListeners;
+        if (listeners != null && !listeners.isEmpty()) {
+            for (OnEnterAnimationCompleteListener listener : listeners) {
+                listener.onEnterAnimationComplete();
+            }
+        }
+    }
+
+    void dispatchOnSystemGestureExclusionRectsChanged(@NonNull List<Rect> rects) {
+        final CopyOnWriteArray<Consumer<List<Rect>>> listeners = mGestureExclusionListeners;
+        if (listeners != null && listeners.size() > 0) {
+            CopyOnWriteArray.Access<Consumer<List<Rect>>> access = listeners.start();
+            try {
+                final int count = access.size();
+                for (int i = 0; i < count; i++) {
+                    access.get(i).accept(rects);
+                }
+            } finally {
+                listeners.end();
+            }
+        }
+    }
+
+    /**
+     * Copy on write array. This array is not thread safe, and only one loop can
+     * iterate over this array at any given time. This class avoids allocations
+     * until a concurrent modification happens.
+     * 
+     * Usage:
+     * 
+     * CopyOnWriteArray.Access<MyData> access = array.start();
+     * try {
+     *     for (int i = 0; i < access.size(); i++) {
+     *         MyData d = access.get(i);
+     *     }
+     * } finally {
+     *     access.end();
+     * }
+     */
+    static class CopyOnWriteArray<T> {
+        private ArrayList<T> mData = new ArrayList<T>();
+        private ArrayList<T> mDataCopy;
+
+        private final Access<T> mAccess = new Access<T>();
+
+        private boolean mStart;
+
+        static class Access<T> {
+            private ArrayList<T> mData;
+            private int mSize;
+
+            T get(int index) {
+                return mData.get(index);
+            }
+
+            int size() {
+                return mSize;
+            }
+        }
+
+        CopyOnWriteArray() {
+        }
+
+        private ArrayList<T> getArray() {
+            if (mStart) {
+                if (mDataCopy == null) mDataCopy = new ArrayList<T>(mData);
+                return mDataCopy;
+            }
+            return mData;
+        }
+
+        Access<T> start() {
+            if (mStart) throw new IllegalStateException("Iteration already started");
+            mStart = true;
+            mDataCopy = null;
+            mAccess.mData = mData;
+            mAccess.mSize = mData.size();
+            return mAccess;
+        }
+
+        void end() {
+            if (!mStart) throw new IllegalStateException("Iteration not started");
+            mStart = false;
+            if (mDataCopy != null) {
+                mData = mDataCopy;
+                mAccess.mData.clear();
+                mAccess.mSize = 0;
+            }
+            mDataCopy = null;
+        }
+
+        int size() {
+            return getArray().size();
+        }
+
+        void add(T item) {
+            getArray().add(item);
+        }
+
+        void addAll(CopyOnWriteArray<T> array) {
+            getArray().addAll(array.mData);
+        }
+
+        void remove(T item) {
+            getArray().remove(item);
+        }
+
+        void clear() {
+            getArray().clear();
+        }
+    }
+}
diff --git a/android/view/View_Delegate.java b/android/view/View_Delegate.java
new file mode 100644
index 0000000..6d82e8a
--- /dev/null
+++ b/android/view/View_Delegate.java
@@ -0,0 +1,110 @@
+/*
+ * 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.view;
+
+import com.android.ide.common.rendering.api.ILayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.os.IBinder;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link View}
+ *
+ * Through the layoutlib_create tool, the original  methods of View have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class View_Delegate {
+
+    @LayoutlibDelegate
+    /*package*/ static boolean isInEditMode(View thisView) {
+        return true;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static IBinder getWindowToken(View thisView) {
+        Context baseContext = BridgeContext.getBaseContext(thisView.getContext());
+        if (baseContext instanceof BridgeContext) {
+            return ((BridgeContext) baseContext).getBinder();
+        }
+        return null;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void draw(View thisView, Canvas canvas) {
+        try {
+            // This code is run within a catch to prevent misbehaving components from breaking
+            // all the layout.
+            thisView.draw_Original(canvas);
+        } catch (Throwable t) {
+            Bridge.getLog().error(ILayoutLog.TAG_BROKEN, "View draw failed", t, null, null);
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean draw(
+            View thisView, Canvas canvas, ViewGroup parent, long drawingTime) {
+        try {
+            // This code is run within a catch to prevent misbehaving components from breaking
+            // all the layout.
+            return thisView.draw_Original(canvas, parent, drawingTime);
+        } catch (Throwable t) {
+            Bridge.getLog().error(ILayoutLog.TAG_BROKEN, "View draw failed", t, null, null);
+        }
+        return false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void measure(View thisView, int widthMeasureSpec, int heightMeasureSpec) {
+        try {
+            // This code is run within a catch to prevent misbehaving components from breaking
+            // all the layout.
+            thisView.measure_Original(widthMeasureSpec, heightMeasureSpec);
+        } catch (Throwable t) {
+            Bridge.getLog().error(ILayoutLog.TAG_BROKEN, "View measure failed", t, null, null);
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void layout(View thisView, int l, int t, int r, int b) {
+        try {
+            // This code is run within a catch to prevent misbehaving components from breaking
+            // all the layout.
+            thisView.layout_Original(l, t, r, b);
+        } catch (Throwable th) {
+            Bridge.getLog().error(ILayoutLog.TAG_BROKEN, "View layout failed", th, null, null);
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void dispatchDetachedFromWindow(View thisView) {
+        try {
+            // This code is run within a try/catch to prevent components from throwing user-visible
+            // exceptions when being disposed.
+            thisView.dispatchDetachedFromWindow_Original();
+        } catch (Throwable t) {
+            Context context = BridgeContext.getBaseContext(thisView.getContext());
+            if (context instanceof BridgeContext) {
+                ((BridgeContext) context).warn("Exception while detaching " + thisView.getClass(), t);
+            }
+        }
+    }
+}
diff --git a/android/view/Window.java b/android/view/Window.java
new file mode 100644
index 0000000..aa9ea19
--- /dev/null
+++ b/android/view/Window.java
@@ -0,0 +1,2739 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+import static android.Manifest.permission.HIDE_OVERLAY_WINDOWS;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
+import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
+import android.annotation.IdRes;
+import android.annotation.LayoutRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.StyleRes;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UiContext;
+import android.app.WindowConfiguration;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Insets;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.media.session.MediaController;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.transition.Scene;
+import android.transition.Transition;
+import android.transition.TransitionManager;
+import android.util.Pair;
+import android.view.View.OnApplyWindowInsetsListener;
+import android.view.accessibility.AccessibilityEvent;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Abstract base class for a top-level window look and behavior policy.  An
+ * instance of this class should be used as the top-level view added to the
+ * window manager. It provides standard UI policies such as a background, title
+ * area, default key processing, etc.
+ *
+ * <p>The only existing implementation of this abstract class is
+ * android.view.PhoneWindow, which you should instantiate when needing a
+ * Window.
+ */
+public abstract class Window {
+    /** Flag for the "options panel" feature.  This is enabled by default. */
+    public static final int FEATURE_OPTIONS_PANEL = 0;
+    /** Flag for the "no title" feature, turning off the title at the top
+     *  of the screen. */
+    public static final int FEATURE_NO_TITLE = 1;
+
+    /**
+     * Flag for the progress indicator feature.
+     *
+     * @deprecated No longer supported starting in API 21.
+     */
+    @Deprecated
+    public static final int FEATURE_PROGRESS = 2;
+
+    /** Flag for having an icon on the left side of the title bar */
+    public static final int FEATURE_LEFT_ICON = 3;
+    /** Flag for having an icon on the right side of the title bar */
+    public static final int FEATURE_RIGHT_ICON = 4;
+
+    /**
+     * Flag for indeterminate progress.
+     *
+     * @deprecated No longer supported starting in API 21.
+     */
+    @Deprecated
+    public static final int FEATURE_INDETERMINATE_PROGRESS = 5;
+
+    /** Flag for the context menu.  This is enabled by default. */
+    public static final int FEATURE_CONTEXT_MENU = 6;
+    /** Flag for custom title. You cannot combine this feature with other title features. */
+    public static final int FEATURE_CUSTOM_TITLE = 7;
+    /**
+     * Flag for enabling the Action Bar.
+     * This is enabled by default for some devices. The Action Bar
+     * replaces the title bar and provides an alternate location
+     * for an on-screen menu button on some devices.
+     */
+    public static final int FEATURE_ACTION_BAR = 8;
+    /**
+     * Flag for requesting an Action Bar that overlays window content.
+     * Normally an Action Bar will sit in the space above window content, but if this
+     * feature is requested along with {@link #FEATURE_ACTION_BAR} it will be layered over
+     * the window content itself. This is useful if you would like your app to have more control
+     * over how the Action Bar is displayed, such as letting application content scroll beneath
+     * an Action Bar with a transparent background or otherwise displaying a transparent/translucent
+     * Action Bar over application content.
+     *
+     * <p>This mode is especially useful with {@link View#SYSTEM_UI_FLAG_FULLSCREEN
+     * View.SYSTEM_UI_FLAG_FULLSCREEN}, which allows you to seamlessly hide the
+     * action bar in conjunction with other screen decorations.
+     *
+     * <p>As of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}, when an
+     * ActionBar is in this mode it will adjust the insets provided to
+     * {@link View#fitSystemWindows(android.graphics.Rect) View.fitSystemWindows(Rect)}
+     * to include the content covered by the action bar, so you can do layout within
+     * that space.
+     */
+    public static final int FEATURE_ACTION_BAR_OVERLAY = 9;
+    /**
+     * Flag for specifying the behavior of action modes when an Action Bar is not present.
+     * If overlay is enabled, the action mode UI will be allowed to cover existing window content.
+     */
+    public static final int FEATURE_ACTION_MODE_OVERLAY = 10;
+    /**
+     * Flag for requesting a decoration-free window that is dismissed by swiping from the left.
+     *
+     * @deprecated Swipe-to-dismiss isn't functional anymore.
+     */
+    @Deprecated
+    public static final int FEATURE_SWIPE_TO_DISMISS = 11;
+    /**
+     * Flag for requesting that window content changes should be animated using a
+     * TransitionManager.
+     *
+     * <p>The TransitionManager is set using
+     * {@link #setTransitionManager(android.transition.TransitionManager)}. If none is set,
+     * a default TransitionManager will be used.</p>
+     *
+     * @see #setContentView
+     */
+    public static final int FEATURE_CONTENT_TRANSITIONS = 12;
+
+    /**
+     * Enables Activities to run Activity Transitions either through sending or receiving
+     * ActivityOptions bundle created with
+     * {@link android.app.ActivityOptions#makeSceneTransitionAnimation(android.app.Activity,
+     * android.util.Pair[])} or {@link android.app.ActivityOptions#makeSceneTransitionAnimation(
+     * android.app.Activity, View, String)}.
+     */
+    public static final int FEATURE_ACTIVITY_TRANSITIONS = 13;
+
+    /**
+     * Max value used as a feature ID
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public static final int FEATURE_MAX = FEATURE_ACTIVITY_TRANSITIONS;
+
+    /**
+     * Flag for setting the progress bar's visibility to VISIBLE.
+     *
+     * @deprecated {@link #FEATURE_PROGRESS} and related methods are no longer
+     *             supported starting in API 21.
+     */
+    @Deprecated
+    public static final int PROGRESS_VISIBILITY_ON = -1;
+
+    /**
+     * Flag for setting the progress bar's visibility to GONE.
+     *
+     * @deprecated {@link #FEATURE_PROGRESS} and related methods are no longer
+     *             supported starting in API 21.
+     */
+    @Deprecated
+    public static final int PROGRESS_VISIBILITY_OFF = -2;
+
+    /**
+     * Flag for setting the progress bar's indeterminate mode on.
+     *
+     * @deprecated {@link #FEATURE_INDETERMINATE_PROGRESS} and related methods
+     *             are no longer supported starting in API 21.
+     */
+    @Deprecated
+    public static final int PROGRESS_INDETERMINATE_ON = -3;
+
+    /**
+     * Flag for setting the progress bar's indeterminate mode off.
+     *
+     * @deprecated {@link #FEATURE_INDETERMINATE_PROGRESS} and related methods
+     *             are no longer supported starting in API 21.
+     */
+    @Deprecated
+    public static final int PROGRESS_INDETERMINATE_OFF = -4;
+
+    /**
+     * Starting value for the (primary) progress.
+     *
+     * @deprecated {@link #FEATURE_PROGRESS} and related methods are no longer
+     *             supported starting in API 21.
+     */
+    @Deprecated
+    public static final int PROGRESS_START = 0;
+
+    /**
+     * Ending value for the (primary) progress.
+     *
+     * @deprecated {@link #FEATURE_PROGRESS} and related methods are no longer
+     *             supported starting in API 21.
+     */
+    @Deprecated
+    public static final int PROGRESS_END = 10000;
+
+    /**
+     * Lowest possible value for the secondary progress.
+     *
+     * @deprecated {@link #FEATURE_PROGRESS} and related methods are no longer
+     *             supported starting in API 21.
+     */
+    @Deprecated
+    public static final int PROGRESS_SECONDARY_START = 20000;
+
+    /**
+     * Highest possible value for the secondary progress.
+     *
+     * @deprecated {@link #FEATURE_PROGRESS} and related methods are no longer
+     *             supported starting in API 21.
+     */
+    @Deprecated
+    public static final int PROGRESS_SECONDARY_END = 30000;
+
+    /**
+     * The transitionName for the status bar background View when a custom background is used.
+     * @see android.view.Window#setStatusBarColor(int)
+     */
+    public static final String STATUS_BAR_BACKGROUND_TRANSITION_NAME = "android:status:background";
+
+    /**
+     * The transitionName for the navigation bar background View when a custom background is used.
+     * @see android.view.Window#setNavigationBarColor(int)
+     */
+    public static final String NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME =
+            "android:navigation:background";
+
+    /**
+     * The default features enabled.
+     * @deprecated use {@link #getDefaultFeatures(android.content.Context)} instead.
+     */
+    @Deprecated
+    @SuppressWarnings({"PointlessBitwiseExpression"})
+    protected static final int DEFAULT_FEATURES = (1 << FEATURE_OPTIONS_PANEL) |
+            (1 << FEATURE_CONTEXT_MENU);
+
+    /**
+     * The ID that the main layout in the XML layout file should have.
+     */
+    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
+
+    /**
+     * Flag for letting the theme drive the color of the window caption controls. Use with
+     * {@link #setDecorCaptionShade(int)}. This is the default value.
+     */
+    public static final int DECOR_CAPTION_SHADE_AUTO = 0;
+    /**
+     * Flag for setting light-color controls on the window caption. Use with
+     * {@link #setDecorCaptionShade(int)}.
+     */
+    public static final int DECOR_CAPTION_SHADE_LIGHT = 1;
+    /**
+     * Flag for setting dark-color controls on the window caption. Use with
+     * {@link #setDecorCaptionShade(int)}.
+     */
+    public static final int DECOR_CAPTION_SHADE_DARK = 2;
+
+    @UnsupportedAppUsage
+    @UiContext
+    private final Context mContext;
+
+    @UnsupportedAppUsage
+    private TypedArray mWindowStyle;
+    @UnsupportedAppUsage
+    private Callback mCallback;
+    private OnWindowDismissedCallback mOnWindowDismissedCallback;
+    private OnWindowSwipeDismissedCallback mOnWindowSwipeDismissedCallback;
+    private WindowControllerCallback mWindowControllerCallback;
+    private OnRestrictedCaptionAreaChangedListener mOnRestrictedCaptionAreaChangedListener;
+    private Rect mRestrictedCaptionAreaRect;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    private WindowManager mWindowManager;
+    @UnsupportedAppUsage
+    private IBinder mAppToken;
+    @UnsupportedAppUsage
+    private String mAppName;
+    @UnsupportedAppUsage
+    private boolean mHardwareAccelerated;
+    private Window mContainer;
+    private Window mActiveChild;
+    private boolean mIsActive = false;
+    private boolean mHasChildren = false;
+    private boolean mCloseOnTouchOutside = false;
+    private boolean mSetCloseOnTouchOutside = false;
+    private int mForcedWindowFlags = 0;
+
+    @UnsupportedAppUsage
+    private int mFeatures;
+    @UnsupportedAppUsage
+    private int mLocalFeatures;
+
+    private boolean mHaveWindowFormat = false;
+    private boolean mHaveDimAmount = false;
+    private int mDefaultWindowFormat = PixelFormat.OPAQUE;
+
+    private boolean mHasSoftInputMode = false;
+
+    @UnsupportedAppUsage
+    private boolean mDestroyed;
+
+    private boolean mOverlayWithDecorCaptionEnabled = true;
+    private boolean mCloseOnSwipeEnabled = false;
+
+    // The current window attributes.
+    @UnsupportedAppUsage
+    private final WindowManager.LayoutParams mWindowAttributes =
+        new WindowManager.LayoutParams();
+
+    /**
+     * API from a Window back to its caller.  This allows the client to
+     * intercept key dispatching, panels and menus, etc.
+     */
+    public interface Callback {
+        /**
+         * Called to process key events.  At the very least your
+         * implementation must call
+         * {@link android.view.Window#superDispatchKeyEvent} to do the
+         * standard key processing.
+         *
+         * @param event The key event.
+         *
+         * @return boolean Return true if this event was consumed.
+         */
+        public boolean dispatchKeyEvent(KeyEvent event);
+
+        /**
+         * Called to process a key shortcut event.
+         * At the very least your implementation must call
+         * {@link android.view.Window#superDispatchKeyShortcutEvent} to do the
+         * standard key shortcut processing.
+         *
+         * @param event The key shortcut event.
+         * @return True if this event was consumed.
+         */
+        public boolean dispatchKeyShortcutEvent(KeyEvent event);
+
+        /**
+         * Called to process touch screen events.  At the very least your
+         * implementation must call
+         * {@link android.view.Window#superDispatchTouchEvent} to do the
+         * standard touch screen processing.
+         *
+         * @param event The touch screen event.
+         *
+         * @return boolean Return true if this event was consumed.
+         */
+        public boolean dispatchTouchEvent(MotionEvent event);
+
+        /**
+         * Called to process trackball events.  At the very least your
+         * implementation must call
+         * {@link android.view.Window#superDispatchTrackballEvent} to do the
+         * standard trackball processing.
+         *
+         * @param event The trackball event.
+         *
+         * @return boolean Return true if this event was consumed.
+         */
+        public boolean dispatchTrackballEvent(MotionEvent event);
+
+        /**
+         * Called to process generic motion events.  At the very least your
+         * implementation must call
+         * {@link android.view.Window#superDispatchGenericMotionEvent} to do the
+         * standard processing.
+         *
+         * @param event The generic motion event.
+         *
+         * @return boolean Return true if this event was consumed.
+         */
+        public boolean dispatchGenericMotionEvent(MotionEvent event);
+
+        /**
+         * Called to process population of {@link AccessibilityEvent}s.
+         *
+         * @param event The event.
+         *
+         * @return boolean Return true if event population was completed.
+         */
+        public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event);
+
+        /**
+         * Instantiate the view to display in the panel for 'featureId'.
+         * You can return null, in which case the default content (typically
+         * a menu) will be created for you.
+         *
+         * @param featureId Which panel is being created.
+         *
+         * @return view The top-level view to place in the panel.
+         *
+         * @see #onPreparePanel
+         */
+        @Nullable
+        public View onCreatePanelView(int featureId);
+
+        /**
+         * Initialize the contents of the menu for panel 'featureId'.  This is
+         * called if onCreatePanelView() returns null, giving you a standard
+         * menu in which you can place your items.  It is only called once for
+         * the panel, the first time it is shown.
+         *
+         * <p>You can safely hold on to <var>menu</var> (and any items created
+         * from it), making modifications to it as desired, until the next
+         * time onCreatePanelMenu() is called for this feature.
+         *
+         * @param featureId The panel being created.
+         * @param menu The menu inside the panel.
+         *
+         * @return boolean You must return true for the panel to be displayed;
+         *         if you return false it will not be shown.
+         */
+        boolean onCreatePanelMenu(int featureId, @NonNull Menu menu);
+
+        /**
+         * Prepare a panel to be displayed.  This is called right before the
+         * panel window is shown, every time it is shown.
+         *
+         * @param featureId The panel that is being displayed.
+         * @param view The View that was returned by onCreatePanelView().
+         * @param menu If onCreatePanelView() returned null, this is the Menu
+         *             being displayed in the panel.
+         *
+         * @return boolean You must return true for the panel to be displayed;
+         *         if you return false it will not be shown.
+         *
+         * @see #onCreatePanelView
+         */
+        boolean onPreparePanel(int featureId, @Nullable View view, @NonNull Menu menu);
+
+        /**
+         * Called when a panel's menu is opened by the user. This may also be
+         * called when the menu is changing from one type to another (for
+         * example, from the icon menu to the expanded menu).
+         *
+         * @param featureId The panel that the menu is in.
+         * @param menu The menu that is opened.
+         * @return Return true to allow the menu to open, or false to prevent
+         *         the menu from opening.
+         */
+        boolean onMenuOpened(int featureId, @NonNull Menu menu);
+
+        /**
+         * Called when a panel's menu item has been selected by the user.
+         *
+         * @param featureId The panel that the menu is in.
+         * @param item The menu item that was selected.
+         *
+         * @return boolean Return true to finish processing of selection, or
+         *         false to perform the normal menu handling (calling its
+         *         Runnable or sending a Message to its target Handler).
+         */
+        boolean onMenuItemSelected(int featureId, @NonNull MenuItem item);
+
+        /**
+         * This is called whenever the current window attributes change.
+         *
+         */
+        public void onWindowAttributesChanged(WindowManager.LayoutParams attrs);
+
+        /**
+         * This hook is called whenever the content view of the screen changes
+         * (due to a call to
+         * {@link Window#setContentView(View, android.view.ViewGroup.LayoutParams)
+         * Window.setContentView} or
+         * {@link Window#addContentView(View, android.view.ViewGroup.LayoutParams)
+         * Window.addContentView}).
+         */
+        public void onContentChanged();
+
+        /**
+         * This hook is called whenever the window focus changes.  See
+         * {@link View#onWindowFocusChanged(boolean)
+         * View.onWindowFocusChangedNotLocked(boolean)} for more information.
+         *
+         * @param hasFocus Whether the window now has focus.
+         */
+        public void onWindowFocusChanged(boolean hasFocus);
+
+        /**
+         * Called when the window has been attached to the window manager.
+         * See {@link View#onAttachedToWindow() View.onAttachedToWindow()}
+         * for more information.
+         */
+        public void onAttachedToWindow();
+
+        /**
+         * Called when the window has been detached from the window manager.
+         * See {@link View#onDetachedFromWindow() View.onDetachedFromWindow()}
+         * for more information.
+         */
+        public void onDetachedFromWindow();
+
+        /**
+         * Called when a panel is being closed.  If another logical subsequent
+         * panel is being opened (and this panel is being closed to make room for the subsequent
+         * panel), this method will NOT be called.
+         *
+         * @param featureId The panel that is being displayed.
+         * @param menu If onCreatePanelView() returned null, this is the Menu
+         *            being displayed in the panel.
+         */
+        void onPanelClosed(int featureId, @NonNull Menu menu);
+
+        /**
+         * Called when the user signals the desire to start a search.
+         *
+         * @return true if search launched, false if activity refuses (blocks)
+         *
+         * @see android.app.Activity#onSearchRequested()
+         */
+        public boolean onSearchRequested();
+
+        /**
+         * Called when the user signals the desire to start a search.
+         *
+         * @param searchEvent A {@link SearchEvent} describing the signal to
+         *                   start a search.
+         * @return true if search launched, false if activity refuses (blocks)
+         */
+        public boolean onSearchRequested(SearchEvent searchEvent);
+
+        /**
+         * Called when an action mode is being started for this window. Gives the
+         * callback an opportunity to handle the action mode in its own unique and
+         * beautiful way. If this method returns null the system can choose a way
+         * to present the mode or choose not to start the mode at all. This is equivalent
+         * to {@link #onWindowStartingActionMode(android.view.ActionMode.Callback, int)}
+         * with type {@link ActionMode#TYPE_PRIMARY}.
+         *
+         * @param callback Callback to control the lifecycle of this action mode
+         * @return The ActionMode that was started, or null if the system should present it
+         */
+        @Nullable
+        public ActionMode onWindowStartingActionMode(ActionMode.Callback callback);
+
+        /**
+         * Called when an action mode is being started for this window. Gives the
+         * callback an opportunity to handle the action mode in its own unique and
+         * beautiful way. If this method returns null the system can choose a way
+         * to present the mode or choose not to start the mode at all.
+         *
+         * @param callback Callback to control the lifecycle of this action mode
+         * @param type One of {@link ActionMode#TYPE_PRIMARY} or {@link ActionMode#TYPE_FLOATING}.
+         * @return The ActionMode that was started, or null if the system should present it
+         */
+        @Nullable
+        public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int type);
+
+        /**
+         * Called when an action mode has been started. The appropriate mode callback
+         * method will have already been invoked.
+         *
+         * @param mode The new mode that has just been started.
+         */
+        public void onActionModeStarted(ActionMode mode);
+
+        /**
+         * Called when an action mode has been finished. The appropriate mode callback
+         * method will have already been invoked.
+         *
+         * @param mode The mode that was just finished.
+         */
+        public void onActionModeFinished(ActionMode mode);
+
+        /**
+         * Called when Keyboard Shortcuts are requested for the current window.
+         *
+         * @param data The data list to populate with shortcuts.
+         * @param menu The current menu, which may be null.
+         * @param deviceId The id for the connected device the shortcuts should be provided for.
+         */
+        default public void onProvideKeyboardShortcuts(
+                List<KeyboardShortcutGroup> data, @Nullable Menu menu, int deviceId) { };
+
+        /**
+         * Called when pointer capture is enabled or disabled for the current window.
+         *
+         * @param hasCapture True if the window has pointer capture.
+         */
+        default public void onPointerCaptureChanged(boolean hasCapture) { };
+    }
+
+    /** @hide */
+    public interface OnWindowDismissedCallback {
+        /**
+         * Called when a window is dismissed. This informs the callback that the
+         * window is gone, and it should finish itself.
+         * @param finishTask True if the task should also be finished.
+         * @param suppressWindowTransition True if the resulting exit and enter window transition
+         * animations should be suppressed.
+         */
+        void onWindowDismissed(boolean finishTask, boolean suppressWindowTransition);
+    }
+
+    /** @hide */
+    public interface OnWindowSwipeDismissedCallback {
+        /**
+         * Called when a window is swipe dismissed. This informs the callback that the
+         * window is gone, and it should finish itself.
+         * @param finishTask True if the task should also be finished.
+         * @param suppressWindowTransition True if the resulting exit and enter window transition
+         * animations should be suppressed.
+         */
+        void onWindowSwipeDismissed();
+    }
+
+    /** @hide */
+    public interface WindowControllerCallback {
+        /**
+         * Moves the activity between {@link WindowConfiguration#WINDOWING_MODE_FREEFORM} windowing
+         * mode and {@link WindowConfiguration#WINDOWING_MODE_FULLSCREEN}.
+         */
+        void toggleFreeformWindowingMode();
+
+        /**
+         * Puts the activity in picture-in-picture mode if the activity supports.
+         * @see android.R.attr#supportsPictureInPicture
+         */
+        void enterPictureInPictureModeIfPossible();
+
+        /** Returns whether the window belongs to the task root. */
+        boolean isTaskRoot();
+
+        /**
+         * Update the status bar color to a forced one.
+         */
+        void updateStatusBarColor(int color);
+
+        /**
+         * Update the navigation bar color to a forced one.
+         */
+        void updateNavigationBarColor(int color);
+    }
+
+    /**
+     * Callback for clients that want to be aware of where caption draws content.
+     */
+    public interface OnRestrictedCaptionAreaChangedListener {
+        /**
+         * Called when the area where caption draws content changes.
+         *
+         * @param rect The area where caption content is positioned, relative to the top view.
+         */
+        void onRestrictedCaptionAreaChanged(Rect rect);
+    }
+
+    /**
+     * Callback for clients that want frame timing information for each
+     * frame rendered by the Window.
+     */
+    public interface OnFrameMetricsAvailableListener {
+        /**
+         * Called when information is available for the previously rendered frame.
+         *
+         * Reports can be dropped if this callback takes too
+         * long to execute, as the report producer cannot wait for the consumer to
+         * complete.
+         *
+         * It is highly recommended that clients copy the passed in FrameMetrics
+         * via {@link FrameMetrics#FrameMetrics(FrameMetrics)} within this method and defer
+         * additional computation or storage to another thread to avoid unnecessarily
+         * dropping reports.
+         *
+         * @param window The {@link Window} on which the frame was displayed.
+         * @param frameMetrics the available metrics. This object is reused on every call
+         * and thus <strong>this reference is not valid outside the scope of this method</strong>.
+         * @param dropCountSinceLastInvocation the number of reports dropped since the last time
+         * this callback was invoked.
+         */
+        void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics,
+                int dropCountSinceLastInvocation);
+    }
+
+    /**
+     * Listener for applying window insets on the content of a window. Used only by the framework to
+     * fit content according to legacy SystemUI flags.
+     *
+     * @hide
+     */
+    public interface OnContentApplyWindowInsetsListener {
+
+        /**
+         * Called when the window needs to apply insets on the container of its content view which
+         * are set by calling {@link #setContentView}. The method should determine what insets to
+         * apply on the container of the root level content view and what should be dispatched to
+         * the content view's
+         * {@link View#setOnApplyWindowInsetsListener(OnApplyWindowInsetsListener)} through the view
+         * hierarchy.
+         *
+         * @param view The view for which to apply insets. Must not be directly modified.
+         * @param insets The root level insets that are about to be dispatched
+         * @return A pair, with the first element containing the insets to apply as margin to the
+         * root-level content views, and the second element determining what should be
+         * dispatched to the content view.
+         */
+        @NonNull
+        Pair<Insets, WindowInsets> onContentApplyWindowInsets(@NonNull View view,
+                @NonNull WindowInsets insets);
+    }
+
+
+    public Window(@UiContext Context context) {
+        mContext = context;
+        mFeatures = mLocalFeatures = getDefaultFeatures(context);
+    }
+
+    /**
+     * Return the Context this window policy is running in, for retrieving
+     * resources and other information.
+     *
+     * @return Context The Context that was supplied to the constructor.
+     */
+    @UiContext
+    public final Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * Return the {@link android.R.styleable#Window} attributes from this
+     * window's theme.
+     */
+    public final TypedArray getWindowStyle() {
+        synchronized (this) {
+            if (mWindowStyle == null) {
+                mWindowStyle = mContext.obtainStyledAttributes(
+                        com.android.internal.R.styleable.Window);
+            }
+            return mWindowStyle;
+        }
+    }
+
+    /**
+     * Set the container for this window.  If not set, the DecorWindow
+     * operates as a top-level window; otherwise, it negotiates with the
+     * container to display itself appropriately.
+     *
+     * @param container The desired containing Window.
+     */
+    public void setContainer(Window container) {
+        mContainer = container;
+        if (container != null) {
+            // Embedded screens never have a title.
+            mFeatures |= 1<<FEATURE_NO_TITLE;
+            mLocalFeatures |= 1<<FEATURE_NO_TITLE;
+            container.mHasChildren = true;
+        }
+    }
+
+    /**
+     * Return the container for this Window.
+     *
+     * @return Window The containing window, or null if this is a
+     *         top-level window.
+     */
+    public final Window getContainer() {
+        return mContainer;
+    }
+
+    public final boolean hasChildren() {
+        return mHasChildren;
+    }
+
+    /** @hide */
+    public final void destroy() {
+        mDestroyed = true;
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public final boolean isDestroyed() {
+        return mDestroyed;
+    }
+
+    /**
+     * Set the window manager for use by this Window to, for example,
+     * display panels.  This is <em>not</em> used for displaying the
+     * Window itself -- that must be done by the client.
+     *
+     * @param wm The window manager for adding new windows.
+     */
+    public void setWindowManager(WindowManager wm, IBinder appToken, String appName) {
+        setWindowManager(wm, appToken, appName, false);
+    }
+
+    /**
+     * Set the window manager for use by this Window to, for example,
+     * display panels.  This is <em>not</em> used for displaying the
+     * Window itself -- that must be done by the client.
+     *
+     * @param wm The window manager for adding new windows.
+     */
+    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
+            boolean hardwareAccelerated) {
+        mAppToken = appToken;
+        mAppName = appName;
+        mHardwareAccelerated = hardwareAccelerated;
+        if (wm == null) {
+            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
+        }
+        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
+    }
+
+    void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
+        CharSequence curTitle = wp.getTitle();
+        if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
+                wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
+            if (wp.token == null) {
+                View decor = peekDecorView();
+                if (decor != null) {
+                    wp.token = decor.getWindowToken();
+                }
+            }
+            if (curTitle == null || curTitle.length() == 0) {
+                final StringBuilder title = new StringBuilder(32);
+                if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA) {
+                    title.append("Media");
+                } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY) {
+                    title.append("MediaOvr");
+                } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
+                    title.append("Panel");
+                } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL) {
+                    title.append("SubPanel");
+                } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL) {
+                    title.append("AboveSubPanel");
+                } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG) {
+                    title.append("AtchDlg");
+                } else {
+                    title.append(wp.type);
+                }
+                if (mAppName != null) {
+                    title.append(":").append(mAppName);
+                }
+                wp.setTitle(title);
+            }
+        } else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&
+                wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
+            // We don't set the app token to this system window because the life cycles should be
+            // independent. If an app creates a system window and then the app goes to the stopped
+            // state, the system window should not be affected (can still show and receive input
+            // events).
+            if (curTitle == null || curTitle.length() == 0) {
+                final StringBuilder title = new StringBuilder(32);
+                title.append("Sys").append(wp.type);
+                if (mAppName != null) {
+                    title.append(":").append(mAppName);
+                }
+                wp.setTitle(title);
+            }
+        } else {
+            if (wp.token == null) {
+                wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
+            }
+            if ((curTitle == null || curTitle.length() == 0)
+                    && mAppName != null) {
+                wp.setTitle(mAppName);
+            }
+        }
+        if (wp.packageName == null) {
+            wp.packageName = mContext.getPackageName();
+        }
+        if (mHardwareAccelerated ||
+                (mWindowAttributes.flags & FLAG_HARDWARE_ACCELERATED) != 0) {
+            wp.flags |= FLAG_HARDWARE_ACCELERATED;
+        }
+    }
+
+    /**
+     * Return the window manager allowing this Window to display its own
+     * windows.
+     *
+     * @return WindowManager The ViewManager.
+     */
+    public WindowManager getWindowManager() {
+        return mWindowManager;
+    }
+
+    /**
+     * Set the Callback interface for this window, used to intercept key
+     * events and other dynamic operations in the window.
+     *
+     * @param callback The desired Callback interface.
+     */
+    public void setCallback(Callback callback) {
+        mCallback = callback;
+    }
+
+    /**
+     * Return the current Callback interface for this window.
+     */
+    public final Callback getCallback() {
+        return mCallback;
+    }
+
+    /**
+     * Set an observer to collect frame stats for each frame rendered in this window.
+     *
+     * Must be in hardware rendering mode.
+     */
+    public final void addOnFrameMetricsAvailableListener(
+            @NonNull OnFrameMetricsAvailableListener listener,
+            Handler handler) {
+        final View decorView = getDecorView();
+        if (decorView == null) {
+            throw new IllegalStateException("can't observe a Window without an attached view");
+        }
+
+        if (listener == null) {
+            throw new NullPointerException("listener cannot be null");
+        }
+
+        decorView.addFrameMetricsListener(this, listener, handler);
+    }
+
+    /**
+     * Remove observer and stop listening to frame stats for this window.
+     */
+    public final void removeOnFrameMetricsAvailableListener(OnFrameMetricsAvailableListener listener) {
+        final View decorView = getDecorView();
+        if (decorView != null) {
+            getDecorView().removeFrameMetricsListener(listener);
+        }
+    }
+
+    /** @hide */
+    public final void setOnWindowDismissedCallback(OnWindowDismissedCallback dcb) {
+        mOnWindowDismissedCallback = dcb;
+    }
+
+    /** @hide */
+    public final void dispatchOnWindowDismissed(
+            boolean finishTask, boolean suppressWindowTransition) {
+        if (mOnWindowDismissedCallback != null) {
+            mOnWindowDismissedCallback.onWindowDismissed(finishTask, suppressWindowTransition);
+        }
+    }
+
+    /** @hide */
+    public final void setOnWindowSwipeDismissedCallback(OnWindowSwipeDismissedCallback sdcb) {
+        mOnWindowSwipeDismissedCallback = sdcb;
+    }
+
+    /** @hide */
+    public final void dispatchOnWindowSwipeDismissed() {
+        if (mOnWindowSwipeDismissedCallback != null) {
+            mOnWindowSwipeDismissedCallback.onWindowSwipeDismissed();
+        }
+    }
+
+    /** @hide */
+    public final void setWindowControllerCallback(WindowControllerCallback wccb) {
+        mWindowControllerCallback = wccb;
+    }
+
+    /** @hide */
+    public final WindowControllerCallback getWindowControllerCallback() {
+        return mWindowControllerCallback;
+    }
+
+    /**
+     * Set a callback for changes of area where caption will draw its content.
+     *
+     * @param listener Callback that will be called when the area changes.
+     */
+    public final void setRestrictedCaptionAreaListener(OnRestrictedCaptionAreaChangedListener listener) {
+        mOnRestrictedCaptionAreaChangedListener = listener;
+        mRestrictedCaptionAreaRect = listener != null ? new Rect() : null;
+    }
+
+    /**
+     * Prevent non-system overlay windows from being drawn on top of this window.
+     *
+     * @param hide whether non-system overlay windows should be hidden.
+     */
+    @RequiresPermission(HIDE_OVERLAY_WINDOWS)
+    public final void setHideOverlayWindows(boolean hide) {
+        // This permission check is here to throw early and let the developer know that they need
+        // to hold HIDE_OVERLAY_WINDOWS for the flag to have any effect. The WM verifies that the
+        // owner of the window has the permission before applying the flag, but this is done
+        // asynchronously.
+        if (mContext.checkSelfPermission(HIDE_NON_SYSTEM_OVERLAY_WINDOWS) != PERMISSION_GRANTED
+                && mContext.checkSelfPermission(HIDE_OVERLAY_WINDOWS) != PERMISSION_GRANTED) {
+            throw new SecurityException(
+                    "Permission denial: setHideOverlayWindows: HIDE_OVERLAY_WINDOWS");
+        }
+        setPrivateFlags(hide ? SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS : 0,
+                SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+    }
+
+    /**
+     * Take ownership of this window's surface.  The window's view hierarchy
+     * will no longer draw into the surface, though it will otherwise continue
+     * to operate (such as for receiving input events).  The given SurfaceHolder
+     * callback will be used to tell you about state changes to the surface.
+     */
+    public abstract void takeSurface(SurfaceHolder.Callback2 callback);
+
+    /**
+     * Take ownership of this window's InputQueue.  The window will no
+     * longer read and dispatch input events from the queue; it is your
+     * responsibility to do so.
+     */
+    public abstract void takeInputQueue(InputQueue.Callback callback);
+
+    /**
+     * Return whether this window is being displayed with a floating style
+     * (based on the {@link android.R.attr#windowIsFloating} attribute in
+     * the style/theme).
+     *
+     * @return Returns true if the window is configured to be displayed floating
+     * on top of whatever is behind it.
+     */
+    public abstract boolean isFloating();
+
+    /**
+     * Set the width and height layout parameters of the window.  The default
+     * for both of these is MATCH_PARENT; you can change them to WRAP_CONTENT
+     * or an absolute value to make a window that is not full-screen.
+     *
+     * @param width The desired layout width of the window.
+     * @param height The desired layout height of the window.
+     *
+     * @see ViewGroup.LayoutParams#height
+     * @see ViewGroup.LayoutParams#width
+     */
+    public void setLayout(int width, int height) {
+        final WindowManager.LayoutParams attrs = getAttributes();
+        attrs.width = width;
+        attrs.height = height;
+        dispatchWindowAttributesChanged(attrs);
+    }
+
+    /**
+     * Set the gravity of the window, as per the Gravity constants.  This
+     * controls how the window manager is positioned in the overall window; it
+     * is only useful when using WRAP_CONTENT for the layout width or height.
+     *
+     * @param gravity The desired gravity constant.
+     *
+     * @see Gravity
+     * @see #setLayout
+     */
+    public void setGravity(int gravity)
+    {
+        final WindowManager.LayoutParams attrs = getAttributes();
+        attrs.gravity = gravity;
+        dispatchWindowAttributesChanged(attrs);
+    }
+
+    /**
+     * Set the type of the window, as per the WindowManager.LayoutParams
+     * types.
+     *
+     * @param type The new window type (see WindowManager.LayoutParams).
+     */
+    public void setType(int type) {
+        final WindowManager.LayoutParams attrs = getAttributes();
+        attrs.type = type;
+        dispatchWindowAttributesChanged(attrs);
+    }
+
+    /**
+     * Set the format of window, as per the PixelFormat types.  This overrides
+     * the default format that is selected by the Window based on its
+     * window decorations.
+     *
+     * @param format The new window format (see PixelFormat).  Use
+     *               PixelFormat.UNKNOWN to allow the Window to select
+     *               the format.
+     *
+     * @see PixelFormat
+     */
+    public void setFormat(int format) {
+        final WindowManager.LayoutParams attrs = getAttributes();
+        if (format != PixelFormat.UNKNOWN) {
+            attrs.format = format;
+            mHaveWindowFormat = true;
+        } else {
+            attrs.format = mDefaultWindowFormat;
+            mHaveWindowFormat = false;
+        }
+        dispatchWindowAttributesChanged(attrs);
+    }
+
+    /**
+     * Specify custom animations to use for the window, as per
+     * {@link WindowManager.LayoutParams#windowAnimations
+     * WindowManager.LayoutParams.windowAnimations}.  Providing anything besides
+     * 0 here will override the animations the window would
+     * normally retrieve from its theme.
+     */
+    public void setWindowAnimations(@StyleRes int resId) {
+        final WindowManager.LayoutParams attrs = getAttributes();
+        attrs.windowAnimations = resId;
+        dispatchWindowAttributesChanged(attrs);
+    }
+
+    /**
+     * Specify an explicit soft input mode to use for the window, as per
+     * {@link WindowManager.LayoutParams#softInputMode
+     * WindowManager.LayoutParams.softInputMode}.  Providing anything besides
+     * "unspecified" here will override the input mode the window would
+     * normally retrieve from its theme.
+     */
+    public void setSoftInputMode(int mode) {
+        final WindowManager.LayoutParams attrs = getAttributes();
+        if (mode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
+            attrs.softInputMode = mode;
+            mHasSoftInputMode = true;
+        } else {
+            mHasSoftInputMode = false;
+        }
+        dispatchWindowAttributesChanged(attrs);
+    }
+
+    /**
+     * Convenience function to set the flag bits as specified in flags, as
+     * per {@link #setFlags}.
+     * @param flags The flag bits to be set.
+     * @see #setFlags
+     * @see #clearFlags
+     */
+    public void addFlags(int flags) {
+        setFlags(flags, flags);
+    }
+
+    /**
+     * Add private flag bits.
+     *
+     * <p>Refer to the individual flags for the permissions needed.
+     *
+     * @param flags The flag bits to add.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public void addPrivateFlags(int flags) {
+        setPrivateFlags(flags, flags);
+    }
+
+    /**
+     * Add system flag bits.
+     *
+     * <p>Refer to the individual flags for the permissions needed.
+     *
+     * <p>Note: Only for updateable system components (aka. mainline modules)
+     *
+     * @param flags The flag bits to add.
+     *
+     * @hide
+     */
+    @SystemApi
+    public void addSystemFlags(@WindowManager.LayoutParams.SystemFlags int flags) {
+        addPrivateFlags(flags);
+    }
+
+    /**
+     * Convenience function to clear the flag bits as specified in flags, as
+     * per {@link #setFlags}.
+     * @param flags The flag bits to be cleared.
+     * @see #setFlags
+     * @see #addFlags
+     */
+    public void clearFlags(int flags) {
+        setFlags(0, flags);
+    }
+
+    /**
+     * Set the flags of the window, as per the
+     * {@link WindowManager.LayoutParams WindowManager.LayoutParams}
+     * flags.
+     *
+     * <p>Note that some flags must be set before the window decoration is
+     * created (by the first call to
+     * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)} or
+     * {@link #getDecorView()}:
+     * {@link WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN} and
+     * {@link WindowManager.LayoutParams#FLAG_LAYOUT_INSET_DECOR}.  These
+     * will be set for you based on the {@link android.R.attr#windowIsFloating}
+     * attribute.
+     *
+     * @param flags The new window flags (see WindowManager.LayoutParams).
+     * @param mask Which of the window flag bits to modify.
+     * @see #addFlags
+     * @see #clearFlags
+     */
+    public void setFlags(int flags, int mask) {
+        final WindowManager.LayoutParams attrs = getAttributes();
+        attrs.flags = (attrs.flags&~mask) | (flags&mask);
+        mForcedWindowFlags |= mask;
+        dispatchWindowAttributesChanged(attrs);
+    }
+
+    private void setPrivateFlags(int flags, int mask) {
+        final WindowManager.LayoutParams attrs = getAttributes();
+        attrs.privateFlags = (attrs.privateFlags & ~mask) | (flags & mask);
+        dispatchWindowAttributesChanged(attrs);
+    }
+
+    /**
+     * {@hide}
+     */
+    protected void dispatchWindowAttributesChanged(WindowManager.LayoutParams attrs) {
+        if (mCallback != null) {
+            mCallback.onWindowAttributesChanged(attrs);
+        }
+    }
+
+    /**
+     * <p>Sets the requested color mode of the window. The requested the color mode might
+     * override the window's pixel {@link WindowManager.LayoutParams#format format}.</p>
+     *
+     * <p>The requested color mode must be one of {@link ActivityInfo#COLOR_MODE_DEFAULT},
+     * {@link ActivityInfo#COLOR_MODE_WIDE_COLOR_GAMUT} or {@link ActivityInfo#COLOR_MODE_HDR}.</p>
+     *
+     * <p>The requested color mode is not guaranteed to be honored. Please refer to
+     * {@link #getColorMode()} for more information.</p>
+     *
+     * @see #getColorMode()
+     * @see Display#isWideColorGamut()
+     * @see Configuration#isScreenWideColorGamut()
+     */
+    public void setColorMode(@ActivityInfo.ColorMode int colorMode) {
+        final WindowManager.LayoutParams attrs = getAttributes();
+        attrs.setColorMode(colorMode);
+        dispatchWindowAttributesChanged(attrs);
+    }
+
+    /**
+     * If {@code isPreferred} is true, this method requests that the connected display does minimal
+     * post processing when this window is visible on the screen. Otherwise, it requests that the
+     * display switches back to standard image processing.
+     *
+     * <p> By default, the display does not do minimal post processing and if this is desired, this
+     * method should not be used. It should be used with {@code isPreferred=true} when low
+     * latency has a higher priority than image enhancement processing (e.g. for games or video
+     * conferencing). The display will automatically go back into standard image processing mode
+     * when no window requesting minimal posst processing is visible on screen anymore.
+     * {@code setPreferMinimalPostProcessing(false)} can be used if
+     * {@code setPreferMinimalPostProcessing(true)} was previously called for this window and
+     * minimal post processing is no longer required.
+     *
+     * <p>If the Display sink is connected via HDMI, the device will begin to send infoframes with
+     * Auto Low Latency Mode enabled and Game Content Type. This will switch the connected display
+     * to a minimal image processing mode (if available), which reduces latency, improving the user
+     * experience for gaming or video conferencing applications. For more information, see HDMI 2.1
+     * specification.
+     *
+     * <p>If the Display sink has an internal connection or uses some other protocol than HDMI,
+     * effects may be similar but implementation-defined.
+     *
+     * <p>The ability to switch to a mode with minimal post proessing may be disabled by a user
+     * setting in the system settings menu. In that case, this method does nothing.
+     *
+     * @see android.content.pm.ActivityInfo#FLAG_PREFER_MINIMAL_POST_PROCESSING
+     * @see android.view.Display#isMinimalPostProcessingSupported
+     * @see android.view.WindowManager.LayoutParams#preferMinimalPostProcessing
+     *
+     * @param isPreferred Indicates whether minimal post processing is preferred for this window
+     *                    ({@code isPreferred=true}) or not ({@code isPreferred=false}).
+     */
+    public void setPreferMinimalPostProcessing(boolean isPreferred) {
+        mWindowAttributes.preferMinimalPostProcessing = isPreferred;
+        dispatchWindowAttributesChanged(mWindowAttributes);
+    }
+
+    /**
+     * Returns the requested color mode of the window, one of
+     * {@link ActivityInfo#COLOR_MODE_DEFAULT}, {@link ActivityInfo#COLOR_MODE_WIDE_COLOR_GAMUT}
+     * or {@link ActivityInfo#COLOR_MODE_HDR}. If {@link ActivityInfo#COLOR_MODE_WIDE_COLOR_GAMUT}
+     * was requested it is possible the window will not be put in wide color gamut mode depending
+     * on device and display support for that mode. Use {@link #isWideColorGamut} to determine
+     * if the window is currently in wide color gamut mode.
+     *
+     * @see #setColorMode(int)
+     * @see Display#isWideColorGamut()
+     * @see Configuration#isScreenWideColorGamut()
+     */
+    @ActivityInfo.ColorMode
+    public int getColorMode() {
+        return getAttributes().getColorMode();
+    }
+
+    /**
+     * Returns true if this window's color mode is {@link ActivityInfo#COLOR_MODE_WIDE_COLOR_GAMUT},
+     * the display has a wide color gamut and this device supports wide color gamut rendering.
+     *
+     * @see Display#isWideColorGamut()
+     * @see Configuration#isScreenWideColorGamut()
+     */
+    public boolean isWideColorGamut() {
+        return getColorMode() == ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT
+                && getContext().getResources().getConfiguration().isScreenWideColorGamut();
+    }
+
+    /**
+     * Set the amount of dim behind the window when using
+     * {@link WindowManager.LayoutParams#FLAG_DIM_BEHIND}.  This overrides
+     * the default dim amount of that is selected by the Window based on
+     * its theme.
+     *
+     * @param amount The new dim amount, from 0 for no dim to 1 for full dim.
+     */
+    public void setDimAmount(float amount) {
+        final WindowManager.LayoutParams attrs = getAttributes();
+        attrs.dimAmount = amount;
+        mHaveDimAmount = true;
+        dispatchWindowAttributesChanged(attrs);
+    }
+
+    /**
+     * Sets whether the decor view should fit root-level content views for {@link WindowInsets}.
+     * <p>
+     * If set to {@code true}, the framework will inspect the now deprecated
+     * {@link View#SYSTEM_UI_LAYOUT_FLAGS} as well the
+     * {@link WindowManager.LayoutParams#SOFT_INPUT_ADJUST_RESIZE} flag and fits content according
+     * to these flags.
+     * </p>
+     * <p>
+     * If set to {@code false}, the framework will not fit the content view to the insets and will
+     * just pass through the {@link WindowInsets} to the content view.
+     * </p>
+     * @param decorFitsSystemWindows Whether the decor view should fit root-level content views for
+     *                               insets.
+     */
+    public void setDecorFitsSystemWindows(boolean decorFitsSystemWindows) {
+    }
+
+    /** @hide */
+    public boolean decorFitsSystemWindows() {
+        return false;
+    }
+
+    /**
+     * Specify custom window attributes.  <strong>PLEASE NOTE:</strong> the
+     * layout params you give here should generally be from values previously
+     * retrieved with {@link #getAttributes()}; you probably do not want to
+     * blindly create and apply your own, since this will blow away any values
+     * set by the framework that you are not interested in.
+     *
+     * @param a The new window attributes, which will completely override any
+     *          current values.
+     */
+    public void setAttributes(WindowManager.LayoutParams a) {
+        mWindowAttributes.copyFrom(a);
+        dispatchWindowAttributesChanged(mWindowAttributes);
+    }
+
+    /**
+     * Retrieve the current window attributes associated with this panel.
+     *
+     * @return WindowManager.LayoutParams Either the existing window
+     *         attributes object, or a freshly created one if there is none.
+     */
+    public final WindowManager.LayoutParams getAttributes() {
+        return mWindowAttributes;
+    }
+
+    /**
+     * Return the window flags that have been explicitly set by the client,
+     * so will not be modified by {@link #getDecorView}.
+     */
+    protected final int getForcedWindowFlags() {
+        return mForcedWindowFlags;
+    }
+
+    /**
+     * Has the app specified their own soft input mode?
+     */
+    protected final boolean hasSoftInputMode() {
+        return mHasSoftInputMode;
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public void setCloseOnTouchOutside(boolean close) {
+        mCloseOnTouchOutside = close;
+        mSetCloseOnTouchOutside = true;
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public void setCloseOnTouchOutsideIfNotSet(boolean close) {
+        if (!mSetCloseOnTouchOutside) {
+            mCloseOnTouchOutside = close;
+            mSetCloseOnTouchOutside = true;
+        }
+    }
+
+    /** @hide */
+    @SuppressWarnings("HiddenAbstractMethod")
+    @UnsupportedAppUsage
+    public abstract void alwaysReadCloseOnTouchAttr();
+
+    /** @hide */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
+        final boolean isOutside =
+                event.getAction() == MotionEvent.ACTION_UP && isOutOfBounds(context, event)
+                || event.getAction() == MotionEvent.ACTION_OUTSIDE;
+        if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) {
+            return true;
+        }
+        return false;
+    }
+
+    /* Sets the Sustained Performance requirement for the calling window.
+     * @param enable disables or enables the mode.
+     */
+    public void setSustainedPerformanceMode(boolean enable) {
+        setPrivateFlags(enable
+                ? WindowManager.LayoutParams.PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE : 0,
+                WindowManager.LayoutParams.PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE);
+    }
+
+    private boolean isOutOfBounds(Context context, MotionEvent event) {
+        final int x = (int) event.getX();
+        final int y = (int) event.getY();
+        final int slop = ViewConfiguration.get(context).getScaledWindowTouchSlop();
+        final View decorView = getDecorView();
+        return (x < -slop) || (y < -slop)
+                || (x > (decorView.getWidth()+slop))
+                || (y > (decorView.getHeight()+slop));
+    }
+
+    /**
+     * Enable extended screen features.  This must be called before
+     * setContentView().  May be called as many times as desired as long as it
+     * is before setContentView().  If not called, no extended features
+     * will be available.  You can not turn off a feature once it is requested.
+     * You canot use other title features with {@link #FEATURE_CUSTOM_TITLE}.
+     *
+     * @param featureId The desired features, defined as constants by Window.
+     * @return The features that are now set.
+     */
+    public boolean requestFeature(int featureId) {
+        final int flag = 1<<featureId;
+        mFeatures |= flag;
+        mLocalFeatures |= mContainer != null ? (flag&~mContainer.mFeatures) : flag;
+        return (mFeatures&flag) != 0;
+    }
+
+    /**
+     * @hide Used internally to help resolve conflicting features.
+     */
+    protected void removeFeature(int featureId) {
+        final int flag = 1<<featureId;
+        mFeatures &= ~flag;
+        mLocalFeatures &= ~(mContainer != null ? (flag&~mContainer.mFeatures) : flag);
+    }
+
+    public final void makeActive() {
+        if (mContainer != null) {
+            if (mContainer.mActiveChild != null) {
+                mContainer.mActiveChild.mIsActive = false;
+            }
+            mContainer.mActiveChild = this;
+        }
+        mIsActive = true;
+        onActive();
+    }
+
+    public final boolean isActive()
+    {
+        return mIsActive;
+    }
+
+    /**
+     * Finds a view that was identified by the {@code android:id} XML attribute
+     * that was processed in {@link android.app.Activity#onCreate}.
+     * <p>
+     * This will implicitly call {@link #getDecorView} with all of the associated side-effects.
+     * <p>
+     * <strong>Note:</strong> In most cases -- depending on compiler support --
+     * the resulting view is automatically cast to the target class type. If
+     * the target class type is unconstrained, an explicit cast may be
+     * necessary.
+     *
+     * @param id the ID to search for
+     * @return a view with given ID if found, or {@code null} otherwise
+     * @see View#findViewById(int)
+     * @see Window#requireViewById(int)
+     */
+    @Nullable
+    public <T extends View> T findViewById(@IdRes int id) {
+        return getDecorView().findViewById(id);
+    }
+    /**
+     * Finds a view that was identified by the {@code android:id} XML attribute
+     * that was processed in {@link android.app.Activity#onCreate}, or throws an
+     * IllegalArgumentException if the ID is invalid, or there is no matching view in the hierarchy.
+     * <p>
+     * <strong>Note:</strong> In most cases -- depending on compiler support --
+     * the resulting view is automatically cast to the target class type. If
+     * the target class type is unconstrained, an explicit cast may be
+     * necessary.
+     *
+     * @param id the ID to search for
+     * @return a view with given ID
+     * @see View#requireViewById(int)
+     * @see Window#findViewById(int)
+     */
+    @NonNull
+    public final <T extends View> T requireViewById(@IdRes int id) {
+        T view = findViewById(id);
+        if (view == null) {
+            throw new IllegalArgumentException("ID does not reference a View inside this Window");
+        }
+        return view;
+    }
+
+    /**
+     * Convenience for
+     * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}
+     * to set the screen content from a layout resource.  The resource will be
+     * inflated, adding all top-level views to the screen.
+     *
+     * @param layoutResID Resource ID to be inflated.
+     * @see #setContentView(View, android.view.ViewGroup.LayoutParams)
+     */
+    public abstract void setContentView(@LayoutRes int layoutResID);
+
+    /**
+     * Convenience for
+     * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}
+     * set the screen content to an explicit view.  This view is placed
+     * directly into the screen's view hierarchy.  It can itself be a complex
+     * view hierarhcy.
+     *
+     * @param view The desired content to display.
+     * @see #setContentView(View, android.view.ViewGroup.LayoutParams)
+     */
+    public abstract void setContentView(View view);
+
+    /**
+     * Set the screen content to an explicit view.  This view is placed
+     * directly into the screen's view hierarchy.  It can itself be a complex
+     * view hierarchy.
+     *
+     * <p>Note that calling this function "locks in" various characteristics
+     * of the window that can not, from this point forward, be changed: the
+     * features that have been requested with {@link #requestFeature(int)},
+     * and certain window flags as described in {@link #setFlags(int, int)}.</p>
+     *
+     * <p>If {@link #FEATURE_CONTENT_TRANSITIONS} is set, the window's
+     * TransitionManager will be used to animate content from the current
+     * content View to view.</p>
+     *
+     * @param view The desired content to display.
+     * @param params Layout parameters for the view.
+     * @see #getTransitionManager()
+     * @see #setTransitionManager(android.transition.TransitionManager)
+     */
+    public abstract void setContentView(View view, ViewGroup.LayoutParams params);
+
+    /**
+     * Variation on
+     * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}
+     * to add an additional content view to the screen.  Added after any existing
+     * ones in the screen -- existing views are NOT removed.
+     *
+     * @param view The desired content to display.
+     * @param params Layout parameters for the view.
+     */
+    public abstract void addContentView(View view, ViewGroup.LayoutParams params);
+
+    /**
+     * Remove the view that was used as the screen content.
+     *
+     * @hide
+     */
+    @SuppressWarnings("HiddenAbstractMethod")
+    public abstract void clearContentView();
+
+    /**
+     * Return the view in this Window that currently has focus, or null if
+     * there are none.  Note that this does not look in any containing
+     * Window.
+     *
+     * @return View The current View with focus or null.
+     */
+    @Nullable
+    public abstract View getCurrentFocus();
+
+    /**
+     * Quick access to the {@link LayoutInflater} instance that this Window
+     * retrieved from its Context.
+     *
+     * @return LayoutInflater The shared LayoutInflater.
+     */
+    @NonNull
+    public abstract LayoutInflater getLayoutInflater();
+
+    public abstract void setTitle(CharSequence title);
+
+    @Deprecated
+    public abstract void setTitleColor(@ColorInt int textColor);
+
+    public abstract void openPanel(int featureId, KeyEvent event);
+
+    public abstract void closePanel(int featureId);
+
+    public abstract void togglePanel(int featureId, KeyEvent event);
+
+    public abstract void invalidatePanelMenu(int featureId);
+
+    public abstract boolean performPanelShortcut(int featureId,
+                                                 int keyCode,
+                                                 KeyEvent event,
+                                                 int flags);
+    public abstract boolean performPanelIdentifierAction(int featureId,
+                                                 int id,
+                                                 int flags);
+
+    public abstract void closeAllPanels();
+
+    public abstract boolean performContextMenuIdentifierAction(int id, int flags);
+
+    /**
+     * Should be called when the configuration is changed.
+     *
+     * @param newConfig The new configuration.
+     */
+    public abstract void onConfigurationChanged(Configuration newConfig);
+
+    /**
+     * Sets the window elevation.
+     * <p>
+     * Changes to this property take effect immediately and will cause the
+     * window surface to be recreated. This is an expensive operation and as a
+     * result, this property should not be animated.
+     *
+     * @param elevation The window elevation.
+     * @see View#setElevation(float)
+     * @see android.R.styleable#Window_windowElevation
+     */
+    public void setElevation(float elevation) {}
+
+    /**
+     * Gets the window elevation.
+     *
+     * @hide
+     */
+    public float getElevation() {
+        return 0.0f;
+    }
+
+    /**
+     * Sets whether window content should be clipped to the outline of the
+     * window background.
+     *
+     * @param clipToOutline Whether window content should be clipped to the
+     *                      outline of the window background.
+     * @see View#setClipToOutline(boolean)
+     * @see android.R.styleable#Window_windowClipToOutline
+     */
+    public void setClipToOutline(boolean clipToOutline) {}
+
+    /**
+     * Change the background of this window to a Drawable resource. Setting the
+     * background to null will make the window be opaque. To make the window
+     * transparent, you can use an empty drawable (for instance a ColorDrawable
+     * with the color 0 or the system drawable android:drawable/empty.)
+     *
+     * @param resId The resource identifier of a drawable resource which will
+     *              be installed as the new background.
+     */
+    public void setBackgroundDrawableResource(@DrawableRes int resId) {
+        setBackgroundDrawable(mContext.getDrawable(resId));
+    }
+
+    /**
+     * Change the background of this window to a custom Drawable. Setting the
+     * background to null will make the window be opaque. To make the window
+     * transparent, you can use an empty drawable (for instance a ColorDrawable
+     * with the color 0 or the system drawable android:drawable/empty.)
+     *
+     * @param drawable The new Drawable to use for this window's background.
+     */
+    public abstract void setBackgroundDrawable(Drawable drawable);
+
+    /**
+     * <p>
+     * Blurs the screen behind the window within the bounds of the window.
+     * </p><p>
+     * The density of the blur is set by the blur radius. The radius defines the size
+     * of the neighbouring area, from which pixels will be averaged to form the final
+     * color for each pixel. The operation approximates a Gaussian blur.
+     * A radius of 0 means no blur. The higher the radius, the denser the blur.
+     * </p><p>
+     * The window background drawable is drawn on top of the blurred region. The blur
+     * region bounds and rounded corners will mimic those of the background drawable.
+     * </p><p>
+     * For the blur region to be visible, the window has to be translucent
+     * (see {@link android.R.attr#windowIsTranslucent}) and floating
+     * (see {@link android.R.attr#windowIsFloating}).
+     * </p><p>
+     * Note the difference with {@link WindowManager.LayoutParams#setBlurBehindRadius},
+     * which blurs the whole screen behind the window. Background blur blurs the screen behind
+     * only within the bounds of the window.
+     * </p><p>
+     * Some devices might not support cross-window blur due to GPU limitations. It can also be
+     * disabled at runtime, e.g. during battery saving mode, when multimedia tunneling is used or
+     * when minimal post processing is requested. In such situations, no blur will be computed or
+     * drawn, resulting in a transparent window background. To avoid this, the app might want to
+     * change its theme to one that does not use blurs. To listen for cross-window blur
+     * enabled/disabled events, use {@link WindowManager#addCrossWindowBlurEnabledListener}.
+     * </p>
+     *
+     * @param blurRadius The blur radius to use for window background blur in pixels
+     *
+     * @see android.R.styleable#Window_windowBackgroundBlurRadius
+     * @see WindowManager.LayoutParams#setBlurBehindRadius
+     * @see WindowManager#addCrossWindowBlurEnabledListener
+     */
+    public void setBackgroundBlurRadius(int blurRadius) {}
+
+    /**
+     * Set the value for a drawable feature of this window, from a resource
+     * identifier.  You must have called requestFeature(featureId) before
+     * calling this function.
+     *
+     * @see android.content.res.Resources#getDrawable(int)
+     *
+     * @param featureId The desired drawable feature to change, defined as a
+     * constant by Window.
+     * @param resId Resource identifier of the desired image.
+     */
+    public abstract void setFeatureDrawableResource(int featureId, @DrawableRes int resId);
+
+    /**
+     * Set the value for a drawable feature of this window, from a URI. You
+     * must have called requestFeature(featureId) before calling this
+     * function.
+     *
+     * <p>The only URI currently supported is "content:", specifying an image
+     * in a content provider.
+     *
+     * @see android.widget.ImageView#setImageURI
+     *
+     * @param featureId The desired drawable feature to change. Features are
+     * constants defined by Window.
+     * @param uri The desired URI.
+     */
+    public abstract void setFeatureDrawableUri(int featureId, Uri uri);
+
+    /**
+     * Set an explicit Drawable value for feature of this window. You must
+     * have called requestFeature(featureId) before calling this function.
+     *
+     * @param featureId The desired drawable feature to change. Features are
+     *                  constants defined by Window.
+     * @param drawable A Drawable object to display.
+     */
+    public abstract void setFeatureDrawable(int featureId, Drawable drawable);
+
+    /**
+     * Set a custom alpha value for the given drawable feature, controlling how
+     * much the background is visible through it.
+     *
+     * @param featureId The desired drawable feature to change. Features are
+     *                  constants defined by Window.
+     * @param alpha The alpha amount, 0 is completely transparent and 255 is
+     *              completely opaque.
+     */
+    public abstract void setFeatureDrawableAlpha(int featureId, int alpha);
+
+    /**
+     * Set the integer value for a feature. The range of the value depends on
+     * the feature being set. For {@link #FEATURE_PROGRESS}, it should go from
+     * 0 to 10000. At 10000 the progress is complete and the indicator hidden.
+     *
+     * @param featureId The desired feature to change. Features are constants
+     *                  defined by Window.
+     * @param value The value for the feature. The interpretation of this
+     *              value is feature-specific.
+     */
+    public abstract void setFeatureInt(int featureId, int value);
+
+    /**
+     * Request that key events come to this activity. Use this if your
+     * activity has no views with focus, but the activity still wants
+     * a chance to process key events.
+     */
+    public abstract void takeKeyEvents(boolean get);
+
+    /**
+     * Used by custom windows, such as Dialog, to pass the key press event
+     * further down the view hierarchy. Application developers should
+     * not need to implement or call this.
+     *
+     */
+    public abstract boolean superDispatchKeyEvent(KeyEvent event);
+
+    /**
+     * Used by custom windows, such as Dialog, to pass the key shortcut press event
+     * further down the view hierarchy. Application developers should
+     * not need to implement or call this.
+     *
+     */
+    public abstract boolean superDispatchKeyShortcutEvent(KeyEvent event);
+
+    /**
+     * Used by custom windows, such as Dialog, to pass the touch screen event
+     * further down the view hierarchy. Application developers should
+     * not need to implement or call this.
+     *
+     */
+    public abstract boolean superDispatchTouchEvent(MotionEvent event);
+
+    /**
+     * Used by custom windows, such as Dialog, to pass the trackball event
+     * further down the view hierarchy. Application developers should
+     * not need to implement or call this.
+     *
+     */
+    public abstract boolean superDispatchTrackballEvent(MotionEvent event);
+
+    /**
+     * Used by custom windows, such as Dialog, to pass the generic motion event
+     * further down the view hierarchy. Application developers should
+     * not need to implement or call this.
+     *
+     */
+    public abstract boolean superDispatchGenericMotionEvent(MotionEvent event);
+
+    /**
+     * Retrieve the top-level window decor view (containing the standard
+     * window frame/decorations and the client's content inside of that), which
+     * can be added as a window to the window manager.
+     *
+     * <p><em>Note that calling this function for the first time "locks in"
+     * various window characteristics as described in
+     * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}.</em></p>
+     *
+     * @return Returns the top-level window decor view.
+     */
+    public abstract @NonNull View getDecorView();
+
+    /**
+     * @return the status bar background view or null.
+     * @hide
+     */
+    @TestApi
+    public @Nullable View getStatusBarBackgroundView() {
+        return null;
+    }
+
+    /**
+     * @return the navigation bar background view or null.
+     * @hide
+     */
+    @TestApi
+    public @Nullable View getNavigationBarBackgroundView() {
+        return null;
+    }
+
+    /**
+     * Retrieve the current decor view, but only if it has already been created;
+     * otherwise returns null.
+     *
+     * @return Returns the top-level window decor or null.
+     * @see #getDecorView
+     */
+    public abstract View peekDecorView();
+
+    public abstract Bundle saveHierarchyState();
+
+    public abstract void restoreHierarchyState(Bundle savedInstanceState);
+
+    protected abstract void onActive();
+
+    /**
+     * Return the feature bits that are enabled.  This is the set of features
+     * that were given to requestFeature(), and are being handled by this
+     * Window itself or its container.  That is, it is the set of
+     * requested features that you can actually use.
+     *
+     * <p>To do: add a public version of this API that allows you to check for
+     * features by their feature ID.
+     *
+     * @return int The feature bits.
+     */
+    protected final int getFeatures()
+    {
+        return mFeatures;
+    }
+
+    /**
+     * Return the feature bits set by default on a window.
+     * @param context The context used to access resources
+     */
+    public static int getDefaultFeatures(Context context) {
+        int features = 0;
+
+        final Resources res = context.getResources();
+        if (res.getBoolean(com.android.internal.R.bool.config_defaultWindowFeatureOptionsPanel)) {
+            features |= 1 << FEATURE_OPTIONS_PANEL;
+        }
+
+        if (res.getBoolean(com.android.internal.R.bool.config_defaultWindowFeatureContextMenu)) {
+            features |= 1 << FEATURE_CONTEXT_MENU;
+        }
+
+        return features;
+    }
+
+    /**
+     * Query for the availability of a certain feature.
+     *
+     * @param feature The feature ID to check
+     * @return true if the feature is enabled, false otherwise.
+     */
+    public boolean hasFeature(int feature) {
+        return (getFeatures() & (1 << feature)) != 0;
+    }
+
+    /**
+     * Return the feature bits that are being implemented by this Window.
+     * This is the set of features that were given to requestFeature(), and are
+     * being handled by only this Window itself, not by its containers.
+     *
+     * @return int The feature bits.
+     */
+    protected final int getLocalFeatures()
+    {
+        return mLocalFeatures;
+    }
+
+    /**
+     * Set the default format of window, as per the PixelFormat types.  This
+     * is the format that will be used unless the client specifies in explicit
+     * format with setFormat();
+     *
+     * @param format The new window format (see PixelFormat).
+     *
+     * @see #setFormat
+     * @see PixelFormat
+     */
+    protected void setDefaultWindowFormat(int format) {
+        mDefaultWindowFormat = format;
+        if (!mHaveWindowFormat) {
+            final WindowManager.LayoutParams attrs = getAttributes();
+            attrs.format = format;
+            dispatchWindowAttributesChanged(attrs);
+        }
+    }
+
+    /** @hide */
+    protected boolean haveDimAmount() {
+        return mHaveDimAmount;
+    }
+
+    public abstract void setChildDrawable(int featureId, Drawable drawable);
+
+    public abstract void setChildInt(int featureId, int value);
+
+    /**
+     * Is a keypress one of the defined shortcut keys for this window.
+     * @param keyCode the key code from {@link android.view.KeyEvent} to check.
+     * @param event the {@link android.view.KeyEvent} to use to help check.
+     */
+    public abstract boolean isShortcutKey(int keyCode, KeyEvent event);
+
+    /**
+     * @see android.app.Activity#setVolumeControlStream(int)
+     */
+    public abstract void setVolumeControlStream(int streamType);
+
+    /**
+     * @see android.app.Activity#getVolumeControlStream()
+     */
+    public abstract int getVolumeControlStream();
+
+    /**
+     * Sets a {@link MediaController} to send media keys and volume changes to.
+     * If set, this should be preferred for all media keys and volume requests
+     * sent to this window.
+     *
+     * @param controller The controller for the session which should receive
+     *            media keys and volume changes.
+     * @see android.app.Activity#setMediaController(android.media.session.MediaController)
+     */
+    public void setMediaController(MediaController controller) {
+    }
+
+    /**
+     * Gets the {@link MediaController} that was previously set.
+     *
+     * @return The controller which should receive events.
+     * @see #setMediaController(android.media.session.MediaController)
+     * @see android.app.Activity#getMediaController()
+     */
+    public MediaController getMediaController() {
+        return null;
+    }
+
+    /**
+     * Set extra options that will influence the UI for this window.
+     * @param uiOptions Flags specifying extra options for this window.
+     */
+    public void setUiOptions(int uiOptions) { }
+
+    /**
+     * Set extra options that will influence the UI for this window.
+     * Only the bits filtered by mask will be modified.
+     * @param uiOptions Flags specifying extra options for this window.
+     * @param mask Flags specifying which options should be modified. Others will remain unchanged.
+     */
+    public void setUiOptions(int uiOptions, int mask) { }
+
+    /**
+     * Set the primary icon for this window.
+     *
+     * @param resId resource ID of a drawable to set
+     */
+    public void setIcon(@DrawableRes int resId) { }
+
+    /**
+     * Set the default icon for this window.
+     * This will be overridden by any other icon set operation which could come from the
+     * theme or another explicit set.
+     *
+     * @hide
+     */
+    public void setDefaultIcon(@DrawableRes int resId) { }
+
+    /**
+     * Set the logo for this window. A logo is often shown in place of an
+     * {@link #setIcon(int) icon} but is generally wider and communicates window title information
+     * as well.
+     *
+     * @param resId resource ID of a drawable to set
+     */
+    public void setLogo(@DrawableRes int resId) { }
+
+    /**
+     * Set the default logo for this window.
+     * This will be overridden by any other logo set operation which could come from the
+     * theme or another explicit set.
+     *
+     * @hide
+     */
+    public void setDefaultLogo(@DrawableRes int resId) { }
+
+    /**
+     * Set focus locally. The window should have the
+     * {@link WindowManager.LayoutParams#FLAG_LOCAL_FOCUS_MODE} flag set already.
+     * @param hasFocus Whether this window has focus or not.
+     * @param inTouchMode Whether this window is in touch mode or not.
+     */
+    public void setLocalFocus(boolean hasFocus, boolean inTouchMode) { }
+
+    /**
+     * Inject an event to window locally.
+     * @param event A key or touch event to inject to this window.
+     */
+    public void injectInputEvent(InputEvent event) { }
+
+    /**
+     * Retrieve the {@link TransitionManager} responsible for  for default transitions
+     * in this window. Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+     *
+     * <p>This method will return non-null after content has been initialized (e.g. by using
+     * {@link #setContentView}) if {@link #FEATURE_CONTENT_TRANSITIONS} has been granted.</p>
+     *
+     * @return This window's content TransitionManager or null if none is set.
+     * @attr ref android.R.styleable#Window_windowContentTransitionManager
+     */
+    public TransitionManager getTransitionManager() {
+        return null;
+    }
+
+    /**
+     * Set the {@link TransitionManager} to use for default transitions in this window.
+     * Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+     *
+     * @param tm The TransitionManager to use for scene changes.
+     * @attr ref android.R.styleable#Window_windowContentTransitionManager
+     */
+    public void setTransitionManager(TransitionManager tm) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Retrieve the {@link Scene} representing this window's current content.
+     * Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+     *
+     * <p>This method will return null if the current content is not represented by a Scene.</p>
+     *
+     * @return Current Scene being shown or null
+     */
+    public Scene getContentScene() {
+        return null;
+    }
+
+    /**
+     * Sets the Transition that will be used to move Views into the initial scene. The entering
+     * Views will be those that are regular Views or ViewGroups that have
+     * {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+     * {@link android.transition.Visibility} as entering is governed by changing visibility from
+     * {@link View#INVISIBLE} to {@link View#VISIBLE}. If <code>transition</code> is null,
+     * entering Views will remain unaffected.
+     *
+     * @param transition The Transition to use to move Views into the initial Scene.
+     * @attr ref android.R.styleable#Window_windowEnterTransition
+     */
+    public void setEnterTransition(Transition transition) {}
+
+    /**
+     * Sets the Transition that will be used to move Views out of the scene when the Window is
+     * preparing to close, for example after a call to
+     * {@link android.app.Activity#finishAfterTransition()}. The exiting
+     * Views will be those that are regular Views or ViewGroups that have
+     * {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+     * {@link android.transition.Visibility} as entering is governed by changing visibility from
+     * {@link View#VISIBLE} to {@link View#INVISIBLE}. If <code>transition</code> is null,
+     * entering Views will remain unaffected. If nothing is set, the default will be to
+     * use the same value as set in {@link #setEnterTransition(android.transition.Transition)}.
+     *
+     * @param transition The Transition to use to move Views out of the Scene when the Window
+     *                   is preparing to close.
+     * @attr ref android.R.styleable#Window_windowReturnTransition
+     */
+    public void setReturnTransition(Transition transition) {}
+
+    /**
+     * Sets the Transition that will be used to move Views out of the scene when starting a
+     * new Activity. The exiting Views will be those that are regular Views or ViewGroups that
+     * have {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+     * {@link android.transition.Visibility} as exiting is governed by changing visibility
+     * from {@link View#VISIBLE} to {@link View#INVISIBLE}. If transition is null, the views will
+     * remain unaffected. Requires {@link #FEATURE_ACTIVITY_TRANSITIONS}.
+     *
+     * @param transition The Transition to use to move Views out of the scene when calling a
+     *                   new Activity.
+     * @attr ref android.R.styleable#Window_windowExitTransition
+     */
+    public void setExitTransition(Transition transition) {}
+
+    /**
+     * Sets the Transition that will be used to move Views in to the scene when returning from
+     * a previously-started Activity. The entering Views will be those that are regular Views
+     * or ViewGroups that have {@link ViewGroup#isTransitionGroup} return true. Typical Transitions
+     * will extend {@link android.transition.Visibility} as exiting is governed by changing
+     * visibility from {@link View#VISIBLE} to {@link View#INVISIBLE}. If transition is null,
+     * the views will remain unaffected. If nothing is set, the default will be to use the same
+     * transition as {@link #setExitTransition(android.transition.Transition)}.
+     * Requires {@link #FEATURE_ACTIVITY_TRANSITIONS}.
+     *
+     * @param transition The Transition to use to move Views into the scene when reentering from a
+     *                   previously-started Activity.
+     * @attr ref android.R.styleable#Window_windowReenterTransition
+     */
+    public void setReenterTransition(Transition transition) {}
+
+    /**
+     * Returns the transition used to move Views into the initial scene. The entering
+     * Views will be those that are regular Views or ViewGroups that have
+     * {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+     * {@link android.transition.Visibility} as entering is governed by changing visibility from
+     * {@link View#INVISIBLE} to {@link View#VISIBLE}. If <code>transition</code> is null,
+     * entering Views will remain unaffected.  Requires {@link #FEATURE_ACTIVITY_TRANSITIONS}.
+     *
+     * @return the Transition to use to move Views into the initial Scene.
+     * @attr ref android.R.styleable#Window_windowEnterTransition
+     */
+    public Transition getEnterTransition() { return null; }
+
+    /**
+     * Returns the Transition that will be used to move Views out of the scene when the Window is
+     * preparing to close, for example after a call to
+     * {@link android.app.Activity#finishAfterTransition()}. The exiting
+     * Views will be those that are regular Views or ViewGroups that have
+     * {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+     * {@link android.transition.Visibility} as entering is governed by changing visibility from
+     * {@link View#VISIBLE} to {@link View#INVISIBLE}.
+     *
+     * @return The Transition to use to move Views out of the Scene when the Window
+     *         is preparing to close.
+     * @attr ref android.R.styleable#Window_windowReturnTransition
+     */
+    public Transition getReturnTransition() { return null; }
+
+    /**
+     * Returns the Transition that will be used to move Views out of the scene when starting a
+     * new Activity. The exiting Views will be those that are regular Views or ViewGroups that
+     * have {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+     * {@link android.transition.Visibility} as exiting is governed by changing visibility
+     * from {@link View#VISIBLE} to {@link View#INVISIBLE}. If transition is null, the views will
+     * remain unaffected. Requires {@link #FEATURE_ACTIVITY_TRANSITIONS}.
+     *
+     * @return the Transition to use to move Views out of the scene when calling a
+     * new Activity.
+     * @attr ref android.R.styleable#Window_windowExitTransition
+     */
+    public Transition getExitTransition() { return null; }
+
+    /**
+     * Returns the Transition that will be used to move Views in to the scene when returning from
+     * a previously-started Activity. The entering Views will be those that are regular Views
+     * or ViewGroups that have {@link ViewGroup#isTransitionGroup} return true. Typical Transitions
+     * will extend {@link android.transition.Visibility} as exiting is governed by changing
+     * visibility from {@link View#VISIBLE} to {@link View#INVISIBLE}.
+     * Requires {@link #FEATURE_ACTIVITY_TRANSITIONS}.
+     *
+     * @return The Transition to use to move Views into the scene when reentering from a
+     *         previously-started Activity.
+     * @attr ref android.R.styleable#Window_windowReenterTransition
+     */
+    public Transition getReenterTransition() { return null; }
+
+    /**
+     * Sets the Transition that will be used for shared elements transferred into the content
+     * Scene. Typical Transitions will affect size and location, such as
+     * {@link android.transition.ChangeBounds}. A null
+     * value will cause transferred shared elements to blink to the final position.
+     * Requires {@link #FEATURE_ACTIVITY_TRANSITIONS}.
+     *
+     * @param transition The Transition to use for shared elements transferred into the content
+     *                   Scene.
+     * @attr ref android.R.styleable#Window_windowSharedElementEnterTransition
+     */
+    public void setSharedElementEnterTransition(Transition transition) {}
+
+    /**
+     * Sets the Transition that will be used for shared elements transferred back to a
+     * calling Activity. Typical Transitions will affect size and location, such as
+     * {@link android.transition.ChangeBounds}. A null
+     * value will cause transferred shared elements to blink to the final position.
+     * If no value is set, the default will be to use the same value as
+     * {@link #setSharedElementEnterTransition(android.transition.Transition)}.
+     * Requires {@link #FEATURE_ACTIVITY_TRANSITIONS}.
+     *
+     * @param transition The Transition to use for shared elements transferred out of the content
+     *                   Scene.
+     * @attr ref android.R.styleable#Window_windowSharedElementReturnTransition
+     */
+    public void setSharedElementReturnTransition(Transition transition) {}
+
+    /**
+     * Returns the Transition that will be used for shared elements transferred into the content
+     * Scene. Requires {@link #FEATURE_ACTIVITY_TRANSITIONS}.
+     *
+     * @return Transition to use for sharend elements transferred into the content Scene.
+     * @attr ref android.R.styleable#Window_windowSharedElementEnterTransition
+     */
+    public Transition getSharedElementEnterTransition() { return null; }
+
+    /**
+     * Returns the Transition that will be used for shared elements transferred back to a
+     * calling Activity. Requires {@link #FEATURE_ACTIVITY_TRANSITIONS}.
+     *
+     * @return Transition to use for sharend elements transferred into the content Scene.
+     * @attr ref android.R.styleable#Window_windowSharedElementReturnTransition
+     */
+    public Transition getSharedElementReturnTransition() { return null; }
+
+    /**
+     * Sets the Transition that will be used for shared elements after starting a new Activity
+     * before the shared elements are transferred to the called Activity. If the shared elements
+     * must animate during the exit transition, this Transition should be used. Upon completion,
+     * the shared elements may be transferred to the started Activity.
+     * Requires {@link #FEATURE_ACTIVITY_TRANSITIONS}.
+     *
+     * @param transition The Transition to use for shared elements in the launching Window
+     *                   prior to transferring to the launched Activity's Window.
+     * @attr ref android.R.styleable#Window_windowSharedElementExitTransition
+     */
+    public void setSharedElementExitTransition(Transition transition) {}
+
+    /**
+     * Sets the Transition that will be used for shared elements reentering from a started
+     * Activity after it has returned the shared element to it start location. If no value
+     * is set, this will default to
+     * {@link #setSharedElementExitTransition(android.transition.Transition)}.
+     * Requires {@link #FEATURE_ACTIVITY_TRANSITIONS}.
+     *
+     * @param transition The Transition to use for shared elements in the launching Window
+     *                   after the shared element has returned to the Window.
+     * @attr ref android.R.styleable#Window_windowSharedElementReenterTransition
+     */
+    public void setSharedElementReenterTransition(Transition transition) {}
+
+    /**
+     * Returns the Transition to use for shared elements in the launching Window prior
+     * to transferring to the launched Activity's Window.
+     * Requires {@link #FEATURE_ACTIVITY_TRANSITIONS}.
+     *
+     * @return the Transition to use for shared elements in the launching Window prior
+     * to transferring to the launched Activity's Window.
+     * @attr ref android.R.styleable#Window_windowSharedElementExitTransition
+     */
+    public Transition getSharedElementExitTransition() { return null; }
+
+    /**
+     * Returns the Transition that will be used for shared elements reentering from a started
+     * Activity after it has returned the shared element to it start location.
+     * Requires {@link #FEATURE_ACTIVITY_TRANSITIONS}.
+     *
+     * @return the Transition that will be used for shared elements reentering from a started
+     * Activity after it has returned the shared element to it start location.
+     * @attr ref android.R.styleable#Window_windowSharedElementReenterTransition
+     */
+    public Transition getSharedElementReenterTransition() { return null; }
+
+    /**
+     * Controls how the transition set in
+     * {@link #setEnterTransition(android.transition.Transition)} overlaps with the exit
+     * transition of the calling Activity. When true, the transition will start as soon as possible.
+     * When false, the transition will wait until the remote exiting transition completes before
+     * starting. The default value is true.
+     *
+     * @param allow true to start the enter transition when possible or false to
+     *              wait until the exiting transition completes.
+     * @attr ref android.R.styleable#Window_windowAllowEnterTransitionOverlap
+     */
+    public void setAllowEnterTransitionOverlap(boolean allow) {}
+
+    /**
+     * Returns how the transition set in
+     * {@link #setEnterTransition(android.transition.Transition)} overlaps with the exit
+     * transition of the calling Activity. When true, the transition will start as soon as possible.
+     * When false, the transition will wait until the remote exiting transition completes before
+     * starting. The default value is true.
+     *
+     * @return true when the enter transition should start as soon as possible or false to
+     * when it should wait until the exiting transition completes.
+     * @attr ref android.R.styleable#Window_windowAllowEnterTransitionOverlap
+     */
+    public boolean getAllowEnterTransitionOverlap() { return true; }
+
+    /**
+     * Controls how the transition set in
+     * {@link #setExitTransition(android.transition.Transition)} overlaps with the exit
+     * transition of the called Activity when reentering after if finishes. When true,
+     * the transition will start as soon as possible. When false, the transition will wait
+     * until the called Activity's exiting transition completes before starting.
+     * The default value is true.
+     *
+     * @param allow true to start the transition when possible or false to wait until the
+     *              called Activity's exiting transition completes.
+     * @attr ref android.R.styleable#Window_windowAllowReturnTransitionOverlap
+     */
+    public void setAllowReturnTransitionOverlap(boolean allow) {}
+
+    /**
+     * Returns how the transition set in
+     * {@link #setExitTransition(android.transition.Transition)} overlaps with the exit
+     * transition of the called Activity when reentering after if finishes. When true,
+     * the transition will start as soon as possible. When false, the transition will wait
+     * until the called Activity's exiting transition completes before starting.
+     * The default value is true.
+     *
+     * @return true when the transition should start when possible or false when it should wait
+     * until the called Activity's exiting transition completes.
+     * @attr ref android.R.styleable#Window_windowAllowReturnTransitionOverlap
+     */
+    public boolean getAllowReturnTransitionOverlap() { return true; }
+
+    /**
+     * Returns the duration, in milliseconds, of the window background fade
+     * when transitioning into or away from an Activity when called with an Activity Transition.
+     * <p>When executing the enter transition, the background starts transparent
+     * and fades in. This requires {@link #FEATURE_ACTIVITY_TRANSITIONS}. The default is
+     * 300 milliseconds.</p>
+     *
+     * @return The duration of the window background fade to opaque during enter transition.
+     * @see #getEnterTransition()
+     * @attr ref android.R.styleable#Window_windowTransitionBackgroundFadeDuration
+     */
+    public long getTransitionBackgroundFadeDuration() { return 0; }
+
+    /**
+     * Sets the duration, in milliseconds, of the window background fade
+     * when transitioning into or away from an Activity when called with an Activity Transition.
+     * <p>When executing the enter transition, the background starts transparent
+     * and fades in. This requires {@link #FEATURE_ACTIVITY_TRANSITIONS}. The default is
+     * 300 milliseconds.</p>
+     *
+     * @param fadeDurationMillis The duration of the window background fade to or from opaque
+     *                           during enter transition.
+     * @see #setEnterTransition(android.transition.Transition)
+     * @attr ref android.R.styleable#Window_windowTransitionBackgroundFadeDuration
+     */
+    public void setTransitionBackgroundFadeDuration(long fadeDurationMillis) { }
+
+    /**
+     * Returns <code>true</code> when shared elements should use an Overlay during
+     * shared element transitions or <code>false</code> when they should animate as
+     * part of the normal View hierarchy. The default value is true.
+     *
+     * @return <code>true</code> when shared elements should use an Overlay during
+     * shared element transitions or <code>false</code> when they should animate as
+     * part of the normal View hierarchy.
+     * @attr ref android.R.styleable#Window_windowSharedElementsUseOverlay
+     */
+    public boolean getSharedElementsUseOverlay() { return true; }
+
+    /**
+     * Sets whether or not shared elements should use an Overlay during shared element transitions.
+     * The default value is true.
+     *
+     * @param sharedElementsUseOverlay <code>true</code> indicates that shared elements should
+     *                                 be transitioned with an Overlay or <code>false</code>
+     *                                 to transition within the normal View hierarchy.
+     * @attr ref android.R.styleable#Window_windowSharedElementsUseOverlay
+     */
+    public void setSharedElementsUseOverlay(boolean sharedElementsUseOverlay) { }
+
+    /**
+     * @return the color of the status bar.
+     */
+    @ColorInt
+    public abstract int getStatusBarColor();
+
+    /**
+     * Sets the color of the status bar to {@code color}.
+     *
+     * For this to take effect,
+     * the window must be drawing the system bar backgrounds with
+     * {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} and
+     * {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_STATUS} must not be set.
+     *
+     * If {@code color} is not opaque, consider setting
+     * {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and
+     * {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}.
+     * <p>
+     * The transitionName for the view background will be "android:status:background".
+     * </p>
+     */
+    public abstract void setStatusBarColor(@ColorInt int color);
+
+    /**
+     * @return the color of the navigation bar.
+     */
+    @ColorInt
+    public abstract int getNavigationBarColor();
+
+    /**
+     * Sets the color of the navigation bar to {@param color}.
+     *
+     * For this to take effect,
+     * the window must be drawing the system bar backgrounds with
+     * {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} and
+     * {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_NAVIGATION} must not be set.
+     *
+     * If {@param color} is not opaque, consider setting
+     * {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and
+     * {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION}.
+     * <p>
+     * The transitionName for the view background will be "android:navigation:background".
+     * </p>
+     * @attr ref android.R.styleable#Window_navigationBarColor
+     */
+    public abstract void setNavigationBarColor(@ColorInt int color);
+
+    /**
+     * Shows a thin line of the specified color between the navigation bar and the app
+     * content.
+     * <p>
+     * For this to take effect,
+     * the window must be drawing the system bar backgrounds with
+     * {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} and
+     * {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_NAVIGATION} must not be set.
+     *
+     * @param dividerColor The color of the thin line.
+     * @attr ref android.R.styleable#Window_navigationBarDividerColor
+     */
+    public void setNavigationBarDividerColor(@ColorInt int dividerColor) {
+    }
+
+    /**
+     * Retrieves the color of the navigation bar divider.
+     *
+     * @return The color of the navigation bar divider color.
+     * @see #setNavigationBarColor(int)
+     * @attr ref android.R.styleable#Window_navigationBarDividerColor
+     */
+    public @ColorInt int getNavigationBarDividerColor() {
+        return 0;
+    }
+
+    /**
+     * Sets whether the system should ensure that the status bar has enough
+     * contrast when a fully transparent background is requested.
+     *
+     * <p>If set to this value, the system will determine whether a scrim is necessary
+     * to ensure that the status bar has enough contrast with the contents of
+     * this app, and set an appropriate effective bar background color accordingly.
+     *
+     * <p>When the status bar color has a non-zero alpha value, the value of this
+     * property has no effect.
+     *
+     * @see android.R.attr#enforceStatusBarContrast
+     * @see #isStatusBarContrastEnforced
+     * @see #setStatusBarColor
+     */
+    public void setStatusBarContrastEnforced(boolean ensureContrast) {
+    }
+
+    /**
+     * Returns whether the system is ensuring that the status bar has enough contrast when a
+     * fully transparent background is requested.
+     *
+     * <p>When the status bar color has a non-zero alpha value, the value of this
+     * property has no effect.
+     *
+     * @return true, if the system is ensuring contrast, false otherwise.
+     * @see android.R.attr#enforceStatusBarContrast
+     * @see #setStatusBarContrastEnforced
+     * @see #setStatusBarColor
+     */
+    public boolean isStatusBarContrastEnforced() {
+        return false;
+    }
+
+    /**
+     * Sets whether the system should ensure that the navigation bar has enough
+     * contrast when a fully transparent background is requested.
+     *
+     * <p>If set to this value, the system will determine whether a scrim is necessary
+     * to ensure that the navigation bar has enough contrast with the contents of
+     * this app, and set an appropriate effective bar background color accordingly.
+     *
+     * <p>When the navigation bar color has a non-zero alpha value, the value of this
+     * property has no effect.
+     *
+     * @see android.R.attr#enforceNavigationBarContrast
+     * @see #isNavigationBarContrastEnforced
+     * @see #setNavigationBarColor
+     */
+    public void setNavigationBarContrastEnforced(boolean enforceContrast) {
+    }
+
+    /**
+     * Returns whether the system is ensuring that the navigation bar has enough contrast when a
+     * fully transparent background is requested.
+     *
+     * <p>When the navigation bar color has a non-zero alpha value, the value of this
+     * property has no effect.
+     *
+     * @return true, if the system is ensuring contrast, false otherwise.
+     * @see android.R.attr#enforceNavigationBarContrast
+     * @see #setNavigationBarContrastEnforced
+     * @see #setNavigationBarColor
+     */
+    public boolean isNavigationBarContrastEnforced() {
+        return false;
+    }
+
+    /**
+     * Sets a list of areas within this window's coordinate space where the system should not
+     * intercept touch or other pointing device gestures.
+     *
+     * <p>This method should be used by apps that make use of
+     * {@link #takeSurface(SurfaceHolder.Callback2)} and do not have a view hierarchy available.
+     * Apps that do have a view hierarchy should use
+     * {@link View#setSystemGestureExclusionRects(List)} instead. This method does not modify or
+     * replace the gesture exclusion rects populated by individual views in this window's view
+     * hierarchy using {@link View#setSystemGestureExclusionRects(List)}.</p>
+     *
+     * <p>Use this to tell the system which specific sub-areas of a view need to receive gesture
+     * input in order to function correctly in the presence of global system gestures that may
+     * conflict. For example, if the system wishes to capture swipe-in-from-screen-edge gestures
+     * to provide system-level navigation functionality, a view such as a navigation drawer
+     * container can mark the left (or starting) edge of itself as requiring gesture capture
+     * priority using this API. The system may then choose to relax its own gesture recognition
+     * to allow the app to consume the user's gesture. It is not necessary for an app to register
+     * exclusion rects for broadly spanning regions such as the entirety of a
+     * <code>ScrollView</code> or for simple press and release click targets such as
+     * <code>Button</code>. Mark an exclusion rect when interacting with a view requires
+     * a precision touch gesture in a small area in either the X or Y dimension, such as
+     * an edge swipe or dragging a <code>SeekBar</code> thumb.</p>
+     *
+     * <p>Do not modify the provided list after this method is called.</p>
+     *
+     * @param rects A list of precision gesture regions that this window needs to function correctly
+     */
+    @SuppressWarnings("unused")
+    public void setSystemGestureExclusionRects(@NonNull List<Rect> rects) {
+        throw new UnsupportedOperationException("window does not support gesture exclusion rects");
+    }
+
+    /**
+     * Retrieve the list of areas within this window's coordinate space where the system should not
+     * intercept touch or other pointing device gestures. This is the list as set by
+     * {@link #setSystemGestureExclusionRects(List)} or an empty list if
+     * {@link #setSystemGestureExclusionRects(List)} has not been called. It does not include
+     * exclusion rects set by this window's view hierarchy.
+     *
+     * @return a list of system gesture exclusion rects specific to this window
+     */
+    @NonNull
+    public List<Rect> getSystemGestureExclusionRects() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * System request to begin scroll capture.
+     *
+     * @param listener to receive the response
+     * @hide
+     */
+    public void requestScrollCapture(IScrollCaptureResponseListener listener) {
+    }
+
+    /**
+     * Used to provide scroll capture support for an arbitrary window. This registeres the given
+     * callback with the root view of the window.
+     *
+     * @param callback the callback to add
+     */
+    public void registerScrollCaptureCallback(@NonNull ScrollCaptureCallback callback) {
+    }
+
+    /**
+     * Unregisters a {@link ScrollCaptureCallback} previously registered with this window.
+     *
+     * @param callback the callback to remove
+     */
+    public void unregisterScrollCaptureCallback(@NonNull ScrollCaptureCallback callback) {
+    }
+
+    /** @hide */
+    public void setTheme(int resId) {
+    }
+
+    /**
+     * Whether the caption should be displayed directly on the content rather than push the content
+     * down. This affects only freeform windows since they display the caption.
+     * @hide
+     */
+    public void setOverlayWithDecorCaptionEnabled(boolean enabled) {
+        mOverlayWithDecorCaptionEnabled = enabled;
+    }
+
+    /** @hide */
+    public boolean isOverlayWithDecorCaptionEnabled() {
+        return mOverlayWithDecorCaptionEnabled;
+    }
+
+    /** @hide */
+    public void notifyRestrictedCaptionAreaCallback(int left, int top, int right, int bottom) {
+        if (mOnRestrictedCaptionAreaChangedListener != null) {
+            mRestrictedCaptionAreaRect.set(left, top, right, bottom);
+            mOnRestrictedCaptionAreaChangedListener.onRestrictedCaptionAreaChanged(
+                    mRestrictedCaptionAreaRect);
+        }
+    }
+
+    /**
+     * Set what color should the caption controls be. By default the system will try to determine
+     * the color from the theme. You can overwrite this by using {@link #DECOR_CAPTION_SHADE_DARK},
+     * {@link #DECOR_CAPTION_SHADE_LIGHT}, or {@link #DECOR_CAPTION_SHADE_AUTO}.
+     * @see #DECOR_CAPTION_SHADE_DARK
+     * @see #DECOR_CAPTION_SHADE_LIGHT
+     * @see #DECOR_CAPTION_SHADE_AUTO
+     */
+    public abstract void setDecorCaptionShade(int decorCaptionShade);
+
+    /**
+     * Set the drawable that is drawn underneath the caption during the resizing.
+     *
+     * During the resizing the caption might not be drawn fast enough to match the new dimensions.
+     * There is a second caption drawn underneath it that will be fast enough. By default the
+     * caption is constructed from the theme. You can provide a drawable, that will be drawn instead
+     * to better match your application.
+     */
+    public abstract void setResizingCaptionDrawable(Drawable drawable);
+
+    /**
+     * Called when the activity changes from fullscreen mode to multi-window mode and visa-versa.
+     * @hide
+     */
+    @SuppressWarnings("HiddenAbstractMethod")
+    public abstract void onMultiWindowModeChanged();
+
+    /**
+     * Called when the activity changes to/from picture-in-picture mode.
+     * @hide
+     */
+    @SuppressWarnings("HiddenAbstractMethod")
+    public abstract void onPictureInPictureModeChanged(boolean isInPictureInPictureMode);
+
+    /**
+     * Called when the activity just relaunched.
+     * @hide
+     */
+    @SuppressWarnings("HiddenAbstractMethod")
+    public abstract void reportActivityRelaunched();
+
+    /**
+     * @return The {@link WindowInsetsController} associated with this window
+     * @see View#getWindowInsetsController()
+     */
+    public @Nullable WindowInsetsController getInsetsController() {
+        return null;
+    }
+
+    /**
+     * This will be null before a content view is added, e.g. via
+     * {@link #setContentView} or {@link #addContentView}. See
+     * {@link android.view.View#getRootSurfaceControl}.
+     *
+     * @return The {@link android.view.AttachedSurfaceControl} interface for this Window
+     */
+    public @Nullable AttachedSurfaceControl getRootSurfaceControl() {
+        return null;
+    }
+}
diff --git a/android/view/WindowAnimationFrameStats.java b/android/view/WindowAnimationFrameStats.java
new file mode 100644
index 0000000..251ae9b
--- /dev/null
+++ b/android/view/WindowAnimationFrameStats.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class contains window animation frame statistics. For example, a window
+ * animation is usually performed when the application is transitioning from one
+ * activity to another. The frame statistics are a snapshot for the time interval
+ * from {@link #getStartTimeNano()} to {@link #getEndTimeNano()}.
+ * <p>
+ * The key idea is that in order to provide a smooth user experience the system should
+ * run window animations at a specific time interval obtained by calling {@link
+ * #getRefreshPeriodNano()}. If the system does not render a frame every refresh
+ * period the user will see irregular window transitions. The time when the frame was
+ * actually presented on the display by calling {@link #getFramePresentedTimeNano(int)}.
+ */
+public final class WindowAnimationFrameStats extends FrameStats implements Parcelable {
+    /**
+     * @hide
+     */
+    public WindowAnimationFrameStats() {
+        /* do nothing */
+    }
+
+    /**
+     * Initializes this isntance.
+     *
+     * @param refreshPeriodNano The display refresh period.
+     * @param framesPresentedTimeNano The presented frame times.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public void init(long refreshPeriodNano, long[] framesPresentedTimeNano) {
+        mRefreshPeriodNano = refreshPeriodNano;
+        mFramesPresentedTimeNano = framesPresentedTimeNano;
+    }
+
+    private WindowAnimationFrameStats(Parcel parcel) {
+        mRefreshPeriodNano = parcel.readLong();
+        mFramesPresentedTimeNano = parcel.createLongArray();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeLong(mRefreshPeriodNano);
+        parcel.writeLongArray(mFramesPresentedTimeNano);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("WindowAnimationFrameStats[");
+        builder.append("frameCount:" + getFrameCount());
+        builder.append(", fromTimeNano:" + getStartTimeNano());
+        builder.append(", toTimeNano:" + getEndTimeNano());
+        builder.append(']');
+        return builder.toString();
+    }
+
+    public static final @android.annotation.NonNull Creator<WindowAnimationFrameStats> CREATOR =
+            new Creator<WindowAnimationFrameStats>() {
+                @Override
+                public WindowAnimationFrameStats createFromParcel(Parcel parcel) {
+                    return new WindowAnimationFrameStats(parcel);
+                }
+
+                @Override
+                public WindowAnimationFrameStats[] newArray(int size) {
+                    return new WindowAnimationFrameStats[size];
+                }
+            };
+}
diff --git a/android/view/WindowCallback.java b/android/view/WindowCallback.java
new file mode 100644
index 0000000..1ea8a9f
--- /dev/null
+++ b/android/view/WindowCallback.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.Nullable;
+import android.view.ActionMode.Callback;
+import android.view.WindowManager.LayoutParams;
+import android.view.accessibility.AccessibilityEvent;
+
+import java.util.List;
+
+/**
+ * An empty implementation of {@link Window.Callback} that always returns null/false.
+ */
+public class WindowCallback implements Window.Callback {
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        return false;
+    }
+
+    @Override
+    public boolean dispatchKeyShortcutEvent(KeyEvent event) {
+        return false;
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent event) {
+        return false;
+    }
+
+    @Override
+    public boolean dispatchTrackballEvent(MotionEvent event) {
+        return false;
+    }
+
+    @Override
+    public boolean dispatchGenericMotionEvent(MotionEvent event) {
+        return false;
+    }
+
+    @Override
+    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+        return false;
+    }
+
+    @Override
+    public View onCreatePanelView(int featureId) {
+        return null;
+    }
+
+    @Override
+    public boolean onCreatePanelMenu(int featureId, Menu menu) {
+        return false;
+    }
+
+    @Override
+    public boolean onPreparePanel(int featureId, View view, Menu menu) {
+        return false;
+    }
+
+    @Override
+    public boolean onMenuOpened(int featureId, Menu menu) {
+        return false;
+    }
+
+    @Override
+    public boolean onMenuItemSelected(int featureId, MenuItem item) {
+        return false;
+    }
+
+    @Override
+    public void onWindowAttributesChanged(LayoutParams attrs) {
+
+    }
+
+    @Override
+    public void onContentChanged() {
+
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus) {
+
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+
+    }
+
+    @Override
+    public void onPanelClosed(int featureId, Menu menu) {
+
+    }
+
+    @Override
+    public boolean onSearchRequested(SearchEvent searchEvent) {
+        return onSearchRequested();
+    }
+
+    @Override
+    public boolean onSearchRequested() {
+        return false;
+    }
+
+    @Override
+    public ActionMode onWindowStartingActionMode(Callback callback) {
+        return null;
+    }
+
+    @Override
+    public ActionMode onWindowStartingActionMode(Callback callback, int type) {
+        return null;
+    }
+
+    @Override
+    public void onActionModeStarted(ActionMode mode) {
+
+    }
+
+    @Override
+    public void onActionModeFinished(ActionMode mode) {
+
+    }
+}
diff --git a/android/view/WindowCallbackWrapper.java b/android/view/WindowCallbackWrapper.java
new file mode 100644
index 0000000..02c8945
--- /dev/null
+++ b/android/view/WindowCallbackWrapper.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.view;
+
+import android.view.accessibility.AccessibilityEvent;
+
+import java.util.List;
+
+/**
+ * A simple decorator stub for Window.Callback that passes through any calls
+ * to the wrapped instance as a base implementation. Call super.foo() to call into
+ * the wrapped callback for any subclasses.
+ *
+ * @hide for internal use
+ */
+public class WindowCallbackWrapper implements Window.Callback {
+    private Window.Callback mWrapped;
+
+    public WindowCallbackWrapper(Window.Callback wrapped) {
+        if (wrapped == null) {
+            throw new IllegalArgumentException("Window callback may not be null");
+        }
+        mWrapped = wrapped;
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        return mWrapped.dispatchKeyEvent(event);
+    }
+
+    @Override
+    public boolean dispatchKeyShortcutEvent(KeyEvent event) {
+        return mWrapped.dispatchKeyShortcutEvent(event);
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent event) {
+        return mWrapped.dispatchTouchEvent(event);
+    }
+
+    @Override
+    public boolean dispatchTrackballEvent(MotionEvent event) {
+        return mWrapped.dispatchTrackballEvent(event);
+    }
+
+    @Override
+    public boolean dispatchGenericMotionEvent(MotionEvent event) {
+        return mWrapped.dispatchGenericMotionEvent(event);
+    }
+
+    @Override
+    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+        return mWrapped.dispatchPopulateAccessibilityEvent(event);
+    }
+
+    @Override
+    public View onCreatePanelView(int featureId) {
+        return mWrapped.onCreatePanelView(featureId);
+    }
+
+    @Override
+    public boolean onCreatePanelMenu(int featureId, Menu menu) {
+        return mWrapped.onCreatePanelMenu(featureId, menu);
+    }
+
+    @Override
+    public boolean onPreparePanel(int featureId, View view, Menu menu) {
+        return mWrapped.onPreparePanel(featureId, view, menu);
+    }
+
+    @Override
+    public boolean onMenuOpened(int featureId, Menu menu) {
+        return mWrapped.onMenuOpened(featureId, menu);
+    }
+
+    @Override
+    public boolean onMenuItemSelected(int featureId, MenuItem item) {
+        return mWrapped.onMenuItemSelected(featureId, item);
+    }
+
+    @Override
+    public void onWindowAttributesChanged(WindowManager.LayoutParams attrs) {
+        mWrapped.onWindowAttributesChanged(attrs);
+    }
+
+    @Override
+    public void onContentChanged() {
+        mWrapped.onContentChanged();
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus) {
+        mWrapped.onWindowFocusChanged(hasFocus);
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        mWrapped.onAttachedToWindow();
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        mWrapped.onDetachedFromWindow();
+    }
+
+    @Override
+    public void onPanelClosed(int featureId, Menu menu) {
+        mWrapped.onPanelClosed(featureId, menu);
+    }
+
+    @Override
+    public boolean onSearchRequested(SearchEvent searchEvent) {
+        return mWrapped.onSearchRequested(searchEvent);
+    }
+
+    @Override
+    public boolean onSearchRequested() {
+        return mWrapped.onSearchRequested();
+    }
+
+    @Override
+    public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) {
+        return mWrapped.onWindowStartingActionMode(callback);
+    }
+
+    @Override
+    public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int type) {
+        return mWrapped.onWindowStartingActionMode(callback, type);
+    }
+
+    @Override
+    public void onActionModeStarted(ActionMode mode) {
+        mWrapped.onActionModeStarted(mode);
+    }
+
+    @Override
+    public void onActionModeFinished(ActionMode mode) {
+        mWrapped.onActionModeFinished(mode);
+    }
+
+    @Override
+    public void onProvideKeyboardShortcuts(
+            List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
+        mWrapped.onProvideKeyboardShortcuts(data, menu, deviceId);
+    }
+
+    @Override
+    public void onPointerCaptureChanged(boolean hasCapture) {
+        mWrapped.onPointerCaptureChanged(hasCapture);
+    }
+}
+
diff --git a/android/view/WindowCallbacks.java b/android/view/WindowCallbacks.java
new file mode 100644
index 0000000..a997302
--- /dev/null
+++ b/android/view/WindowCallbacks.java
@@ -0,0 +1,87 @@
+/*
+ * 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.view;
+
+import android.graphics.RecordingCanvas;
+import android.graphics.Rect;
+
+/**
+ * These callbacks are used to communicate window configuration changes while the user is performing
+ * window changes.
+ * Note: Note that at the time of onWindowDragResizeStart the content size isn't known. A consumer
+ * should therfore not draw anything before the additional onContentDraw call has arrived.
+ * @hide
+ */
+public interface WindowCallbacks {
+
+    public static final int RESIZE_MODE_INVALID = -1;
+    public static final int RESIZE_MODE_FREEFORM = 0;
+    public static final int RESIZE_MODE_DOCKED_DIVIDER = 1;
+
+    /**
+     * Called by the system when the window got changed by the user, before the layouter got called.
+     * It also gets called when the insets changed, or when the window switched between a fullscreen
+     * layout or a non-fullscreen layout. It can be used to perform a "quick and dirty" resize which
+     * should never take more then 4ms to complete.
+     *
+     * <p>At the time the layouting has not happened yet.
+     *
+     * @param newBounds The new window frame bounds.
+     * @param fullscreen Whether the window is currently drawing in fullscreen.
+     * @param systemInsets The current visible system insets for the window.
+     * @param stableInsets The stable insets for the window.
+     */
+    void onWindowSizeIsChanging(Rect newBounds, boolean fullscreen, Rect systemInsets,
+            Rect stableInsets);
+
+    /**
+     * Called when a drag resize starts.
+     *
+     * @param initialBounds The initial bounds where the window will be.
+     * @param fullscreen Whether the window is currently drawing in fullscreen.
+     * @param systemInsets The current visible system insets for the window.
+     * @param stableInsets The stable insets for the window.
+     */
+    void onWindowDragResizeStart(Rect initialBounds, boolean fullscreen, Rect systemInsets,
+            Rect stableInsets, int resizeMode);
+
+    /**
+     * Called when a drag resize ends.
+     */
+    void onWindowDragResizeEnd();
+
+    /**
+     * The content will now be drawn to these bounds. Returns true if
+     * a draw should be requested after the next content draw.
+     */
+    boolean onContentDrawn(int offsetX, int offsetY, int sizeX, int sizeY);
+
+    /**
+     * Called to request the window to draw one frame.
+     * @param reportNextDraw Whether it should report when the requested draw finishes.
+     */
+    void onRequestDraw(boolean reportNextDraw);
+
+    /**
+     * Called after all the content has drawn and the callback now has the ability to draw something
+     * on top of everything. Call {@link ViewRootImpl#requestInvalidateRootRenderNode} when this
+     * content needs to be redrawn.
+     *
+     * @param canvas The canvas to draw on.
+     */
+    void onPostDraw(RecordingCanvas canvas);
+}
diff --git a/android/view/WindowContentFrameStats.java b/android/view/WindowContentFrameStats.java
new file mode 100644
index 0000000..c788346
--- /dev/null
+++ b/android/view/WindowContentFrameStats.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class contains window content frame statistics. For example, a window content
+ * is rendred in frames when a view is scrolled. The frame statistics are a snapshot
+ * for the time interval from {@link #getStartTimeNano()} to {@link #getEndTimeNano()}.
+ * <p>
+ * The key idea is that in order to provide a smooth user experience an application
+ * has to draw a frame at a specific time interval obtained by calling {@link
+ * #getRefreshPeriodNano()}. If the application does not render a frame every refresh
+ * period the user will see irregular UI transitions.
+ * </p>
+ * <p>
+ * An application posts a frame for presentation by synchronously rendering its contents
+ * in a buffer which is then posted or posting a buffer to which the application is
+ * asychronously rendering the content via GL. After the frame is posted and rendered
+ * (potentially asynchronosly) it is presented to the user. The time a frame was posted
+ * can be obtained via {@link #getFramePostedTimeNano(int)}, the time a frame content
+ * was rendered and ready for dsiplay (GL case) via {@link #getFrameReadyTimeNano(int)},
+ * and the time a frame was presented on the screen via {@link #getFramePresentedTimeNano(int)}.
+ * </p>
+ */
+public final class WindowContentFrameStats extends FrameStats implements Parcelable {
+    private long[] mFramesPostedTimeNano;
+    private long[] mFramesReadyTimeNano;
+
+    /**
+     * @hide
+     */
+    public WindowContentFrameStats() {
+        /* do nothing */
+    }
+
+    /**
+     * Initializes this isntance.
+     *
+     * @param refreshPeriodNano The display refresh period.
+     * @param framesPostedTimeNano The times in milliseconds for when the frame contents were posted.
+     * @param framesPresentedTimeNano The times in milliseconds for when the frame contents were presented.
+     * @param framesReadyTimeNano The times in milliseconds for when the frame contents were ready to be presented.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public void init(long refreshPeriodNano, long[] framesPostedTimeNano,
+            long[] framesPresentedTimeNano, long[] framesReadyTimeNano) {
+        mRefreshPeriodNano = refreshPeriodNano;
+        mFramesPostedTimeNano = framesPostedTimeNano;
+        mFramesPresentedTimeNano = framesPresentedTimeNano;
+        mFramesReadyTimeNano = framesReadyTimeNano;
+    }
+
+    private WindowContentFrameStats(Parcel parcel) {
+        mRefreshPeriodNano = parcel.readLong();
+        mFramesPostedTimeNano = parcel.createLongArray();
+        mFramesPresentedTimeNano = parcel.createLongArray();
+        mFramesReadyTimeNano = parcel.createLongArray();
+    }
+
+    /**
+     * Get the time a frame at a given index was posted by the producer (e.g. the application).
+     * It is either explicitly set or defaulted to the time when the render buffer was posted.
+     * <p>
+     * <strong>Note:</strong> A frame can be posted and still it contents being rendered
+     * asynchronously in GL. To get the time the frame content was completely rendered and
+     * ready to display call {@link #getFrameReadyTimeNano(int)}.
+     * </p>
+     *
+     * @param index The frame index.
+     * @return The posted time in nanoseconds.
+     */
+    public long getFramePostedTimeNano(int index) {
+        if (mFramesPostedTimeNano == null) {
+            throw new IndexOutOfBoundsException();
+        }
+        return mFramesPostedTimeNano[index];
+    }
+
+    /**
+     * Get the time a frame at a given index was ready for presentation.
+     * <p>
+     * <strong>Note:</strong> A frame can be posted and still it contents being rendered
+     * asynchronously in GL. In such a case this is the time when the frame contents were
+     * completely rendered.
+     * </p>
+     *
+     * @param index The frame index.
+     * @return The ready time in nanoseconds or {@link #UNDEFINED_TIME_NANO}
+     *         if the frame is not ready yet.
+     */
+    public long getFrameReadyTimeNano(int index) {
+        if (mFramesReadyTimeNano == null) {
+            throw new IndexOutOfBoundsException();
+        }
+        return mFramesReadyTimeNano[index];
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeLong(mRefreshPeriodNano);
+        parcel.writeLongArray(mFramesPostedTimeNano);
+        parcel.writeLongArray(mFramesPresentedTimeNano);
+        parcel.writeLongArray(mFramesReadyTimeNano);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("WindowContentFrameStats[");
+        builder.append("frameCount:" + getFrameCount());
+        builder.append(", fromTimeNano:" + getStartTimeNano());
+        builder.append(", toTimeNano:" + getEndTimeNano());
+        builder.append(']');
+        return builder.toString();
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<WindowContentFrameStats> CREATOR =
+            new Creator<WindowContentFrameStats>() {
+                @Override
+                public WindowContentFrameStats createFromParcel(Parcel parcel) {
+                    return new WindowContentFrameStats(parcel);
+                }
+
+                @Override
+                public WindowContentFrameStats[] newArray(int size) {
+                    return new WindowContentFrameStats[size];
+                }
+            };
+}
diff --git a/android/view/WindowId.java b/android/view/WindowId.java
new file mode 100644
index 0000000..26d3405
--- /dev/null
+++ b/android/view/WindowId.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+import java.util.HashMap;
+
+/**
+ * Safe identifier for a window.  This currently allows you to retrieve and observe
+ * the input focus state of the window.  Most applications will
+ * not use this, instead relying on the simpler (and more efficient) methods available
+ * on {@link View}.  This classes is useful when window input interactions need to be
+ * done across processes: the class itself is a Parcelable that can be passed to other
+ * processes for them to interact with your window, and it provides a limited safe API
+ * that doesn't allow the other process to negatively harm your window.
+ */
+public class WindowId implements Parcelable {
+    @NonNull
+    private final IWindowId mToken;
+
+    /**
+     * Subclass for observing changes to the focus state of an {@link WindowId}.
+     * You should use the same instance of this class for observing multiple
+     * {@link WindowId} objects, since this class is fairly heavy-weight -- the
+     * base class includes all of the mechanisms for connecting to and receiving updates
+     * from the window.
+     */
+    public static abstract class FocusObserver {
+        final IWindowFocusObserver.Stub mIObserver = new IWindowFocusObserver.Stub() {
+
+            @Override
+            public void focusGained(IBinder inputToken) {
+                WindowId token;
+                synchronized (mRegistrations) {
+                    token = mRegistrations.get(inputToken);
+                }
+                if (mHandler != null) {
+                    mHandler.sendMessage(mHandler.obtainMessage(1, token));
+                } else {
+                    onFocusGained(token);
+                }
+            }
+
+            @Override
+            public void focusLost(IBinder inputToken) {
+                WindowId token;
+                synchronized (mRegistrations) {
+                    token = mRegistrations.get(inputToken);
+                }
+                if (mHandler != null) {
+                    mHandler.sendMessage(mHandler.obtainMessage(2, token));
+                } else {
+                    onFocusLost(token);
+                }
+            }
+        };
+
+        final HashMap<IBinder, WindowId> mRegistrations = new HashMap<>();
+
+        class H extends Handler {
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case 1:
+                        onFocusGained((WindowId)msg.obj);
+                        break;
+                    case 2:
+                        onFocusLost((WindowId)msg.obj);
+                        break;
+                    default:
+                        super.handleMessage(msg);
+                }
+            }
+        }
+
+        final Handler mHandler;
+
+        /**
+         * Construct a new observer.  This observer will be configured so that all
+         * of its callbacks are dispatched on the current calling thread.
+         */
+        public FocusObserver() {
+            mHandler = new H();
+        }
+
+        /**
+         * Called when one of the monitored windows gains input focus.
+         */
+        public abstract void onFocusGained(WindowId token);
+
+        /**
+         * Called when one of the monitored windows loses input focus.
+         */
+        public abstract void onFocusLost(WindowId token);
+    }
+
+    /**
+     * Retrieve the current focus state of the associated window.
+     */
+    public boolean isFocused() {
+        try {
+            return mToken.isFocused();
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Start monitoring for changes in the focus state of the window.
+     */
+    public void registerFocusObserver(FocusObserver observer) {
+        synchronized (observer.mRegistrations) {
+            if (observer.mRegistrations.containsKey(mToken.asBinder())) {
+                throw new IllegalStateException(
+                        "Focus observer already registered with input token");
+            }
+            observer.mRegistrations.put(mToken.asBinder(), this);
+            try {
+                mToken.registerFocusObserver(observer.mIObserver);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /**
+     * Stop monitoring changes in the focus state of the window.
+     */
+    public void unregisterFocusObserver(FocusObserver observer) {
+        synchronized (observer.mRegistrations) {
+            if (observer.mRegistrations.remove(mToken.asBinder()) == null) {
+                throw new IllegalStateException("Focus observer not registered with input token");
+            }
+            try {
+                mToken.unregisterFocusObserver(observer.mIObserver);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /**
+     * Comparison operator on two IntentSender objects, such that true
+     * is returned then they both represent the same operation from the
+     * same package.
+     */
+    @Override
+    public boolean equals(@Nullable Object otherObj) {
+        if (otherObj instanceof WindowId) {
+            return mToken.asBinder().equals(((WindowId) otherObj).mToken.asBinder());
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return mToken.asBinder().hashCode();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(128);
+        sb.append("IntentSender{");
+        sb.append(Integer.toHexString(System.identityHashCode(this)));
+        sb.append(": ");
+        sb.append(mToken.asBinder());
+        sb.append('}');
+        return sb.toString();
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeStrongBinder(mToken.asBinder());
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<WindowId> CREATOR = new Parcelable.Creator<WindowId>() {
+        @Override
+        public WindowId createFromParcel(Parcel in) {
+            IBinder target = in.readStrongBinder();
+            return target != null ? new WindowId(target) : null;
+        }
+
+        @Override
+        public WindowId[] newArray(int size) {
+            return new WindowId[size];
+        }
+    };
+
+    /** @hide */
+    @NonNull
+    public IWindowId getTarget() {
+        return mToken;
+    }
+
+    /** @hide */
+    public WindowId(@NonNull IWindowId target) {
+        mToken = target;
+    }
+
+    /** @hide */
+    public WindowId(@NonNull IBinder target) {
+        mToken = IWindowId.Stub.asInterface(target);
+    }
+}
diff --git a/android/view/WindowInfo.java b/android/view/WindowInfo.java
new file mode 100644
index 0000000..57dfc62
--- /dev/null
+++ b/android/view/WindowInfo.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.graphics.Region;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Pools;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class represents information about a window from the
+ * window manager to another part of the system.
+ *
+ * @hide
+ */
+public class WindowInfo implements Parcelable {
+    private static final int MAX_POOL_SIZE = 10;
+
+    private static final Pools.SynchronizedPool<WindowInfo> sPool =
+            new Pools.SynchronizedPool<WindowInfo>(MAX_POOL_SIZE);
+
+    public int type;
+    public int layer;
+    public IBinder token;
+    public IBinder parentToken;
+    public IBinder activityToken;
+    public boolean focused;
+    public Region regionInScreen = new Region();
+    public List<IBinder> childTokens;
+    public CharSequence title;
+    public long accessibilityIdOfAnchor = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
+    public boolean inPictureInPicture;
+    public boolean hasFlagWatchOutsideTouch;
+    public int displayId = Display.INVALID_DISPLAY;
+
+    private WindowInfo() {
+        /* do nothing - hide constructor */
+    }
+
+    public static WindowInfo obtain() {
+        WindowInfo window = sPool.acquire();
+        if (window == null) {
+            window = new WindowInfo();
+        }
+        return window;
+    }
+
+    public static WindowInfo obtain(WindowInfo other) {
+        WindowInfo window = obtain();
+        window.displayId = other.displayId;
+        window.type = other.type;
+        window.layer = other.layer;
+        window.token = other.token;
+        window.parentToken = other.parentToken;
+        window.activityToken = other.activityToken;
+        window.focused = other.focused;
+        window.regionInScreen.set(other.regionInScreen);
+        window.title = other.title;
+        window.accessibilityIdOfAnchor = other.accessibilityIdOfAnchor;
+        window.inPictureInPicture = other.inPictureInPicture;
+        window.hasFlagWatchOutsideTouch = other.hasFlagWatchOutsideTouch;
+
+        if (other.childTokens != null && !other.childTokens.isEmpty()) {
+            if (window.childTokens == null) {
+                window.childTokens = new ArrayList<IBinder>(other.childTokens);
+            } else {
+                window.childTokens.addAll(other.childTokens);
+            }
+        }
+
+        return window;
+    }
+
+    public void recycle() {
+        clear();
+        sPool.release(this);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeInt(displayId);
+        parcel.writeInt(type);
+        parcel.writeInt(layer);
+        parcel.writeStrongBinder(token);
+        parcel.writeStrongBinder(parentToken);
+        parcel.writeStrongBinder(activityToken);
+        parcel.writeInt(focused ? 1 : 0);
+        regionInScreen.writeToParcel(parcel, flags);
+        parcel.writeCharSequence(title);
+        parcel.writeLong(accessibilityIdOfAnchor);
+        parcel.writeInt(inPictureInPicture ? 1 : 0);
+        parcel.writeInt(hasFlagWatchOutsideTouch ? 1 : 0);
+
+        if (childTokens != null && !childTokens.isEmpty()) {
+            parcel.writeInt(1);
+            parcel.writeBinderList(childTokens);
+        } else {
+            parcel.writeInt(0);
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("WindowInfo[");
+        builder.append("title=").append(title);
+        builder.append(", displayId=").append(displayId);
+        builder.append(", type=").append(type);
+        builder.append(", layer=").append(layer);
+        builder.append(", token=").append(token);
+        builder.append(", region=").append(regionInScreen);
+        builder.append(", bounds=").append(regionInScreen.getBounds());
+        builder.append(", parent=").append(parentToken);
+        builder.append(", focused=").append(focused);
+        builder.append(", children=").append(childTokens);
+        builder.append(", accessibility anchor=").append(accessibilityIdOfAnchor);
+        builder.append(", pictureInPicture=").append(inPictureInPicture);
+        builder.append(", watchOutsideTouch=").append(hasFlagWatchOutsideTouch);
+        builder.append(']');
+        return builder.toString();
+    }
+
+    private void initFromParcel(Parcel parcel) {
+        displayId = parcel.readInt();
+        type = parcel.readInt();
+        layer = parcel.readInt();
+        token = parcel.readStrongBinder();
+        parentToken = parcel.readStrongBinder();
+        activityToken = parcel.readStrongBinder();
+        focused = (parcel.readInt() == 1);
+        regionInScreen = Region.CREATOR.createFromParcel(parcel);
+        title = parcel.readCharSequence();
+        accessibilityIdOfAnchor = parcel.readLong();
+        inPictureInPicture = (parcel.readInt() == 1);
+        hasFlagWatchOutsideTouch = (parcel.readInt() == 1);
+
+        final boolean hasChildren = (parcel.readInt() == 1);
+        if (hasChildren) {
+            if (childTokens == null) {
+                childTokens = new ArrayList<IBinder>();
+            }
+            parcel.readBinderList(childTokens);
+        }
+    }
+
+    private void clear() {
+        displayId = Display.INVALID_DISPLAY;
+        type = 0;
+        layer = 0;
+        token = null;
+        parentToken = null;
+        activityToken = null;
+        focused = false;
+        regionInScreen.setEmpty();
+        if (childTokens != null) {
+            childTokens.clear();
+        }
+        inPictureInPicture = false;
+        hasFlagWatchOutsideTouch = false;
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<WindowInfo> CREATOR =
+            new Creator<WindowInfo>() {
+        @Override
+        public WindowInfo createFromParcel(Parcel parcel) {
+            WindowInfo window = obtain();
+            window.initFromParcel(parcel);
+            return window;
+        }
+
+        @Override
+        public WindowInfo[] newArray(int size) {
+            return new WindowInfo[size];
+        }
+    };
+}
diff --git a/android/view/WindowInsets.java b/android/view/WindowInsets.java
new file mode 100644
index 0000000..0f1a9d9
--- /dev/null
+++ b/android/view/WindowInsets.java
@@ -0,0 +1,1624 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.view;
+
+import static android.view.Surface.ROTATION_0;
+import static android.view.WindowInsets.Type.DISPLAY_CUTOUT;
+import static android.view.WindowInsets.Type.FIRST;
+import static android.view.WindowInsets.Type.IME;
+import static android.view.WindowInsets.Type.LAST;
+import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES;
+import static android.view.WindowInsets.Type.NAVIGATION_BARS;
+import static android.view.WindowInsets.Type.SIZE;
+import static android.view.WindowInsets.Type.STATUS_BARS;
+import static android.view.WindowInsets.Type.SYSTEM_GESTURES;
+import static android.view.WindowInsets.Type.TAPPABLE_ELEMENT;
+import static android.view.WindowInsets.Type.all;
+import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.indexOf;
+import static android.view.WindowInsets.Type.systemBars;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Intent;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.util.SparseArray;
+import android.view.View.OnApplyWindowInsetsListener;
+import android.view.WindowInsets.Type.InsetsType;
+import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethod;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Describes a set of insets for window content.
+ *
+ * <p>WindowInsets are immutable and may be expanded to include more inset types in the future.
+ * To adjust insets, use one of the supplied clone methods to obtain a new WindowInsets instance
+ * with the adjusted properties.</p>
+ *
+ * <p>Note: Before {@link android.os.Build.VERSION_CODES#P P}, WindowInsets instances were only
+ * immutable during a single layout pass (i.e. would return the same values between
+ * {@link View#onApplyWindowInsets} and {@link View#onLayout}, but could return other values
+ * otherwise). Starting with {@link android.os.Build.VERSION_CODES#P P}, WindowInsets are
+ * always immutable and implement equality.
+ *
+ * @see View.OnApplyWindowInsetsListener
+ * @see View#onApplyWindowInsets(WindowInsets)
+ */
+public final class WindowInsets {
+
+    private final Insets[] mTypeInsetsMap;
+    private final Insets[] mTypeMaxInsetsMap;
+    private final boolean[] mTypeVisibilityMap;
+
+    @Nullable private Rect mTempRect;
+    private final boolean mIsRound;
+    @Nullable private final DisplayCutout mDisplayCutout;
+    @Nullable private final RoundedCorners mRoundedCorners;
+    @Nullable private final PrivacyIndicatorBounds mPrivacyIndicatorBounds;
+
+    /**
+     * In multi-window we force show the navigation bar. Because we don't want that the surface size
+     * changes in this mode, we instead have a flag whether the navigation bar size should always
+     * be consumed, so the app is treated like there is no virtual navigation bar at all.
+     */
+    private final boolean mAlwaysConsumeSystemBars;
+
+    private final boolean mSystemWindowInsetsConsumed;
+    private final boolean mStableInsetsConsumed;
+    private final boolean mDisplayCutoutConsumed;
+
+    private final int mCompatInsetsTypes;
+    private final boolean mCompatIgnoreVisibility;
+
+    /**
+     * A {@link WindowInsets} instance for which {@link #isConsumed()} returns {@code true}.
+     * <p>
+     * This can be used during insets dispatch in the view hierarchy by returning this value from
+     * {@link View#onApplyWindowInsets(WindowInsets)} or
+     * {@link OnApplyWindowInsetsListener#onApplyWindowInsets(View, WindowInsets)} to stop dispatch
+     * the insets to its children to avoid traversing the entire view hierarchy.
+     * <p>
+     * The application should return this instance once it has taken care of all insets on a certain
+     * level in the view hierarchy, and doesn't need to dispatch to its children anymore for better
+     * performance.
+     *
+     * @see #isConsumed()
+     */
+    public static final @NonNull WindowInsets CONSUMED;
+
+    static {
+        CONSUMED = new WindowInsets((Rect) null, null, false, false, null);
+    }
+
+    /**
+     * Construct a new WindowInsets from individual insets.
+     *
+     * A {@code null} inset indicates that the respective inset is consumed.
+     *
+     * @hide
+     * @deprecated Use {@link WindowInsets(SparseArray, SparseArray, boolean, boolean, DisplayCutout)}
+     */
+    @Deprecated
+    public WindowInsets(Rect systemWindowInsetsRect, Rect stableInsetsRect, boolean isRound,
+            boolean alwaysConsumeSystemBars, DisplayCutout displayCutout) {
+        this(createCompatTypeMap(systemWindowInsetsRect), createCompatTypeMap(stableInsetsRect),
+                createCompatVisibilityMap(createCompatTypeMap(systemWindowInsetsRect)),
+                isRound, alwaysConsumeSystemBars, displayCutout, null, null,
+                systemBars(), false /* compatIgnoreVisibility */);
+    }
+
+    /**
+     * Construct a new WindowInsets from individual insets.
+     *
+     * {@code typeInsetsMap} and {@code typeMaxInsetsMap} are a map of indexOf(type) -> insets that
+     * contain the information what kind of system bars causes how much insets. The insets in this
+     * map are non-additive; i.e. they have the same origin. In other words: If two system bars
+     * overlap on one side, the insets of the larger bar will also include the insets of the smaller
+     * bar.
+     *
+     * {@code null} type inset map indicates that the respective inset is fully consumed.
+     * @hide
+     */
+    public WindowInsets(@Nullable Insets[] typeInsetsMap,
+            @Nullable Insets[] typeMaxInsetsMap,
+            boolean[] typeVisibilityMap,
+            boolean isRound,
+            boolean alwaysConsumeSystemBars, DisplayCutout displayCutout,
+            RoundedCorners roundedCorners,
+            PrivacyIndicatorBounds privacyIndicatorBounds,
+            @InsetsType int compatInsetsTypes, boolean compatIgnoreVisibility) {
+        mSystemWindowInsetsConsumed = typeInsetsMap == null;
+        mTypeInsetsMap = mSystemWindowInsetsConsumed
+                ? new Insets[SIZE]
+                : typeInsetsMap.clone();
+
+        mStableInsetsConsumed = typeMaxInsetsMap == null;
+        mTypeMaxInsetsMap = mStableInsetsConsumed
+                ? new Insets[SIZE]
+                : typeMaxInsetsMap.clone();
+
+        mTypeVisibilityMap = typeVisibilityMap;
+        mIsRound = isRound;
+        mAlwaysConsumeSystemBars = alwaysConsumeSystemBars;
+        mCompatInsetsTypes = compatInsetsTypes;
+        mCompatIgnoreVisibility = compatIgnoreVisibility;
+
+        mDisplayCutoutConsumed = displayCutout == null;
+        mDisplayCutout = (mDisplayCutoutConsumed || displayCutout.isEmpty())
+                ? null : displayCutout;
+
+        mRoundedCorners = roundedCorners;
+        mPrivacyIndicatorBounds = privacyIndicatorBounds;
+    }
+
+    /**
+     * Construct a new WindowInsets, copying all values from a source WindowInsets.
+     *
+     * @param src Source to copy insets from
+     */
+    public WindowInsets(WindowInsets src) {
+        this(src.mSystemWindowInsetsConsumed ? null : src.mTypeInsetsMap,
+                src.mStableInsetsConsumed ? null : src.mTypeMaxInsetsMap,
+                src.mTypeVisibilityMap, src.mIsRound,
+                src.mAlwaysConsumeSystemBars, displayCutoutCopyConstructorArgument(src),
+                src.mRoundedCorners,
+                src.mPrivacyIndicatorBounds,
+                src.mCompatInsetsTypes,
+                src.mCompatIgnoreVisibility);
+    }
+
+    private static DisplayCutout displayCutoutCopyConstructorArgument(WindowInsets w) {
+        if (w.mDisplayCutoutConsumed) {
+            return null;
+        } else if (w.mDisplayCutout == null) {
+            return DisplayCutout.NO_CUTOUT;
+        } else {
+            return w.mDisplayCutout;
+        }
+    }
+
+    /**
+     * @return The insets that include system bars indicated by {@code typeMask}, taken from
+     *         {@code typeInsetsMap}.
+     */
+    static Insets getInsets(Insets[] typeInsetsMap, @InsetsType int typeMask) {
+        Insets result = null;
+        for (int i = FIRST; i <= LAST; i = i << 1) {
+            if ((typeMask & i) == 0) {
+                continue;
+            }
+            Insets insets = typeInsetsMap[indexOf(i)];
+            if (insets == null) {
+                continue;
+            }
+            if (result == null) {
+                result = insets;
+            } else {
+                result = Insets.max(result, insets);
+            }
+        }
+        return result == null ? Insets.NONE : result;
+    }
+
+    /**
+     * Sets all entries in {@code typeInsetsMap} that belong to {@code typeMask} to {@code insets},
+     */
+    private static void setInsets(Insets[] typeInsetsMap, @InsetsType int typeMask, Insets insets) {
+        for (int i = FIRST; i <= LAST; i = i << 1) {
+            if ((typeMask & i) == 0) {
+                continue;
+            }
+            typeInsetsMap[indexOf(i)] = insets;
+        }
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public WindowInsets(Rect systemWindowInsets) {
+        this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, null,
+                null, null, systemBars(), false /* compatIgnoreVisibility */);
+    }
+
+    /**
+     * Creates a indexOf(type) -> inset map for which the {@code insets} is just mapped to
+     * {@link Type#statusBars()} and {@link Type#navigationBars()}, depending on the
+     * location of the inset.
+     */
+    private static Insets[] createCompatTypeMap(@Nullable Rect insets) {
+        if (insets == null) {
+            return null;
+        }
+        Insets[] typeInsetsMap = new Insets[SIZE];
+        assignCompatInsets(typeInsetsMap, insets);
+        return typeInsetsMap;
+    }
+
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    public static void assignCompatInsets(Insets[] typeInsetsMap, Rect insets) {
+        typeInsetsMap[indexOf(STATUS_BARS)] = Insets.of(0, insets.top, 0, 0);
+        typeInsetsMap[indexOf(NAVIGATION_BARS)] =
+                Insets.of(insets.left, 0, insets.right, insets.bottom);
+    }
+
+    private static boolean[] createCompatVisibilityMap(@Nullable Insets[] typeInsetsMap) {
+        boolean[] typeVisibilityMap = new boolean[SIZE];
+        if (typeInsetsMap == null) {
+            return typeVisibilityMap;
+        }
+        for (int i = FIRST; i <= LAST; i = i << 1) {
+            int index = indexOf(i);
+            if (!Insets.NONE.equals(typeInsetsMap[index])) {
+                typeVisibilityMap[index] = true;
+            }
+        }
+        return typeVisibilityMap;
+    }
+
+    /**
+     * Used to provide a safe copy of the system window insets to pass through
+     * to the existing fitSystemWindows method and other similar internals.
+     * @hide
+     *
+     * @deprecated use {@link #getSystemWindowInsets()} instead.
+     */
+    @Deprecated
+    @NonNull
+    public Rect getSystemWindowInsetsAsRect() {
+        if (mTempRect == null) {
+            mTempRect = new Rect();
+        }
+        Insets insets = getSystemWindowInsets();
+        mTempRect.set(insets.left, insets.top, insets.right, insets.bottom);
+        return mTempRect;
+    }
+
+    /**
+     * Returns the system window insets in pixels.
+     *
+     * <p>The system window inset represents the area of a full-screen window that is
+     * partially or fully obscured by the status bar, navigation bar, IME or other system windows.
+     * </p>
+     *
+     * @return The system window insets
+     * @deprecated Use {@link #getInsets(int)} with {@link Type#systemBars()}
+     * instead.
+     */
+    @Deprecated
+    @NonNull
+    public Insets getSystemWindowInsets() {
+        Insets result = mCompatIgnoreVisibility
+                ? getInsetsIgnoringVisibility(mCompatInsetsTypes & ~ime())
+                : getInsets(mCompatInsetsTypes);
+
+        // We can't query max insets for IME, so we need to add it manually after.
+        if ((mCompatInsetsTypes & ime()) != 0 && mCompatIgnoreVisibility) {
+            result = Insets.max(result, getInsets(ime()));
+        }
+        return result;
+    }
+
+    /**
+     * Returns the insets of a specific set of windows causing insets, denoted by the
+     * {@code typeMask} bit mask of {@link Type}s.
+     *
+     * @param typeMask Bit mask of {@link Type}s to query the insets for.
+     * @return The insets.
+     */
+    @NonNull
+    public Insets getInsets(@InsetsType int typeMask) {
+        return getInsets(mTypeInsetsMap, typeMask);
+    }
+
+    /**
+     * Returns the insets a specific set of windows can cause, denoted by the
+     * {@code typeMask} bit mask of {@link Type}s, regardless of whether that type is
+     * currently visible or not.
+     *
+     * <p>The insets represents the area of a a window that that <b>may</b> be partially
+     * or fully obscured by the system window identified by {@code type}. This value does not
+     * change based on the visibility state of those elements. For example, if the status bar is
+     * normally shown, but temporarily hidden, the inset returned here will still provide the inset
+     * associated with the status bar being shown.</p>
+     *
+     * @param typeMask Bit mask of {@link Type}s to query the insets for.
+     * @return The insets.
+     *
+     * @throws IllegalArgumentException If the caller tries to query {@link Type#ime()}. Insets are
+     *                                  not available if the IME isn't visible as the height of the
+     *                                  IME is dynamic depending on the {@link EditorInfo} of the
+     *                                  currently focused view, as well as the UI state of the IME.
+     */
+    @NonNull
+    public Insets getInsetsIgnoringVisibility(@InsetsType int typeMask) {
+        if ((typeMask & IME) != 0) {
+            throw new IllegalArgumentException("Unable to query the maximum insets for IME");
+        }
+        return getInsets(mTypeMaxInsetsMap, typeMask);
+    }
+
+    /**
+     * Returns whether a set of windows that may cause insets is currently visible on screen,
+     * regardless of whether it actually overlaps with this window.
+     *
+     * @param typeMask Bit mask of {@link Type}s to query visibility status.
+     * @return {@code true} if and only if all windows included in {@code typeMask} are currently
+     *         visible on screen.
+     */
+    public boolean isVisible(@InsetsType int typeMask) {
+        for (int i = FIRST; i <= LAST; i = i << 1) {
+            if ((typeMask & i) == 0) {
+                continue;
+            }
+            if (!mTypeVisibilityMap[indexOf(i)]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns the left system window inset in pixels.
+     *
+     * <p>The system window inset represents the area of a full-screen window that is
+     * partially or fully obscured by the status bar, navigation bar, IME or other system windows.
+     * </p>
+     *
+     * @return The left system window inset
+     * @deprecated Use {@link #getInsets(int)} with {@link Type#systemBars()}
+     * instead.
+     */
+    @Deprecated
+    public int getSystemWindowInsetLeft() {
+        return getSystemWindowInsets().left;
+    }
+
+    /**
+     * Returns the top system window inset in pixels.
+     *
+     * <p>The system window inset represents the area of a full-screen window that is
+     * partially or fully obscured by the status bar, navigation bar, IME or other system windows.
+     * </p>
+     *
+     * @return The top system window inset
+     * @deprecated Use {@link #getInsets(int)} with {@link Type#systemBars()}
+     * instead.
+     */
+    @Deprecated
+    public int getSystemWindowInsetTop() {
+        return getSystemWindowInsets().top;
+    }
+
+    /**
+     * Returns the right system window inset in pixels.
+     *
+     * <p>The system window inset represents the area of a full-screen window that is
+     * partially or fully obscured by the status bar, navigation bar, IME or other system windows.
+     * </p>
+     *
+     * @return The right system window inset
+     * @deprecated Use {@link #getInsets(int)} with {@link Type#systemBars()}
+     * instead.
+     */
+    @Deprecated
+    public int getSystemWindowInsetRight() {
+        return getSystemWindowInsets().right;
+    }
+
+    /**
+     * Returns the bottom system window inset in pixels.
+     *
+     * <p>The system window inset represents the area of a full-screen window that is
+     * partially or fully obscured by the status bar, navigation bar, IME or other system windows.
+     * </p>
+     *
+     * @return The bottom system window inset
+     * @deprecated Use {@link #getInsets(int)} with {@link Type#systemBars()}
+     * instead.
+     */
+    @Deprecated
+    public int getSystemWindowInsetBottom() {
+        return getSystemWindowInsets().bottom;
+    }
+
+    /**
+     * Returns true if this WindowInsets has nonzero system window insets.
+     *
+     * <p>The system window inset represents the area of a full-screen window that is
+     * partially or fully obscured by the status bar, navigation bar, IME or other system windows.
+     * </p>
+     *
+     * @return true if any of the system window inset values are nonzero
+     * @deprecated Use {@link #getInsets(int)} with {@link Type#systemBars()}
+     * instead.
+     */
+    @Deprecated
+    public boolean hasSystemWindowInsets() {
+        return !getSystemWindowInsets().equals(Insets.NONE);
+    }
+
+    /**
+     * Returns true if this WindowInsets has any nonzero insets.
+     *
+     * @return true if any inset values are nonzero
+     */
+    public boolean hasInsets() {
+        return !getInsets(mTypeInsetsMap, all()).equals(Insets.NONE)
+                || !getInsets(mTypeMaxInsetsMap, all()).equals(Insets.NONE)
+                || mDisplayCutout != null || mRoundedCorners != null;
+    }
+
+    /**
+     * Returns the display cutout if there is one.
+     *
+     * <p>Note: the display cutout will already be {@link #consumeDisplayCutout consumed} during
+     * dispatch to {@link View#onApplyWindowInsets}, unless the window has requested a
+     * {@link WindowManager.LayoutParams#layoutInDisplayCutoutMode} other than
+     * {@link WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER never} or
+     * {@link WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT default}.
+     *
+     * @return the display cutout or null if there is none
+     * @see DisplayCutout
+     */
+    @Nullable
+    public DisplayCutout getDisplayCutout() {
+        return mDisplayCutout;
+    }
+
+    /**
+     * Returns the {@link RoundedCorner} of the given position if there is one.
+     *
+     * @param position the position of the rounded corner on the display. The value should be one of
+     *                 the following:
+     *                 {@link RoundedCorner#POSITION_TOP_LEFT},
+     *                 {@link RoundedCorner#POSITION_TOP_RIGHT},
+     *                 {@link RoundedCorner#POSITION_BOTTOM_RIGHT},
+     *                 {@link RoundedCorner#POSITION_BOTTOM_LEFT}.
+     * @return the rounded corner of the given position. Returns {@code null} if there is none or
+     *         the rounded corner area is not inside the application's bounds.
+     */
+    @Nullable
+    public RoundedCorner getRoundedCorner(@RoundedCorner.Position int position) {
+        return mRoundedCorners == null ? null : mRoundedCorners.getRoundedCorner(position);
+    }
+
+    /**
+     * Returns the {@link Rect} of the maximum bounds of the system privacy indicator, for the
+     * current orientation, in relative coordinates, or null if the bounds have not been loaded yet.
+     * The privacy indicator shows over apps when an app uses the microphone or camera permissions,
+     * while an app is in immersive mode.
+     *
+     * @return A rectangle representing the maximum bounds of the indicator
+     */
+    public @Nullable Rect getPrivacyIndicatorBounds() {
+        return mPrivacyIndicatorBounds == null ? null
+                : mPrivacyIndicatorBounds.getStaticPrivacyIndicatorBounds();
+    }
+
+    /**
+     * Returns a copy of this WindowInsets with the cutout fully consumed.
+     *
+     * @return A modified copy of this WindowInsets
+     * @deprecated Consuming of different parts individually of a {@link WindowInsets} instance is
+     * deprecated, since {@link WindowInsets} contains many different insets. Use {@link #CONSUMED}
+     * instead to stop dispatching insets.
+     */
+    @Deprecated
+    @NonNull
+    public WindowInsets consumeDisplayCutout() {
+        return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap,
+                mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
+                mTypeVisibilityMap,
+                mIsRound, mAlwaysConsumeSystemBars,
+                null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds,
+                mCompatInsetsTypes, mCompatIgnoreVisibility);
+    }
+
+
+    /**
+     * Check if these insets have been fully consumed.
+     *
+     * <p>Insets are considered "consumed" if the applicable <code>consume*</code> methods
+     * have been called such that all insets have been set to zero. This affects propagation of
+     * insets through the view hierarchy; insets that have not been fully consumed will continue
+     * to propagate down to child views.</p>
+     *
+     * <p>The result of this method is equivalent to the return value of
+     * {@link View#fitSystemWindows(android.graphics.Rect)}.</p>
+     *
+     * @return true if the insets have been fully consumed.
+     */
+    public boolean isConsumed() {
+        return mSystemWindowInsetsConsumed && mStableInsetsConsumed
+                && mDisplayCutoutConsumed;
+    }
+
+    /**
+     * Returns true if the associated window has a round shape.
+     *
+     * <p>A round window's left, top, right and bottom edges reach all the way to the
+     * associated edges of the window but the corners may not be visible. Views responding
+     * to round insets should take care to not lay out critical elements within the corners
+     * where they may not be accessible.</p>
+     *
+     * @return True if the window is round
+     */
+    public boolean isRound() {
+        return mIsRound;
+    }
+
+    /**
+     * Returns a copy of this WindowInsets with the system window insets fully consumed.
+     *
+     * @return A modified copy of this WindowInsets
+     * @deprecated Consuming of different parts individually of a {@link WindowInsets} instance is
+     * deprecated, since {@link WindowInsets} contains many different insets. Use {@link #CONSUMED}
+     * instead to stop dispatching insets.
+     */
+    @Deprecated
+    @NonNull
+    public WindowInsets consumeSystemWindowInsets() {
+        return new WindowInsets(null, null,
+                mTypeVisibilityMap,
+                mIsRound, mAlwaysConsumeSystemBars,
+                displayCutoutCopyConstructorArgument(this),
+                mRoundedCorners, mPrivacyIndicatorBounds, mCompatInsetsTypes,
+                mCompatIgnoreVisibility);
+    }
+
+    // TODO(b/119190588): replace @code with @link below
+    /**
+     * Returns a copy of this WindowInsets with selected system window insets replaced
+     * with new values.
+     *
+     * <p>Note: If the system window insets are already consumed, this method will return them
+     * unchanged on {@link android.os.Build.VERSION_CODES#Q Q} and later. Prior to
+     * {@link android.os.Build.VERSION_CODES#Q Q}, the new values were applied regardless of
+     * whether they were consumed, and this method returns invalid non-zero consumed insets.
+     *
+     * @param left New left inset in pixels
+     * @param top New top inset in pixels
+     * @param right New right inset in pixels
+     * @param bottom New bottom inset in pixels
+     * @return A modified copy of this WindowInsets
+     * @deprecated use {@code Builder#Builder(WindowInsets)} with
+     *             {@link Builder#setSystemWindowInsets(Insets)} instead.
+     */
+    @Deprecated
+    @NonNull
+    public WindowInsets replaceSystemWindowInsets(int left, int top, int right, int bottom) {
+        // Compat edge case: what should this do if the insets have already been consumed?
+        // On platforms prior to Q, the behavior was to override the insets with non-zero values,
+        // but leave them consumed, which is invalid (consumed insets must be zero).
+        // The behavior is now keeping them consumed and discarding the new insets.
+        if (mSystemWindowInsetsConsumed) {
+            return this;
+        }
+        return new Builder(this).setSystemWindowInsets(Insets.of(left, top, right, bottom)).build();
+    }
+
+    // TODO(b/119190588): replace @code with @link below
+    /**
+     * Returns a copy of this WindowInsets with selected system window insets replaced
+     * with new values.
+     *
+     * <p>Note: If the system window insets are already consumed, this method will return them
+     * unchanged on {@link android.os.Build.VERSION_CODES#Q Q} and later. Prior to
+     * {@link android.os.Build.VERSION_CODES#Q Q}, the new values were applied regardless of
+     * whether they were consumed, and this method returns invalid non-zero consumed insets.
+     *
+     * @param systemWindowInsets New system window insets. Each field is the inset in pixels
+     *                           for that edge
+     * @return A modified copy of this WindowInsets
+     * @deprecated use {@code Builder#Builder(WindowInsets)} with
+     *             {@link Builder#setSystemWindowInsets(Insets)} instead.
+     */
+    @Deprecated
+    @NonNull
+    public WindowInsets replaceSystemWindowInsets(Rect systemWindowInsets) {
+        return replaceSystemWindowInsets(systemWindowInsets.left, systemWindowInsets.top,
+                systemWindowInsets.right, systemWindowInsets.bottom);
+    }
+
+    /**
+     * Returns the stable insets in pixels.
+     *
+     * <p>The stable inset represents the area of a full-screen window that <b>may</b> be
+     * partially or fully obscured by the system UI elements.  This value does not change
+     * based on the visibility state of those elements; for example, if the status bar is
+     * normally shown, but temporarily hidden, the stable inset will still provide the inset
+     * associated with the status bar being shown.</p>
+     *
+     * @return The stable insets
+     * @deprecated Use {@link #getInsetsIgnoringVisibility(int)} with {@link Type#systemBars()}
+     * instead.
+     */
+    @Deprecated
+    @NonNull
+    public Insets getStableInsets() {
+        return getInsets(mTypeMaxInsetsMap, mCompatInsetsTypes);
+    }
+
+    /**
+     * Returns the top stable inset in pixels.
+     *
+     * <p>The stable inset represents the area of a full-screen window that <b>may</b> be
+     * partially or fully obscured by the system UI elements.  This value does not change
+     * based on the visibility state of those elements; for example, if the status bar is
+     * normally shown, but temporarily hidden, the stable inset will still provide the inset
+     * associated with the status bar being shown.</p>
+     *
+     * @return The top stable inset
+     * @deprecated Use {@link #getInsetsIgnoringVisibility(int)} with {@link Type#systemBars()}
+     * instead.
+     */
+    @Deprecated
+    public int getStableInsetTop() {
+        return getStableInsets().top;
+    }
+
+    /**
+     * Returns the left stable inset in pixels.
+     *
+     * <p>The stable inset represents the area of a full-screen window that <b>may</b> be
+     * partially or fully obscured by the system UI elements.  This value does not change
+     * based on the visibility state of those elements; for example, if the status bar is
+     * normally shown, but temporarily hidden, the stable inset will still provide the inset
+     * associated with the status bar being shown.</p>
+     *
+     * @return The left stable inset
+     * @deprecated Use {@link #getInsetsIgnoringVisibility(int)} with {@link Type#systemBars()}
+     * instead.
+     */
+    @Deprecated
+    public int getStableInsetLeft() {
+        return getStableInsets().left;
+    }
+
+    /**
+     * Returns the right stable inset in pixels.
+     *
+     * <p>The stable inset represents the area of a full-screen window that <b>may</b> be
+     * partially or fully obscured by the system UI elements.  This value does not change
+     * based on the visibility state of those elements; for example, if the status bar is
+     * normally shown, but temporarily hidden, the stable inset will still provide the inset
+     * associated with the status bar being shown.</p>
+     *
+     * @return The right stable inset
+     * @deprecated Use {@link #getInsetsIgnoringVisibility(int)} with {@link Type#systemBars()}
+     * instead.
+     */
+    @Deprecated
+    public int getStableInsetRight() {
+        return getStableInsets().right;
+    }
+
+    /**
+     * Returns the bottom stable inset in pixels.
+     *
+     * <p>The stable inset represents the area of a full-screen window that <b>may</b> be
+     * partially or fully obscured by the system UI elements.  This value does not change
+     * based on the visibility state of those elements; for example, if the status bar is
+     * normally shown, but temporarily hidden, the stable inset will still provide the inset
+     * associated with the status bar being shown.</p>
+     *
+     * @return The bottom stable inset
+     * @deprecated Use {@link #getInsetsIgnoringVisibility(int)} with {@link Type#systemBars()}
+     * instead.
+     */
+    @Deprecated
+    public int getStableInsetBottom() {
+        return getStableInsets().bottom;
+    }
+
+    /**
+     * Returns true if this WindowInsets has nonzero stable insets.
+     *
+     * <p>The stable inset represents the area of a full-screen window that <b>may</b> be
+     * partially or fully obscured by the system UI elements.  This value does not change
+     * based on the visibility state of those elements; for example, if the status bar is
+     * normally shown, but temporarily hidden, the stable inset will still provide the inset
+     * associated with the status bar being shown.</p>
+     *
+     * @return true if any of the stable inset values are nonzero
+     * @deprecated Use {@link #getInsetsIgnoringVisibility(int)} with {@link Type#systemBars()}
+     * instead.
+     */
+    @Deprecated
+    public boolean hasStableInsets() {
+        return !getStableInsets().equals(Insets.NONE);
+    }
+
+    /**
+     * Returns the system gesture insets.
+     *
+     * <p>The system gesture insets represent the area of a window where system gestures have
+     * priority and may consume some or all touch input, e.g. due to the a system bar
+     * occupying it, or it being reserved for touch-only gestures.
+     *
+     * <p>An app can declare priority over system gestures with
+     * {@link View#setSystemGestureExclusionRects} outside of the
+     * {@link #getMandatorySystemGestureInsets() mandatory system gesture insets}.
+     *
+     * <p>Note: the system will put a limit of <code>200dp</code> on the vertical extent of the
+     * exclusions it takes into account. The limit does not apply while the navigation
+     * bar is {@link View#SYSTEM_UI_FLAG_IMMERSIVE_STICKY stickily} hidden, nor to the
+     * {@link android.inputmethodservice.InputMethodService input method} and
+     * {@link Intent#CATEGORY_HOME home activity}.
+     * </p>
+     *
+     *
+     * <p>Simple taps are guaranteed to reach the window even within the system gesture insets,
+     * as long as they are outside the {@link #getTappableElementInsets() system window insets}.
+     *
+     * <p>When {@link View#SYSTEM_UI_FLAG_LAYOUT_STABLE} is requested, an inset will be returned
+     * even when the system gestures are inactive due to
+     * {@link View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN} or
+     * {@link View#SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION}.
+     *
+     * <p>This inset is consumed together with the {@link #getSystemWindowInsets()
+     * system window insets} by {@link #consumeSystemWindowInsets()}.
+     *
+     * @see #getMandatorySystemGestureInsets
+     * @deprecated Use {@link #getInsets(int)} with {@link Type#systemGestures()} instead.
+     */
+    @Deprecated
+    @NonNull
+    public Insets getSystemGestureInsets() {
+        return getInsets(mTypeInsetsMap, SYSTEM_GESTURES);
+    }
+
+    /**
+     * Returns the mandatory system gesture insets.
+     *
+     * <p>The mandatory system gesture insets represent the area of a window where mandatory system
+     * gestures have priority and may consume some or all touch input, e.g. due to the a system bar
+     * occupying it, or it being reserved for touch-only gestures.
+     *
+     * <p>In contrast to {@link #getSystemGestureInsets regular system gestures}, <b>mandatory</b>
+     * system gestures cannot be overriden by {@link View#setSystemGestureExclusionRects}.
+     *
+     * <p>Simple taps are guaranteed to reach the window even within the system gesture insets,
+     * as long as they are outside the {@link #getTappableElementInsets() system window insets}.
+     *
+     * <p>When {@link View#SYSTEM_UI_FLAG_LAYOUT_STABLE} is requested, an inset will be returned
+     * even when the system gestures are inactive due to
+     * {@link View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN} or
+     * {@link View#SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION}.
+     *
+     * <p>This inset is consumed together with the {@link #getSystemWindowInsets()
+     * system window insets} by {@link #consumeSystemWindowInsets()}.
+     *
+     * @see #getSystemGestureInsets
+     * @deprecated Use {@link #getInsets(int)} with {@link Type#mandatorySystemGestures()} instead.
+     */
+    @Deprecated
+    @NonNull
+    public Insets getMandatorySystemGestureInsets() {
+        return getInsets(mTypeInsetsMap, MANDATORY_SYSTEM_GESTURES);
+    }
+
+    /**
+     * Returns the tappable element insets.
+     *
+     * <p>The tappable element insets represent how much tappable elements <b>must at least</b> be
+     * inset to remain both tappable and visually unobstructed by persistent system windows.
+     *
+     * <p>This may be smaller than {@link #getSystemWindowInsets()} if the system window is
+     * largely transparent and lets through simple taps (but not necessarily more complex gestures).
+     *
+     * <p>Note that generally, tappable elements <strong>should</strong> be aligned with the
+     * {@link #getSystemWindowInsets() system window insets} instead to avoid overlapping with the
+     * system bars.
+     *
+     * <p>When {@link View#SYSTEM_UI_FLAG_LAYOUT_STABLE} is requested, an inset will be returned
+     * even when the area covered by the inset would be tappable due to
+     * {@link View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN} or
+     * {@link View#SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION}.
+     *
+     * <p>This inset is consumed together with the {@link #getSystemWindowInsets()
+     * system window insets} by {@link #consumeSystemWindowInsets()}.
+     *
+     * @deprecated Use {@link #getInsets(int)} with {@link Type#tappableElement()} instead.
+     */
+    @Deprecated
+    @NonNull
+    public Insets getTappableElementInsets() {
+        return getInsets(mTypeInsetsMap, TAPPABLE_ELEMENT);
+    }
+
+    /**
+     * Returns a copy of this WindowInsets with the stable insets fully consumed.
+     *
+     * @return A modified copy of this WindowInsets
+     * @deprecated Consuming of different parts individually of a {@link WindowInsets} instance is
+     * deprecated, since {@link WindowInsets} contains many different insets. Use {@link #CONSUMED}
+     * instead to stop dispatching insets. On {@link android.os.Build.VERSION_CODES#R R}, this
+     * method has no effect.
+     */
+    @Deprecated
+    @NonNull
+    public WindowInsets consumeStableInsets() {
+        return this;
+    }
+
+    /**
+     * @hide
+     */
+    public boolean shouldAlwaysConsumeSystemBars() {
+        return mAlwaysConsumeSystemBars;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder result = new StringBuilder("WindowInsets{\n    ");
+        for (int i = 0; i < SIZE; i++) {
+            Insets insets = mTypeInsetsMap[i];
+            Insets maxInsets = mTypeMaxInsetsMap[i];
+            boolean visible = mTypeVisibilityMap[i];
+            if (!Insets.NONE.equals(insets) || !Insets.NONE.equals(maxInsets) || visible) {
+                result.append(Type.toString(1 << i)).append("=").append(insets)
+                        .append(" max=").append(maxInsets)
+                        .append(" vis=").append(visible)
+                        .append("\n    ");
+            }
+        }
+
+        result.append(mDisplayCutout != null ? "cutout=" + mDisplayCutout : "");
+        result.append("\n    ");
+        result.append(mRoundedCorners != null ? "roundedCorners=" + mRoundedCorners : "");
+        result.append("\n    ");
+        result.append(mPrivacyIndicatorBounds != null ? "privacyIndicatorBounds="
+                + mPrivacyIndicatorBounds : "");
+        result.append("\n    ");
+        result.append(isRound() ? "round" : "");
+        result.append("}");
+        return result.toString();
+    }
+
+    /**
+     * Returns a copy of this instance inset in the given directions.
+     *
+     * @see #inset(int, int, int, int)
+     * @deprecated use {@link #inset(Insets)}
+     * @hide
+     */
+    @Deprecated
+    @NonNull
+    public WindowInsets inset(Rect r) {
+        return inset(r.left, r.top, r.right, r.bottom);
+    }
+
+    /**
+     * Returns a copy of this instance inset in the given directions.
+     *
+     * This is intended for dispatching insets to areas of the window that are smaller than the
+     * current area.
+     *
+     * <p>Example:
+     * <pre>
+     * childView.dispatchApplyWindowInsets(insets.inset(childMargins));
+     * </pre>
+     *
+     * @param insets the amount of insets to remove from all sides.
+     *
+     * @see #inset(int, int, int, int)
+     */
+    @NonNull
+    public WindowInsets inset(@NonNull Insets insets) {
+        Objects.requireNonNull(insets);
+        return inset(insets.left, insets.top, insets.right, insets.bottom);
+    }
+
+    /**
+     * Returns a copy of this instance inset in the given directions.
+     *
+     * This is intended for dispatching insets to areas of the window that are smaller than the
+     * current area.
+     *
+     * <p>Example:
+     * <pre>
+     * childView.dispatchApplyWindowInsets(insets.inset(
+     *         childMarginLeft, childMarginTop, childMarginBottom, childMarginRight));
+     * </pre>
+     *
+     * @param left the amount of insets to remove from the left. Must be non-negative.
+     * @param top the amount of insets to remove from the top. Must be non-negative.
+     * @param right the amount of insets to remove from the right. Must be non-negative.
+     * @param bottom the amount of insets to remove from the bottom. Must be non-negative.
+     *
+     * @return the inset insets
+     *
+     * @see #inset(Insets)
+     */
+    @NonNull
+    public WindowInsets inset(@IntRange(from = 0) int left, @IntRange(from = 0) int top,
+            @IntRange(from = 0) int right, @IntRange(from = 0) int bottom) {
+        Preconditions.checkArgumentNonnegative(left);
+        Preconditions.checkArgumentNonnegative(top);
+        Preconditions.checkArgumentNonnegative(right);
+        Preconditions.checkArgumentNonnegative(bottom);
+
+        return insetUnchecked(left, top, right, bottom);
+    }
+
+    /**
+     * @see #inset(int, int, int, int)
+     * @hide
+     */
+    @NonNull
+    public WindowInsets insetUnchecked(int left, int top, int right, int bottom) {
+        return new WindowInsets(
+                mSystemWindowInsetsConsumed
+                        ? null
+                        : insetInsets(mTypeInsetsMap, left, top, right, bottom),
+                mStableInsetsConsumed
+                        ? null
+                        : insetInsets(mTypeMaxInsetsMap, left, top, right, bottom),
+                mTypeVisibilityMap,
+                mIsRound, mAlwaysConsumeSystemBars,
+                mDisplayCutoutConsumed
+                        ? null
+                        : mDisplayCutout == null
+                                ? DisplayCutout.NO_CUTOUT
+                                : mDisplayCutout.inset(left, top, right, bottom),
+                mRoundedCorners == null
+                        ? RoundedCorners.NO_ROUNDED_CORNERS
+                        : mRoundedCorners.inset(left, top, right, bottom),
+                mPrivacyIndicatorBounds == null
+                        ? null
+                        : mPrivacyIndicatorBounds.inset(left, top, right, bottom),
+                mCompatInsetsTypes, mCompatIgnoreVisibility);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || !(o instanceof WindowInsets)) return false;
+        WindowInsets that = (WindowInsets) o;
+
+        return mIsRound == that.mIsRound
+                && mAlwaysConsumeSystemBars == that.mAlwaysConsumeSystemBars
+                && mSystemWindowInsetsConsumed == that.mSystemWindowInsetsConsumed
+                && mStableInsetsConsumed == that.mStableInsetsConsumed
+                && mDisplayCutoutConsumed == that.mDisplayCutoutConsumed
+                && Arrays.equals(mTypeInsetsMap, that.mTypeInsetsMap)
+                && Arrays.equals(mTypeMaxInsetsMap, that.mTypeMaxInsetsMap)
+                && Arrays.equals(mTypeVisibilityMap, that.mTypeVisibilityMap)
+                && Objects.equals(mDisplayCutout, that.mDisplayCutout)
+                && Objects.equals(mRoundedCorners, that.mRoundedCorners)
+                && Objects.equals(mPrivacyIndicatorBounds, that.mPrivacyIndicatorBounds);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(Arrays.hashCode(mTypeInsetsMap), Arrays.hashCode(mTypeMaxInsetsMap),
+                Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, mRoundedCorners,
+                mAlwaysConsumeSystemBars, mSystemWindowInsetsConsumed, mStableInsetsConsumed,
+                mDisplayCutoutConsumed, mPrivacyIndicatorBounds);
+    }
+
+
+    /**
+     * Insets every inset in {@code typeInsetsMap} by the specified left, top, right, bottom.
+     *
+     * @return {@code typeInsetsMap} if no inset was modified; a copy of the map with the modified
+     *          insets otherwise.
+     */
+    private static Insets[] insetInsets(
+            Insets[] typeInsetsMap, int left, int top, int right, int bottom) {
+        boolean cloned = false;
+        for (int i = 0; i < SIZE; i++) {
+            Insets insets = typeInsetsMap[i];
+            if (insets == null) {
+                continue;
+            }
+            Insets insetInsets = insetInsets(insets, left, top, right, bottom);
+            if (insetInsets != insets) {
+                if (!cloned) {
+                    typeInsetsMap = typeInsetsMap.clone();
+                    cloned = true;
+                }
+                typeInsetsMap[i] = insetInsets;
+            }
+        }
+        return typeInsetsMap;
+    }
+
+    static Insets insetInsets(Insets insets, int left, int top, int right, int bottom) {
+        int newLeft = Math.max(0, insets.left - left);
+        int newTop = Math.max(0, insets.top - top);
+        int newRight = Math.max(0, insets.right - right);
+        int newBottom = Math.max(0, insets.bottom - bottom);
+        if (newLeft == left && newTop == top && newRight == right && newBottom == bottom) {
+            return insets;
+        }
+        return Insets.of(newLeft, newTop, newRight, newBottom);
+    }
+
+    /**
+     * @return whether system window insets have been consumed.
+     */
+    boolean isSystemWindowInsetsConsumed() {
+        return mSystemWindowInsetsConsumed;
+    }
+
+    /**
+     * Builder for WindowInsets.
+     */
+    public static final class Builder {
+
+        private final Insets[] mTypeInsetsMap;
+        private final Insets[] mTypeMaxInsetsMap;
+        private final boolean[] mTypeVisibilityMap;
+        private boolean mSystemInsetsConsumed = true;
+        private boolean mStableInsetsConsumed = true;
+
+        private DisplayCutout mDisplayCutout;
+        private RoundedCorners mRoundedCorners = RoundedCorners.NO_ROUNDED_CORNERS;
+
+        private boolean mIsRound;
+        private boolean mAlwaysConsumeSystemBars;
+
+        private PrivacyIndicatorBounds mPrivacyIndicatorBounds = new PrivacyIndicatorBounds();
+
+        /**
+         * Creates a builder where all insets are initially consumed.
+         */
+        public Builder() {
+            mTypeInsetsMap = new Insets[SIZE];
+            mTypeMaxInsetsMap = new Insets[SIZE];
+            mTypeVisibilityMap = new boolean[SIZE];
+        }
+
+        /**
+         * Creates a builder where all insets are initialized from {@link WindowInsets}.
+         *
+         * @param insets the instance to initialize from.
+         */
+        public Builder(@NonNull WindowInsets insets) {
+            mTypeInsetsMap = insets.mTypeInsetsMap.clone();
+            mTypeMaxInsetsMap = insets.mTypeMaxInsetsMap.clone();
+            mTypeVisibilityMap = insets.mTypeVisibilityMap.clone();
+            mSystemInsetsConsumed = insets.mSystemWindowInsetsConsumed;
+            mStableInsetsConsumed = insets.mStableInsetsConsumed;
+            mDisplayCutout = displayCutoutCopyConstructorArgument(insets);
+            mRoundedCorners = insets.mRoundedCorners;
+            mIsRound = insets.mIsRound;
+            mAlwaysConsumeSystemBars = insets.mAlwaysConsumeSystemBars;
+            mPrivacyIndicatorBounds = insets.mPrivacyIndicatorBounds;
+        }
+
+        /**
+         * Sets system window insets in pixels.
+         *
+         * <p>The system window inset represents the area of a full-screen window that is
+         * partially or fully obscured by the status bar, navigation bar, IME or other system
+         * windows.</p>
+         *
+         * @see #getSystemWindowInsets()
+         * @return itself
+         * @deprecated Use {@link #setInsets(int, Insets)} with {@link Type#systemBars()}.
+         */
+        @Deprecated
+        @NonNull
+        public Builder setSystemWindowInsets(@NonNull Insets systemWindowInsets) {
+            Preconditions.checkNotNull(systemWindowInsets);
+            assignCompatInsets(mTypeInsetsMap, systemWindowInsets.toRect());
+            mSystemInsetsConsumed = false;
+            return this;
+        }
+
+        /**
+         * Sets system gesture insets in pixels.
+         *
+         * <p>The system gesture insets represent the area of a window where system gestures have
+         * priority and may consume some or all touch input, e.g. due to the a system bar
+         * occupying it, or it being reserved for touch-only gestures.
+         *
+         * @see #getSystemGestureInsets()
+         * @return itself
+         * @deprecated Use {@link #setInsets(int, Insets)} with {@link Type#systemGestures()}.
+         */
+        @Deprecated
+        @NonNull
+        public Builder setSystemGestureInsets(@NonNull Insets insets) {
+            WindowInsets.setInsets(mTypeInsetsMap, SYSTEM_GESTURES, insets);
+            return this;
+        }
+
+        /**
+         * Sets mandatory system gesture insets in pixels.
+         *
+         * <p>The mandatory system gesture insets represent the area of a window where mandatory
+         * system gestures have priority and may consume some or all touch input, e.g. due to the a
+         * system bar occupying it, or it being reserved for touch-only gestures.
+         *
+         * <p>In contrast to {@link #setSystemGestureInsets regular system gestures},
+         * <b>mandatory</b> system gestures cannot be overriden by
+         * {@link View#setSystemGestureExclusionRects}.
+         *
+         * @see #getMandatorySystemGestureInsets()
+         * @return itself
+         * @deprecated Use {@link #setInsets(int, Insets)} with
+         *             {@link Type#mandatorySystemGestures()}.
+         */
+        @Deprecated
+        @NonNull
+        public Builder setMandatorySystemGestureInsets(@NonNull Insets insets) {
+            WindowInsets.setInsets(mTypeInsetsMap, MANDATORY_SYSTEM_GESTURES, insets);
+            return this;
+        }
+
+        /**
+         * Sets tappable element insets in pixels.
+         *
+         * <p>The tappable element insets represent how much tappable elements <b>must at least</b>
+         * be inset to remain both tappable and visually unobstructed by persistent system windows.
+         *
+         * @see #getTappableElementInsets()
+         * @return itself
+         * @deprecated Use {@link #setInsets(int, Insets)} with {@link Type#tappableElement()}.
+         */
+        @Deprecated
+        @NonNull
+        public Builder setTappableElementInsets(@NonNull Insets insets) {
+            WindowInsets.setInsets(mTypeInsetsMap, TAPPABLE_ELEMENT, insets);
+            return this;
+        }
+
+        /**
+         * Sets the insets of a specific window type in pixels.
+         *
+         * <p>The insets represents the area of a a window that is partially or fully obscured by
+         * the system windows identified by {@code typeMask}.
+         * </p>
+         *
+         * @see #getInsets(int)
+         *
+         * @param typeMask The bitmask of {@link Type} to set the insets for.
+         * @param insets The insets to set.
+         *
+         * @return itself
+         */
+        @NonNull
+        public Builder setInsets(@InsetsType int typeMask, @NonNull Insets insets) {
+            Preconditions.checkNotNull(insets);
+            WindowInsets.setInsets(mTypeInsetsMap, typeMask, insets);
+            mSystemInsetsConsumed = false;
+            return this;
+        }
+
+        /**
+         * Sets the insets a specific window type in pixels, while ignoring its visibility state.
+         *
+         * <p>The insets represents the area of a a window that that <b>may</b> be partially
+         * or fully obscured by the system window identified by {@code type}. This value does not
+         * change based on the visibility state of those elements. For example, if the status bar is
+         * normally shown, but temporarily hidden, the inset returned here will still provide the
+         * inset associated with the status bar being shown.</p>
+         *
+         * @see #getInsetsIgnoringVisibility(int)
+         *
+         * @param typeMask The bitmask of {@link Type} to set the insets for.
+         * @param insets The insets to set.
+         *
+         * @return itself
+         *
+         * @throws IllegalArgumentException If {@code typeMask} contains {@link Type#ime()}. Maximum
+         *                                  insets are not available for this type as the height of
+         *                                  the IME is dynamic depending on the {@link EditorInfo}
+         *                                  of the currently focused view, as well as the UI
+         *                                  state of the IME.
+         */
+        @NonNull
+        public Builder setInsetsIgnoringVisibility(@InsetsType int typeMask, @NonNull Insets insets)
+                throws IllegalArgumentException{
+            if (typeMask == IME) {
+                throw new IllegalArgumentException("Maximum inset not available for IME");
+            }
+            Preconditions.checkNotNull(insets);
+            WindowInsets.setInsets(mTypeMaxInsetsMap, typeMask, insets);
+            mStableInsetsConsumed = false;
+            return this;
+        }
+
+        /**
+         * Sets whether windows that can cause insets are currently visible on screen.
+         *
+         *
+         * @see #isVisible(int)
+         *
+         * @param typeMask The bitmask of {@link Type} to set the visibility for.
+         * @param visible Whether to mark the windows as visible or not.
+         *
+         * @return itself
+         */
+        @NonNull
+        public Builder setVisible(@InsetsType int typeMask, boolean visible) {
+            for (int i = FIRST; i <= LAST; i = i << 1) {
+                if ((typeMask & i) == 0) {
+                    continue;
+                }
+                mTypeVisibilityMap[indexOf(i)] = visible;
+            }
+            return this;
+        }
+
+        /**
+         * Sets the stable insets in pixels.
+         *
+         * <p>The stable inset represents the area of a full-screen window that <b>may</b> be
+         * partially or fully obscured by the system UI elements.  This value does not change
+         * based on the visibility state of those elements; for example, if the status bar is
+         * normally shown, but temporarily hidden, the stable inset will still provide the inset
+         * associated with the status bar being shown.</p>
+         *
+         * @see #getStableInsets()
+         * @return itself
+         * @deprecated Use {@link #setInsetsIgnoringVisibility(int, Insets)} with
+         *             {@link Type#systemBars()}.
+         */
+        @Deprecated
+        @NonNull
+        public Builder setStableInsets(@NonNull Insets stableInsets) {
+            Preconditions.checkNotNull(stableInsets);
+            assignCompatInsets(mTypeMaxInsetsMap, stableInsets.toRect());
+            mStableInsetsConsumed = false;
+            return this;
+        }
+
+        /**
+         * Sets the display cutout.
+         *
+         * @see #getDisplayCutout()
+         * @param displayCutout the display cutout or null if there is none
+         * @return itself
+         */
+        @NonNull
+        public Builder setDisplayCutout(@Nullable DisplayCutout displayCutout) {
+            mDisplayCutout = displayCutout != null ? displayCutout : DisplayCutout.NO_CUTOUT;
+            if (!mDisplayCutout.isEmpty()) {
+                final Insets safeInsets = Insets.of(mDisplayCutout.getSafeInsets());
+                final int index = indexOf(DISPLAY_CUTOUT);
+                mTypeInsetsMap[index] = safeInsets;
+                mTypeMaxInsetsMap[index] = safeInsets;
+                mTypeVisibilityMap[index] = true;
+            }
+            return this;
+        }
+
+        /** @hide */
+        @NonNull
+        public Builder setRoundedCorners(RoundedCorners roundedCorners) {
+            mRoundedCorners = roundedCorners != null
+                    ? roundedCorners : RoundedCorners.NO_ROUNDED_CORNERS;
+            return this;
+        }
+
+        /**
+         * Sets the rounded corner of given position.
+         *
+         * @see #getRoundedCorner(int)
+         * @param position the position of this rounded corner
+         * @param roundedCorner the rounded corner or null if there is none
+         * @return itself
+         */
+        @NonNull
+        public Builder setRoundedCorner(@RoundedCorner.Position int position,
+                @Nullable RoundedCorner roundedCorner) {
+            mRoundedCorners.setRoundedCorner(position, roundedCorner);
+            return this;
+        }
+
+        /** @hide */
+        @NonNull
+        public Builder setPrivacyIndicatorBounds(@Nullable PrivacyIndicatorBounds bounds) {
+            mPrivacyIndicatorBounds = bounds;
+            return this;
+        }
+
+        /**
+         * Sets the bounds of the system privacy indicator.
+         *
+         * @param bounds The bounds of the system privacy indicator
+         */
+        @NonNull
+        public Builder setPrivacyIndicatorBounds(@Nullable Rect bounds) {
+            //TODO 188788786: refactor the indicator bounds
+            Rect[] boundsArr = { bounds, bounds, bounds, bounds };
+            mPrivacyIndicatorBounds = new PrivacyIndicatorBounds(boundsArr, ROTATION_0);
+            return this;
+        }
+
+        /** @hide */
+        @NonNull
+        public Builder setRound(boolean round) {
+            mIsRound = round;
+            return this;
+        }
+
+        /** @hide */
+        @NonNull
+        public Builder setAlwaysConsumeSystemBars(boolean alwaysConsumeSystemBars) {
+            mAlwaysConsumeSystemBars = alwaysConsumeSystemBars;
+            return this;
+        }
+
+        /**
+         * Builds a {@link WindowInsets} instance.
+         *
+         * @return the {@link WindowInsets} instance.
+         */
+        @NonNull
+        public WindowInsets build() {
+            return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap,
+                    mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap,
+                    mIsRound, mAlwaysConsumeSystemBars, mDisplayCutout, mRoundedCorners,
+                    mPrivacyIndicatorBounds, systemBars(), false /* compatIgnoreVisibility */);
+        }
+    }
+
+    /**
+     * Class that defines different types of sources causing window insets.
+     */
+    public static final class Type {
+
+        static final int FIRST = 1 << 0;
+        static final int STATUS_BARS = FIRST;
+        static final int NAVIGATION_BARS = 1 << 1;
+        static final int CAPTION_BAR = 1 << 2;
+
+        static final int IME = 1 << 3;
+
+        static final int SYSTEM_GESTURES = 1 << 4;
+        static final int MANDATORY_SYSTEM_GESTURES = 1 << 5;
+        static final int TAPPABLE_ELEMENT = 1 << 6;
+
+        static final int DISPLAY_CUTOUT = 1 << 7;
+
+        static final int LAST = 1 << 8;
+        static final int SIZE = 9;
+        static final int WINDOW_DECOR = LAST;
+
+        static int indexOf(@InsetsType int type) {
+            switch (type) {
+                case STATUS_BARS:
+                    return 0;
+                case NAVIGATION_BARS:
+                    return 1;
+                case CAPTION_BAR:
+                    return 2;
+                case IME:
+                    return 3;
+                case SYSTEM_GESTURES:
+                    return 4;
+                case MANDATORY_SYSTEM_GESTURES:
+                    return 5;
+                case TAPPABLE_ELEMENT:
+                    return 6;
+                case DISPLAY_CUTOUT:
+                    return 7;
+                case WINDOW_DECOR:
+                    return 8;
+                default:
+                    throw new IllegalArgumentException("type needs to be >= FIRST and <= LAST,"
+                            + " type=" + type);
+            }
+        }
+
+        static String toString(@InsetsType int types) {
+            StringBuilder result = new StringBuilder();
+            if ((types & STATUS_BARS) != 0) {
+                result.append("statusBars |");
+            }
+            if ((types & NAVIGATION_BARS) != 0) {
+                result.append("navigationBars |");
+            }
+            if ((types & CAPTION_BAR) != 0) {
+                result.append("captionBar |");
+            }
+            if ((types & IME) != 0) {
+                result.append("ime |");
+            }
+            if ((types & SYSTEM_GESTURES) != 0) {
+                result.append("systemGestures |");
+            }
+            if ((types & MANDATORY_SYSTEM_GESTURES) != 0) {
+                result.append("mandatorySystemGestures |");
+            }
+            if ((types & TAPPABLE_ELEMENT) != 0) {
+                result.append("tappableElement |");
+            }
+            if ((types & DISPLAY_CUTOUT) != 0) {
+                result.append("displayCutout |");
+            }
+            if ((types & WINDOW_DECOR) != 0) {
+                result.append("windowDecor |");
+            }
+            if (result.length() > 0) {
+                result.delete(result.length() - 2, result.length());
+            }
+            return result.toString();
+        }
+
+        private Type() {
+        }
+
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(flag = true, value = {STATUS_BARS, NAVIGATION_BARS, CAPTION_BAR, IME, WINDOW_DECOR,
+                SYSTEM_GESTURES, MANDATORY_SYSTEM_GESTURES, TAPPABLE_ELEMENT, DISPLAY_CUTOUT})
+        public @interface InsetsType {
+        }
+
+        /**
+         * @return An insets type representing any system bars for displaying status.
+         */
+        public static @InsetsType int statusBars() {
+            return STATUS_BARS;
+        }
+
+        /**
+         * @return An insets type representing any system bars for navigation.
+         */
+        public static @InsetsType int navigationBars() {
+            return NAVIGATION_BARS;
+        }
+
+        /**
+         * @return An insets type representing the window of a caption bar.
+         */
+        public static @InsetsType int captionBar() {
+            return CAPTION_BAR;
+        }
+
+        /**
+         * @return An insets type representing the window of an {@link InputMethod}.
+         */
+        public static @InsetsType int ime() {
+            return IME;
+        }
+
+        /**
+         * Returns an insets type representing the system gesture insets.
+         *
+         * <p>The system gesture insets represent the area of a window where system gestures have
+         * priority and may consume some or all touch input, e.g. due to the a system bar
+         * occupying it, or it being reserved for touch-only gestures.
+         *
+         * <p>Simple taps are guaranteed to reach the window even within the system gesture insets,
+         * as long as they are outside the {@link #getSystemWindowInsets() system window insets}.
+         *
+         * <p>When {@link View#SYSTEM_UI_FLAG_LAYOUT_STABLE} is requested, an inset will be returned
+         * even when the system gestures are inactive due to
+         * {@link View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN} or
+         * {@link View#SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION}.
+         *
+         * @see #getSystemGestureInsets()
+         */
+        public static @InsetsType int systemGestures() {
+            return SYSTEM_GESTURES;
+        }
+
+        /**
+         * @see #getMandatorySystemGestureInsets
+         */
+        public static @InsetsType int mandatorySystemGestures() {
+            return MANDATORY_SYSTEM_GESTURES;
+        }
+
+        /**
+         * @see #getTappableElementInsets
+         */
+        public static @InsetsType int tappableElement() {
+            return TAPPABLE_ELEMENT;
+        }
+
+        /**
+         * Returns an insets type representing the area that used by {@link DisplayCutout}.
+         *
+         * <p>This is equivalent to the safe insets on {@link #getDisplayCutout()}.
+         *
+         * <p>Note: During dispatch to {@link View#onApplyWindowInsets}, if the window is using
+         * the {@link WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT default}
+         * {@link WindowManager.LayoutParams#layoutInDisplayCutoutMode}, {@link #getDisplayCutout()}
+         * will return {@code null} even if the window overlaps a display cutout area, in which case
+         * the {@link #displayCutout() displayCutout() inset} will still report the accurate value.
+         *
+         * @see DisplayCutout#getSafeInsetLeft()
+         * @see DisplayCutout#getSafeInsetTop()
+         * @see DisplayCutout#getSafeInsetRight()
+         * @see DisplayCutout#getSafeInsetBottom()
+         */
+        public static @InsetsType int displayCutout() {
+            return DISPLAY_CUTOUT;
+        }
+
+        /**
+         * @return All system bars. Includes {@link #statusBars()}, {@link #captionBar()} as well as
+         *         {@link #navigationBars()}, but not {@link #ime()}.
+         */
+        public static @InsetsType int systemBars() {
+            return STATUS_BARS | NAVIGATION_BARS | CAPTION_BAR;
+        }
+
+        /**
+         * @return All inset types combined.
+         *
+         * @hide
+         */
+        public static @InsetsType int all() {
+            return 0xFFFFFFFF;
+        }
+
+        /**
+         * Checks whether the specified type is considered to be part of visible insets.
+         * @hide
+         */
+        public static boolean isVisibleInsetsType(int type,
+                @SoftInputModeFlags int softInputModeFlags) {
+            int softInputMode = softInputModeFlags & SOFT_INPUT_MASK_ADJUST;
+            return (type & Type.systemBars()) != 0
+                    || (softInputMode != SOFT_INPUT_ADJUST_NOTHING && (type & Type.ime()) != 0);
+        }
+    }
+
+    /**
+     * Class that defines different sides for insets.
+     */
+    public static final class Side {
+
+        public static final int LEFT = 1 << 0;
+        public static final int TOP = 1 << 1;
+        public static final int RIGHT = 1 << 2;
+        public static final int BOTTOM = 1 << 3;
+
+        private Side() {
+        }
+
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(flag = true, value = {LEFT, TOP, RIGHT, BOTTOM})
+        public @interface InsetsSide {}
+
+        /**
+         * @return all four sides.
+         */
+        public static @InsetsSide int all() {
+            return LEFT | TOP | RIGHT | BOTTOM;
+        }
+    }
+}
diff --git a/android/view/WindowInsetsAnimation.java b/android/view/WindowInsetsAnimation.java
new file mode 100644
index 0000000..6576eea
--- /dev/null
+++ b/android/view/WindowInsetsAnimation.java
@@ -0,0 +1,442 @@
+/*
+ * 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.view;
+
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.graphics.Insets;
+import android.view.animation.Interpolator;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * Class representing an animation of a set of windows that cause insets.
+ */
+public final class WindowInsetsAnimation {
+
+    @WindowInsets.Type.InsetsType
+    private final int mTypeMask;
+    private float mFraction;
+    @Nullable
+    private final Interpolator mInterpolator;
+    private final long mDurationMillis;
+    private float mAlpha;
+
+    /**
+     * Creates a new {@link WindowInsetsAnimation} object.
+     * <p>
+     * This should only be used for testing, as usually the system creates this object for the
+     * application to listen to with {@link Callback}.
+     * </p>
+     * @param typeMask The bitmask of {@link WindowInsets.Type}s that are animating.
+     * @param interpolator The interpolator of the animation.
+     * @param durationMillis The duration of the animation in
+     *                   {@link java.util.concurrent.TimeUnit#MILLISECONDS}.
+     */
+    public WindowInsetsAnimation(
+            @WindowInsets.Type.InsetsType int typeMask, @Nullable Interpolator interpolator,
+            long durationMillis) {
+        mTypeMask = typeMask;
+        mInterpolator = interpolator;
+        mDurationMillis = durationMillis;
+    }
+
+    /**
+     * @return The bitmask of {@link WindowInsets.Type}s that are animating.
+     */
+    @WindowInsets.Type.InsetsType
+    public int getTypeMask() {
+        return mTypeMask;
+    }
+
+    /**
+     * Returns the raw fractional progress of this animation between
+     * start state of the animation and the end state of the animation. Note
+     * that this progress is the global progress of the animation, whereas
+     * {@link Callback#onProgress} will only dispatch the insets that may
+     * be inset with {@link WindowInsets#inset} by parents of views in the hierarchy.
+     * Progress per insets animation is global for the entire animation. One animation animates
+     * all things together (in, out, ...). If they don't animate together, we'd have
+     * multiple animations.
+     * <p>
+     * Note: In case the application is controlling the animation, the valued returned here will
+     * be the same as the application passed into
+     * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)}.
+     * </p>
+     * @return The current progress of this animation.
+     */
+    @FloatRange(from = 0f, to = 1f)
+    public float getFraction() {
+        return mFraction;
+    }
+
+    /**
+     * Returns the interpolated fractional progress of this animation between
+     * start state of the animation and the end state of the animation. Note
+     * that this progress is the global progress of the animation, whereas
+     * {@link Callback#onProgress} will only dispatch the insets that may
+     * be inset with {@link WindowInsets#inset} by parents of views in the hierarchy.
+     * Progress per insets animation is global for the entire animation. One animation animates
+     * all things together (in, out, ...). If they don't animate together, we'd have
+     * multiple animations.
+     * <p>
+     * Note: In case the application is controlling the animation, the valued returned here will
+     * be the same as the application passed into
+     * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)},
+     * interpolated with the interpolator passed into
+     * {@link WindowInsetsController#controlWindowInsetsAnimation}.
+     * </p>
+     * <p>
+     * Note: For system-initiated animations, this will always return a valid value between 0
+     * and 1.
+     * </p>
+     * @see #getFraction() for raw fraction.
+     * @return The current interpolated progress of this animation.
+     */
+    public float getInterpolatedFraction() {
+        if (mInterpolator != null) {
+            return mInterpolator.getInterpolation(mFraction);
+        }
+        return mFraction;
+    }
+
+    /**
+     * Retrieves the interpolator used for this animation, or {@code null} if this animation
+     * doesn't follow an interpolation curved. For system-initiated animations, this will never
+     * return {@code null}.
+     *
+     * @return The interpolator used for this animation.
+     */
+    @Nullable
+    public Interpolator getInterpolator() {
+        return mInterpolator;
+    }
+
+    /**
+     * @return duration of animation in {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or
+     *         -1 if the animation doesn't have a fixed duration.
+     */
+    public long getDurationMillis() {
+        return mDurationMillis;
+    }
+
+    /**
+     * Set fraction of the progress if {@link WindowInsets.Type} animation is
+     * controlled by the app.
+     * <p>
+     * Note: This should only be used for testing, as the system fills in the fraction for the
+     * application or the fraction that was passed into
+     * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)} is being
+     * used.
+     * </p>
+     * @param fraction fractional progress between 0 and 1 where 0 represents hidden and
+     *                zero progress and 1 represent fully shown final state.
+     * @see #getFraction()
+     */
+    public void setFraction(@FloatRange(from = 0f, to = 1f) float fraction) {
+        mFraction = fraction;
+    }
+
+    /**
+     * Retrieves the translucency of the windows that are animating.
+     *
+     * @return Alpha of windows that cause insets of type {@link WindowInsets.Type}.
+     */
+    @FloatRange(from = 0f, to = 1f)
+    public float getAlpha() {
+        return mAlpha;
+    }
+
+    /**
+     * Sets the translucency of the windows that are animating.
+     * <p>
+     * Note: This should only be used for testing, as the system fills in the alpha for the
+     * application or the alpha that was passed into
+     * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)} is being
+     * used.
+     * </p>
+     * @param alpha Alpha of windows that cause insets of type {@link WindowInsets.Type}.
+     * @see #getAlpha()
+     */
+    public void setAlpha(@FloatRange(from = 0f, to = 1f) float alpha) {
+        mAlpha = alpha;
+    }
+
+    /**
+     * Class representing the range of an {@link WindowInsetsAnimation}
+     */
+    public static final class Bounds {
+
+        private final Insets mLowerBound;
+        private final Insets mUpperBound;
+
+        public Bounds(@NonNull Insets lowerBound, @NonNull Insets upperBound) {
+            mLowerBound = lowerBound;
+            mUpperBound = upperBound;
+        }
+
+        /**
+         * Queries the lower inset bound of the animation. If the animation is about showing or
+         * hiding a window that cause insets, the lower bound is {@link Insets#NONE} and the upper
+         * bound is the same as {@link WindowInsets#getInsets(int)} for the fully shown state. This
+         * is the same as {@link WindowInsetsAnimationController#getHiddenStateInsets} and
+         * {@link WindowInsetsAnimationController#getShownStateInsets} in case the listener gets
+         * invoked because of an animation that originates from
+         * {@link WindowInsetsAnimationController}.
+         * <p>
+         * However, if the size of a window that causes insets is changing, these are the
+         * lower/upper bounds of that size animation.
+         * </p>
+         * There are no overlapping animations for a specific type, but there may be multiple
+         * animations running at the same time for different inset types.
+         *
+         * @see #getUpperBound()
+         * @see WindowInsetsAnimationController#getHiddenStateInsets
+         */
+        @NonNull
+        public Insets getLowerBound() {
+            return mLowerBound;
+        }
+
+        /**
+         * Queries the upper inset bound of the animation. If the animation is about showing or
+         * hiding a window that cause insets, the lower bound is {@link Insets#NONE}
+         * nd the upper bound is the same as {@link WindowInsets#getInsets(int)} for the fully
+         * shown state. This is the same as
+         * {@link WindowInsetsAnimationController#getHiddenStateInsets} and
+         * {@link WindowInsetsAnimationController#getShownStateInsets} in case the listener gets
+         * invoked because of an animation that originates from
+         * {@link WindowInsetsAnimationController}.
+         * <p>
+         * However, if the size of a window that causes insets is changing, these are the
+         * lower/upper bounds of that size animation.
+         * <p>
+         * There are no overlapping animations for a specific type, but there may be multiple
+         * animations running at the same time for different inset types.
+         *
+         * @see #getLowerBound()
+         * @see WindowInsetsAnimationController#getShownStateInsets
+         */
+        @NonNull
+        public Insets getUpperBound() {
+            return mUpperBound;
+        }
+
+        /**
+         * Insets both the lower and upper bound by the specified insets. This is to be used in
+         * {@link Callback#onStart} to indicate that a part of the insets has
+         * been used to offset or clip its children, and the children shouldn't worry about that
+         * part anymore.
+         *
+         * @param insets The amount to inset.
+         * @return A copy of this instance inset in the given directions.
+         * @see WindowInsets#inset
+         * @see Callback#onStart
+         */
+        @NonNull
+        public Bounds inset(@NonNull Insets insets) {
+            return new Bounds(
+                    // TODO: refactor so that WindowInsets.insetInsets() is in a more appropriate
+                    //  place eventually.
+                    WindowInsets.insetInsets(
+                            mLowerBound, insets.left, insets.top, insets.right, insets.bottom),
+                    WindowInsets.insetInsets(
+                            mUpperBound, insets.left, insets.top, insets.right, insets.bottom));
+        }
+
+        @Override
+        public String toString() {
+            return "Bounds{lower=" + mLowerBound + " upper=" + mUpperBound + "}";
+        }
+    }
+
+    /**
+     * Interface that allows the application to listen to animation events for windows that cause
+     * insets.
+     */
+    @SuppressLint("CallbackMethodName") // TODO(b/149430296) Should be on method, not class.
+    public abstract static class Callback {
+
+        /**
+         * Return value for {@link #getDispatchMode()}: Dispatching of animation events should
+         * stop at this level in the view hierarchy, and no animation events should be dispatch to
+         * the subtree of the view hierarchy.
+         */
+        public static final int DISPATCH_MODE_STOP = 0;
+
+        /**
+         * Return value for {@link #getDispatchMode()}: Dispatching of animation events should
+         * continue in the view hierarchy.
+         */
+        public static final int DISPATCH_MODE_CONTINUE_ON_SUBTREE = 1;
+
+        /** @hide */
+        @IntDef(prefix = { "DISPATCH_MODE_" }, value = {
+                DISPATCH_MODE_STOP,
+                DISPATCH_MODE_CONTINUE_ON_SUBTREE
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface DispatchMode {}
+
+        @DispatchMode
+        private final int mDispatchMode;
+
+        /**
+         * Creates a new {@link WindowInsetsAnimation} callback with the given
+         * {@link #getDispatchMode() dispatch mode}.
+         *
+         * @param dispatchMode The dispatch mode for this callback. See {@link #getDispatchMode()}.
+         */
+        public Callback(@DispatchMode int dispatchMode) {
+            mDispatchMode = dispatchMode;
+        }
+
+        /**
+         * Retrieves the dispatch mode of this listener. Dispatch of the all animation events is
+         * hierarchical: It will starts at the root of the view hierarchy and then traverse it and
+         * invoke the callback of the specific {@link View} that is being traversed.
+         * The method may return either {@link #DISPATCH_MODE_CONTINUE_ON_SUBTREE} to indicate that
+         * animation events should be propagated to the subtree of the view hierarchy, or
+         * {@link #DISPATCH_MODE_STOP} to stop dispatching. In that case, all animation callbacks
+         * related to the animation passed in will be stopped from propagating to the subtree of the
+         * hierarchy.
+         * <p>
+         * Also note that {@link #DISPATCH_MODE_STOP} behaves the same way as
+         * returning {@link WindowInsets#CONSUMED} during the regular insets dispatch in
+         * {@link View#onApplyWindowInsets}.
+         *
+         * @return Either {@link #DISPATCH_MODE_CONTINUE_ON_SUBTREE} to indicate that dispatching of
+         *         animation events will continue to the subtree of the view hierarchy, or
+         *         {@link #DISPATCH_MODE_STOP} to indicate that animation events will stop
+         *         dispatching.
+         */
+        @DispatchMode
+        @SuppressLint("CallbackMethodName") // TODO(b/149430296) False positive: not a callback.
+        public final int getDispatchMode() {
+            return mDispatchMode;
+        }
+
+        /**
+         * Called when an insets animation is about to start and before the views have been laid out
+         * in the end state of the animation. The ordering of events during an insets animation is
+         * the following:
+         * <p>
+         * <ul>
+         *     <li>Application calls {@link WindowInsetsController#hide(int)},
+         *     {@link WindowInsetsController#show(int)},
+         *     {@link WindowInsetsController#controlWindowInsetsAnimation}</li>
+         *     <li>onPrepare is called on the view hierarchy listeners</li>
+         *     <li>{@link View#onApplyWindowInsets} will be called with the end state of the
+         *     animation</li>
+         *     <li>View hierarchy gets laid out according to the changes the application has
+         *     requested due to the new insets being dispatched</li>
+         *     <li>{@link #onStart} is called <em>before</em> the view
+         *     hierarchy gets drawn in the new laid out state</li>
+         *     <li>{@link #onProgress} is called immediately after with the animation start
+         *     state</li>
+         *     <li>The frame gets drawn.</li>
+         * </ul>
+         * <p>
+         * This ordering allows the application to inspect the end state after the animation has
+         * finished, and then revert to the starting state of the animation in the first
+         * {@link #onProgress} callback by using post-layout view properties like {@link View#setX}
+         * and related methods.
+         *
+         * <p>Note that the animation might be cancelled before {@link #onStart} is dispatched. On
+         * {@link android.os.Build.VERSION_CODES#S S} and later, {@link #onEnd} is immediately
+         * dispatched without an {@link #onStart} in that case.
+         * On {@link android.os.Build.VERSION_CODES#R R}, no callbacks are dispatched after
+         * {@code #onPrepare} for such an animation.
+         *
+         * <p>
+         * Note: If the animation is application controlled by using
+         * {@link WindowInsetsController#controlWindowInsetsAnimation}, the end state of the
+         * animation is undefined as the application may decide on the end state only by passing in
+         * {@code shown} parameter when calling {@link WindowInsetsAnimationController#finish}. In
+         * this situation, the system will dispatch the insets in the opposite visibility state
+         * before the animation starts. Example: When controlling the input method with
+         * {@link WindowInsetsController#controlWindowInsetsAnimation} and the input method is
+         * currently showing, {@link View#onApplyWindowInsets} will receive a {@link WindowInsets}
+         * instance for which {@link WindowInsets#isVisible} will return {@code false} for
+         * {@link WindowInsets.Type#ime}.
+         *
+         * @param animation The animation that is about to start.
+         */
+        public void onPrepare(@NonNull WindowInsetsAnimation animation) {
+        }
+
+        /**
+         * Called when an insets animation gets started.
+         * <p>
+         * Note that, like {@link #onProgress}, dispatch of the animation start event is
+         * hierarchical: It will starts at the root of the view hierarchy and then traverse it
+         * and invoke the callback of the specific {@link View} that is being traversed.
+         * The method may return a modified
+         * instance of the bounds by calling {@link Bounds#inset} to indicate that a part of
+         * the insets have been used to offset or clip its children, and the children shouldn't
+         * worry about that part anymore. Furthermore, if {@link #getDispatchMode()} returns
+         * {@link #DISPATCH_MODE_STOP}, children of this view will not receive the callback anymore.
+         *
+         * @param animation The animation that is about to start.
+         * @param bounds The bounds in which animation happens.
+         * @return The animation representing the part of the insets that should be dispatched to
+         *         the subtree of the hierarchy.
+         */
+        @NonNull
+        public Bounds onStart(
+                @NonNull WindowInsetsAnimation animation, @NonNull Bounds bounds) {
+            return bounds;
+        }
+
+        /**
+         * Called when the insets change as part of running an animation. Note that even if multiple
+         * animations for different types are running, there will only be one progress callback per
+         * frame. The {@code insets} passed as an argument represents the overall state and will
+         * include all types, regardless of whether they are animating or not.
+         * <p>
+         * Note that insets dispatch is hierarchical: It will start at the root of the view
+         * hierarchy, and then traverse it and invoke the callback of the specific {@link View}
+         * being traversed. The method may return a modified instance by calling
+         * {@link WindowInsets#inset(int, int, int, int)} to indicate that a part of the insets have
+         * been used to offset or clip its children, and the children shouldn't worry about that
+         * part anymore. Furthermore, if {@link #getDispatchMode()} returns
+         * {@link #DISPATCH_MODE_STOP}, children of this view will not receive the callback anymore.
+         *
+         * @param insets The current insets.
+         * @param runningAnimations The currently running animations.
+         * @return The insets to dispatch to the subtree of the hierarchy.
+         */
+        @NonNull
+        public abstract WindowInsets onProgress(@NonNull WindowInsets insets,
+                @NonNull List<WindowInsetsAnimation> runningAnimations);
+
+        /**
+         * Called when an insets animation has ended.
+         *
+         * @param animation The animation that has ended. This will be the same instance
+         *                  as passed into {@link #onStart}
+         */
+        public void onEnd(@NonNull WindowInsetsAnimation animation) {
+        }
+
+    }
+}
diff --git a/android/view/WindowInsetsAnimationControlListener.java b/android/view/WindowInsetsAnimationControlListener.java
new file mode 100644
index 0000000..140a9a8
--- /dev/null
+++ b/android/view/WindowInsetsAnimationControlListener.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.view.WindowInsets.Type.InsetsType;
+import android.view.inputmethod.EditorInfo;
+
+/**
+ * Listener that encapsulates a request to
+ * {@link WindowInsetsController#controlWindowInsetsAnimation}.
+ *
+ * <p>
+ * Insets can be controlled with the supplied {@link WindowInsetsAnimationController} from
+ * {@link #onReady} until either {@link #onFinished} or {@link #onCancelled}.
+ *
+ * <p>
+ * Once the control over insets is finished or cancelled, it will not be regained until a new
+ * request to {@link WindowInsetsController#controlWindowInsetsAnimation} is made.
+ *
+ * <p>
+ * The request to control insets can fail immediately. In that case {@link #onCancelled} will be
+ * invoked without a preceding {@link #onReady}.
+ *
+ * @see WindowInsetsController#controlWindowInsetsAnimation
+ */
+public interface WindowInsetsAnimationControlListener {
+
+    /**
+     * Called when the animation is ready to be controlled. This may be delayed when the IME needs
+     * to redraw because of an {@link EditorInfo} change, or when the window is starting up.
+     *
+     * @param controller The controller to control the inset animation.
+     * @param types The {@link WindowInsets.Type}s it was able to gain control over. Note that this
+     *              may be different than the types passed into
+     *              {@link WindowInsetsController#controlWindowInsetsAnimation} in case the window
+     *              wasn't able to gain the controls because it wasn't the IME target or not
+     *              currently the window that's controlling the system bars.
+     * @see WindowInsetsAnimationController#isReady
+     */
+    void onReady(@NonNull WindowInsetsAnimationController controller, @InsetsType int types);
+
+    /**
+     * Called when the request for control over the insets has
+     * {@link WindowInsetsAnimationController#finish finished}.
+     *
+     * Once this callback is invoked, the supplied {@link WindowInsetsAnimationController}
+     * is no longer {@link WindowInsetsAnimationController#isReady() ready}.
+     *
+     * Control will not be regained until a new request
+     * to {@link WindowInsetsController#controlWindowInsetsAnimation} is made.
+     *
+     * @param controller the controller which has finished.
+     * @see WindowInsetsAnimationController#isFinished
+     */
+    void onFinished(@NonNull WindowInsetsAnimationController controller);
+
+    /**
+     * Called when the request for control over the insets has been cancelled, either
+     * because the {@link android.os.CancellationSignal} associated with the
+     * {@link WindowInsetsController#controlWindowInsetsAnimation request} has been invoked, or
+     * the window has lost control over the insets (e.g. because it lost focus).
+     *
+     * Once this callback is invoked, the supplied {@link WindowInsetsAnimationController}
+     * is no longer {@link WindowInsetsAnimationController#isReady() ready}.
+     *
+     * Control will not be regained until a new request
+     * to {@link WindowInsetsController#controlWindowInsetsAnimation} is made.
+     *
+     * @param controller the controller which has been cancelled, or null if the request
+     *                   was cancelled before {@link #onReady} was invoked.
+     * @see WindowInsetsAnimationController#isCancelled
+     */
+    void onCancelled(@Nullable WindowInsetsAnimationController controller);
+}
diff --git a/android/view/WindowInsetsAnimationController.java b/android/view/WindowInsetsAnimationController.java
new file mode 100644
index 0000000..6578e9b
--- /dev/null
+++ b/android/view/WindowInsetsAnimationController.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.graphics.Insets;
+import android.view.WindowInsets.Type.InsetsType;
+import android.view.WindowInsetsAnimation.Bounds;
+
+/**
+ * Controller for app-driven animation of system windows.
+ *  <p>
+ *  {@code WindowInsetsAnimationController} lets apps animate system windows such as
+ *  the {@link android.inputmethodservice.InputMethodService IME}. The animation is
+ *  synchronized, such that changes the system windows and the app's current frame
+ *  are rendered at the same time.
+ *  <p>
+ *  Control is obtained through {@link WindowInsetsController#controlWindowInsetsAnimation}.
+ */
+@SuppressLint("NotCloseable")
+public interface WindowInsetsAnimationController {
+
+    /**
+     * Retrieves the {@link Insets} when the windows this animation is controlling are fully hidden.
+     * <p>
+     * Note that these insets are always relative to the window, which is the same as being relative
+     * to {@link View#getRootView}
+     * <p>
+     * If there are any animation listeners registered, this value is the same as
+     * {@link Bounds#getLowerBound()} that is being be passed into the root view of the
+     * hierarchy.
+     *
+     * @return Insets when the windows this animation is controlling are fully hidden.
+     *
+     * @see Bounds#getLowerBound()
+     */
+    @NonNull Insets getHiddenStateInsets();
+
+    /**
+     * Retrieves the {@link Insets} when the windows this animation is controlling are fully shown.
+     * <p>
+     * Note that these insets are always relative to the window, which is the same as being relative
+     * to {@link View#getRootView}
+     * <p>
+     * If there are any animation listeners registered, this value is the same as
+     * {@link Bounds#getUpperBound()} that is being passed into the root view of hierarchy.
+     *
+     * @return Insets when the windows this animation is controlling are fully shown.
+     *
+     * @see Bounds#getUpperBound()
+     */
+    @NonNull Insets getShownStateInsets();
+
+    /**
+     * Retrieves the current insets.
+     * <p>
+     * Note that these insets are always relative to the window, which is the same as
+     * being relative
+     * to {@link View#getRootView}
+     * @return The current insets on the currently showing frame. These insets will change as the
+     * animation progresses to reflect the current insets provided by the controlled window.
+     */
+    @NonNull Insets getCurrentInsets();
+
+    /**
+     *  Returns the progress as previously set by {@code fraction} in {@link #setInsetsAndAlpha}
+     *
+     *  @return the progress of the animation, where {@code 0} is fully hidden and {@code 1} is
+     *  fully shown.
+     * <p>
+     *  Note: this value represents raw overall progress of the animation
+     *  i.e. the combined progress of insets and alpha.
+     *  <p>
+     */
+    @FloatRange(from = 0f, to = 1f)
+    float getCurrentFraction();
+
+    /**
+     * Current alpha value of the window.
+     * @return float value between 0 and 1.
+     */
+    float getCurrentAlpha();
+
+    /**
+     * @return The {@link WindowInsets.Type}s this object is currently controlling.
+     */
+    @InsetsType int getTypes();
+
+    /**
+     * Modifies the insets for the frame being drawn by indirectly moving the windows around in the
+     * system that are causing window insets.
+     * <p>
+     * Note that these insets are always relative to the window, which is the same as being relative
+     * to {@link View#getRootView}
+     * <p>
+     * Also note that this will <b>not</b> inform the view system of a full inset change via
+     * {@link View#dispatchApplyWindowInsets} in order to avoid a full layout pass during the
+     * animation. If you'd like to animate views during a window inset animation, register a
+     * {@link WindowInsetsAnimation.Callback} by calling
+     * {@link View#setWindowInsetsAnimationCallback(WindowInsetsAnimation.Callback)} that will be
+     * notified about any insets change via {@link WindowInsetsAnimation.Callback#onProgress} during
+     * the animation.
+     * <p>
+     * {@link View#dispatchApplyWindowInsets} will instead be called once the animation has
+     * finished, i.e. once {@link #finish} has been called.
+     * Note: If there are no insets, alpha animation is still applied.
+     *
+     * @param insets The new insets to apply. Based on the requested insets, the system will
+     *               calculate the positions of the windows in the system causing insets such that
+     *               the resulting insets of that configuration will match the passed in parameter.
+     *               Note that these insets are being clamped to the range from
+     *               {@link #getHiddenStateInsets} to {@link #getShownStateInsets}.
+     *               If you intend on changing alpha only, pass null or {@link #getCurrentInsets()}.
+     * @param alpha  The new alpha to apply to the inset side.
+     * @param fraction instantaneous animation progress. This value is dispatched to
+     *                 {@link WindowInsetsAnimation.Callback}.
+     *
+     * @see WindowInsetsAnimation.Callback
+     * @see View#setWindowInsetsAnimationCallback(WindowInsetsAnimation.Callback)
+     */
+    void setInsetsAndAlpha(@Nullable Insets insets, @FloatRange(from = 0f, to = 1f) float alpha,
+            @FloatRange(from = 0f, to = 1f) float fraction);
+
+    /**
+     * Finishes the animation, and leaves the windows shown or hidden.
+     * <p>
+     * After invoking {@link #finish}, this instance is no longer {@link #isReady ready}.
+     * <p>
+     * Note: Finishing an animation implicitly {@link #setInsetsAndAlpha sets insets and alpha}
+     * according to the requested end state without any further animation.
+     *
+     * @param shown if {@code true}, the windows will be shown after finishing the
+     *              animation. Otherwise they will be hidden.
+     */
+    void finish(boolean shown);
+
+    /**
+     * Returns whether this instance is ready to be used to control window insets.
+     * <p>
+     * Instances are ready when passed in {@link WindowInsetsAnimationControlListener#onReady}
+     * and stop being ready when it is either {@link #isFinished() finished} or
+     * {@link #isCancelled() cancelled}.
+     *
+     * @return {@code true} if the instance is ready, {@code false} otherwise.
+     */
+    default boolean isReady() {
+        return !isFinished() && !isCancelled();
+    }
+
+    /**
+     * Returns whether this instance has been finished by a call to {@link #finish}.
+     *
+     * @see WindowInsetsAnimationControlListener#onFinished
+     * @return {@code true} if the instance is finished, {@code false} otherwise.
+     */
+    boolean isFinished();
+
+    /**
+     * Returns whether this instance has been cancelled by the system, or by invoking the
+     * {@link android.os.CancellationSignal} passed into
+     * {@link WindowInsetsController#controlWindowInsetsAnimation}.
+     *
+     * @see WindowInsetsAnimationControlListener#onCancelled
+     * @return {@code true} if the instance is cancelled, {@code false} otherwise.
+     */
+    boolean isCancelled();
+
+    /**
+     * @hide
+     * @return {@code true} when controller controls IME and IME has no insets (floating,
+     *  fullscreen or non-overlapping).
+     */
+    boolean hasZeroInsetsIme();
+}
diff --git a/android/view/WindowInsetsController.java b/android/view/WindowInsetsController.java
new file mode 100644
index 0000000..227b9f4
--- /dev/null
+++ b/android/view/WindowInsetsController.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Insets;
+import android.inputmethodservice.InputMethodService;
+import android.os.Build;
+import android.os.CancellationSignal;
+import android.view.InsetsState.InternalInsetsType;
+import android.view.WindowInsets.Type;
+import android.view.WindowInsets.Type.InsetsType;
+import android.view.animation.Interpolator;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Interface to control windows that generate insets.
+ */
+public interface WindowInsetsController {
+
+    /**
+     * Makes status bars become opaque with solid dark background and light foreground.
+     * @hide
+     */
+    int APPEARANCE_OPAQUE_STATUS_BARS = 1;
+
+    /**
+     * Makes navigation bars become opaque with solid dark background and light foreground.
+     * @hide
+     */
+    int APPEARANCE_OPAQUE_NAVIGATION_BARS = 1 << 1;
+
+    /**
+     * Makes items on system bars become less noticeable without changing the layout of the bars.
+     * @hide
+     */
+    int APPEARANCE_LOW_PROFILE_BARS = 1 << 2;
+
+    /**
+     * Changes the foreground color for light status bars so that the items on the bar can be read
+     * clearly.
+     */
+    int APPEARANCE_LIGHT_STATUS_BARS = 1 << 3;
+
+    /**
+     * Changes the foreground color for light navigation bars so that the items on the bar can be
+     * read clearly.
+     */
+    int APPEARANCE_LIGHT_NAVIGATION_BARS = 1 << 4;
+
+    /**
+     * Makes status bars semi-transparent with dark background and light foreground.
+     * @hide
+     */
+    int APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS = 1 << 5;
+
+    /**
+     * Makes navigation bars semi-transparent with dark background and light foreground.
+     * @hide
+     */
+    int APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS = 1 << 6;
+
+    /**
+     * Determines the appearance of system bars.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, value = {APPEARANCE_OPAQUE_STATUS_BARS, APPEARANCE_OPAQUE_NAVIGATION_BARS,
+            APPEARANCE_LOW_PROFILE_BARS, APPEARANCE_LIGHT_STATUS_BARS,
+            APPEARANCE_LIGHT_NAVIGATION_BARS, APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS,
+            APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS})
+    @interface Appearance {
+    }
+
+    /**
+     * Option for {@link #setSystemBarsBehavior(int)}. System bars will be forcibly shown on any
+     * user interaction on the corresponding display if navigation bars are hidden by
+     * {@link #hide(int)} or
+     * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)}.
+     * @deprecated This is not supported on Android {@link Build.VERSION_CODES#S} and later. Use
+     *             {@link #BEHAVIOR_DEFAULT} or {@link #BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE}
+     *             instead.
+     */
+    @Deprecated
+    int BEHAVIOR_SHOW_BARS_BY_TOUCH = 0;
+
+    /**
+     * The default option for {@link #setSystemBarsBehavior(int)}: Window would like to remain
+     * interactive when hiding navigation bars by calling {@link #hide(int)} or
+     * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)}.
+     *
+     * <p>When system bars are hidden in this mode, they can be revealed with system gestures, such
+     * as swiping from the edge of the screen where the bar is hidden from.</p>
+     *
+     * <p>When the gesture navigation is enabled, the system gestures can be triggered regardless
+     * the visibility of system bars.</p>
+     */
+    int BEHAVIOR_DEFAULT = 1;
+
+    /**
+     * Option for {@link #setSystemBarsBehavior(int)}: Window would like to remain interactive when
+     * hiding navigation bars by calling {@link #hide(int)} or
+     * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)}.
+     *
+     * <p>When system bars are hidden in this mode, they can be revealed with system gestures, such
+     * as swiping from the edge of the screen where the bar is hidden from.</p>
+     * @deprecated Use {@link #BEHAVIOR_DEFAULT} instead.
+     */
+    @Deprecated
+    int BEHAVIOR_SHOW_BARS_BY_SWIPE = BEHAVIOR_DEFAULT;
+
+    /**
+     * Option for {@link #setSystemBarsBehavior(int)}: Window would like to remain interactive when
+     * hiding navigation bars by calling {@link #hide(int)} or
+     * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)}.
+     *
+     * <p>When system bars are hidden in this mode, they can be revealed temporarily with system
+     * gestures, such as swiping from the edge of the screen where the bar is hidden from. These
+     * transient system bars will overlay app’s content, may have some degree of transparency, and
+     * will automatically hide after a short timeout.</p>
+     */
+    int BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE = 2;
+
+    /**
+     * Determines the behavior of system bars when hiding them by calling {@link #hide}.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {BEHAVIOR_DEFAULT, BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE})
+    @interface Behavior {
+    }
+
+    /**
+     * Makes a set of windows that cause insets appear on screen.
+     * <p>
+     * Note that if the window currently doesn't have control over a certain type, it will apply the
+     * change as soon as the window gains control. The app can listen to the event by observing
+     * {@link View#onApplyWindowInsets} and checking visibility with {@link WindowInsets#isVisible}.
+     *
+     * @param types A bitmask of {@link WindowInsets.Type} specifying what windows the app
+     *              would like to make appear on screen.
+     */
+    void show(@InsetsType int types);
+
+    /**
+     * Makes a set of windows causing insets disappear.
+     * <p>
+     * Note that if the window currently doesn't have control over a certain type, it will apply the
+     * change as soon as the window gains control. The app can listen to the event by observing
+     * {@link View#onApplyWindowInsets} and checking visibility with {@link WindowInsets#isVisible}.
+     *
+     * @param types A bitmask of {@link WindowInsets.Type} specifying what windows the app
+     *              would like to make disappear.
+     */
+    void hide(@InsetsType int types);
+
+    /**
+     * Lets the application control window inset animations in a frame-by-frame manner by modifying
+     * the position of the windows in the system causing insets directly.
+     *
+     * @param types The {@link WindowInsets.Type}s the application has requested to control.
+     * @param durationMillis Duration of animation in
+     *                       {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or -1 if the
+     *                       animation doesn't have a predetermined duration. This value will be
+     *                       passed to {@link WindowInsetsAnimation#getDurationMillis()}
+     * @param interpolator The interpolator used for this animation, or {@code null} if this
+     *                     animation doesn't follow an interpolation curve. This value will be
+     *                     passed to {@link WindowInsetsAnimation#getInterpolator()} and used to
+     *                     calculate {@link WindowInsetsAnimation#getInterpolatedFraction()}.
+     * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the
+     *                 windows are ready to be controlled, among other callbacks.
+     * @param cancellationSignal A cancellation signal that the caller can use to cancel the
+     *                           request to obtain control, or once they have control, to cancel the
+     *                           control.
+     * @see WindowInsetsAnimation#getFraction()
+     * @see WindowInsetsAnimation#getInterpolatedFraction()
+     * @see WindowInsetsAnimation#getInterpolator()
+     * @see WindowInsetsAnimation#getDurationMillis()
+     */
+    void controlWindowInsetsAnimation(@InsetsType int types, long durationMillis,
+            @Nullable Interpolator interpolator,
+            @Nullable CancellationSignal cancellationSignal,
+            @NonNull WindowInsetsAnimationControlListener listener);
+
+    /**
+     * Controls the appearance of system bars.
+     * <p>
+     * For example, the following statement adds {@link #APPEARANCE_LIGHT_STATUS_BARS}:
+     * <pre>
+     * setSystemBarsAppearance(APPEARANCE_LIGHT_STATUS_BARS, APPEARANCE_LIGHT_STATUS_BARS)
+     * </pre>
+     * And the following statement clears it:
+     * <pre>
+     * setSystemBarsAppearance(0, APPEARANCE_LIGHT_STATUS_BARS)
+     * </pre>
+     *
+     * @param appearance Bitmask of appearance flags.
+     * @param mask Specifies which flags of appearance should be changed.
+     * @see #getSystemBarsAppearance
+     */
+    void setSystemBarsAppearance(@Appearance int appearance, @Appearance int mask);
+
+    /**
+     * Retrieves the requested appearance of system bars.
+     *
+     * @return The requested bitmask of system bar appearance controlled by this window.
+     * @see #setSystemBarsAppearance(int, int)
+     */
+    @Appearance int getSystemBarsAppearance();
+
+    /**
+     * Notify the caption insets height change. The information will be used on the client side to,
+     * make sure the InsetsState has the correct caption insets.
+     *
+     * @param height the height of caption bar insets.
+     * @hide
+     */
+    void setCaptionInsetsHeight(int height);
+
+    /**
+     * Controls the behavior of system bars.
+     *
+     * @param behavior Determines how the bars behave when being hidden by the application.
+     * @see #getSystemBarsBehavior
+     */
+    void setSystemBarsBehavior(@Behavior int behavior);
+
+    /**
+     * Retrieves the requested behavior of system bars.
+     *
+     * @return the system bar behavior controlled by this window.
+     * @see #setSystemBarsBehavior(int)
+     */
+    @Behavior int getSystemBarsBehavior();
+
+    /**
+     * Disables or enables the animations.
+     *
+     * @hide
+     */
+    void setAnimationsDisabled(boolean disable);
+
+    /**
+     * @hide
+     */
+    InsetsState getState();
+
+    /**
+     * @return Whether the specified insets source is currently requested to be visible by the
+     *         application.
+     * @hide
+     */
+    boolean isRequestedVisible(@InternalInsetsType int type);
+
+    /**
+     * Adds a {@link OnControllableInsetsChangedListener} to the window insets controller.
+     *
+     * @param listener The listener to add.
+     *
+     * @see OnControllableInsetsChangedListener
+     * @see #removeOnControllableInsetsChangedListener(OnControllableInsetsChangedListener)
+     */
+    void addOnControllableInsetsChangedListener(
+            @NonNull OnControllableInsetsChangedListener listener);
+
+    /**
+     * Removes a {@link OnControllableInsetsChangedListener} from the window insets controller.
+     *
+     * @param listener The listener to remove.
+     *
+     * @see OnControllableInsetsChangedListener
+     * @see #addOnControllableInsetsChangedListener(OnControllableInsetsChangedListener)
+     */
+    void removeOnControllableInsetsChangedListener(
+            @NonNull OnControllableInsetsChangedListener listener);
+
+    /**
+     * Listener to be notified when the set of controllable {@link WindowInsets.Type} controlled by
+     * a {@link WindowInsetsController} changes.
+     * <p>
+     * Once a {@link WindowInsets.Type} becomes controllable, the app will be able to control the
+     * window that is causing this type of insets by calling {@link #controlWindowInsetsAnimation}.
+     * <p>
+     * Note: When listening to controllability of the {@link Type#ime},
+     * {@link #controlWindowInsetsAnimation} may still fail in case the {@link InputMethodService}
+     * decides to cancel the show request. This could happen when there is a hardware keyboard
+     * attached.
+     *
+     * @see #addOnControllableInsetsChangedListener(OnControllableInsetsChangedListener)
+     * @see #removeOnControllableInsetsChangedListener(OnControllableInsetsChangedListener)
+     */
+    interface OnControllableInsetsChangedListener {
+
+        /**
+         * Called when the set of controllable {@link WindowInsets.Type} changes.
+         *
+         * @param controller The controller for which the set of controllable
+         *                   {@link WindowInsets.Type}s are changing.
+         * @param typeMask Bitwise type-mask of the {@link WindowInsets.Type}s the controller is
+         *                 currently able to control.
+         */
+        void onControllableInsetsChanged(@NonNull WindowInsetsController controller,
+                @InsetsType int typeMask);
+    }
+}
diff --git a/android/view/WindowManager.java b/android/view/WindowManager.java
new file mode 100644
index 0000000..55beae0
--- /dev/null
+++ b/android/view/WindowManager.java
@@ -0,0 +1,4621 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.content.pm.ActivityInfo.COLOR_MODE_DEFAULT;
+import static android.view.View.STATUS_BAR_DISABLE_BACK;
+import static android.view.View.STATUS_BAR_DISABLE_CLOCK;
+import static android.view.View.STATUS_BAR_DISABLE_EXPAND;
+import static android.view.View.STATUS_BAR_DISABLE_HOME;
+import static android.view.View.STATUS_BAR_DISABLE_NOTIFICATION_ALERTS;
+import static android.view.View.STATUS_BAR_DISABLE_NOTIFICATION_ICONS;
+import static android.view.View.STATUS_BAR_DISABLE_NOTIFICATION_TICKER;
+import static android.view.View.STATUS_BAR_DISABLE_RECENT;
+import static android.view.View.STATUS_BAR_DISABLE_SEARCH;
+import static android.view.View.STATUS_BAR_DISABLE_SYSTEM_INFO;
+import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
+import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE;
+import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
+import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+import static android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+import static android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE;
+import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
+import static android.view.WindowInsets.Side.BOTTOM;
+import static android.view.WindowInsets.Side.LEFT;
+import static android.view.WindowInsets.Side.RIGHT;
+import static android.view.WindowInsets.Side.TOP;
+import static android.view.WindowInsets.Type.CAPTION_BAR;
+import static android.view.WindowInsets.Type.IME;
+import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES;
+import static android.view.WindowInsets.Type.NAVIGATION_BARS;
+import static android.view.WindowInsets.Type.STATUS_BARS;
+import static android.view.WindowInsets.Type.SYSTEM_GESTURES;
+import static android.view.WindowInsets.Type.TAPPABLE_ELEMENT;
+import static android.view.WindowInsets.Type.WINDOW_DECOR;
+import static android.view.WindowLayoutParamsProto.ALPHA;
+import static android.view.WindowLayoutParamsProto.APPEARANCE;
+import static android.view.WindowLayoutParamsProto.BEHAVIOR;
+import static android.view.WindowLayoutParamsProto.BUTTON_BRIGHTNESS;
+import static android.view.WindowLayoutParamsProto.COLOR_MODE;
+import static android.view.WindowLayoutParamsProto.FIT_IGNORE_VISIBILITY;
+import static android.view.WindowLayoutParamsProto.FIT_INSETS_SIDES;
+import static android.view.WindowLayoutParamsProto.FIT_INSETS_TYPES;
+import static android.view.WindowLayoutParamsProto.FLAGS;
+import static android.view.WindowLayoutParamsProto.FORMAT;
+import static android.view.WindowLayoutParamsProto.GRAVITY;
+import static android.view.WindowLayoutParamsProto.HAS_SYSTEM_UI_LISTENERS;
+import static android.view.WindowLayoutParamsProto.HEIGHT;
+import static android.view.WindowLayoutParamsProto.HORIZONTAL_MARGIN;
+import static android.view.WindowLayoutParamsProto.INPUT_FEATURE_FLAGS;
+import static android.view.WindowLayoutParamsProto.PREFERRED_REFRESH_RATE;
+import static android.view.WindowLayoutParamsProto.PRIVATE_FLAGS;
+import static android.view.WindowLayoutParamsProto.ROTATION_ANIMATION;
+import static android.view.WindowLayoutParamsProto.SCREEN_BRIGHTNESS;
+import static android.view.WindowLayoutParamsProto.SOFT_INPUT_MODE;
+import static android.view.WindowLayoutParamsProto.SUBTREE_SYSTEM_UI_VISIBILITY_FLAGS;
+import static android.view.WindowLayoutParamsProto.SYSTEM_UI_VISIBILITY_FLAGS;
+import static android.view.WindowLayoutParamsProto.TYPE;
+import static android.view.WindowLayoutParamsProto.USER_ACTIVITY_TIMEOUT;
+import static android.view.WindowLayoutParamsProto.VERTICAL_MARGIN;
+import static android.view.WindowLayoutParamsProto.WIDTH;
+import static android.view.WindowLayoutParamsProto.WINDOW_ANIMATIONS;
+import static android.view.WindowLayoutParamsProto.X;
+import static android.view.WindowLayoutParamsProto.Y;
+
+import android.Manifest.permission;
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.app.KeyguardManager;
+import android.app.Presentation;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ClipData;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+import android.view.Gravity.GravityFlags;
+import android.view.View.OnApplyWindowInsetsListener;
+import android.view.WindowInsets.Side;
+import android.view.WindowInsets.Side.InsetsSide;
+import android.view.WindowInsets.Type;
+import android.view.WindowInsets.Type.InsetsType;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * The interface that apps use to talk to the window manager.
+ * </p><p>
+ * Each window manager instance is bound to a particular {@link Display}.
+ * To obtain a {@link WindowManager} for a different display, use
+ * {@link Context#createDisplayContext} to obtain a {@link Context} for that
+ * display, then use <code>Context.getSystemService(Context.WINDOW_SERVICE)</code>
+ * to get the WindowManager.
+ * </p><p>
+ * The simplest way to show a window on another display is to create a
+ * {@link Presentation}.  The presentation will automatically obtain a
+ * {@link WindowManager} and {@link Context} for that display.
+ * </p>
+ */
+@SystemService(Context.WINDOW_SERVICE)
+public interface WindowManager extends ViewManager {
+
+    /** @hide */
+    int DOCKED_INVALID = -1;
+    /** @hide */
+    int DOCKED_LEFT = 1;
+    /** @hide */
+    int DOCKED_TOP = 2;
+    /** @hide */
+    int DOCKED_RIGHT = 3;
+    /** @hide */
+    int DOCKED_BOTTOM = 4;
+
+    /** @hide */
+    String INPUT_CONSUMER_PIP = "pip_input_consumer";
+    /** @hide */
+    String INPUT_CONSUMER_NAVIGATION = "nav_input_consumer";
+    /** @hide */
+    String INPUT_CONSUMER_WALLPAPER = "wallpaper_input_consumer";
+    /** @hide */
+    String INPUT_CONSUMER_RECENTS_ANIMATION = "recents_animation_input_consumer";
+
+    /** @hide */
+    int SHELL_ROOT_LAYER_DIVIDER = 0;
+    /** @hide */
+    int SHELL_ROOT_LAYER_PIP = 1;
+
+    /**
+     * Declares the layer the shell root will belong to. This is for z-ordering.
+     * @hide
+     */
+    @IntDef(prefix = { "SHELL_ROOT_LAYER_" }, value = {
+            SHELL_ROOT_LAYER_DIVIDER,
+            SHELL_ROOT_LAYER_PIP
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ShellRootLayer {}
+
+    /**
+     * Not set up for a transition.
+     * @hide
+     */
+    int TRANSIT_OLD_UNSET = -1;
+
+    /**
+     * No animation for transition.
+     * @hide
+     */
+    int TRANSIT_OLD_NONE = 0;
+
+    /**
+     * A window in a new activity is being opened on top of an existing one in the same task.
+     * @hide
+     */
+    int TRANSIT_OLD_ACTIVITY_OPEN = 6;
+
+    /**
+     * The window in the top-most activity is being closed to reveal the previous activity in the
+     * same task.
+     * @hide
+     */
+    int TRANSIT_OLD_ACTIVITY_CLOSE = 7;
+
+    /**
+     * A window in a new task is being opened on top of an existing one in another activity's task.
+     * @hide
+     */
+    int TRANSIT_OLD_TASK_OPEN = 8;
+
+    /**
+     * A window in the top-most activity is being closed to reveal the previous activity in a
+     * different task.
+     * @hide
+     */
+    int TRANSIT_OLD_TASK_CLOSE = 9;
+
+    /**
+     * A window in an existing task is being displayed on top of an existing one in another
+     * activity's task.
+     * @hide
+     */
+    int TRANSIT_OLD_TASK_TO_FRONT = 10;
+
+    /**
+     * A window in an existing task is being put below all other tasks.
+     * @hide
+     */
+    int TRANSIT_OLD_TASK_TO_BACK = 11;
+
+    /**
+     * A window in a new activity that doesn't have a wallpaper is being opened on top of one that
+     * does, effectively closing the wallpaper.
+     * @hide
+     */
+    int TRANSIT_OLD_WALLPAPER_CLOSE = 12;
+
+    /**
+     * A window in a new activity that does have a wallpaper is being opened on one that didn't,
+     * effectively opening the wallpaper.
+     * @hide
+     */
+    int TRANSIT_OLD_WALLPAPER_OPEN = 13;
+
+    /**
+     * A window in a new activity is being opened on top of an existing one, and both are on top
+     * of the wallpaper.
+     * @hide
+     */
+    int TRANSIT_OLD_WALLPAPER_INTRA_OPEN = 14;
+
+    /**
+     * The window in the top-most activity is being closed to reveal the previous activity, and
+     * both are on top of the wallpaper.
+     * @hide
+     */
+    int TRANSIT_OLD_WALLPAPER_INTRA_CLOSE = 15;
+
+    /**
+     * A window in a new task is being opened behind an existing one in another activity's task.
+     * The new window will show briefly and then be gone.
+     * @hide
+     */
+    int TRANSIT_OLD_TASK_OPEN_BEHIND = 16;
+
+    /**
+     * An activity is being relaunched (e.g. due to configuration change).
+     * @hide
+     */
+    int TRANSIT_OLD_ACTIVITY_RELAUNCH = 18;
+
+    /**
+     * Keyguard is going away.
+     * @hide
+     */
+    int TRANSIT_OLD_KEYGUARD_GOING_AWAY = 20;
+
+    /**
+     * Keyguard is going away with showing an activity behind that requests wallpaper.
+     * @hide
+     */
+    int TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER = 21;
+
+    /**
+     * Keyguard is being occluded.
+     * @hide
+     */
+    int TRANSIT_OLD_KEYGUARD_OCCLUDE = 22;
+
+    /**
+     * Keyguard is being unoccluded.
+     * @hide
+     */
+    int TRANSIT_OLD_KEYGUARD_UNOCCLUDE = 23;
+
+    /**
+     * A translucent activity is being opened.
+     * @hide
+     */
+    int TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN = 24;
+
+    /**
+     * A translucent activity is being closed.
+     * @hide
+     */
+    int TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE = 25;
+
+    /**
+     * A crashing activity is being closed.
+     * @hide
+     */
+    int TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE = 26;
+
+    /**
+     * A task is changing windowing modes
+     * @hide
+     */
+    int TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE = 27;
+
+    /**
+     * @hide
+     */
+    @IntDef(prefix = { "TRANSIT_OLD_" }, value = {
+            TRANSIT_OLD_UNSET,
+            TRANSIT_OLD_NONE,
+            TRANSIT_OLD_ACTIVITY_OPEN,
+            TRANSIT_OLD_ACTIVITY_CLOSE,
+            TRANSIT_OLD_TASK_OPEN,
+            TRANSIT_OLD_TASK_CLOSE,
+            TRANSIT_OLD_TASK_TO_FRONT,
+            TRANSIT_OLD_TASK_TO_BACK,
+            TRANSIT_OLD_WALLPAPER_CLOSE,
+            TRANSIT_OLD_WALLPAPER_OPEN,
+            TRANSIT_OLD_WALLPAPER_INTRA_OPEN,
+            TRANSIT_OLD_WALLPAPER_INTRA_CLOSE,
+            TRANSIT_OLD_TASK_OPEN_BEHIND,
+            TRANSIT_OLD_ACTIVITY_RELAUNCH,
+            TRANSIT_OLD_KEYGUARD_GOING_AWAY,
+            TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
+            TRANSIT_OLD_KEYGUARD_OCCLUDE,
+            TRANSIT_OLD_KEYGUARD_UNOCCLUDE,
+            TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN,
+            TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE,
+            TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE,
+            TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface TransitionOldType {}
+
+    /** @hide */
+    int TRANSIT_NONE = 0;
+    /**
+     * A window that didn't exist before has been created and made visible.
+     * @hide
+     */
+    int TRANSIT_OPEN = 1;
+    /**
+     * A window that was visible no-longer exists (was finished or destroyed).
+     * @hide
+     */
+    int TRANSIT_CLOSE = 2;
+    /**
+     * A window that already existed but was not visible is made visible.
+     * @hide
+     */
+    int TRANSIT_TO_FRONT = 3;
+    /**
+     * A window that was visible is made invisible but still exists.
+     * @hide
+     */
+    int TRANSIT_TO_BACK = 4;
+    /** @hide */
+    int TRANSIT_RELAUNCH = 5;
+    /**
+     * A window is visible before and after but changes in some way (eg. it resizes or changes
+     * windowing-mode).
+     * @hide
+     */
+    int TRANSIT_CHANGE = 6;
+    /**
+     * The keyguard was visible and has been dismissed.
+     * @hide
+     */
+    int TRANSIT_KEYGUARD_GOING_AWAY = 7;
+    /**
+     * A window is appearing above a locked keyguard.
+     * @hide
+     */
+    int TRANSIT_KEYGUARD_OCCLUDE = 8;
+    /**
+     * A window is made invisible revealing a locked keyguard.
+     * @hide
+     */
+    int TRANSIT_KEYGUARD_UNOCCLUDE = 9;
+    /**
+     * The first slot for custom transition types. Callers (like Shell) can make use of custom
+     * transition types for dealing with special cases. These types are effectively ignored by
+     * Core and will just be passed along as part of TransitionInfo objects. An example is
+     * split-screen using a custom type for it's snap-to-dismiss action. By using a custom type,
+     * Shell can properly dispatch the results of that transition to the split-screen
+     * implementation.
+     * @hide
+     */
+    int TRANSIT_FIRST_CUSTOM = 10;
+
+    /**
+     * @hide
+     */
+    @IntDef(prefix = { "TRANSIT_" }, value = {
+            TRANSIT_NONE,
+            TRANSIT_OPEN,
+            TRANSIT_CLOSE,
+            TRANSIT_TO_FRONT,
+            TRANSIT_TO_BACK,
+            TRANSIT_RELAUNCH,
+            TRANSIT_CHANGE,
+            TRANSIT_KEYGUARD_GOING_AWAY,
+            TRANSIT_KEYGUARD_OCCLUDE,
+            TRANSIT_KEYGUARD_UNOCCLUDE,
+            TRANSIT_FIRST_CUSTOM
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface TransitionType {}
+
+    /**
+     * Transition flag: Keyguard is going away, but keeping the notification shade open
+     * @hide
+     */
+    int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE = 0x1;
+
+    /**
+     * Transition flag: Keyguard is going away, but doesn't want an animation for it
+     * @hide
+     */
+    int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION = 0x2;
+
+    /**
+     * Transition flag: Keyguard is going away while it was showing the system wallpaper.
+     * @hide
+     */
+    int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER = 0x4;
+
+    /**
+     * Transition flag: Keyguard is going away with subtle animation.
+     * @hide
+     */
+    int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION = 0x8;
+
+    /**
+     * Transition flag: App is crashed.
+     * @hide
+     */
+    int TRANSIT_FLAG_APP_CRASHED = 0x10;
+
+    /**
+     * Transition flag: A window in a new task is being opened behind an existing one in another
+     * activity's task.
+     * @hide
+     */
+    int TRANSIT_FLAG_OPEN_BEHIND = 0x20;
+
+    /**
+     * Transition flag: The keyguard is locked throughout the whole transition.
+     * @hide
+     */
+    int TRANSIT_FLAG_KEYGUARD_LOCKED = 0x40;
+
+    /**
+     * @hide
+     */
+    @IntDef(flag = true, prefix = { "TRANSIT_FLAG_" }, value = {
+            TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE,
+            TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION,
+            TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER,
+            TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION,
+            TRANSIT_FLAG_APP_CRASHED,
+            TRANSIT_FLAG_OPEN_BEHIND,
+            TRANSIT_FLAG_KEYGUARD_LOCKED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface TransitionFlags {}
+
+    /**
+     * Remove content mode: Indicates remove content mode is currently not defined.
+     * @hide
+     */
+    int REMOVE_CONTENT_MODE_UNDEFINED = 0;
+
+    /**
+     * Remove content mode: Indicates that when display is removed, all its activities will be moved
+     * to the primary display and the topmost activity should become focused.
+     * @hide
+     */
+    int REMOVE_CONTENT_MODE_MOVE_TO_PRIMARY = 1;
+
+    /**
+     * Remove content mode: Indicates that when display is removed, all its stacks and tasks will be
+     * removed, all activities will be destroyed according to the usual lifecycle.
+     * @hide
+     */
+    int REMOVE_CONTENT_MODE_DESTROY = 2;
+
+    /**
+     * @hide
+     */
+    @IntDef(prefix = { "REMOVE_CONTENT_MODE_" }, value = {
+            REMOVE_CONTENT_MODE_UNDEFINED,
+            REMOVE_CONTENT_MODE_MOVE_TO_PRIMARY,
+            REMOVE_CONTENT_MODE_DESTROY,
+    })
+    @interface RemoveContentMode {}
+
+    /**
+     * Display IME Policy: The IME should appear on the local display.
+     * @hide
+     */
+    @TestApi
+    int DISPLAY_IME_POLICY_LOCAL = 0;
+
+    /**
+     * Display IME Policy: The IME should appear on the fallback display.
+     * @hide
+     */
+    @TestApi
+    int DISPLAY_IME_POLICY_FALLBACK_DISPLAY = 1;
+
+    /**
+     * Display IME Policy: The IME should be hidden.
+     *
+     * Setting this policy will prevent the IME from making a connection. This
+     * will prevent any IME from receiving metadata about input.
+     * @hide
+     */
+    @TestApi
+    int DISPLAY_IME_POLICY_HIDE = 2;
+
+    /**
+     * @hide
+     */
+    @IntDef({
+            DISPLAY_IME_POLICY_LOCAL,
+            DISPLAY_IME_POLICY_FALLBACK_DISPLAY,
+            DISPLAY_IME_POLICY_HIDE,
+    })
+    @interface DisplayImePolicy {}
+
+    /**
+     * Exception that is thrown when trying to add view whose
+     * {@link LayoutParams} {@link LayoutParams#token}
+     * is invalid.
+     */
+    public static class BadTokenException extends RuntimeException {
+        public BadTokenException() {
+        }
+
+        public BadTokenException(String name) {
+            super(name);
+        }
+    }
+
+    /**
+     * Exception that is thrown when calling {@link #addView} to a secondary display that cannot
+     * be found. See {@link android.app.Presentation} for more information on secondary displays.
+     */
+    public static class InvalidDisplayException extends RuntimeException {
+        public InvalidDisplayException() {
+        }
+
+        public InvalidDisplayException(String name) {
+            super(name);
+        }
+    }
+
+    /**
+     * Returns the {@link Display} upon which this {@link WindowManager} instance
+     * will create new windows.
+     * <p>
+     * Despite the name of this method, the display that is returned is not
+     * necessarily the primary display of the system (see {@link Display#DEFAULT_DISPLAY}).
+     * The returned display could instead be a secondary display that this
+     * window manager instance is managing.  Think of it as the display that
+     * this {@link WindowManager} instance uses by default.
+     * </p><p>
+     * To create windows on a different display, you need to obtain a
+     * {@link WindowManager} for that {@link Display}.  (See the {@link WindowManager}
+     * class documentation for more information.)
+     * </p>
+     *
+     * @return The display that this window manager is managing.
+     * @deprecated Use {@link Context#getDisplay()} instead.
+     */
+    @Deprecated
+    public Display getDefaultDisplay();
+
+    /**
+     * Special variation of {@link #removeView} that immediately invokes
+     * the given view hierarchy's {@link View#onDetachedFromWindow()
+     * View.onDetachedFromWindow()} methods before returning.  This is not
+     * for normal applications; using it correctly requires great care.
+     *
+     * @param view The view to be removed.
+     */
+    public void removeViewImmediate(View view);
+
+    /**
+     * Returns the {@link WindowMetrics} according to the current system state.
+     * <p>
+     * The metrics describe the size of the area the window would occupy with
+     * {@link LayoutParams#MATCH_PARENT MATCH_PARENT} width and height, and the {@link WindowInsets}
+     * such a window would have.
+     * <p>
+     * The value of this is based on the <b>current</b> windowing state of the system.
+     *
+     * For example, for activities in multi-window mode, the metrics returned are based on the
+     * current bounds that the user has selected for the {@link android.app.Activity Activity}'s
+     * task.
+     * <p>
+     * In most scenarios, {@link #getCurrentWindowMetrics()} rather than
+     * {@link #getMaximumWindowMetrics()} is the correct API to use, since it ensures values reflect
+     * window size when the app is not fullscreen.
+     *
+     * @see #getMaximumWindowMetrics()
+     * @see WindowMetrics
+     */
+    default @NonNull WindowMetrics getCurrentWindowMetrics() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Returns the largest {@link WindowMetrics} an app may expect in the current system state.
+     * <p>
+     * The value of this is based on the largest <b>potential</b> windowing state of the system.
+     *
+     * For example, for activities in multi-window mode, the metrics returned are based on the
+     * what the bounds would be if the user expanded the {@link android.app.Activity Activity}'s
+     * task to cover the entire screen.
+     * <p>
+     * The metrics describe the size of the largest potential area the window might occupy with
+     * {@link LayoutParams#MATCH_PARENT MATCH_PARENT} width and height, and the {@link WindowInsets}
+     * such a window would have.
+     * <p>
+     * Note that this might still be smaller than the size of the physical display if certain areas
+     * of the display are not available to windows created in this {@link Context}.
+     *
+     * For example, given that there's a device which have a multi-task mode to limit activities
+     * to a half screen. In this case, {@link #getMaximumWindowMetrics()} reports the bounds of
+     * the half screen which the activity is located.
+     * <p>
+     * <b>Generally {@link #getCurrentWindowMetrics()} is the correct API to use</b> for choosing
+     * UI layouts. {@link #getMaximumWindowMetrics()} are only appropriate when the application
+     * needs to know the largest possible size it can occupy if the user expands/maximizes it on the
+     * screen.
+     *
+     * @see #getCurrentWindowMetrics()
+     * @see WindowMetrics
+     * @see Display#getRealSize(Point)
+     */
+    default @NonNull WindowMetrics getMaximumWindowMetrics() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Used to asynchronously request Keyboard Shortcuts from the focused window.
+     *
+     * @hide
+     */
+    public interface KeyboardShortcutsReceiver {
+        /**
+         * Callback used when the focused window keyboard shortcuts are ready to be displayed.
+         *
+         * @param result The keyboard shortcuts to be displayed.
+         */
+        void onKeyboardShortcutsReceived(List<KeyboardShortcutGroup> result);
+    }
+
+    /**
+     * Invoke screenshot flow to capture a full-screen image.
+     * @hide
+     */
+    int TAKE_SCREENSHOT_FULLSCREEN = 1;
+
+    /**
+     * Invoke screenshot flow allowing the user to select a region.
+     * @hide
+     */
+    int TAKE_SCREENSHOT_SELECTED_REGION = 2;
+
+    /**
+     * Invoke screenshot flow with an image provided by the caller.
+     * @hide
+     */
+    int TAKE_SCREENSHOT_PROVIDED_IMAGE = 3;
+
+    /**
+     * Enum listing the types of screenshot requests available.
+     *
+     * @hide
+     */
+    @IntDef({TAKE_SCREENSHOT_FULLSCREEN,
+            TAKE_SCREENSHOT_SELECTED_REGION,
+            TAKE_SCREENSHOT_PROVIDED_IMAGE})
+    @interface ScreenshotType {}
+
+    /**
+     * Enum listing the possible sources from which a screenshot was originated. Used for logging.
+     *
+     * @hide
+     */
+    @IntDef({ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS,
+            ScreenshotSource.SCREENSHOT_KEY_CHORD,
+            ScreenshotSource.SCREENSHOT_KEY_OTHER,
+            ScreenshotSource.SCREENSHOT_OVERVIEW,
+            ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS,
+            ScreenshotSource.SCREENSHOT_OTHER,
+            ScreenshotSource.SCREENSHOT_VENDOR_GESTURE})
+    @interface ScreenshotSource {
+        int SCREENSHOT_GLOBAL_ACTIONS = 0;
+        int SCREENSHOT_KEY_CHORD = 1;
+        int SCREENSHOT_KEY_OTHER = 2;
+        int SCREENSHOT_OVERVIEW = 3;
+        int SCREENSHOT_ACCESSIBILITY_ACTIONS = 4;
+        int SCREENSHOT_OTHER = 5;
+        int SCREENSHOT_VENDOR_GESTURE = 6;
+    }
+
+    /**
+     * @hide
+     */
+    public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array";
+
+    /**
+     * Request for keyboard shortcuts to be retrieved asynchronously.
+     *
+     * @param receiver The callback to be triggered when the result is ready.
+     *
+     * @hide
+     */
+    public void requestAppKeyboardShortcuts(final KeyboardShortcutsReceiver receiver, int deviceId);
+
+    /**
+     * Return the touch region for the current IME window, or an empty region if there is none.
+     *
+     * @return The region of the IME that is accepting touch inputs, or null if there is no IME, no
+     *         region or there was an error.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
+    public Region getCurrentImeTouchRegion();
+
+    /**
+     * Sets that the display should show its content when non-secure keyguard is shown.
+     *
+     * @param displayId Display ID.
+     * @param shouldShow Indicates that the display should show its content when non-secure keyguard
+     *                  is shown.
+     * @see KeyguardManager#isDeviceSecure()
+     * @see KeyguardManager#isDeviceLocked()
+     * @hide
+     */
+    @TestApi
+    default void setShouldShowWithInsecureKeyguard(int displayId, boolean shouldShow) {
+    }
+
+    /**
+     * Sets that the display should show system decors.
+     * <p>
+     * System decors include status bar, navigation bar, launcher.
+     * </p>
+     *
+     * @param displayId The id of the display.
+     * @param shouldShow Indicates that the display should show system decors.
+     * @see #shouldShowSystemDecors(int)
+     * @hide
+     */
+    @TestApi
+    default void setShouldShowSystemDecors(int displayId, boolean shouldShow) {
+    }
+
+    /**
+     * Checks if the display supports showing system decors.
+     * <p>
+     * System decors include status bar, navigation bar, launcher.
+     * </p>
+     *
+     * @param displayId The id of the display.
+     * @see #setShouldShowSystemDecors(int, boolean)
+     * @hide
+     */
+    @TestApi
+    default boolean shouldShowSystemDecors(int displayId) {
+        return false;
+    }
+
+    /**
+     * Sets the policy for how the display should show IME.
+     *
+     * @param displayId Display ID.
+     * @param imePolicy Indicates the policy for how the display should show IME.
+     * @hide
+     */
+    @TestApi
+    default void setDisplayImePolicy(int displayId, @DisplayImePolicy int imePolicy) {
+    }
+
+    /**
+     * Indicates the policy for how the display should show IME.
+     *
+     * @param displayId The id of the display.
+     * @return The policy for how the display should show IME.
+     * @hide
+     */
+    @TestApi
+    default @DisplayImePolicy int getDisplayImePolicy(int displayId) {
+        return DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
+    }
+
+    /**
+     * <p>
+     * Returns whether cross-window blur is currently enabled. This affects both window blur behind
+     * (see {@link LayoutParams#setBlurBehindRadius}) and window background blur (see
+     * {@link Window#setBackgroundBlurRadius}).
+     * </p><p>
+     * Cross-window blur might not be supported by some devices due to GPU limitations. It can also
+     * be disabled at runtime, e.g. during battery saving mode, when multimedia tunneling is used or
+     * when minimal post processing is requested. In such situations, no blur will be computed or
+     * drawn, so the blur target area will not be blurred. To handle this, the app might want to
+     * change its theme to one that does not use blurs. To listen for cross-window blur
+     * enabled/disabled events, use {@link #addCrossWindowBlurEnabledListener}.
+     * </p>
+     *
+     * @see #addCrossWindowBlurEnabledListener
+     * @see LayoutParams#setBlurBehindRadius
+     * @see Window#setBackgroundBlurRadius
+     */
+    default boolean isCrossWindowBlurEnabled() {
+        return false;
+    }
+
+    /**
+     * <p>
+     * Adds a listener, which will be called when cross-window blurs are enabled/disabled at
+     * runtime. This affects both window blur behind (see {@link LayoutParams#setBlurBehindRadius})
+     * and window background blur (see {@link Window#setBackgroundBlurRadius}).
+     * </p><p>
+     * Cross-window blur might not be supported by some devices due to GPU limitations. It can also
+     * be disabled at runtime, e.g. during battery saving mode, when multimedia tunneling is used or
+     * when minimal post processing is requested. In such situations, no blur will be computed or
+     * drawn, so the blur target area will not be blurred. To handle this, the app might want to
+     * change its theme to one that does not use blurs.
+     * </p><p>
+     * The listener will be called on the main thread.
+     * </p><p>
+     * If the listener is added successfully, it will be called immediately with the current
+     * cross-window blur enabled state.
+     * </p>
+     *
+     * @param listener the listener to be added. It will be called back with a boolean parameter,
+     *                 which is true if cross-window blur is enabled and false if it is disabled
+     *
+     * @see #removeCrossWindowBlurEnabledListener
+     * @see #isCrossWindowBlurEnabled
+     * @see LayoutParams#setBlurBehindRadius
+     * @see Window#setBackgroundBlurRadius
+     */
+    default void addCrossWindowBlurEnabledListener(@NonNull Consumer<Boolean> listener) {
+    }
+
+    /**
+     * <p>
+     * Adds a listener, which will be called when cross-window blurs are enabled/disabled at
+     * runtime. This affects both window blur behind (see {@link LayoutParams#setBlurBehindRadius})
+     * and window background blur (see {@link Window#setBackgroundBlurRadius}).
+     * </p><p>
+     * Cross-window blur might not be supported by some devices due to GPU limitations. It can also
+     * be disabled at runtime, e.g. during battery saving mode, when multimedia tunneling is used or
+     * when minimal post processing is requested. In such situations, no blur will be computed or
+     * drawn, so the blur target area will not be blurred. To handle this, the app might want to
+     * change its theme to one that does not use blurs.
+     * </p><p>
+     * If the listener is added successfully, it will be called immediately with the current
+     * cross-window blur enabled state.
+     * </p>
+     *
+     * @param executor {@link Executor} to handle the listener callback
+     * @param listener the listener to be added. It will be called back with a boolean parameter,
+     *                 which is true if cross-window blur is enabled and false if it is disabled
+     *
+     * @see #removeCrossWindowBlurEnabledListener
+     * @see #isCrossWindowBlurEnabled
+     * @see LayoutParams#setBlurBehindRadius
+     * @see Window#setBackgroundBlurRadius
+     */
+    default void addCrossWindowBlurEnabledListener(@NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<Boolean> listener) {
+    }
+
+    /**
+     * Removes a listener, previously added with {@link #addCrossWindowBlurEnabledListener}
+     *
+     * @param listener the listener to be removed
+     *
+     * @see #addCrossWindowBlurEnabledListener
+     */
+    default void removeCrossWindowBlurEnabledListener(@NonNull Consumer<Boolean> listener) {
+    }
+
+    /**
+     * @hide
+     */
+    static String transitTypeToString(@TransitionType int type) {
+        switch (type) {
+            case TRANSIT_NONE: return "NONE";
+            case TRANSIT_OPEN: return "OPEN";
+            case TRANSIT_CLOSE: return "CLOSE";
+            case TRANSIT_TO_FRONT: return "TO_FRONT";
+            case TRANSIT_TO_BACK: return "TO_BACK";
+            case TRANSIT_RELAUNCH: return "RELAUNCH";
+            case TRANSIT_CHANGE: return "CHANGE";
+            case TRANSIT_KEYGUARD_GOING_AWAY: return "KEYGUARD_GOING_AWAY";
+            case TRANSIT_KEYGUARD_OCCLUDE: return "KEYGUARD_OCCLUDE";
+            case TRANSIT_KEYGUARD_UNOCCLUDE: return "KEYGUARD_UNOCCLUDE";
+            case TRANSIT_FIRST_CUSTOM: return "FIRST_CUSTOM";
+            default:
+                if (type > TRANSIT_FIRST_CUSTOM) {
+                    return "FIRST_CUSTOM+" + (type - TRANSIT_FIRST_CUSTOM);
+                }
+                return "UNKNOWN(" + type + ")";
+        }
+    }
+
+    public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
+        /**
+         * X position for this window.  With the default gravity it is ignored.
+         * When using {@link Gravity#LEFT} or {@link Gravity#START} or {@link Gravity#RIGHT} or
+         * {@link Gravity#END} it provides an offset from the given edge.
+         */
+        @ViewDebug.ExportedProperty
+        public int x;
+
+        /**
+         * Y position for this window.  With the default gravity it is ignored.
+         * When using {@link Gravity#TOP} or {@link Gravity#BOTTOM} it provides
+         * an offset from the given edge.
+         */
+        @ViewDebug.ExportedProperty
+        public int y;
+
+        /**
+         * Indicates how much of the extra space will be allocated horizontally
+         * to the view associated with these LayoutParams. Specify 0 if the view
+         * should not be stretched. Otherwise the extra pixels will be pro-rated
+         * among all views whose weight is greater than 0.
+         */
+        @ViewDebug.ExportedProperty
+        public float horizontalWeight;
+
+        /**
+         * Indicates how much of the extra space will be allocated vertically
+         * to the view associated with these LayoutParams. Specify 0 if the view
+         * should not be stretched. Otherwise the extra pixels will be pro-rated
+         * among all views whose weight is greater than 0.
+         */
+        @ViewDebug.ExportedProperty
+        public float verticalWeight;
+
+        /**
+         * The general type of window.  There are three main classes of
+         * window types:
+         * <ul>
+         * <li> <strong>Application windows</strong> (ranging from
+         * {@link #FIRST_APPLICATION_WINDOW} to
+         * {@link #LAST_APPLICATION_WINDOW}) are normal top-level application
+         * windows.  For these types of windows, the {@link #token} must be
+         * set to the token of the activity they are a part of (this will
+         * normally be done for you if {@link #token} is null).
+         * <li> <strong>Sub-windows</strong> (ranging from
+         * {@link #FIRST_SUB_WINDOW} to
+         * {@link #LAST_SUB_WINDOW}) are associated with another top-level
+         * window.  For these types of windows, the {@link #token} must be
+         * the token of the window it is attached to.
+         * <li> <strong>System windows</strong> (ranging from
+         * {@link #FIRST_SYSTEM_WINDOW} to
+         * {@link #LAST_SYSTEM_WINDOW}) are special types of windows for
+         * use by the system for specific purposes.  They should not normally
+         * be used by applications, and a special permission is required
+         * to use them.
+         * </ul>
+         *
+         * @see #TYPE_BASE_APPLICATION
+         * @see #TYPE_APPLICATION
+         * @see #TYPE_APPLICATION_STARTING
+         * @see #TYPE_DRAWN_APPLICATION
+         * @see #TYPE_APPLICATION_PANEL
+         * @see #TYPE_APPLICATION_MEDIA
+         * @see #TYPE_APPLICATION_SUB_PANEL
+         * @see #TYPE_APPLICATION_ATTACHED_DIALOG
+         * @see #TYPE_STATUS_BAR
+         * @see #TYPE_SEARCH_BAR
+         * @see #TYPE_PHONE
+         * @see #TYPE_SYSTEM_ALERT
+         * @see #TYPE_TOAST
+         * @see #TYPE_SYSTEM_OVERLAY
+         * @see #TYPE_PRIORITY_PHONE
+         * @see #TYPE_SYSTEM_DIALOG
+         * @see #TYPE_KEYGUARD_DIALOG
+         * @see #TYPE_SYSTEM_ERROR
+         * @see #TYPE_INPUT_METHOD
+         * @see #TYPE_INPUT_METHOD_DIALOG
+         */
+        @ViewDebug.ExportedProperty(mapping = {
+                @ViewDebug.IntToString(from = TYPE_BASE_APPLICATION,
+                        to = "BASE_APPLICATION"),
+                @ViewDebug.IntToString(from = TYPE_APPLICATION,
+                        to = "APPLICATION"),
+                @ViewDebug.IntToString(from = TYPE_APPLICATION_STARTING,
+                        to = "APPLICATION_STARTING"),
+                @ViewDebug.IntToString(from = TYPE_DRAWN_APPLICATION,
+                        to = "DRAWN_APPLICATION"),
+                @ViewDebug.IntToString(from = TYPE_APPLICATION_PANEL,
+                        to = "APPLICATION_PANEL"),
+                @ViewDebug.IntToString(from = TYPE_APPLICATION_MEDIA,
+                        to = "APPLICATION_MEDIA"),
+                @ViewDebug.IntToString(from = TYPE_APPLICATION_SUB_PANEL,
+                        to = "APPLICATION_SUB_PANEL"),
+                @ViewDebug.IntToString(from = TYPE_APPLICATION_ABOVE_SUB_PANEL,
+                        to = "APPLICATION_ABOVE_SUB_PANEL"),
+                @ViewDebug.IntToString(from = TYPE_APPLICATION_ATTACHED_DIALOG,
+                        to = "APPLICATION_ATTACHED_DIALOG"),
+                @ViewDebug.IntToString(from = TYPE_APPLICATION_MEDIA_OVERLAY,
+                        to = "APPLICATION_MEDIA_OVERLAY"),
+                @ViewDebug.IntToString(from = TYPE_STATUS_BAR,
+                        to = "STATUS_BAR"),
+                @ViewDebug.IntToString(from = TYPE_SEARCH_BAR,
+                        to = "SEARCH_BAR"),
+                @ViewDebug.IntToString(from = TYPE_PHONE,
+                        to = "PHONE"),
+                @ViewDebug.IntToString(from = TYPE_SYSTEM_ALERT,
+                        to = "SYSTEM_ALERT"),
+                @ViewDebug.IntToString(from = TYPE_TOAST,
+                        to = "TOAST"),
+                @ViewDebug.IntToString(from = TYPE_SYSTEM_OVERLAY,
+                        to = "SYSTEM_OVERLAY"),
+                @ViewDebug.IntToString(from = TYPE_PRIORITY_PHONE,
+                        to = "PRIORITY_PHONE"),
+                @ViewDebug.IntToString(from = TYPE_SYSTEM_DIALOG,
+                        to = "SYSTEM_DIALOG"),
+                @ViewDebug.IntToString(from = TYPE_KEYGUARD_DIALOG,
+                        to = "KEYGUARD_DIALOG"),
+                @ViewDebug.IntToString(from = TYPE_SYSTEM_ERROR,
+                        to = "SYSTEM_ERROR"),
+                @ViewDebug.IntToString(from = TYPE_INPUT_METHOD,
+                        to = "INPUT_METHOD"),
+                @ViewDebug.IntToString(from = TYPE_INPUT_METHOD_DIALOG,
+                        to = "INPUT_METHOD_DIALOG"),
+                @ViewDebug.IntToString(from = TYPE_WALLPAPER,
+                        to = "WALLPAPER"),
+                @ViewDebug.IntToString(from = TYPE_STATUS_BAR_PANEL,
+                        to = "STATUS_BAR_PANEL"),
+                @ViewDebug.IntToString(from = TYPE_SECURE_SYSTEM_OVERLAY,
+                        to = "SECURE_SYSTEM_OVERLAY"),
+                @ViewDebug.IntToString(from = TYPE_DRAG,
+                        to = "DRAG"),
+                @ViewDebug.IntToString(from = TYPE_STATUS_BAR_SUB_PANEL,
+                        to = "STATUS_BAR_SUB_PANEL"),
+                @ViewDebug.IntToString(from = TYPE_POINTER,
+                        to = "POINTER"),
+                @ViewDebug.IntToString(from = TYPE_NAVIGATION_BAR,
+                        to = "NAVIGATION_BAR"),
+                @ViewDebug.IntToString(from = TYPE_VOLUME_OVERLAY,
+                        to = "VOLUME_OVERLAY"),
+                @ViewDebug.IntToString(from = TYPE_BOOT_PROGRESS,
+                        to = "BOOT_PROGRESS"),
+                @ViewDebug.IntToString(from = TYPE_INPUT_CONSUMER,
+                        to = "INPUT_CONSUMER"),
+                @ViewDebug.IntToString(from = TYPE_NAVIGATION_BAR_PANEL,
+                        to = "NAVIGATION_BAR_PANEL"),
+                @ViewDebug.IntToString(from = TYPE_DISPLAY_OVERLAY,
+                        to = "DISPLAY_OVERLAY"),
+                @ViewDebug.IntToString(from = TYPE_MAGNIFICATION_OVERLAY,
+                        to = "MAGNIFICATION_OVERLAY"),
+                @ViewDebug.IntToString(from = TYPE_PRESENTATION,
+                        to = "PRESENTATION"),
+                @ViewDebug.IntToString(from = TYPE_PRIVATE_PRESENTATION,
+                        to = "PRIVATE_PRESENTATION"),
+                @ViewDebug.IntToString(from = TYPE_VOICE_INTERACTION,
+                        to = "VOICE_INTERACTION"),
+                @ViewDebug.IntToString(from = TYPE_VOICE_INTERACTION_STARTING,
+                        to = "VOICE_INTERACTION_STARTING"),
+                @ViewDebug.IntToString(from = TYPE_DOCK_DIVIDER,
+                        to = "DOCK_DIVIDER"),
+                @ViewDebug.IntToString(from = TYPE_QS_DIALOG,
+                        to = "QS_DIALOG"),
+                @ViewDebug.IntToString(from = TYPE_SCREENSHOT,
+                        to = "SCREENSHOT"),
+                @ViewDebug.IntToString(from = TYPE_APPLICATION_OVERLAY,
+                        to = "APPLICATION_OVERLAY")
+        })
+        @WindowType
+        public int type;
+
+        /**
+         * Start of window types that represent normal application windows.
+         */
+        public static final int FIRST_APPLICATION_WINDOW = 1;
+
+        /**
+         * Window type: an application window that serves as the "base" window
+         * of the overall application; all other application windows will
+         * appear on top of it.
+         * In multiuser systems shows only on the owning user's window.
+         */
+        public static final int TYPE_BASE_APPLICATION   = 1;
+
+        /**
+         * Window type: a normal application window.  The {@link #token} must be
+         * an Activity token identifying who the window belongs to.
+         * In multiuser systems shows only on the owning user's window.
+         */
+        public static final int TYPE_APPLICATION        = 2;
+
+        /**
+         * Window type: special application window that is displayed while the
+         * application is starting.  Not for use by applications themselves;
+         * this is used by the system to display something until the
+         * application can show its own windows.
+         * In multiuser systems shows on all users' windows.
+         */
+        public static final int TYPE_APPLICATION_STARTING = 3;
+
+        /**
+         * Window type: a variation on TYPE_APPLICATION that ensures the window
+         * manager will wait for this window to be drawn before the app is shown.
+         * In multiuser systems shows only on the owning user's window.
+         */
+        public static final int TYPE_DRAWN_APPLICATION = 4;
+
+        /**
+         * End of types of application windows.
+         */
+        public static final int LAST_APPLICATION_WINDOW = 99;
+
+        /**
+         * Start of types of sub-windows.  The {@link #token} of these windows
+         * must be set to the window they are attached to.  These types of
+         * windows are kept next to their attached window in Z-order, and their
+         * coordinate space is relative to their attached window.
+         */
+        public static final int FIRST_SUB_WINDOW = 1000;
+
+        /**
+         * Window type: a panel on top of an application window.  These windows
+         * appear on top of their attached window.
+         */
+        public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
+
+        /**
+         * Window type: window for showing media (such as video).  These windows
+         * are displayed behind their attached window.
+         */
+        public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;
+
+        /**
+         * Window type: a sub-panel on top of an application window.  These
+         * windows are displayed on top their attached window and any
+         * {@link #TYPE_APPLICATION_PANEL} panels.
+         */
+        public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;
+
+        /** Window type: like {@link #TYPE_APPLICATION_PANEL}, but layout
+         * of the window happens as that of a top-level window, <em>not</em>
+         * as a child of its container.
+         */
+        public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;
+
+        /**
+         * Window type: window for showing overlays on top of media windows.
+         * These windows are displayed between TYPE_APPLICATION_MEDIA and the
+         * application window.  They should be translucent to be useful.  This
+         * is a big ugly hack so:
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW + 4;
+
+        /**
+         * Window type: a above sub-panel on top of an application window and it's
+         * sub-panel windows. These windows are displayed on top of their attached window
+         * and any {@link #TYPE_APPLICATION_SUB_PANEL} panels.
+         * @hide
+         */
+        public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;
+
+        /**
+         * End of types of sub-windows.
+         */
+        public static final int LAST_SUB_WINDOW = 1999;
+
+        /**
+         * Start of system-specific window types.  These are not normally
+         * created by applications.
+         */
+        public static final int FIRST_SYSTEM_WINDOW     = 2000;
+
+        /**
+         * Window type: the status bar.  There can be only one status bar
+         * window; it is placed at the top of the screen, and all other
+         * windows are shifted down so they are below it.
+         * In multiuser systems shows on all users' windows.
+         */
+        public static final int TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW;
+
+        /**
+         * Window type: the search bar.  There can be only one search bar
+         * window; it is placed at the top of the screen.
+         * In multiuser systems shows on all users' windows.
+         */
+        public static final int TYPE_SEARCH_BAR         = FIRST_SYSTEM_WINDOW+1;
+
+        /**
+         * Window type: phone.  These are non-application windows providing
+         * user interaction with the phone (in particular incoming calls).
+         * These windows are normally placed above all applications, but behind
+         * the status bar.
+         * In multiuser systems shows on all users' windows.
+         * @deprecated for non-system apps. Use {@link #TYPE_APPLICATION_OVERLAY} instead.
+         */
+        @Deprecated
+        public static final int TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2;
+
+        /**
+         * Window type: system window, such as low power alert. These windows
+         * are always on top of application windows.
+         * In multiuser systems shows only on the owning user's window.
+         * @deprecated for non-system apps. Use {@link #TYPE_APPLICATION_OVERLAY} instead.
+         */
+        @Deprecated
+        public static final int TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3;
+
+        /**
+         * Window type: keyguard window.
+         * In multiuser systems shows on all users' windows.
+         * @removed
+         */
+        public static final int TYPE_KEYGUARD           = FIRST_SYSTEM_WINDOW+4;
+
+        /**
+         * Window type: transient notifications.
+         * In multiuser systems shows only on the owning user's window.
+         * @deprecated for non-system apps. Use {@link #TYPE_APPLICATION_OVERLAY} instead.
+         */
+        @Deprecated
+        public static final int TYPE_TOAST              = FIRST_SYSTEM_WINDOW+5;
+
+        /**
+         * Window type: system overlay windows, which need to be displayed
+         * on top of everything else.  These windows must not take input
+         * focus, or they will interfere with the keyguard.
+         * In multiuser systems shows only on the owning user's window.
+         * @deprecated for non-system apps. Use {@link #TYPE_APPLICATION_OVERLAY} instead.
+         */
+        @Deprecated
+        public static final int TYPE_SYSTEM_OVERLAY     = FIRST_SYSTEM_WINDOW+6;
+
+        /**
+         * Window type: priority phone UI, which needs to be displayed even if
+         * the keyguard is active.  These windows must not take input
+         * focus, or they will interfere with the keyguard.
+         * In multiuser systems shows on all users' windows.
+         * @deprecated for non-system apps. Use {@link #TYPE_APPLICATION_OVERLAY} instead.
+         */
+        @Deprecated
+        public static final int TYPE_PRIORITY_PHONE     = FIRST_SYSTEM_WINDOW+7;
+
+        /**
+         * Window type: panel that slides out from the status bar
+         * In multiuser systems shows on all users' windows.
+         */
+        public static final int TYPE_SYSTEM_DIALOG      = FIRST_SYSTEM_WINDOW+8;
+
+        /**
+         * Window type: dialogs that the keyguard shows
+         * In multiuser systems shows on all users' windows.
+         */
+        public static final int TYPE_KEYGUARD_DIALOG    = FIRST_SYSTEM_WINDOW+9;
+
+        /**
+         * Window type: internal system error windows, appear on top of
+         * everything they can.
+         * In multiuser systems shows only on the owning user's window.
+         * @deprecated for non-system apps. Use {@link #TYPE_APPLICATION_OVERLAY} instead.
+         */
+        @Deprecated
+        public static final int TYPE_SYSTEM_ERROR       = FIRST_SYSTEM_WINDOW+10;
+
+        /**
+         * Window type: internal input methods windows, which appear above
+         * the normal UI.  Application windows may be resized or panned to keep
+         * the input focus visible while this window is displayed.
+         * In multiuser systems shows only on the owning user's window.
+         */
+        public static final int TYPE_INPUT_METHOD       = FIRST_SYSTEM_WINDOW+11;
+
+        /**
+         * Window type: internal input methods dialog windows, which appear above
+         * the current input method window.
+         * In multiuser systems shows only on the owning user's window.
+         */
+        public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;
+
+        /**
+         * Window type: wallpaper window, placed behind any window that wants
+         * to sit on top of the wallpaper.
+         * In multiuser systems shows only on the owning user's window.
+         */
+        public static final int TYPE_WALLPAPER          = FIRST_SYSTEM_WINDOW+13;
+
+        /**
+         * Window type: panel that slides out from over the status bar
+         * In multiuser systems shows on all users' windows.
+         *
+         * @removed
+         */
+        public static final int TYPE_STATUS_BAR_PANEL   = FIRST_SYSTEM_WINDOW+14;
+
+        /**
+         * Window type: secure system overlay windows, which need to be displayed
+         * on top of everything else.  These windows must not take input
+         * focus, or they will interfere with the keyguard.
+         *
+         * This is exactly like {@link #TYPE_SYSTEM_OVERLAY} except that only the
+         * system itself is allowed to create these overlays.  Applications cannot
+         * obtain permission to create secure system overlays.
+         *
+         * In multiuser systems shows only on the owning user's window.
+         * @hide
+         */
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15;
+
+        /**
+         * Window type: the drag-and-drop pseudowindow.  There is only one
+         * drag layer (at most), and it is placed on top of all other windows.
+         * In multiuser systems shows only on the owning user's window.
+         * @hide
+         */
+        public static final int TYPE_DRAG               = FIRST_SYSTEM_WINDOW+16;
+
+        /**
+         * Window type: panel that slides out from over the status bar
+         * In multiuser systems shows on all users' windows. These windows
+         * are displayed on top of the stauts bar and any {@link #TYPE_STATUS_BAR_PANEL}
+         * windows.
+         * @hide
+         */
+        public static final int TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17;
+
+        /**
+         * Window type: (mouse) pointer
+         * In multiuser systems shows on all users' windows.
+         * @hide
+         */
+        public static final int TYPE_POINTER = FIRST_SYSTEM_WINDOW+18;
+
+        /**
+         * Window type: Navigation bar (when distinct from status bar)
+         * In multiuser systems shows on all users' windows.
+         * @hide
+         */
+        public static final int TYPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW+19;
+
+        /**
+         * Window type: The volume level overlay/dialog shown when the user
+         * changes the system volume.
+         * In multiuser systems shows on all users' windows.
+         * @hide
+         */
+        public static final int TYPE_VOLUME_OVERLAY = FIRST_SYSTEM_WINDOW+20;
+
+        /**
+         * Window type: The boot progress dialog, goes on top of everything
+         * in the world.
+         * In multiuser systems shows on all users' windows.
+         * @hide
+         */
+        public static final int TYPE_BOOT_PROGRESS = FIRST_SYSTEM_WINDOW+21;
+
+        /**
+         * Window type to consume input events when the systemUI bars are hidden.
+         * In multiuser systems shows on all users' windows.
+         * @hide
+         */
+        public static final int TYPE_INPUT_CONSUMER = FIRST_SYSTEM_WINDOW+22;
+
+        /**
+         * Window type: Navigation bar panel (when navigation bar is distinct from status bar)
+         * In multiuser systems shows on all users' windows.
+         * @hide
+         */
+        public static final int TYPE_NAVIGATION_BAR_PANEL = FIRST_SYSTEM_WINDOW+24;
+
+        /**
+         * Window type: Display overlay window.  Used to simulate secondary display devices.
+         * In multiuser systems shows on all users' windows.
+         * @hide
+         */
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        public static final int TYPE_DISPLAY_OVERLAY = FIRST_SYSTEM_WINDOW+26;
+
+        /**
+         * Window type: Magnification overlay window. Used to highlight the magnified
+         * portion of a display when accessibility magnification is enabled.
+         * In multiuser systems shows on all users' windows.
+         * @hide
+         */
+        public static final int TYPE_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW+27;
+
+        /**
+         * Window type: Window for Presentation on top of private
+         * virtual display.
+         */
+        public static final int TYPE_PRIVATE_PRESENTATION = FIRST_SYSTEM_WINDOW+30;
+
+        /**
+         * Window type: Windows in the voice interaction layer.
+         * @hide
+         */
+        public static final int TYPE_VOICE_INTERACTION = FIRST_SYSTEM_WINDOW+31;
+
+        /**
+         * Window type: Windows that are overlaid <em>only</em> by a connected {@link
+         * android.accessibilityservice.AccessibilityService} for interception of
+         * user interactions without changing the windows an accessibility service
+         * can introspect. In particular, an accessibility service can introspect
+         * only windows that a sighted user can interact with which is they can touch
+         * these windows or can type into these windows. For example, if there
+         * is a full screen accessibility overlay that is touchable, the windows
+         * below it will be introspectable by an accessibility service even though
+         * they are covered by a touchable window.
+         */
+        public static final int TYPE_ACCESSIBILITY_OVERLAY = FIRST_SYSTEM_WINDOW+32;
+
+        /**
+         * Window type: Starting window for voice interaction layer.
+         * @hide
+         */
+        public static final int TYPE_VOICE_INTERACTION_STARTING = FIRST_SYSTEM_WINDOW+33;
+
+        /**
+         * Window for displaying a handle used for resizing docked stacks. This window is owned
+         * by the system process.
+         * @hide
+         */
+        public static final int TYPE_DOCK_DIVIDER = FIRST_SYSTEM_WINDOW+34;
+
+        /**
+         * Window type: like {@link #TYPE_APPLICATION_ATTACHED_DIALOG}, but used
+         * by Quick Settings Tiles.
+         * @hide
+         */
+        public static final int TYPE_QS_DIALOG = FIRST_SYSTEM_WINDOW+35;
+
+        /**
+         * Window type: shows directly above the keyguard. The layer is
+         * reserved for screenshot animation, region selection and UI.
+         * In multiuser systems shows only on the owning user's window.
+         * @hide
+         */
+        public static final int TYPE_SCREENSHOT = FIRST_SYSTEM_WINDOW + 36;
+
+        /**
+         * Window type: Window for Presentation on an external display.
+         * @see android.app.Presentation
+         * @hide
+         */
+        public static final int TYPE_PRESENTATION = FIRST_SYSTEM_WINDOW + 37;
+
+        /**
+         * Window type: Application overlay windows are displayed above all activity windows
+         * (types between {@link #FIRST_APPLICATION_WINDOW} and {@link #LAST_APPLICATION_WINDOW})
+         * but below critical system windows like the status bar or IME.
+         * <p>
+         * The system may change the position, size, or visibility of these windows at anytime
+         * to reduce visual clutter to the user and also manage resources.
+         * <p>
+         * Requires {@link android.Manifest.permission#SYSTEM_ALERT_WINDOW} permission.
+         * <p>
+         * The system will adjust the importance of processes with this window type to reduce the
+         * chance of the low-memory-killer killing them.
+         * <p>
+         * In multi-user systems shows only on the owning user's screen.
+         */
+        public static final int TYPE_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38;
+
+        /**
+         * Window type: Window for adding accessibility window magnification above other windows.
+         * This will place the window in the overlay windows.
+         * @hide
+         */
+        public static final int TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 39;
+
+        /**
+         * Window type: the notification shade and keyguard. There can be only one status bar
+         * window; it is placed at the top of the screen, and all other
+         * windows are shifted down so they are below it.
+         * In multiuser systems shows on all users' windows.
+         * @hide
+         */
+        public static final int TYPE_NOTIFICATION_SHADE = FIRST_SYSTEM_WINDOW + 40;
+
+        /**
+         * Window type: used to show the status bar in non conventional parts of the screen (i.e.
+         * the left or the bottom of the screen).
+         * In multiuser systems shows on all users' windows.
+         * @hide
+         */
+        public static final int TYPE_STATUS_BAR_ADDITIONAL = FIRST_SYSTEM_WINDOW + 41;
+
+        /**
+         * End of types of system windows.
+         */
+        public static final int LAST_SYSTEM_WINDOW      = 2999;
+
+        /**
+         * @hide
+         * Used internally when there is no suitable type available.
+         */
+        public static final int INVALID_WINDOW_TYPE = -1;
+
+        /**
+         * @hide
+         */
+        @IntDef(prefix = "TYPE_", value = {
+                TYPE_BASE_APPLICATION,
+                TYPE_APPLICATION,
+                TYPE_APPLICATION_STARTING,
+                TYPE_DRAWN_APPLICATION,
+                TYPE_APPLICATION_PANEL,
+                TYPE_APPLICATION_MEDIA,
+                TYPE_APPLICATION_SUB_PANEL,
+                TYPE_APPLICATION_ATTACHED_DIALOG,
+                TYPE_APPLICATION_MEDIA_OVERLAY,
+                TYPE_APPLICATION_ABOVE_SUB_PANEL,
+                TYPE_STATUS_BAR,
+                TYPE_SEARCH_BAR,
+                TYPE_PHONE,
+                TYPE_SYSTEM_ALERT,
+                TYPE_KEYGUARD,
+                TYPE_TOAST,
+                TYPE_SYSTEM_OVERLAY,
+                TYPE_PRIORITY_PHONE,
+                TYPE_SYSTEM_DIALOG,
+                TYPE_KEYGUARD_DIALOG,
+                TYPE_SYSTEM_ERROR,
+                TYPE_INPUT_METHOD,
+                TYPE_INPUT_METHOD_DIALOG,
+                TYPE_WALLPAPER,
+                TYPE_STATUS_BAR_PANEL,
+                TYPE_SECURE_SYSTEM_OVERLAY,
+                TYPE_DRAG,
+                TYPE_STATUS_BAR_SUB_PANEL,
+                TYPE_POINTER,
+                TYPE_NAVIGATION_BAR,
+                TYPE_VOLUME_OVERLAY,
+                TYPE_BOOT_PROGRESS,
+                TYPE_INPUT_CONSUMER,
+                TYPE_NAVIGATION_BAR_PANEL,
+                TYPE_DISPLAY_OVERLAY,
+                TYPE_MAGNIFICATION_OVERLAY,
+                TYPE_PRIVATE_PRESENTATION,
+                TYPE_VOICE_INTERACTION,
+                TYPE_ACCESSIBILITY_OVERLAY,
+                TYPE_VOICE_INTERACTION_STARTING,
+                TYPE_DOCK_DIVIDER,
+                TYPE_QS_DIALOG,
+                TYPE_SCREENSHOT,
+                TYPE_PRESENTATION,
+                TYPE_APPLICATION_OVERLAY,
+                TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY,
+                TYPE_NOTIFICATION_SHADE,
+                TYPE_STATUS_BAR_ADDITIONAL
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface WindowType {}
+
+        /**
+         * Return true if the window type is an alert window.
+         *
+         * @param type The window type.
+         * @return If the window type is an alert window.
+         * @hide
+         */
+        public static boolean isSystemAlertWindowType(@WindowType int type) {
+            switch (type) {
+                case TYPE_PHONE:
+                case TYPE_PRIORITY_PHONE:
+                case TYPE_SYSTEM_ALERT:
+                case TYPE_SYSTEM_ERROR:
+                case TYPE_SYSTEM_OVERLAY:
+                case TYPE_APPLICATION_OVERLAY:
+                    return true;
+            }
+            return false;
+        }
+
+        /** @deprecated this is ignored, this value is set automatically when needed. */
+        @Deprecated
+        public static final int MEMORY_TYPE_NORMAL = 0;
+        /** @deprecated this is ignored, this value is set automatically when needed. */
+        @Deprecated
+        public static final int MEMORY_TYPE_HARDWARE = 1;
+        /** @deprecated this is ignored, this value is set automatically when needed. */
+        @Deprecated
+        public static final int MEMORY_TYPE_GPU = 2;
+        /** @deprecated this is ignored, this value is set automatically when needed. */
+        @Deprecated
+        public static final int MEMORY_TYPE_PUSH_BUFFERS = 3;
+
+        /**
+         * @deprecated this is ignored
+         */
+        @Deprecated
+        public int memoryType;
+
+        /** Window flag: as long as this window is visible to the user, allow
+         *  the lock screen to activate while the screen is on.
+         *  This can be used independently, or in combination with
+         *  {@link #FLAG_KEEP_SCREEN_ON} and/or {@link #FLAG_SHOW_WHEN_LOCKED} */
+        public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON     = 0x00000001;
+
+        /** Window flag: everything behind this window will be dimmed.
+         *  Use {@link #dimAmount} to control the amount of dim. */
+        public static final int FLAG_DIM_BEHIND        = 0x00000002;
+
+        /** Window flag: enable blur behind for this window. */
+        public static final int FLAG_BLUR_BEHIND        = 0x00000004;
+
+        /** Window flag: this window won't ever get key input focus, so the
+         * user can not send key or other button events to it.  Those will
+         * instead go to whatever focusable window is behind it.  This flag
+         * will also enable {@link #FLAG_NOT_TOUCH_MODAL} whether or not that
+         * is explicitly set.
+         *
+         * <p>Setting this flag also implies that the window will not need to
+         * interact with
+         * a soft input method, so it will be Z-ordered and positioned
+         * independently of any active input method (typically this means it
+         * gets Z-ordered on top of the input method, so it can use the full
+         * screen for its content and cover the input method if needed.  You
+         * can use {@link #FLAG_ALT_FOCUSABLE_IM} to modify this behavior. */
+        public static final int FLAG_NOT_FOCUSABLE      = 0x00000008;
+
+        /**
+         * Window flag: this window can never receive touch events.
+         *
+         * <p>The intention of this flag is to leave the touch to be handled by some window below
+         * this window (in Z order).
+         *
+         * <p>Starting from Android {@link Build.VERSION_CODES#S}, for security reasons, touch
+         * events that pass through windows containing this flag (ie. are within the bounds of the
+         * window) will only be delivered to the touch-consuming window if one (or more) of the
+         * items below are true:
+         * <ol>
+         *   <li><b>Same UID</b>: This window belongs to the same UID that owns the touch-consuming
+         *   window.
+         *   <li><b>Trusted windows</b>: This window is trusted. Trusted windows include (but are
+         *   not limited to) accessibility windows ({@link #TYPE_ACCESSIBILITY_OVERLAY}), the IME
+         *   ({@link #TYPE_INPUT_METHOD}) and assistant windows (TYPE_VOICE_INTERACTION). Windows of
+         *   type {@link #TYPE_APPLICATION_OVERLAY} are <b>not</b> trusted, see below.
+         *   <li><b>Invisible windows</b>: This window is {@link View#GONE} or
+         *   {@link View#INVISIBLE}.
+         *   <li><b>Fully transparent windows</b>: This window has {@link LayoutParams#alpha} equal
+         *   to 0.
+         *   <li><b>One SAW window with enough transparency</b>: This window is of type {@link
+         *   #TYPE_APPLICATION_OVERLAY}, has {@link LayoutParams#alpha} below or equal to the
+         *   <a href="#MaximumOpacity">maximum obscuring opacity</a> (see below) and it's the
+         *   <b>only</b> window of type {@link #TYPE_APPLICATION_OVERLAY} from this UID in the touch
+         *   path.
+         *   <li><b>Multiple SAW windows with enough transparency</b>: The multiple overlapping
+         *   {@link #TYPE_APPLICATION_OVERLAY} windows in the
+         *   touch path from this UID have a <b>combined obscuring opacity</b> below or equal to
+         *   the <a href="#MaximumOpacity">maximum obscuring opacity</a>. See section
+         *   <a href="#ObscuringOpacity">Combined obscuring opacity</a> below on how to compute this
+         *   value.
+         * </ol>
+         * <p>If none of these cases hold, the touch will not be delivered and a message will be
+         * logged to logcat.</p>
+         *
+         * <a name="MaximumOpacity"></a>
+         * <h3>Maximum obscuring opacity</h3>
+         * <p>This value is <b>0.8</b>. Apps that want to gather this value from the system rather
+         * than hard-coding it might want to use {@link
+         * android.hardware.input.InputManager#getMaximumObscuringOpacityForTouch()}.</p>
+         *
+         * <a name="ObscuringOpacity"></a>
+         * <h3>Combined obscuring opacity</h3>
+         *
+         * <p>The <b>combined obscuring opacity</b> of a set of windows is obtained by combining the
+         * opacity values of all windows in the set using the associative and commutative operation
+         * defined as:
+         * <pre>
+         * opacity({A,B}) = 1 - (1 - opacity(A))*(1 - opacity(B))
+         * </pre>
+         * <p>where {@code opacity(X)} is the {@link LayoutParams#alpha} of window X. So, for a set
+         * of windows {@code {W1, .., Wn}}, the combined obscuring opacity will be:
+         * <pre>
+         * opacity({W1, .., Wn}) = 1 - (1 - opacity(W1)) * ... * (1 - opacity(Wn))
+         * </pre>
+         */
+        public static final int FLAG_NOT_TOUCHABLE      = 0x00000010;
+
+        /** Window flag: even when this window is focusable (its
+         * {@link #FLAG_NOT_FOCUSABLE} is not set), allow any pointer events
+         * outside of the window to be sent to the windows behind it.  Otherwise
+         * it will consume all pointer events itself, regardless of whether they
+         * are inside of the window. */
+        public static final int FLAG_NOT_TOUCH_MODAL    = 0x00000020;
+
+        /** Window flag: when set, if the device is asleep when the touch
+         * screen is pressed, you will receive this first touch event.  Usually
+         * the first touch event is consumed by the system since the user can
+         * not see what they are pressing on.
+         *
+         * @deprecated This flag has no effect.
+         */
+        @Deprecated
+        public static final int FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040;
+
+        /** Window flag: as long as this window is visible to the user, keep
+         *  the device's screen turned on and bright. */
+        public static final int FLAG_KEEP_SCREEN_ON     = 0x00000080;
+
+        /**
+         * Window flag for attached windows: Place the window within the entire screen, ignoring
+         * any constraints from the parent window.
+         *
+         *  <p>Note: on displays that have a {@link DisplayCutout}, the window may be placed
+         *  such that it avoids the {@link DisplayCutout} area if necessary according to the
+         *  {@link #layoutInDisplayCutoutMode}.
+         */
+        public static final int FLAG_LAYOUT_IN_SCREEN   = 0x00000100;
+
+        /** Window flag: allow window to extend outside of the screen. */
+        public static final int FLAG_LAYOUT_NO_LIMITS   = 0x00000200;
+
+        /**
+         * Window flag: hide all screen decorations (such as the status bar) while
+         * this window is displayed.  This allows the window to use the entire
+         * display space for itself -- the status bar will be hidden when
+         * an app window with this flag set is on the top layer. A fullscreen window
+         * will ignore a value of {@link #SOFT_INPUT_ADJUST_RESIZE} for the window's
+         * {@link #softInputMode} field; the window will stay fullscreen
+         * and will not resize.
+         *
+         * <p>This flag can be controlled in your theme through the
+         * {@link android.R.attr#windowFullscreen} attribute; this attribute
+         * is automatically set for you in the standard fullscreen themes
+         * such as {@link android.R.style#Theme_NoTitleBar_Fullscreen},
+         * {@link android.R.style#Theme_Black_NoTitleBar_Fullscreen},
+         * {@link android.R.style#Theme_Light_NoTitleBar_Fullscreen},
+         * {@link android.R.style#Theme_Holo_NoActionBar_Fullscreen},
+         * {@link android.R.style#Theme_Holo_Light_NoActionBar_Fullscreen},
+         * {@link android.R.style#Theme_DeviceDefault_NoActionBar_Fullscreen}, and
+         * {@link android.R.style#Theme_DeviceDefault_Light_NoActionBar_Fullscreen}.</p>
+         *
+         * @deprecated Use {@link WindowInsetsController#hide(int)} with {@link Type#statusBars()}
+         * instead.
+         */
+        @Deprecated
+        public static final int FLAG_FULLSCREEN      = 0x00000400;
+
+        /**
+         * Window flag: override {@link #FLAG_FULLSCREEN} and force the
+         * screen decorations (such as the status bar) to be shown.
+         *
+         * @deprecated This value became API "by accident", and shouldn't be used by 3rd party
+         * applications.
+         */
+        @Deprecated
+        public static final int FLAG_FORCE_NOT_FULLSCREEN   = 0x00000800;
+
+        /** Window flag: turn on dithering when compositing this window to
+         *  the screen.
+         * @deprecated This flag is no longer used. */
+        @Deprecated
+        public static final int FLAG_DITHER             = 0x00001000;
+
+        /** Window flag: treat the content of the window as secure, preventing
+         * it from appearing in screenshots or from being viewed on non-secure
+         * displays.
+         *
+         * <p>See {@link android.view.Display#FLAG_SECURE} for more details about
+         * secure surfaces and secure displays.
+         */
+        public static final int FLAG_SECURE             = 0x00002000;
+
+        /** Window flag: a special mode where the layout parameters are used
+         * to perform scaling of the surface when it is composited to the
+         * screen. */
+        public static final int FLAG_SCALED             = 0x00004000;
+
+        /** Window flag: intended for windows that will often be used when the user is
+         * holding the screen against their face, it will aggressively filter the event
+         * stream to prevent unintended presses in this situation that may not be
+         * desired for a particular window, when such an event stream is detected, the
+         * application will receive a CANCEL motion event to indicate this so applications
+         * can handle this accordingly by taking no action on the event
+         * until the finger is released. */
+        public static final int FLAG_IGNORE_CHEEK_PRESSES    = 0x00008000;
+
+        /**
+         * Window flag: a special option only for use in combination with
+         * {@link #FLAG_LAYOUT_IN_SCREEN}.  When requesting layout in the
+         * screen your window may appear on top of or behind screen decorations
+         * such as the status bar.  By also including this flag, the window
+         * manager will report the inset rectangle needed to ensure your
+         * content is not covered by screen decorations.  This flag is normally
+         * set for you by Window as described in {@link Window#setFlags}
+         *
+         * @deprecated Insets will always be delivered to your application.
+         */
+        @Deprecated
+        public static final int FLAG_LAYOUT_INSET_DECOR = 0x00010000;
+
+        /** Window flag: when set, inverts the input method focusability of the window.
+         *
+         * The effect of setting this flag depends on whether {@link #FLAG_NOT_FOCUSABLE} is set:
+         * <p>
+         * If {@link #FLAG_NOT_FOCUSABLE} is <em>not</em> set, i.e. when the window is focusable,
+         * setting this flag prevents this window from becoming the target of the input method.
+         * Consequently, it will <em>not</em> be able to interact with the input method,
+         * and will be layered above the input method (unless there is another input method
+         * target above it).
+         *
+         * <p>
+         * If {@link #FLAG_NOT_FOCUSABLE} <em>is</em> set, setting this flag requests for the window
+         * to be the input method target even though  the window is <em>not</em> focusable.
+         * Consequently, it will be layered below the input method.
+         * Note: Windows that set {@link #FLAG_NOT_FOCUSABLE} cannot interact with the input method,
+         * regardless of this flag.
+         */
+        public static final int FLAG_ALT_FOCUSABLE_IM = 0x00020000;
+
+        /** Window flag: if you have set {@link #FLAG_NOT_TOUCH_MODAL}, you
+         * can set this flag to receive a single special MotionEvent with
+         * the action
+         * {@link MotionEvent#ACTION_OUTSIDE MotionEvent.ACTION_OUTSIDE} for
+         * touches that occur outside of your window.  Note that you will not
+         * receive the full down/move/up gesture, only the location of the
+         * first down as an ACTION_OUTSIDE.
+         */
+        public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000;
+
+        /** Window flag: special flag to let windows be shown when the screen
+         * is locked. This will let application windows take precedence over
+         * key guard or any other lock screens. Can be used with
+         * {@link #FLAG_KEEP_SCREEN_ON} to turn screen on and display windows
+         * directly before showing the key guard window.  Can be used with
+         * {@link #FLAG_DISMISS_KEYGUARD} to automatically fully dismisss
+         * non-secure keyguards.  This flag only applies to the top-most
+         * full-screen window.
+         * @deprecated Use {@link android.R.attr#showWhenLocked} or
+         * {@link android.app.Activity#setShowWhenLocked(boolean)} instead to prevent an
+         * unintentional double life-cycle event.
+         */
+        @Deprecated
+        public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
+
+        /** Window flag: ask that the system wallpaper be shown behind
+         * your window.  The window surface must be translucent to be able
+         * to actually see the wallpaper behind it; this flag just ensures
+         * that the wallpaper surface will be there if this window actually
+         * has translucent regions.
+         *
+         * <p>This flag can be controlled in your theme through the
+         * {@link android.R.attr#windowShowWallpaper} attribute; this attribute
+         * is automatically set for you in the standard wallpaper themes
+         * such as {@link android.R.style#Theme_Wallpaper},
+         * {@link android.R.style#Theme_Wallpaper_NoTitleBar},
+         * {@link android.R.style#Theme_Wallpaper_NoTitleBar_Fullscreen},
+         * {@link android.R.style#Theme_Holo_Wallpaper},
+         * {@link android.R.style#Theme_Holo_Wallpaper_NoTitleBar},
+         * {@link android.R.style#Theme_DeviceDefault_Wallpaper}, and
+         * {@link android.R.style#Theme_DeviceDefault_Wallpaper_NoTitleBar}.</p>
+         */
+        public static final int FLAG_SHOW_WALLPAPER = 0x00100000;
+
+        /** Window flag: when set as a window is being added or made
+         * visible, once the window has been shown then the system will
+         * poke the power manager's user activity (as if the user had woken
+         * up the device) to turn the screen on.
+         * @deprecated Use {@link android.R.attr#turnScreenOn} or
+         * {@link android.app.Activity#setTurnScreenOn(boolean)} instead to prevent an
+         * unintentional double life-cycle event.
+         */
+        @Deprecated
+        public static final int FLAG_TURN_SCREEN_ON = 0x00200000;
+
+        /**
+         * Window flag: when set the window will cause the keyguard to be
+         * dismissed, only if it is not a secure lock keyguard. Because such a
+         * keyguard is not needed for security, it will never re-appear if the
+         * user navigates to another window (in contrast to
+         * {@link #FLAG_SHOW_WHEN_LOCKED}, which will only temporarily hide both
+         * secure and non-secure keyguards but ensure they reappear when the
+         * user moves to another UI that doesn't hide them). If the keyguard is
+         * currently active and is secure (requires an unlock credential) than
+         * the user will still need to confirm it before seeing this window,
+         * unless {@link #FLAG_SHOW_WHEN_LOCKED} has also been set.
+         *
+         * @deprecated Use {@link #FLAG_SHOW_WHEN_LOCKED} or
+         *             {@link KeyguardManager#requestDismissKeyguard} instead.
+         *             Since keyguard was dismissed all the time as long as an
+         *             activity with this flag on its window was focused,
+         *             keyguard couldn't guard against unintentional touches on
+         *             the screen, which isn't desired.
+         */
+        @Deprecated
+        public static final int FLAG_DISMISS_KEYGUARD = 0x00400000;
+
+        /** Window flag: when set the window will accept for touch events
+         * outside of its bounds to be sent to other windows that also
+         * support split touch.  When this flag is not set, the first pointer
+         * that goes down determines the window to which all subsequent touches
+         * go until all pointers go up.  When this flag is set, each pointer
+         * (not necessarily the first) that goes down determines the window
+         * to which all subsequent touches of that pointer will go until that
+         * pointer goes up thereby enabling touches with multiple pointers
+         * to be split across multiple windows.
+         */
+        public static final int FLAG_SPLIT_TOUCH = 0x00800000;
+
+        /**
+         * <p>Indicates whether this window should be hardware accelerated.
+         * Requesting hardware acceleration does not guarantee it will happen.</p>
+         *
+         * <p>This flag can be controlled programmatically <em>only</em> to enable
+         * hardware acceleration. To enable hardware acceleration for a given
+         * window programmatically, do the following:</p>
+         *
+         * <pre>
+         * Window w = activity.getWindow(); // in Activity's onCreate() for instance
+         * w.setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
+         *         WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+         * </pre>
+         *
+         * <p>It is important to remember that this flag <strong>must</strong>
+         * be set before setting the content view of your activity or dialog.</p>
+         *
+         * <p>This flag cannot be used to disable hardware acceleration after it
+         * was enabled in your manifest using
+         * {@link android.R.attr#hardwareAccelerated}. If you need to selectively
+         * and programmatically disable hardware acceleration (for automated testing
+         * for instance), make sure it is turned off in your manifest and enable it
+         * on your activity or dialog when you need it instead, using the method
+         * described above.</p>
+         *
+         * <p>This flag is automatically set by the system if the
+         * {@link android.R.attr#hardwareAccelerated android:hardwareAccelerated}
+         * XML attribute is set to true on an activity or on the application.</p>
+         */
+        public static final int FLAG_HARDWARE_ACCELERATED = 0x01000000;
+
+        /**
+         * Window flag: allow window contents to extend in to the screen's
+         * overscan area, if there is one.  The window should still correctly
+         * position its contents to take the overscan area into account.
+         *
+         * <p>This flag can be controlled in your theme through the
+         * {@link android.R.attr#windowOverscan} attribute; this attribute
+         * is automatically set for you in the standard overscan themes
+         * such as
+         * {@link android.R.style#Theme_Holo_NoActionBar_Overscan},
+         * {@link android.R.style#Theme_Holo_Light_NoActionBar_Overscan},
+         * {@link android.R.style#Theme_DeviceDefault_NoActionBar_Overscan}, and
+         * {@link android.R.style#Theme_DeviceDefault_Light_NoActionBar_Overscan}.</p>
+         *
+         * <p>When this flag is enabled for a window, its normal content may be obscured
+         * to some degree by the overscan region of the display.  To ensure key parts of
+         * that content are visible to the user, you can use
+         * {@link View#setFitsSystemWindows(boolean) View.setFitsSystemWindows(boolean)}
+         * to set the point in the view hierarchy where the appropriate offsets should
+         * be applied.  (This can be done either by directly calling this function, using
+         * the {@link android.R.attr#fitsSystemWindows} attribute in your view hierarchy,
+         * or implementing you own {@link View#fitSystemWindows(android.graphics.Rect)
+         * View.fitSystemWindows(Rect)} method).</p>
+         *
+         * <p>This mechanism for positioning content elements is identical to its equivalent
+         * use with layout and {@link View#setSystemUiVisibility(int)
+         * View.setSystemUiVisibility(int)}; here is an example layout that will correctly
+         * position its UI elements with this overscan flag is set:</p>
+         *
+         * {@sample development/samples/ApiDemos/res/layout/overscan_activity.xml complete}
+         *
+         * @deprecated Overscan areas aren't set by any Android product anymore as of Android 11.
+         */
+        @Deprecated
+        public static final int FLAG_LAYOUT_IN_OVERSCAN = 0x02000000;
+
+        /**
+         * Window flag: request a translucent status bar with minimal system-provided
+         * background protection.
+         *
+         * <p>This flag can be controlled in your theme through the
+         * {@link android.R.attr#windowTranslucentStatus} attribute; this attribute
+         * is automatically set for you in the standard translucent decor themes
+         * such as
+         * {@link android.R.style#Theme_Holo_NoActionBar_TranslucentDecor},
+         * {@link android.R.style#Theme_Holo_Light_NoActionBar_TranslucentDecor},
+         * {@link android.R.style#Theme_DeviceDefault_NoActionBar_TranslucentDecor}, and
+         * {@link android.R.style#Theme_DeviceDefault_Light_NoActionBar_TranslucentDecor}.</p>
+         *
+         * <p>When this flag is enabled for a window, it automatically sets
+         * the system UI visibility flags {@link View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and
+         * {@link View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}.</p>
+         *
+         * <p>Note: For devices that support
+         * {@link android.content.pm.PackageManager#FEATURE_AUTOMOTIVE} this flag may be ignored.
+         *
+         * @deprecated Use {@link Window#setStatusBarColor(int)} with a half-translucent color
+         * instead.
+         */
+        @Deprecated
+        public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000;
+
+        /**
+         * Window flag: request a translucent navigation bar with minimal system-provided
+         * background protection.
+         *
+         * <p>This flag can be controlled in your theme through the
+         * {@link android.R.attr#windowTranslucentNavigation} attribute; this attribute
+         * is automatically set for you in the standard translucent decor themes
+         * such as
+         * {@link android.R.style#Theme_Holo_NoActionBar_TranslucentDecor},
+         * {@link android.R.style#Theme_Holo_Light_NoActionBar_TranslucentDecor},
+         * {@link android.R.style#Theme_DeviceDefault_NoActionBar_TranslucentDecor}, and
+         * {@link android.R.style#Theme_DeviceDefault_Light_NoActionBar_TranslucentDecor}.</p>
+         *
+         * <p>When this flag is enabled for a window, it automatically sets
+         * the system UI visibility flags {@link View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and
+         * {@link View#SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION}.</p>
+         *
+         * <p>Note: For devices that support
+         * {@link android.content.pm.PackageManager#FEATURE_AUTOMOTIVE} this flag can be disabled
+         * by the car manufacturers.
+         *
+         * @deprecated Use {@link Window#setNavigationBarColor(int)} with a half-translucent color
+         * instead.
+         */
+        @Deprecated
+        public static final int FLAG_TRANSLUCENT_NAVIGATION = 0x08000000;
+
+        /**
+         * Flag for a window in local focus mode.
+         * Window in local focus mode can control focus independent of window manager using
+         * {@link Window#setLocalFocus(boolean, boolean)}.
+         * Usually window in this mode will not get touch/key events from window manager, but will
+         * get events only via local injection using {@link Window#injectInputEvent(InputEvent)}.
+         */
+        public static final int FLAG_LOCAL_FOCUS_MODE = 0x10000000;
+
+        /** Window flag: Enable touches to slide out of a window into neighboring
+         * windows in mid-gesture instead of being captured for the duration of
+         * the gesture.
+         *
+         * This flag changes the behavior of touch focus for this window only.
+         * Touches can slide out of the window but they cannot necessarily slide
+         * back in (unless the other window with touch focus permits it).
+         *
+         * {@hide}
+         */
+        @UnsupportedAppUsage
+        public static final int FLAG_SLIPPERY = 0x20000000;
+
+        /**
+         * Window flag: When requesting layout with an attached window, the attached window may
+         * overlap with the screen decorations of the parent window such as the navigation bar. By
+         * including this flag, the window manager will layout the attached window within the decor
+         * frame of the parent window such that it doesn't overlap with screen decorations.
+         *
+         * @deprecated Use {@link #setFitInsetsTypes(int)} to determine whether the attached
+         * window will overlap with system bars.
+         */
+        @Deprecated
+        public static final int FLAG_LAYOUT_ATTACHED_IN_DECOR = 0x40000000;
+
+        /**
+         * Flag indicating that this Window is responsible for drawing the background for the
+         * system bars. If set, the system bars are drawn with a transparent background and the
+         * corresponding areas in this window are filled with the colors specified in
+         * {@link Window#getStatusBarColor()} and {@link Window#getNavigationBarColor()}.
+         */
+        public static final int FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = 0x80000000;
+
+        /**
+         * @hide
+         */
+        @IntDef(flag = true, prefix = "FLAG_", value = {
+                FLAG_ALLOW_LOCK_WHILE_SCREEN_ON,
+                FLAG_DIM_BEHIND,
+                FLAG_BLUR_BEHIND,
+                FLAG_NOT_FOCUSABLE,
+                FLAG_NOT_TOUCHABLE,
+                FLAG_NOT_TOUCH_MODAL,
+                FLAG_TOUCHABLE_WHEN_WAKING,
+                FLAG_KEEP_SCREEN_ON,
+                FLAG_LAYOUT_IN_SCREEN,
+                FLAG_LAYOUT_NO_LIMITS,
+                FLAG_FULLSCREEN,
+                FLAG_FORCE_NOT_FULLSCREEN,
+                FLAG_DITHER,
+                FLAG_SECURE,
+                FLAG_SCALED,
+                FLAG_IGNORE_CHEEK_PRESSES,
+                FLAG_LAYOUT_INSET_DECOR,
+                FLAG_ALT_FOCUSABLE_IM,
+                FLAG_WATCH_OUTSIDE_TOUCH,
+                FLAG_SHOW_WHEN_LOCKED,
+                FLAG_SHOW_WALLPAPER,
+                FLAG_TURN_SCREEN_ON,
+                FLAG_DISMISS_KEYGUARD,
+                FLAG_SPLIT_TOUCH,
+                FLAG_HARDWARE_ACCELERATED,
+                FLAG_LAYOUT_IN_OVERSCAN,
+                FLAG_TRANSLUCENT_STATUS,
+                FLAG_TRANSLUCENT_NAVIGATION,
+                FLAG_LOCAL_FOCUS_MODE,
+                FLAG_SLIPPERY,
+                FLAG_LAYOUT_ATTACHED_IN_DECOR,
+                FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface Flags {}
+
+        /**
+         * Various behavioral options/flags.  Default is none.
+         *
+         * @see #FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
+         * @see #FLAG_DIM_BEHIND
+         * @see #FLAG_NOT_FOCUSABLE
+         * @see #FLAG_NOT_TOUCHABLE
+         * @see #FLAG_NOT_TOUCH_MODAL
+         * @see #FLAG_TOUCHABLE_WHEN_WAKING
+         * @see #FLAG_KEEP_SCREEN_ON
+         * @see #FLAG_LAYOUT_IN_SCREEN
+         * @see #FLAG_LAYOUT_NO_LIMITS
+         * @see #FLAG_FULLSCREEN
+         * @see #FLAG_FORCE_NOT_FULLSCREEN
+         * @see #FLAG_SECURE
+         * @see #FLAG_SCALED
+         * @see #FLAG_IGNORE_CHEEK_PRESSES
+         * @see #FLAG_LAYOUT_INSET_DECOR
+         * @see #FLAG_ALT_FOCUSABLE_IM
+         * @see #FLAG_WATCH_OUTSIDE_TOUCH
+         * @see #FLAG_SHOW_WHEN_LOCKED
+         * @see #FLAG_SHOW_WALLPAPER
+         * @see #FLAG_TURN_SCREEN_ON
+         * @see #FLAG_DISMISS_KEYGUARD
+         * @see #FLAG_SPLIT_TOUCH
+         * @see #FLAG_HARDWARE_ACCELERATED
+         * @see #FLAG_LOCAL_FOCUS_MODE
+         * @see #FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
+         */
+        @ViewDebug.ExportedProperty(flagMapping = {
+            @ViewDebug.FlagToString(mask = FLAG_ALLOW_LOCK_WHILE_SCREEN_ON, equals = FLAG_ALLOW_LOCK_WHILE_SCREEN_ON,
+                    name = "ALLOW_LOCK_WHILE_SCREEN_ON"),
+            @ViewDebug.FlagToString(mask = FLAG_DIM_BEHIND, equals = FLAG_DIM_BEHIND,
+                    name = "DIM_BEHIND"),
+            @ViewDebug.FlagToString(mask = FLAG_BLUR_BEHIND, equals = FLAG_BLUR_BEHIND,
+                    name = "BLUR_BEHIND"),
+            @ViewDebug.FlagToString(mask = FLAG_NOT_FOCUSABLE, equals = FLAG_NOT_FOCUSABLE,
+                    name = "NOT_FOCUSABLE"),
+            @ViewDebug.FlagToString(mask = FLAG_NOT_TOUCHABLE, equals = FLAG_NOT_TOUCHABLE,
+                    name = "NOT_TOUCHABLE"),
+            @ViewDebug.FlagToString(mask = FLAG_NOT_TOUCH_MODAL, equals = FLAG_NOT_TOUCH_MODAL,
+                    name = "NOT_TOUCH_MODAL"),
+            @ViewDebug.FlagToString(mask = FLAG_TOUCHABLE_WHEN_WAKING, equals = FLAG_TOUCHABLE_WHEN_WAKING,
+                    name = "TOUCHABLE_WHEN_WAKING"),
+            @ViewDebug.FlagToString(mask = FLAG_KEEP_SCREEN_ON, equals = FLAG_KEEP_SCREEN_ON,
+                    name = "KEEP_SCREEN_ON"),
+            @ViewDebug.FlagToString(mask = FLAG_LAYOUT_IN_SCREEN, equals = FLAG_LAYOUT_IN_SCREEN,
+                    name = "LAYOUT_IN_SCREEN"),
+            @ViewDebug.FlagToString(mask = FLAG_LAYOUT_NO_LIMITS, equals = FLAG_LAYOUT_NO_LIMITS,
+                    name = "LAYOUT_NO_LIMITS"),
+            @ViewDebug.FlagToString(mask = FLAG_FULLSCREEN, equals = FLAG_FULLSCREEN,
+                    name = "FULLSCREEN"),
+            @ViewDebug.FlagToString(mask = FLAG_FORCE_NOT_FULLSCREEN, equals = FLAG_FORCE_NOT_FULLSCREEN,
+                    name = "FORCE_NOT_FULLSCREEN"),
+            @ViewDebug.FlagToString(mask = FLAG_DITHER, equals = FLAG_DITHER,
+                    name = "DITHER"),
+            @ViewDebug.FlagToString(mask = FLAG_SECURE, equals = FLAG_SECURE,
+                    name = "SECURE"),
+            @ViewDebug.FlagToString(mask = FLAG_SCALED, equals = FLAG_SCALED,
+                    name = "SCALED"),
+            @ViewDebug.FlagToString(mask = FLAG_IGNORE_CHEEK_PRESSES, equals = FLAG_IGNORE_CHEEK_PRESSES,
+                    name = "IGNORE_CHEEK_PRESSES"),
+            @ViewDebug.FlagToString(mask = FLAG_LAYOUT_INSET_DECOR, equals = FLAG_LAYOUT_INSET_DECOR,
+                    name = "LAYOUT_INSET_DECOR"),
+            @ViewDebug.FlagToString(mask = FLAG_ALT_FOCUSABLE_IM, equals = FLAG_ALT_FOCUSABLE_IM,
+                    name = "ALT_FOCUSABLE_IM"),
+            @ViewDebug.FlagToString(mask = FLAG_WATCH_OUTSIDE_TOUCH, equals = FLAG_WATCH_OUTSIDE_TOUCH,
+                    name = "WATCH_OUTSIDE_TOUCH"),
+            @ViewDebug.FlagToString(mask = FLAG_SHOW_WHEN_LOCKED, equals = FLAG_SHOW_WHEN_LOCKED,
+                    name = "SHOW_WHEN_LOCKED"),
+            @ViewDebug.FlagToString(mask = FLAG_SHOW_WALLPAPER, equals = FLAG_SHOW_WALLPAPER,
+                    name = "SHOW_WALLPAPER"),
+            @ViewDebug.FlagToString(mask = FLAG_TURN_SCREEN_ON, equals = FLAG_TURN_SCREEN_ON,
+                    name = "TURN_SCREEN_ON"),
+            @ViewDebug.FlagToString(mask = FLAG_DISMISS_KEYGUARD, equals = FLAG_DISMISS_KEYGUARD,
+                    name = "DISMISS_KEYGUARD"),
+            @ViewDebug.FlagToString(mask = FLAG_SPLIT_TOUCH, equals = FLAG_SPLIT_TOUCH,
+                    name = "SPLIT_TOUCH"),
+            @ViewDebug.FlagToString(mask = FLAG_HARDWARE_ACCELERATED, equals = FLAG_HARDWARE_ACCELERATED,
+                    name = "HARDWARE_ACCELERATED"),
+            @ViewDebug.FlagToString(mask = FLAG_LAYOUT_IN_OVERSCAN, equals = FLAG_LAYOUT_IN_OVERSCAN,
+                    name = "LOCAL_FOCUS_MODE"),
+            @ViewDebug.FlagToString(mask = FLAG_TRANSLUCENT_STATUS, equals = FLAG_TRANSLUCENT_STATUS,
+                    name = "TRANSLUCENT_STATUS"),
+            @ViewDebug.FlagToString(mask = FLAG_TRANSLUCENT_NAVIGATION, equals = FLAG_TRANSLUCENT_NAVIGATION,
+                    name = "TRANSLUCENT_NAVIGATION"),
+            @ViewDebug.FlagToString(mask = FLAG_LOCAL_FOCUS_MODE, equals = FLAG_LOCAL_FOCUS_MODE,
+                    name = "LOCAL_FOCUS_MODE"),
+            @ViewDebug.FlagToString(mask = FLAG_SLIPPERY, equals = FLAG_SLIPPERY,
+                    name = "FLAG_SLIPPERY"),
+            @ViewDebug.FlagToString(mask = FLAG_LAYOUT_ATTACHED_IN_DECOR, equals = FLAG_LAYOUT_ATTACHED_IN_DECOR,
+                    name = "FLAG_LAYOUT_ATTACHED_IN_DECOR"),
+            @ViewDebug.FlagToString(mask = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, equals = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+                    name = "DRAWS_SYSTEM_BAR_BACKGROUNDS")
+        }, formatToHexString = true)
+        @Flags
+        public int flags;
+
+        /**
+         * In the system process, we globally do not use hardware acceleration
+         * because there are many threads doing UI there and they conflict.
+         * If certain parts of the UI that really do want to use hardware
+         * acceleration, this flag can be set to force it.  This is basically
+         * for the lock screen.  Anyone else using it, you are probably wrong.
+         *
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED = 0x00000002;
+
+        /**
+         * By default, wallpapers are sent new offsets when the wallpaper is scrolled. Wallpapers
+         * may elect to skip these notifications if they are not doing anything productive with
+         * them (they do not affect the wallpaper scrolling operation) by calling
+         * {@link
+         * android.service.wallpaper.WallpaperService.Engine#setOffsetNotificationsEnabled(boolean)}.
+         *
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS = 0x00000004;
+
+        /**
+         * When set {@link LayoutParams#TYPE_APPLICATION_OVERLAY} windows will stay visible, even if
+         * {@link LayoutParams#SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS} is set for another
+         * visible window.
+         * @hide
+         */
+        @RequiresPermission(permission.SYSTEM_APPLICATION_OVERLAY)
+        public static final int PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY = 0x00000008;
+
+        /** In a multiuser system if this flag is set and the owner is a system process then this
+         * window will appear on all user screens. This overrides the default behavior of window
+         * types that normally only appear on the owning user's screen. Refer to each window type
+         * to determine its default behavior.
+         *
+         * {@hide} */
+        @SystemApi
+        @RequiresPermission(permission.INTERNAL_SYSTEM_WINDOW)
+        public static final int SYSTEM_FLAG_SHOW_FOR_ALL_USERS = 0x00000010;
+
+        /**
+         * Never animate position changes of the window.
+         *
+         * {@hide}
+         */
+        @UnsupportedAppUsage
+        @TestApi
+        public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 0x00000040;
+
+        /** Window flag: special flag to limit the size of the window to be
+         * original size ([320x480] x density). Used to create window for applications
+         * running under compatibility mode.
+         *
+         * {@hide} */
+        public static final int PRIVATE_FLAG_COMPATIBLE_WINDOW = 0x00000080;
+
+        /** Window flag: a special option intended for system dialogs.  When
+         * this flag is set, the window will demand focus unconditionally when
+         * it is created.
+         * {@hide} */
+        public static final int PRIVATE_FLAG_SYSTEM_ERROR = 0x00000100;
+
+        /**
+         * Flag that prevents the wallpaper behind the current window from receiving touch events.
+         *
+         * {@hide}
+         */
+        public static final int PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS = 0x00000800;
+
+        /**
+         * Flag to force the status bar window to be visible all the time. If the bar is hidden when
+         * this flag is set it will be shown again.
+         * This can only be set by {@link LayoutParams#TYPE_STATUS_BAR}.
+         *
+         * {@hide}
+         */
+        public static final int PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR = 0x00001000;
+
+        /**
+         * Flag that will make window ignore app visibility and instead depend purely on the decor
+         * view visibility for determining window visibility. This is used by recents to keep
+         * drawing after it launches an app.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY = 0x00004000;
+
+        /**
+         * Flag to indicate that this window is not expected to be replaced across
+         * configuration change triggered activity relaunches. In general the WindowManager
+         * expects Windows to be replaced after relaunch, and thus it will preserve their surfaces
+         * until the replacement is ready to show in order to prevent visual glitch. However
+         * some windows, such as PopupWindows expect to be cleared across configuration change,
+         * and thus should hint to the WindowManager that it should not wait for a replacement.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH = 0x00008000;
+
+        /**
+         * Flag to indicate that this child window should always be laid-out in the parent
+         * frame regardless of the current windowing mode configuration.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME = 0x00010000;
+
+        /**
+         * Flag to indicate that this window is always drawing the status bar background, no matter
+         * what the other flags are.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS = 0x00020000;
+
+        /**
+         * Flag to indicate that this window needs Sustained Performance Mode if
+         * the device supports it.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE = 0x00040000;
+
+        /**
+         * Flag to indicate that any window added by an application process that is of type
+         * {@link #TYPE_TOAST} or that requires
+         * {@link android.app.AppOpsManager#OP_SYSTEM_ALERT_WINDOW} permission should be hidden when
+         * this window is visible.
+         * @hide
+         */
+        @SystemApi
+        @RequiresPermission(permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
+        public static final int SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 0x00080000;
+
+        /**
+         * Indicates that this window is the rounded corners overlay present on some
+         * devices this means that it will be excluded from: screenshots,
+         * screen magnification, and mirroring.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY = 0x00100000;
+
+        /**
+         * Flag to prevent the window from being magnified by the accessibility magnifier.
+         *
+         * TODO(b/190623172): This is a temporary solution and need to find out another way instead.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_NOT_MAGNIFIABLE = 0x00400000;
+
+        /**
+         * Flag to indicate that the status bar window is in a state such that it forces showing
+         * the navigation bar unless the navigation bar window is explicitly set to
+         * {@link View#GONE}.
+         * It only takes effects if this is set by {@link LayoutParams#TYPE_STATUS_BAR}.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION = 0x00800000;
+
+        /**
+         * Flag to indicate that the window is color space agnostic, and the color can be
+         * interpreted to any color space.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC = 0x01000000;
+
+        /**
+         * Flag to request creation of a BLAST (Buffer as LayerState) Layer.
+         * If not specified the client will receive a BufferQueue layer.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_USE_BLAST = 0x02000000;
+
+        /**
+         * Flag to indicate that the window is controlling the appearance of system bars. So we
+         * don't need to adjust it by reading its system UI flags for compatibility.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_APPEARANCE_CONTROLLED = 0x04000000;
+
+        /**
+         * Flag to indicate that the window is controlling the behavior of system bars. So we don't
+         * need to adjust it by reading its window flags or system UI flags for compatibility.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_BEHAVIOR_CONTROLLED = 0x08000000;
+
+        /**
+         * Flag to indicate that the window is controlling how it fits window insets on its own.
+         * So we don't need to adjust its attributes for fitting window insets.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_FIT_INSETS_CONTROLLED = 0x10000000;
+
+        /**
+         * Flag to indicate that the window is a trusted overlay.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_TRUSTED_OVERLAY = 0x20000000;
+
+        /**
+         * Flag to indicate that the parent frame of a window should be inset by IME.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME = 0x40000000;
+
+        /**
+         * Flag to indicate that we want to intercept and handle global drag and drop for all users.
+         * This flag allows a window to considered for drag events even if not visible, and will
+         * receive drags for all active users in the system.
+         *
+         * Additional data is provided to windows with this flag, including the {@link ClipData}
+         * including all items with the {@link DragEvent#ACTION_DRAG_STARTED} event, and the
+         * actual drag surface with the {@link DragEvent#ACTION_DROP} event. If the window consumes,
+         * the drop, then the cleanup of the drag surface (provided as a part of
+         * {@link DragEvent#ACTION_DROP}) will be relinquished to the window.
+         * @hide
+         */
+        @RequiresPermission(permission.MANAGE_ACTIVITY_TASKS)
+        public static final int PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP = 0x80000000;
+
+        /**
+         * An internal annotation for flags that can be specified to {@link #softInputMode}.
+         *
+         * @hide
+         */
+        @SystemApi
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(flag = true, prefix = { "SYSTEM_FLAG_" }, value = {
+                SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+                SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
+        })
+        public @interface SystemFlags {}
+
+        /**
+         * @hide
+         */
+        @IntDef(flag = true, prefix="PRIVATE_FLAG_", value = {
+                PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED,
+                PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS,
+                SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
+                PRIVATE_FLAG_NO_MOVE_ANIMATION,
+                PRIVATE_FLAG_COMPATIBLE_WINDOW,
+                PRIVATE_FLAG_SYSTEM_ERROR,
+                PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
+                PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
+                PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
+                PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH,
+                PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
+                PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS,
+                PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE,
+                SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+                PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY,
+                PRIVATE_FLAG_NOT_MAGNIFIABLE,
+                PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION,
+                PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
+                PRIVATE_FLAG_USE_BLAST,
+                PRIVATE_FLAG_APPEARANCE_CONTROLLED,
+                PRIVATE_FLAG_BEHAVIOR_CONTROLLED,
+                PRIVATE_FLAG_FIT_INSETS_CONTROLLED,
+                PRIVATE_FLAG_TRUSTED_OVERLAY,
+                PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME,
+                PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP,
+                PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY,
+        })
+        public @interface PrivateFlags {}
+
+        /**
+         * Control flags that are private to the platform.
+         * @hide
+         */
+        @UnsupportedAppUsage
+        @ViewDebug.ExportedProperty(flagMapping = {
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED,
+                        equals = PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED,
+                        name = "FORCE_HARDWARE_ACCELERATED"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS,
+                        equals = PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS,
+                        name = "WANTS_OFFSET_NOTIFICATIONS"),
+                @ViewDebug.FlagToString(
+                        mask = SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
+                        equals = SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
+                        name = "SHOW_FOR_ALL_USERS"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_NO_MOVE_ANIMATION,
+                        equals = PRIVATE_FLAG_NO_MOVE_ANIMATION,
+                        name = "NO_MOVE_ANIMATION"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_COMPATIBLE_WINDOW,
+                        equals = PRIVATE_FLAG_COMPATIBLE_WINDOW,
+                        name = "COMPATIBLE_WINDOW"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_SYSTEM_ERROR,
+                        equals = PRIVATE_FLAG_SYSTEM_ERROR,
+                        name = "SYSTEM_ERROR"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
+                        equals = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
+                        name = "DISABLE_WALLPAPER_TOUCH_EVENTS"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
+                        equals = PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
+                        name = "FORCE_STATUS_BAR_VISIBLE"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
+                        equals = PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
+                        name = "FORCE_DECOR_VIEW_VISIBILITY"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH,
+                        equals = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH,
+                        name = "WILL_NOT_REPLACE_ON_RELAUNCH"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
+                        equals = PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
+                        name = "LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS,
+                        equals = PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS,
+                        name = "FORCE_DRAW_STATUS_BAR_BACKGROUND"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE,
+                        equals = PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE,
+                        name = "SUSTAINED_PERFORMANCE_MODE"),
+                @ViewDebug.FlagToString(
+                        mask = SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+                        equals = SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+                        name = "HIDE_NON_SYSTEM_OVERLAY_WINDOWS"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY,
+                        equals = PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY,
+                        name = "IS_ROUNDED_CORNERS_OVERLAY"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_NOT_MAGNIFIABLE,
+                        equals = PRIVATE_FLAG_NOT_MAGNIFIABLE,
+                        name = "NOT_MAGNIFIABLE"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION,
+                        equals = PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION,
+                        name = "STATUS_FORCE_SHOW_NAVIGATION"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
+                        equals = PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
+                        name = "COLOR_SPACE_AGNOSTIC"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_USE_BLAST,
+                        equals = PRIVATE_FLAG_USE_BLAST,
+                        name = "USE_BLAST"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_APPEARANCE_CONTROLLED,
+                        equals = PRIVATE_FLAG_APPEARANCE_CONTROLLED,
+                        name = "APPEARANCE_CONTROLLED"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_BEHAVIOR_CONTROLLED,
+                        equals = PRIVATE_FLAG_BEHAVIOR_CONTROLLED,
+                        name = "BEHAVIOR_CONTROLLED"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_FIT_INSETS_CONTROLLED,
+                        equals = PRIVATE_FLAG_FIT_INSETS_CONTROLLED,
+                        name = "FIT_INSETS_CONTROLLED"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_TRUSTED_OVERLAY,
+                        equals = PRIVATE_FLAG_TRUSTED_OVERLAY,
+                        name = "TRUSTED_OVERLAY"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME,
+                        equals = PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME,
+                        name = "INSET_PARENT_FRAME_BY_IME"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP,
+                        equals = PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP,
+                        name = "INTERCEPT_GLOBAL_DRAG_AND_DROP"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY,
+                        equals = PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY,
+                        name = "PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY")
+        })
+        @PrivateFlags
+        @TestApi
+        public int privateFlags;
+
+        /**
+         * Given a particular set of window manager flags, determine whether
+         * such a window may be a target for an input method when it has
+         * focus.  In particular, this checks the
+         * {@link #FLAG_NOT_FOCUSABLE} and {@link #FLAG_ALT_FOCUSABLE_IM}
+         * flags and returns true if the combination of the two corresponds
+         * to a window that can use the input method.
+         *
+         * @param flags The current window manager flags.
+         *
+         * @return Returns {@code true} if a window with the given flags would be able to
+         * use the input method, {@code false} if not.
+         */
+        public static boolean mayUseInputMethod(int flags) {
+            return (flags & FLAG_NOT_FOCUSABLE) != FLAG_NOT_FOCUSABLE
+                    && (flags & FLAG_ALT_FOCUSABLE_IM) != FLAG_ALT_FOCUSABLE_IM;
+        }
+
+        /**
+         * Mask for {@link #softInputMode} of the bits that determine the
+         * desired visibility state of the soft input area for this window.
+         */
+        public static final int SOFT_INPUT_MASK_STATE = 0x0f;
+
+        /**
+         * Visibility state for {@link #softInputMode}: no state has been specified. The system may
+         * show or hide the software keyboard for better user experience when the window gains
+         * focus.
+         */
+        public static final int SOFT_INPUT_STATE_UNSPECIFIED = 0;
+
+        /**
+         * Visibility state for {@link #softInputMode}: please don't change the state of
+         * the soft input area.
+         */
+        public static final int SOFT_INPUT_STATE_UNCHANGED = 1;
+
+        /**
+         * Visibility state for {@link #softInputMode}: please hide any soft input
+         * area when normally appropriate (when the user is navigating
+         * forward to your window).
+         */
+        public static final int SOFT_INPUT_STATE_HIDDEN = 2;
+
+        /**
+         * Visibility state for {@link #softInputMode}: please always hide any
+         * soft input area when this window receives focus.
+         */
+        public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;
+
+        /**
+         * Visibility state for {@link #softInputMode}: please show the soft
+         * input area when normally appropriate (when the user is navigating
+         * forward to your window).
+         *
+         * <p>Applications that target {@link android.os.Build.VERSION_CODES#P} and later, this flag
+         * is ignored unless there is a focused view that returns {@code true} from
+         * {@link View#isInEditMode()} when the window is focused.</p>
+         */
+        public static final int SOFT_INPUT_STATE_VISIBLE = 4;
+
+        /**
+         * Visibility state for {@link #softInputMode}: please always make the
+         * soft input area visible when this window receives input focus.
+         *
+         * <p>Applications that target {@link android.os.Build.VERSION_CODES#P} and later, this flag
+         * is ignored unless there is a focused view that returns {@code true} from
+         * {@link View#isInEditMode()} when the window is focused.</p>
+         */
+        public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;
+
+        /**
+         * Mask for {@link #softInputMode} of the bits that determine the
+         * way that the window should be adjusted to accommodate the soft
+         * input window.
+         */
+        public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;
+
+        /** Adjustment option for {@link #softInputMode}: nothing specified.
+         * The system will try to pick one or
+         * the other depending on the contents of the window.
+         */
+        public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;
+
+        /** Adjustment option for {@link #softInputMode}: set to allow the
+         * window to be resized when an input
+         * method is shown, so that its contents are not covered by the input
+         * method.  This can <em>not</em> be combined with
+         * {@link #SOFT_INPUT_ADJUST_PAN}; if
+         * neither of these are set, then the system will try to pick one or
+         * the other depending on the contents of the window. If the window's
+         * layout parameter flags include {@link #FLAG_FULLSCREEN}, this
+         * value for {@link #softInputMode} will be ignored; the window will
+         * not resize, but will stay fullscreen.
+         *
+         * @deprecated Call {@link Window#setDecorFitsSystemWindows(boolean)} with {@code false} and
+         * install an {@link OnApplyWindowInsetsListener} on your root content view that fits insets
+         * of type {@link Type#ime()}.
+         */
+        @Deprecated
+        public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;
+
+        /** Adjustment option for {@link #softInputMode}: set to have a window
+         * pan when an input method is
+         * shown, so it doesn't need to deal with resizing but just panned
+         * by the framework to ensure the current input focus is visible.  This
+         * can <em>not</em> be combined with {@link #SOFT_INPUT_ADJUST_RESIZE}; if
+         * neither of these are set, then the system will try to pick one or
+         * the other depending on the contents of the window.
+         */
+        public static final int SOFT_INPUT_ADJUST_PAN = 0x20;
+
+        /** Adjustment option for {@link #softInputMode}: set to have a window
+         * not adjust for a shown input method.  The window will not be resized,
+         * and it will not be panned to make its focus visible.
+         */
+        public static final int SOFT_INPUT_ADJUST_NOTHING = 0x30;
+
+        /**
+         * Bit for {@link #softInputMode}: set when the user has navigated
+         * forward to the window.  This is normally set automatically for
+         * you by the system, though you may want to set it in certain cases
+         * when you are displaying a window yourself.  This flag will always
+         * be cleared automatically after the window is displayed.
+         */
+        public static final int SOFT_INPUT_IS_FORWARD_NAVIGATION = 0x100;
+
+        /**
+         * An internal annotation for flags that can be specified to {@link #softInputMode}.
+         *
+         * @hide
+         */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(flag = true, prefix = { "SOFT_INPUT_" }, value = {
+                SOFT_INPUT_STATE_UNSPECIFIED,
+                SOFT_INPUT_STATE_UNCHANGED,
+                SOFT_INPUT_STATE_HIDDEN,
+                SOFT_INPUT_STATE_ALWAYS_HIDDEN,
+                SOFT_INPUT_STATE_VISIBLE,
+                SOFT_INPUT_STATE_ALWAYS_VISIBLE,
+                SOFT_INPUT_ADJUST_UNSPECIFIED,
+                SOFT_INPUT_ADJUST_RESIZE,
+                SOFT_INPUT_ADJUST_PAN,
+                SOFT_INPUT_ADJUST_NOTHING,
+                SOFT_INPUT_IS_FORWARD_NAVIGATION,
+        })
+        public @interface SoftInputModeFlags {}
+
+        /**
+         * Desired operating mode for any soft input area.  May be any combination
+         * of:
+         *
+         * <ul>
+         * <li> One of the visibility states
+         * {@link #SOFT_INPUT_STATE_UNSPECIFIED}, {@link #SOFT_INPUT_STATE_UNCHANGED},
+         * {@link #SOFT_INPUT_STATE_HIDDEN}, {@link #SOFT_INPUT_STATE_ALWAYS_HIDDEN},
+         * {@link #SOFT_INPUT_STATE_VISIBLE}, or {@link #SOFT_INPUT_STATE_ALWAYS_VISIBLE}.
+         * <li> One of the adjustment options
+         * {@link #SOFT_INPUT_ADJUST_UNSPECIFIED}, {@link #SOFT_INPUT_ADJUST_RESIZE},
+         * {@link #SOFT_INPUT_ADJUST_PAN}, or {@link #SOFT_INPUT_ADJUST_NOTHING}.
+         * </ul>
+         *
+         *
+         * <p>This flag can be controlled in your theme through the
+         * {@link android.R.attr#windowSoftInputMode} attribute.</p>
+         */
+        @SoftInputModeFlags
+        public int softInputMode;
+
+        /**
+         * Placement of window within the screen as per {@link Gravity}.  Both
+         * {@link Gravity#apply(int, int, int, android.graphics.Rect, int, int,
+         * android.graphics.Rect) Gravity.apply} and
+         * {@link Gravity#applyDisplay(int, android.graphics.Rect, android.graphics.Rect)
+         * Gravity.applyDisplay} are used during window layout, with this value
+         * given as the desired gravity.  For example you can specify
+         * {@link Gravity#DISPLAY_CLIP_HORIZONTAL Gravity.DISPLAY_CLIP_HORIZONTAL} and
+         * {@link Gravity#DISPLAY_CLIP_VERTICAL Gravity.DISPLAY_CLIP_VERTICAL} here
+         * to control the behavior of
+         * {@link Gravity#applyDisplay(int, android.graphics.Rect, android.graphics.Rect)
+         * Gravity.applyDisplay}.
+         *
+         * @see Gravity
+         */
+        @GravityFlags
+        public int gravity;
+
+        /**
+         * The horizontal margin, as a percentage of the container's width,
+         * between the container and the widget.  See
+         * {@link Gravity#apply(int, int, int, android.graphics.Rect, int, int,
+         * android.graphics.Rect) Gravity.apply} for how this is used.  This
+         * field is added with {@link #x} to supply the <var>xAdj</var> parameter.
+         */
+        public float horizontalMargin;
+
+        /**
+         * The vertical margin, as a percentage of the container's height,
+         * between the container and the widget.  See
+         * {@link Gravity#apply(int, int, int, android.graphics.Rect, int, int,
+         * android.graphics.Rect) Gravity.apply} for how this is used.  This
+         * field is added with {@link #y} to supply the <var>yAdj</var> parameter.
+         */
+        public float verticalMargin;
+
+        /**
+         * Positive insets between the drawing surface and window content.
+         *
+         * @hide
+         */
+        public final Rect surfaceInsets = new Rect();
+
+        /**
+         * Whether the surface insets have been manually set. When set to
+         * {@code false}, the view root will automatically determine the
+         * appropriate surface insets.
+         *
+         * @see #surfaceInsets
+         * @hide
+         */
+        public boolean hasManualSurfaceInsets;
+
+        /**
+         * Whether we should use global insets state when report insets to the window. When set to
+         * {@code true}, all the insets will be reported to the window regardless of the z-order.
+         * Otherwise, only the insets above the given window will be reported.
+         *
+         * @hide
+         */
+        public boolean receiveInsetsIgnoringZOrder;
+
+        /**
+         * Whether the previous surface insets should be used vs. what is currently set. When set
+         * to {@code true}, the view root will ignore surfaces insets in this object and use what
+         * it currently has.
+         *
+         * @see #surfaceInsets
+         * @hide
+         */
+        public boolean preservePreviousSurfaceInsets = true;
+
+        /**
+         * The desired bitmap format.  May be one of the constants in
+         * {@link android.graphics.PixelFormat}. The choice of format
+         * might be overridden by {@link #setColorMode(int)}. Default is OPAQUE.
+         */
+        public int format;
+
+        /**
+         * A style resource defining the animations to use for this window.
+         * This must be a system resource; it can not be an application resource
+         * because the window manager does not have access to applications.
+         */
+        public int windowAnimations;
+
+        /**
+         * An alpha value to apply to this entire window.
+         * An alpha of 1.0 means fully opaque and 0.0 means fully transparent
+         */
+        public float alpha = 1.0f;
+
+        /**
+         * When {@link #FLAG_DIM_BEHIND} is set, this is the amount of dimming
+         * to apply.  Range is from 1.0 for completely opaque to 0.0 for no
+         * dim.
+         */
+        public float dimAmount = 1.0f;
+
+        /**
+         * Default value for {@link #screenBrightness} and {@link #buttonBrightness}
+         * indicating that the brightness value is not overridden for this window
+         * and normal brightness policy should be used.
+         */
+        public static final float BRIGHTNESS_OVERRIDE_NONE = -1.0f;
+
+        /**
+         * Value for {@link #screenBrightness} and {@link #buttonBrightness}
+         * indicating that the screen or button backlight brightness should be set
+         * to the lowest value when this window is in front.
+         */
+        public static final float BRIGHTNESS_OVERRIDE_OFF = 0.0f;
+
+        /**
+         * Value for {@link #screenBrightness} and {@link #buttonBrightness}
+         * indicating that the screen or button backlight brightness should be set
+         * to the hightest value when this window is in front.
+         */
+        public static final float BRIGHTNESS_OVERRIDE_FULL = 1.0f;
+
+        /**
+         * This can be used to override the user's preferred brightness of
+         * the screen.  A value of less than 0, the default, means to use the
+         * preferred screen brightness.  0 to 1 adjusts the brightness from
+         * dark to full bright.
+         */
+        public float screenBrightness = BRIGHTNESS_OVERRIDE_NONE;
+
+        /**
+         * This can be used to override the standard behavior of the button and
+         * keyboard backlights.  A value of less than 0, the default, means to
+         * use the standard backlight behavior.  0 to 1 adjusts the brightness
+         * from dark to full bright.
+         */
+        public float buttonBrightness = BRIGHTNESS_OVERRIDE_NONE;
+
+        /**
+         * Unspecified value for {@link #rotationAnimation} indicating
+         * a lack of preference.
+         * @hide
+         */
+        public static final int ROTATION_ANIMATION_UNSPECIFIED = -1;
+
+        /**
+         * Value for {@link #rotationAnimation} which specifies that this
+         * window will visually rotate in or out following a rotation.
+         */
+        public static final int ROTATION_ANIMATION_ROTATE = 0;
+
+        /**
+         * Value for {@link #rotationAnimation} which specifies that this
+         * window will fade in or out following a rotation.
+         */
+        public static final int ROTATION_ANIMATION_CROSSFADE = 1;
+
+        /**
+         * Value for {@link #rotationAnimation} which specifies that this window
+         * will immediately disappear or appear following a rotation.
+         */
+        public static final int ROTATION_ANIMATION_JUMPCUT = 2;
+
+        /**
+         * Value for {@link #rotationAnimation} to specify seamless rotation mode.
+         * This works like JUMPCUT but will fall back to CROSSFADE if rotation
+         * can't be applied without pausing the screen. For example, this is ideal
+         * for Camera apps which don't want the viewfinder contents to ever rotate
+         * or fade (and rather to be seamless) but also don't want ROTATION_ANIMATION_JUMPCUT
+         * during app transition scenarios where seamless rotation can't be applied.
+         */
+        public static final int ROTATION_ANIMATION_SEAMLESS = 3;
+
+        /**
+         * Define the exit and entry animations used on this window when the device is rotated.
+         * This only has an affect if the incoming and outgoing topmost
+         * opaque windows have the #FLAG_FULLSCREEN bit set and are not covered
+         * by other windows. All other situations default to the
+         * {@link #ROTATION_ANIMATION_ROTATE} behavior.
+         *
+         * @see #ROTATION_ANIMATION_ROTATE
+         * @see #ROTATION_ANIMATION_CROSSFADE
+         * @see #ROTATION_ANIMATION_JUMPCUT
+         */
+        public int rotationAnimation = ROTATION_ANIMATION_ROTATE;
+
+        /**
+         * Identifier for this window.  This will usually be filled in for
+         * you.
+         */
+        public IBinder token = null;
+
+        /**
+         * The token of {@link android.window.WindowContext}. It is usually a
+         * {@link android.app.WindowTokenClient} and is used for associating the params with an
+         * existing node in the WindowManager hierarchy and getting the corresponding
+         * {@link Configuration} and {@link android.content.res.Resources} values with updates
+         * propagated from the server side.
+         *
+         * @hide
+         */
+        @Nullable
+        public IBinder mWindowContextToken = null;
+
+        /**
+         * Name of the package owning this window.
+         */
+        public String packageName = null;
+
+        /**
+         * Specific orientation value for a window.
+         * May be any of the same values allowed
+         * for {@link android.content.pm.ActivityInfo#screenOrientation}.
+         * If not set, a default value of
+         * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED}
+         * will be used.
+         */
+        @ActivityInfo.ScreenOrientation
+        public int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+
+        /**
+         * The preferred refresh rate for the window.
+         *
+         * This must be one of the supported refresh rates obtained for the display(s) the window
+         * is on. The selected refresh rate will be applied to the display's default mode.
+         *
+         * This value is ignored if {@link #preferredDisplayModeId} is set.
+         *
+         * @see Display#getSupportedRefreshRates()
+         * @deprecated use {@link #preferredDisplayModeId} instead
+         */
+        @Deprecated
+        public float preferredRefreshRate;
+
+        /**
+         * Id of the preferred display mode for the window.
+         * <p>
+         * This must be one of the supported modes obtained for the display(s) the window is on.
+         * A value of {@code 0} means no preference.
+         *
+         * @see Display#getSupportedModes()
+         * @see Display.Mode#getModeId()
+         */
+        public int preferredDisplayModeId;
+
+        /**
+         * The min display refresh rate while the window is in focus.
+         *
+         * This value is ignored if {@link #preferredDisplayModeId} is set.
+         * @hide
+         */
+        public float preferredMinDisplayRefreshRate;
+
+        /**
+         * The max display refresh rate while the window is in focus.
+         *
+         * This value is ignored if {@link #preferredDisplayModeId} is set.
+         * @hide
+         */
+        public float preferredMaxDisplayRefreshRate;
+
+        /**
+         * An internal annotation for flags that can be specified to {@link #systemUiVisibility}
+         * and {@link #subtreeSystemUiVisibility}.
+         *
+         * @hide
+         */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(flag = true, prefix = { "" }, value = {
+            SYSTEM_UI_FLAG_VISIBLE,
+            SYSTEM_UI_FLAG_LOW_PROFILE,
+            SYSTEM_UI_FLAG_HIDE_NAVIGATION,
+            SYSTEM_UI_FLAG_FULLSCREEN,
+            SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
+            SYSTEM_UI_FLAG_LAYOUT_STABLE,
+            SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION,
+            SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,
+            SYSTEM_UI_FLAG_IMMERSIVE,
+            SYSTEM_UI_FLAG_IMMERSIVE_STICKY,
+            SYSTEM_UI_FLAG_LIGHT_STATUS_BAR,
+            STATUS_BAR_DISABLE_EXPAND,
+            STATUS_BAR_DISABLE_NOTIFICATION_ICONS,
+            STATUS_BAR_DISABLE_NOTIFICATION_ALERTS,
+            STATUS_BAR_DISABLE_NOTIFICATION_TICKER,
+            STATUS_BAR_DISABLE_SYSTEM_INFO,
+            STATUS_BAR_DISABLE_HOME,
+            STATUS_BAR_DISABLE_BACK,
+            STATUS_BAR_DISABLE_CLOCK,
+            STATUS_BAR_DISABLE_RECENT,
+            STATUS_BAR_DISABLE_SEARCH,
+        })
+        public @interface SystemUiVisibilityFlags {}
+
+        /**
+         * Control the visibility of the status bar.
+         *
+         * @see View#STATUS_BAR_VISIBLE
+         * @see View#STATUS_BAR_HIDDEN
+         *
+         * @deprecated SystemUiVisibility flags are deprecated. Use {@link WindowInsetsController}
+         * instead.
+         */
+        @SystemUiVisibilityFlags
+        @Deprecated
+        public int systemUiVisibility;
+
+        /**
+         * @hide
+         * The ui visibility as requested by the views in this hierarchy.
+         * the combined value should be systemUiVisibility | subtreeSystemUiVisibility.
+         */
+        @SystemUiVisibilityFlags
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        public int subtreeSystemUiVisibility;
+
+        /**
+         * Get callbacks about the system ui visibility changing.
+         *
+         * TODO: Maybe there should be a bitfield of optional callbacks that we need.
+         *
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public boolean hasSystemUiListeners;
+
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(
+                value = {LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT,
+                        LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES,
+                        LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER,
+                        LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS})
+        @interface LayoutInDisplayCutoutMode {}
+
+        /**
+         * Controls how the window is laid out if there is a {@link DisplayCutout}.
+         *
+         * <p>
+         * Defaults to {@link #LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT}.
+         *
+         * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
+         * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
+         * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
+         * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+         * @see DisplayCutout
+         * @see android.R.attr#windowLayoutInDisplayCutoutMode
+         *         android:windowLayoutInDisplayCutoutMode
+         */
+        @LayoutInDisplayCutoutMode
+        public int layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
+
+        /**
+         * The window is allowed to extend into the {@link DisplayCutout} area, only if the
+         * {@link DisplayCutout} is fully contained within a system bar. Otherwise, the window is
+         * laid out such that it does not overlap with the {@link DisplayCutout} area.
+         *
+         * <p>
+         * In practice, this means that if the window did not set {@link #FLAG_FULLSCREEN} or
+         * {@link View#SYSTEM_UI_FLAG_FULLSCREEN}, it can extend into the cutout area in portrait
+         * if the cutout is at the top edge. Similarly for
+         * {@link View#SYSTEM_UI_FLAG_HIDE_NAVIGATION} and a cutout at the bottom of the screen.
+         * Otherwise (i.e. fullscreen or landscape) it is laid out such that it does not overlap the
+         * cutout area.
+         *
+         * <p>
+         * The usual precautions for not overlapping with the status and navigation bar are
+         * sufficient for ensuring that no important content overlaps with the DisplayCutout.
+         *
+         * @see DisplayCutout
+         * @see WindowInsets
+         * @see #layoutInDisplayCutoutMode
+         * @see android.R.attr#windowLayoutInDisplayCutoutMode
+         *         android:windowLayoutInDisplayCutoutMode
+         */
+        public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT = 0;
+
+        /**
+         * The window is always allowed to extend into the {@link DisplayCutout} areas on the short
+         * edges of the screen.
+         *
+         * The window will never extend into a {@link DisplayCutout} area on the long edges of the
+         * screen.
+         *
+         * <p>
+         * The window must make sure that no important content overlaps with the
+         * {@link DisplayCutout}.
+         *
+         * <p>
+         * In this mode, the window extends under cutouts on the short edge of the display in both
+         * portrait and landscape, regardless of whether the window is hiding the system bars:<br/>
+         * <img src="{@docRoot}reference/android/images/display_cutout/short_edge/fullscreen_top_no_letterbox.png"
+         * height="720"
+         * alt="Screenshot of a fullscreen activity on a display with a cutout at the top edge in
+         *         portrait, no letterbox is applied."/>
+         *
+         * <img src="{@docRoot}reference/android/images/display_cutout/short_edge/landscape_top_no_letterbox.png"
+         * width="720"
+         * alt="Screenshot of an activity on a display with a cutout at the top edge in landscape,
+         *         no letterbox is applied."/>
+         *
+         * <p>
+         * A cutout in the corner is considered to be on the short edge: <br/>
+         * <img src="{@docRoot}reference/android/images/display_cutout/short_edge/fullscreen_corner_no_letterbox.png"
+         * height="720"
+         * alt="Screenshot of a fullscreen activity on a display with a cutout in the corner in
+         *         portrait, no letterbox is applied."/>
+         *
+         * <p>
+         * On the other hand, should the cutout be on the long edge of the display, a letterbox will
+         * be applied such that the window does not extend into the cutout on either long edge:
+         * <br/>
+         * <img src="{@docRoot}reference/android/images/display_cutout/short_edge/portrait_side_letterbox.png"
+         * height="720"
+         * alt="Screenshot of an activity on a display with a cutout on the long edge in portrait,
+         *         letterbox is applied."/>
+         *
+         * @see DisplayCutout
+         * @see WindowInsets#getDisplayCutout()
+         * @see #layoutInDisplayCutoutMode
+         * @see android.R.attr#windowLayoutInDisplayCutoutMode
+         *         android:windowLayoutInDisplayCutoutMode
+         */
+        public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES = 1;
+
+        /**
+         * The window is never allowed to overlap with the DisplayCutout area.
+         *
+         * <p>
+         * This should be used with windows that transiently set
+         * {@link View#SYSTEM_UI_FLAG_FULLSCREEN} or {@link View#SYSTEM_UI_FLAG_HIDE_NAVIGATION}
+         * to avoid a relayout of the window when the respective flag is set or cleared.
+         *
+         * @see DisplayCutout
+         * @see #layoutInDisplayCutoutMode
+         * @see android.R.attr#windowLayoutInDisplayCutoutMode
+         *         android:windowLayoutInDisplayCutoutMode
+         */
+        public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER = 2;
+
+        /**
+         * The window is always allowed to extend into the {@link DisplayCutout} areas on the all
+         * edges of the screen.
+         *
+         * <p>
+         * The window must make sure that no important content overlaps with the
+         * {@link DisplayCutout}.
+         *
+         * <p>
+         * In this mode, the window extends under cutouts on the all edges of the display in both
+         * portrait and landscape, regardless of whether the window is hiding the system bars.
+         *
+         * @see DisplayCutout
+         * @see WindowInsets#getDisplayCutout()
+         * @see #layoutInDisplayCutoutMode
+         * @see android.R.attr#windowLayoutInDisplayCutoutMode
+         *         android:windowLayoutInDisplayCutoutMode
+         */
+        public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS = 3;
+
+        /**
+         * When this window has focus, disable touch pad pointer gesture processing.
+         * The window will receive raw position updates from the touch pad instead
+         * of pointer movements and synthetic touch events.
+         *
+         * @hide
+         */
+        public static final int INPUT_FEATURE_DISABLE_POINTER_GESTURES = 0x00000001;
+
+        /**
+         * Does not construct an input channel for this window.  The channel will therefore
+         * be incapable of receiving input.
+         *
+         * @hide
+         */
+        public static final int INPUT_FEATURE_NO_INPUT_CHANNEL = 0x00000002;
+
+        /**
+         * When this window has focus, does not call user activity for all input events so
+         * the application will have to do it itself.  Should only be used by
+         * the keyguard and phone app.
+         * <p>
+         * Should only be used by the keyguard and phone app.
+         * </p>
+         *
+         * @hide
+         */
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        public static final int INPUT_FEATURE_DISABLE_USER_ACTIVITY = 0x00000004;
+
+        /**
+         * An internal annotation for flags that can be specified to {@link #inputFeatures}.
+         *
+         * @hide
+         */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(flag = true, prefix = { "INPUT_FEATURE_" }, value = {
+            INPUT_FEATURE_DISABLE_POINTER_GESTURES,
+            INPUT_FEATURE_NO_INPUT_CHANNEL,
+            INPUT_FEATURE_DISABLE_USER_ACTIVITY,
+        })
+        public @interface InputFeatureFlags {}
+
+        /**
+         * Control special features of the input subsystem.
+         *
+         * @see #INPUT_FEATURE_DISABLE_POINTER_GESTURES
+         * @see #INPUT_FEATURE_NO_INPUT_CHANNEL
+         * @see #INPUT_FEATURE_DISABLE_USER_ACTIVITY
+         * @hide
+         */
+        @InputFeatureFlags
+        @UnsupportedAppUsage
+        public int inputFeatures;
+
+        /**
+         * Sets the number of milliseconds before the user activity timeout occurs
+         * when this window has focus.  A value of -1 uses the standard timeout.
+         * A value of 0 uses the minimum support display timeout.
+         * <p>
+         * This property can only be used to reduce the user specified display timeout;
+         * it can never make the timeout longer than it normally would be.
+         * </p><p>
+         * Should only be used by the keyguard and phone app.
+         * </p>
+         *
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public long userActivityTimeout = -1;
+
+        /**
+         * For windows with an anchor (e.g. PopupWindow), keeps track of the View that anchors the
+         * window.
+         *
+         * @hide
+         */
+        public long accessibilityIdOfAnchor = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
+
+        /**
+         * The window title isn't kept in sync with what is displayed in the title bar, so we
+         * separately track the currently shown title to provide to accessibility.
+         *
+         * @hide
+         */
+        @TestApi
+        public CharSequence accessibilityTitle;
+
+        /**
+         * Sets a timeout in milliseconds before which the window will be hidden
+         * by the window manager. Useful for transient notifications like toasts
+         * so we don't have to rely on client cooperation to ensure the window
+         * is hidden. Must be specified at window creation time. Note that apps
+         * are not prepared to handle their windows being removed without their
+         * explicit request and may try to interact with the removed window
+         * resulting in undefined behavior and crashes. Therefore, we do hide
+         * such windows to prevent them from overlaying other apps.
+         *
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public long hideTimeoutMilliseconds = -1;
+
+        /**
+         * Indicates whether this window wants the connected display to do minimal post processing
+         * on the produced image or video frames. This will only be requested if the window is
+         * visible on the screen.
+         *
+         * <p>This setting should be used when low latency has a higher priority than image
+         * enhancement processing (e.g. for games or video conferencing).
+         *
+         * <p>If the Display sink is connected via HDMI, the device will begin to send infoframes
+         * with Auto Low Latency Mode enabled and Game Content Type. This will switch the connected
+         * display to a minimal image processing mode (if available), which reduces latency,
+         * improving the user experience for gaming or video conferencing applications. For more
+         * information, see HDMI 2.1 specification.
+         *
+         * <p>If the Display sink has an internal connection or uses some other protocol than HDMI,
+         * effects may be similar but implementation-defined.
+         *
+         * <p>The ability to switch to a mode with minimal post proessing may be disabled by a user
+         * setting in the system settings menu. In that case, this field is ignored and the display
+         * will remain in its current mode.
+         *
+         * @see android.content.pm.ActivityInfo#FLAG_PREFER_MINIMAL_POST_PROCESSING
+         * @see android.view.Display#isMinimalPostProcessingSupported
+         * @see android.view.Window#setPreferMinimalPostProcessing
+         */
+        public boolean preferMinimalPostProcessing = false;
+
+        /**
+         * Specifies the amount of blur to be used to blur everything behind the window.
+         * The effect is similar to the dimAmount, but instead of dimming, the content behind
+         * will be blurred.
+         *
+         * The blur behind radius range starts at 0, which means no blur, and increases until 150
+         * for the densest blur.
+         *
+         * @see #setBlurBehindRadius
+         */
+        private int mBlurBehindRadius = 0;
+
+        /**
+         * The color mode requested by this window. The target display may
+         * not be able to honor the request. When the color mode is not set
+         * to {@link ActivityInfo#COLOR_MODE_DEFAULT}, it might override the
+         * pixel format specified in {@link #format}.
+         *
+         * @hide
+         */
+        @ActivityInfo.ColorMode
+        private int mColorMode = COLOR_MODE_DEFAULT;
+
+        /**
+         * Carries the requests about {@link WindowInsetsController.Appearance} and
+         * {@link WindowInsetsController.Behavior} to the system windows which can produce insets.
+         *
+         * @hide
+         */
+        public final InsetsFlags insetsFlags = new InsetsFlags();
+
+        @ViewDebug.ExportedProperty(flagMapping = {
+                @ViewDebug.FlagToString(
+                        mask = STATUS_BARS,
+                        equals = STATUS_BARS,
+                        name = "STATUS_BARS"),
+                @ViewDebug.FlagToString(
+                        mask = NAVIGATION_BARS,
+                        equals = NAVIGATION_BARS,
+                        name = "NAVIGATION_BARS"),
+                @ViewDebug.FlagToString(
+                        mask = CAPTION_BAR,
+                        equals = CAPTION_BAR,
+                        name = "CAPTION_BAR"),
+                @ViewDebug.FlagToString(
+                        mask = IME,
+                        equals = IME,
+                        name = "IME"),
+                @ViewDebug.FlagToString(
+                        mask = SYSTEM_GESTURES,
+                        equals = SYSTEM_GESTURES,
+                        name = "SYSTEM_GESTURES"),
+                @ViewDebug.FlagToString(
+                        mask = MANDATORY_SYSTEM_GESTURES,
+                        equals = MANDATORY_SYSTEM_GESTURES,
+                        name = "MANDATORY_SYSTEM_GESTURES"),
+                @ViewDebug.FlagToString(
+                        mask = TAPPABLE_ELEMENT,
+                        equals = TAPPABLE_ELEMENT,
+                        name = "TAPPABLE_ELEMENT"),
+                @ViewDebug.FlagToString(
+                        mask = WINDOW_DECOR,
+                        equals = WINDOW_DECOR,
+                        name = "WINDOW_DECOR")
+        })
+        private @InsetsType int mFitInsetsTypes = Type.systemBars();
+
+        @ViewDebug.ExportedProperty(flagMapping = {
+                @ViewDebug.FlagToString(
+                        mask = LEFT,
+                        equals = LEFT,
+                        name = "LEFT"),
+                @ViewDebug.FlagToString(
+                        mask = TOP,
+                        equals = TOP,
+                        name = "TOP"),
+                @ViewDebug.FlagToString(
+                        mask = RIGHT,
+                        equals = RIGHT,
+                        name = "RIGHT"),
+                @ViewDebug.FlagToString(
+                        mask = BOTTOM,
+                        equals = BOTTOM,
+                        name = "BOTTOM")
+        })
+        private @InsetsSide int mFitInsetsSides = Side.all();
+
+        private boolean mFitInsetsIgnoringVisibility = false;
+
+        /**
+         * {@link InsetsState.InternalInsetsType}s to be applied to the window
+         * If {@link #type} has the predefined insets (like {@link #TYPE_STATUS_BAR} or
+         * {@link #TYPE_NAVIGATION_BAR}), this field will be ignored.
+         *
+         * <p>Note: provide only one inset corresponding to the window type (like
+         * {@link InsetsState.InternalInsetsType#ITYPE_STATUS_BAR} or
+         * {@link InsetsState.InternalInsetsType#ITYPE_NAVIGATION_BAR})</p>
+         * @hide
+         */
+        public @InsetsState.InternalInsetsType int[] providesInsetsTypes;
+
+        /**
+         * Specifies types of insets that this window should avoid overlapping during layout.
+         *
+         * @param types which {@link WindowInsets.Type}s of insets that this window should avoid.
+         *              The initial value of this object includes all system bars.
+         */
+        public void setFitInsetsTypes(@InsetsType int types) {
+            mFitInsetsTypes = types;
+            privateFlags |= PRIVATE_FLAG_FIT_INSETS_CONTROLLED;
+        }
+
+        /**
+         * Specifies sides of insets that this window should avoid overlapping during layout.
+         *
+         * @param sides which sides that this window should avoid overlapping with the types
+         *              specified. The initial value of this object includes all sides.
+         */
+        public void setFitInsetsSides(@InsetsSide int sides) {
+            mFitInsetsSides = sides;
+            privateFlags |= PRIVATE_FLAG_FIT_INSETS_CONTROLLED;
+        }
+
+        /**
+         * Specifies if this window should fit the window insets no matter they are visible or not.
+         *
+         * @param ignore if true, this window will fit the given types even if they are not visible.
+         */
+        public void setFitInsetsIgnoringVisibility(boolean ignore) {
+            mFitInsetsIgnoringVisibility = ignore;
+            privateFlags |= PRIVATE_FLAG_FIT_INSETS_CONTROLLED;
+        }
+
+        /**
+         * Specifies that the window should be considered a trusted system overlay. Trusted system
+         * overlays are ignored when considering whether windows are obscured during input
+         * dispatch. Requires the {@link android.Manifest.permission#INTERNAL_SYSTEM_WINDOW}
+         * permission.
+         *
+         * {@see android.view.MotionEvent#FLAG_WINDOW_IS_OBSCURED}
+         * {@see android.view.MotionEvent#FLAG_WINDOW_IS_PARTIALLY_OBSCURED}
+         * @hide
+         */
+        public void setTrustedOverlay() {
+            privateFlags |= PRIVATE_FLAG_TRUSTED_OVERLAY;
+        }
+
+        /**
+         * When set on {@link LayoutParams#TYPE_APPLICATION_OVERLAY} windows they stay visible,
+         * even if {@link LayoutParams#SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS} is set for
+         * another visible window.
+         * @hide
+         */
+        @SystemApi
+        @RequiresPermission(permission.SYSTEM_APPLICATION_OVERLAY)
+        public void setSystemApplicationOverlay(boolean isSystemApplicationOverlay) {
+            if (isSystemApplicationOverlay) {
+                privateFlags |= PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY;
+            } else {
+                privateFlags &= ~PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY;
+            }
+        }
+
+        /**
+         * Returns if this window is marked as being a system application overlay.
+         * @see LayoutParams#setSystemApplicationOverlay(boolean)
+         *
+         * <p>Note: the owner of the window must hold
+         * {@link android.Manifest.permission#SYSTEM_APPLICATION_OVERLAY} for this to have any
+         * effect.
+         * @hide
+         */
+        @SystemApi
+        public boolean isSystemApplicationOverlay() {
+            return (privateFlags & PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY)
+                    == PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY;
+        }
+
+        /**
+         * @return the {@link WindowInsets.Type}s that this window is avoiding overlapping.
+         */
+        public @InsetsType int getFitInsetsTypes() {
+            return mFitInsetsTypes;
+        }
+
+        /**
+         * @return the sides that this window is avoiding overlapping.
+         */
+        public @InsetsSide int getFitInsetsSides() {
+            return mFitInsetsSides;
+        }
+
+        /**
+         * @return {@code true} if this window fits the window insets no matter they are visible or
+         *         not.
+         */
+        public boolean isFitInsetsIgnoringVisibility() {
+            return mFitInsetsIgnoringVisibility;
+        }
+
+        public LayoutParams() {
+            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+            type = TYPE_APPLICATION;
+            format = PixelFormat.OPAQUE;
+        }
+
+        public LayoutParams(int _type) {
+            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+            type = _type;
+            format = PixelFormat.OPAQUE;
+        }
+
+        public LayoutParams(int _type, int _flags) {
+            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+            type = _type;
+            flags = _flags;
+            format = PixelFormat.OPAQUE;
+        }
+
+        public LayoutParams(int _type, int _flags, int _format) {
+            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+            type = _type;
+            flags = _flags;
+            format = _format;
+        }
+
+        public LayoutParams(int w, int h, int _type, int _flags, int _format) {
+            super(w, h);
+            type = _type;
+            flags = _flags;
+            format = _format;
+        }
+
+        public LayoutParams(int w, int h, int xpos, int ypos, int _type,
+                int _flags, int _format) {
+            super(w, h);
+            x = xpos;
+            y = ypos;
+            type = _type;
+            flags = _flags;
+            format = _format;
+        }
+
+        public final void setTitle(CharSequence title) {
+            if (null == title)
+                title = "";
+
+            mTitle = TextUtils.stringOrSpannedString(title);
+        }
+
+        public final CharSequence getTitle() {
+            return mTitle != null ? mTitle : "";
+        }
+
+        /**
+         * Sets the surface insets based on the elevation (visual z position) of the input view.
+         * @hide
+         */
+        public final void setSurfaceInsets(View view, boolean manual, boolean preservePrevious) {
+            final int surfaceInset = (int) Math.ceil(view.getZ() * 2);
+            // Partial workaround for b/28318973. Every inset change causes a freeform window
+            // to jump a little for a few frames. If we never allow surface insets to decrease,
+            // they will stabilize quickly (often from the very beginning, as most windows start
+            // as focused).
+            // TODO(b/22668382) to fix this properly.
+            if (surfaceInset == 0) {
+                // OK to have 0 (this is the case for non-freeform windows).
+                surfaceInsets.set(0, 0, 0, 0);
+            } else {
+                surfaceInsets.set(
+                        Math.max(surfaceInset, surfaceInsets.left),
+                        Math.max(surfaceInset, surfaceInsets.top),
+                        Math.max(surfaceInset, surfaceInsets.right),
+                        Math.max(surfaceInset, surfaceInsets.bottom));
+            }
+            hasManualSurfaceInsets = manual;
+            preservePreviousSurfaceInsets = preservePrevious;
+        }
+
+        /**
+         * <p>Set the color mode of the window. Setting the color mode might
+         * override the window's pixel {@link WindowManager.LayoutParams#format format}.</p>
+         *
+         * <p>The color mode must be one of {@link ActivityInfo#COLOR_MODE_DEFAULT},
+         * {@link ActivityInfo#COLOR_MODE_WIDE_COLOR_GAMUT} or
+         * {@link ActivityInfo#COLOR_MODE_HDR}.</p>
+         *
+         * @see #getColorMode()
+         */
+        public void setColorMode(@ActivityInfo.ColorMode int colorMode) {
+            mColorMode = colorMode;
+        }
+
+        /**
+         * Returns the color mode of the window, one of {@link ActivityInfo#COLOR_MODE_DEFAULT},
+         * {@link ActivityInfo#COLOR_MODE_WIDE_COLOR_GAMUT} or {@link ActivityInfo#COLOR_MODE_HDR}.
+         *
+         * @see #setColorMode(int)
+         */
+        @ActivityInfo.ColorMode
+        public int getColorMode() {
+            return mColorMode;
+        }
+
+        /**
+         * <p>
+         * Blurs the screen behind the window. The effect is similar to that of {@link #dimAmount},
+         * but instead of dimmed, the content behind the window will be blurred (or combined with
+         * the dim amount, if such is specified).
+         * </p><p>
+         * The density of the blur is set by the blur radius. The radius defines the size
+         * of the neighbouring area, from which pixels will be averaged to form the final
+         * color for each pixel. The operation approximates a Gaussian blur.
+         * A radius of 0 means no blur. The higher the radius, the denser the blur.
+         * </p><p>
+         * Note the difference with {@link android.view.Window#setBackgroundBlurRadius},
+         * which blurs only within the bounds of the window. Blur behind blurs the whole screen
+         * behind the window.
+         * </p><p>
+         * Requires {@link #FLAG_BLUR_BEHIND} to be set.
+         * </p><p>
+         * Cross-window blur might not be supported by some devices due to GPU limitations. It can
+         * also be disabled at runtime, e.g. during battery saving mode, when multimedia tunneling
+         * is used or when minimal post processing is requested. In such situations, no blur will
+         * be computed or drawn, resulting in there being no depth separation between the window
+         * and the content behind it. To avoid this, the app might want to use more
+         * {@link #dimAmount} on its window. To listen for cross-window blur enabled/disabled
+         * events, use {@link #addCrossWindowBlurEnabledListener}.
+         * </p>
+         * @param blurBehindRadius The blur radius to use for blur behind in pixels
+         *
+         * @see #FLAG_BLUR_BEHIND
+         * @see #getBlurBehindRadius
+         * @see WindowManager#addCrossWindowBlurEnabledListener
+         * @see Window#setBackgroundBlurRadius
+         */
+        public void setBlurBehindRadius(@IntRange(from = 0) int blurBehindRadius) {
+            mBlurBehindRadius = blurBehindRadius;
+        }
+
+        /**
+         * Returns the blur behind radius of the window.
+         *
+         * @see #setBlurBehindRadius
+         */
+        public int getBlurBehindRadius() {
+            return mBlurBehindRadius;
+        }
+
+        /** @hide */
+        @SystemApi
+        public final void setUserActivityTimeout(long timeout) {
+            userActivityTimeout = timeout;
+        }
+
+        /** @hide */
+        @SystemApi
+        public final long getUserActivityTimeout() {
+            return userActivityTimeout;
+        }
+
+        /**
+         * Sets the {@link android.app.WindowContext} token.
+         *
+         * @see #getWindowContextToken()
+         *
+         * @hide
+         */
+        @TestApi
+        public final void setWindowContextToken(@NonNull IBinder token) {
+            mWindowContextToken = token;
+        }
+
+        /**
+         * Gets the {@link android.app.WindowContext} token.
+         *
+         * The token is usually a {@link android.app.WindowTokenClient} and is used for associating
+         * the params with an existing node in the WindowManager hierarchy and getting the
+         * corresponding {@link Configuration} and {@link android.content.res.Resources} values with
+         * updates propagated from the server side.
+         *
+         * @see android.app.WindowTokenClient
+         * @see Context#createWindowContext(Display, int, Bundle)
+         *
+         * @hide
+         */
+        @TestApi
+        @Nullable
+        public final IBinder getWindowContextToken() {
+            return mWindowContextToken;
+        }
+
+        public int describeContents() {
+            return 0;
+        }
+
+        public void writeToParcel(Parcel out, int parcelableFlags) {
+            out.writeInt(width);
+            out.writeInt(height);
+            out.writeInt(x);
+            out.writeInt(y);
+            out.writeInt(type);
+            out.writeInt(flags);
+            out.writeInt(privateFlags);
+            out.writeInt(softInputMode);
+            out.writeInt(layoutInDisplayCutoutMode);
+            out.writeInt(gravity);
+            out.writeFloat(horizontalMargin);
+            out.writeFloat(verticalMargin);
+            out.writeInt(format);
+            out.writeInt(windowAnimations);
+            out.writeFloat(alpha);
+            out.writeFloat(dimAmount);
+            out.writeFloat(screenBrightness);
+            out.writeFloat(buttonBrightness);
+            out.writeInt(rotationAnimation);
+            out.writeStrongBinder(token);
+            out.writeStrongBinder(mWindowContextToken);
+            out.writeString(packageName);
+            TextUtils.writeToParcel(mTitle, out, parcelableFlags);
+            out.writeInt(screenOrientation);
+            out.writeFloat(preferredRefreshRate);
+            out.writeInt(preferredDisplayModeId);
+            out.writeFloat(preferredMinDisplayRefreshRate);
+            out.writeFloat(preferredMaxDisplayRefreshRate);
+            out.writeInt(systemUiVisibility);
+            out.writeInt(subtreeSystemUiVisibility);
+            out.writeBoolean(hasSystemUiListeners);
+            out.writeInt(inputFeatures);
+            out.writeLong(userActivityTimeout);
+            out.writeInt(surfaceInsets.left);
+            out.writeInt(surfaceInsets.top);
+            out.writeInt(surfaceInsets.right);
+            out.writeInt(surfaceInsets.bottom);
+            out.writeBoolean(hasManualSurfaceInsets);
+            out.writeBoolean(receiveInsetsIgnoringZOrder);
+            out.writeBoolean(preservePreviousSurfaceInsets);
+            out.writeLong(accessibilityIdOfAnchor);
+            TextUtils.writeToParcel(accessibilityTitle, out, parcelableFlags);
+            out.writeInt(mColorMode);
+            out.writeLong(hideTimeoutMilliseconds);
+            out.writeInt(insetsFlags.appearance);
+            out.writeInt(insetsFlags.behavior);
+            out.writeInt(mFitInsetsTypes);
+            out.writeInt(mFitInsetsSides);
+            out.writeBoolean(mFitInsetsIgnoringVisibility);
+            out.writeBoolean(preferMinimalPostProcessing);
+            out.writeInt(mBlurBehindRadius);
+            if (providesInsetsTypes != null) {
+                out.writeInt(providesInsetsTypes.length);
+                out.writeIntArray(providesInsetsTypes);
+            } else {
+                out.writeInt(0);
+            }
+        }
+
+        public static final @android.annotation.NonNull Parcelable.Creator<LayoutParams> CREATOR
+                    = new Parcelable.Creator<LayoutParams>() {
+            public LayoutParams createFromParcel(Parcel in) {
+                return new LayoutParams(in);
+            }
+
+            public LayoutParams[] newArray(int size) {
+                return new LayoutParams[size];
+            }
+        };
+
+
+        public LayoutParams(Parcel in) {
+            width = in.readInt();
+            height = in.readInt();
+            x = in.readInt();
+            y = in.readInt();
+            type = in.readInt();
+            flags = in.readInt();
+            privateFlags = in.readInt();
+            softInputMode = in.readInt();
+            layoutInDisplayCutoutMode = in.readInt();
+            gravity = in.readInt();
+            horizontalMargin = in.readFloat();
+            verticalMargin = in.readFloat();
+            format = in.readInt();
+            windowAnimations = in.readInt();
+            alpha = in.readFloat();
+            dimAmount = in.readFloat();
+            screenBrightness = in.readFloat();
+            buttonBrightness = in.readFloat();
+            rotationAnimation = in.readInt();
+            token = in.readStrongBinder();
+            mWindowContextToken = in.readStrongBinder();
+            packageName = in.readString();
+            mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+            screenOrientation = in.readInt();
+            preferredRefreshRate = in.readFloat();
+            preferredDisplayModeId = in.readInt();
+            preferredMinDisplayRefreshRate = in.readFloat();
+            preferredMaxDisplayRefreshRate = in.readFloat();
+            systemUiVisibility = in.readInt();
+            subtreeSystemUiVisibility = in.readInt();
+            hasSystemUiListeners = in.readBoolean();
+            inputFeatures = in.readInt();
+            userActivityTimeout = in.readLong();
+            surfaceInsets.left = in.readInt();
+            surfaceInsets.top = in.readInt();
+            surfaceInsets.right = in.readInt();
+            surfaceInsets.bottom = in.readInt();
+            hasManualSurfaceInsets = in.readBoolean();
+            receiveInsetsIgnoringZOrder = in.readBoolean();
+            preservePreviousSurfaceInsets = in.readBoolean();
+            accessibilityIdOfAnchor = in.readLong();
+            accessibilityTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+            mColorMode = in.readInt();
+            hideTimeoutMilliseconds = in.readLong();
+            insetsFlags.appearance = in.readInt();
+            insetsFlags.behavior = in.readInt();
+            mFitInsetsTypes = in.readInt();
+            mFitInsetsSides = in.readInt();
+            mFitInsetsIgnoringVisibility = in.readBoolean();
+            preferMinimalPostProcessing = in.readBoolean();
+            mBlurBehindRadius = in.readInt();
+            int insetsTypesLength = in.readInt();
+            if (insetsTypesLength > 0) {
+                providesInsetsTypes = new int[insetsTypesLength];
+                in.readIntArray(providesInsetsTypes);
+            }
+        }
+
+        @SuppressWarnings({"PointlessBitwiseExpression"})
+        public static final int LAYOUT_CHANGED = 1<<0;
+        public static final int TYPE_CHANGED = 1<<1;
+        public static final int FLAGS_CHANGED = 1<<2;
+        public static final int FORMAT_CHANGED = 1<<3;
+        public static final int ANIMATION_CHANGED = 1<<4;
+        public static final int DIM_AMOUNT_CHANGED = 1<<5;
+        public static final int TITLE_CHANGED = 1<<6;
+        public static final int ALPHA_CHANGED = 1<<7;
+        public static final int MEMORY_TYPE_CHANGED = 1<<8;
+        public static final int SOFT_INPUT_MODE_CHANGED = 1<<9;
+        public static final int SCREEN_ORIENTATION_CHANGED = 1<<10;
+        public static final int SCREEN_BRIGHTNESS_CHANGED = 1<<11;
+        public static final int ROTATION_ANIMATION_CHANGED = 1<<12;
+        /** {@hide} */
+        public static final int BUTTON_BRIGHTNESS_CHANGED = 1<<13;
+        /** {@hide} */
+        public static final int SYSTEM_UI_VISIBILITY_CHANGED = 1<<14;
+        /** {@hide} */
+        public static final int SYSTEM_UI_LISTENER_CHANGED = 1<<15;
+        /** {@hide} */
+        public static final int INPUT_FEATURES_CHANGED = 1<<16;
+        /** {@hide} */
+        public static final int PRIVATE_FLAGS_CHANGED = 1<<17;
+        /** {@hide} */
+        public static final int USER_ACTIVITY_TIMEOUT_CHANGED = 1<<18;
+        /** {@hide} */
+        public static final int TRANSLUCENT_FLAGS_CHANGED = 1<<19;
+        /** {@hide} */
+        public static final int SURFACE_INSETS_CHANGED = 1<<20;
+        /** {@hide} */
+        public static final int PREFERRED_REFRESH_RATE_CHANGED = 1 << 21;
+        /** {@hide} */
+        public static final int PREFERRED_DISPLAY_MODE_ID = 1 << 23;
+        /** {@hide} */
+        public static final int ACCESSIBILITY_ANCHOR_CHANGED = 1 << 24;
+        /** {@hide} */
+        @TestApi
+        public static final int ACCESSIBILITY_TITLE_CHANGED = 1 << 25;
+        /** {@hide} */
+        public static final int COLOR_MODE_CHANGED = 1 << 26;
+        /** {@hide} */
+        public static final int INSET_FLAGS_CHANGED = 1 << 27;
+        /** {@hide} */
+        public static final int MINIMAL_POST_PROCESSING_PREFERENCE_CHANGED = 1 << 28;
+        /** {@hide} */
+        public static final int BLUR_BEHIND_RADIUS_CHANGED = 1 << 29;
+        /** {@hide} */
+        public static final int PREFERRED_MIN_DISPLAY_REFRESH_RATE = 1 << 30;
+        /** {@hide} */
+        public static final int PREFERRED_MAX_DISPLAY_REFRESH_RATE = 1 << 31;
+
+        // internal buffer to backup/restore parameters under compatibility mode.
+        private int[] mCompatibilityParamsBackup = null;
+
+        public final int copyFrom(LayoutParams o) {
+            int changes = 0;
+
+            if (width != o.width) {
+                width = o.width;
+                changes |= LAYOUT_CHANGED;
+            }
+            if (height != o.height) {
+                height = o.height;
+                changes |= LAYOUT_CHANGED;
+            }
+            if (x != o.x) {
+                x = o.x;
+                changes |= LAYOUT_CHANGED;
+            }
+            if (y != o.y) {
+                y = o.y;
+                changes |= LAYOUT_CHANGED;
+            }
+            if (horizontalWeight != o.horizontalWeight) {
+                horizontalWeight = o.horizontalWeight;
+                changes |= LAYOUT_CHANGED;
+            }
+            if (verticalWeight != o.verticalWeight) {
+                verticalWeight = o.verticalWeight;
+                changes |= LAYOUT_CHANGED;
+            }
+            if (horizontalMargin != o.horizontalMargin) {
+                horizontalMargin = o.horizontalMargin;
+                changes |= LAYOUT_CHANGED;
+            }
+            if (verticalMargin != o.verticalMargin) {
+                verticalMargin = o.verticalMargin;
+                changes |= LAYOUT_CHANGED;
+            }
+            if (type != o.type) {
+                type = o.type;
+                changes |= TYPE_CHANGED;
+            }
+            if (flags != o.flags) {
+                final int diff = flags ^ o.flags;
+                if ((diff & (FLAG_TRANSLUCENT_STATUS | FLAG_TRANSLUCENT_NAVIGATION)) != 0) {
+                    changes |= TRANSLUCENT_FLAGS_CHANGED;
+                }
+                flags = o.flags;
+                changes |= FLAGS_CHANGED;
+            }
+            if (privateFlags != o.privateFlags) {
+                privateFlags = o.privateFlags;
+                changes |= PRIVATE_FLAGS_CHANGED;
+            }
+            if (softInputMode != o.softInputMode) {
+                softInputMode = o.softInputMode;
+                changes |= SOFT_INPUT_MODE_CHANGED;
+            }
+            if (layoutInDisplayCutoutMode != o.layoutInDisplayCutoutMode) {
+                layoutInDisplayCutoutMode = o.layoutInDisplayCutoutMode;
+                changes |= LAYOUT_CHANGED;
+            }
+            if (gravity != o.gravity) {
+                gravity = o.gravity;
+                changes |= LAYOUT_CHANGED;
+            }
+            if (format != o.format) {
+                format = o.format;
+                changes |= FORMAT_CHANGED;
+            }
+            if (windowAnimations != o.windowAnimations) {
+                windowAnimations = o.windowAnimations;
+                changes |= ANIMATION_CHANGED;
+            }
+            if (token == null) {
+                // NOTE: token only copied if the recipient doesn't
+                // already have one.
+                token = o.token;
+            }
+            if (mWindowContextToken == null) {
+                // NOTE: token only copied if the recipient doesn't
+                // already have one.
+                mWindowContextToken = o.mWindowContextToken;
+            }
+            if (packageName == null) {
+                // NOTE: packageName only copied if the recipient doesn't
+                // already have one.
+                packageName = o.packageName;
+            }
+            if (!Objects.equals(mTitle, o.mTitle) && o.mTitle != null) {
+                // NOTE: mTitle only copied if the originator set one.
+                mTitle = o.mTitle;
+                changes |= TITLE_CHANGED;
+            }
+            if (alpha != o.alpha) {
+                alpha = o.alpha;
+                changes |= ALPHA_CHANGED;
+            }
+            if (dimAmount != o.dimAmount) {
+                dimAmount = o.dimAmount;
+                changes |= DIM_AMOUNT_CHANGED;
+            }
+            if (screenBrightness != o.screenBrightness) {
+                screenBrightness = o.screenBrightness;
+                changes |= SCREEN_BRIGHTNESS_CHANGED;
+            }
+            if (buttonBrightness != o.buttonBrightness) {
+                buttonBrightness = o.buttonBrightness;
+                changes |= BUTTON_BRIGHTNESS_CHANGED;
+            }
+            if (rotationAnimation != o.rotationAnimation) {
+                rotationAnimation = o.rotationAnimation;
+                changes |= ROTATION_ANIMATION_CHANGED;
+            }
+
+            if (screenOrientation != o.screenOrientation) {
+                screenOrientation = o.screenOrientation;
+                changes |= SCREEN_ORIENTATION_CHANGED;
+            }
+
+            if (preferredRefreshRate != o.preferredRefreshRate) {
+                preferredRefreshRate = o.preferredRefreshRate;
+                changes |= PREFERRED_REFRESH_RATE_CHANGED;
+            }
+
+            if (preferredDisplayModeId != o.preferredDisplayModeId) {
+                preferredDisplayModeId = o.preferredDisplayModeId;
+                changes |= PREFERRED_DISPLAY_MODE_ID;
+            }
+
+            if (preferredMinDisplayRefreshRate != o.preferredMinDisplayRefreshRate) {
+                preferredMinDisplayRefreshRate = o.preferredMinDisplayRefreshRate;
+                changes |= PREFERRED_MIN_DISPLAY_REFRESH_RATE;
+            }
+
+            if (preferredMaxDisplayRefreshRate != o.preferredMaxDisplayRefreshRate) {
+                preferredMaxDisplayRefreshRate = o.preferredMaxDisplayRefreshRate;
+                changes |= PREFERRED_MAX_DISPLAY_REFRESH_RATE;
+            }
+
+            if (systemUiVisibility != o.systemUiVisibility
+                    || subtreeSystemUiVisibility != o.subtreeSystemUiVisibility) {
+                systemUiVisibility = o.systemUiVisibility;
+                subtreeSystemUiVisibility = o.subtreeSystemUiVisibility;
+                changes |= SYSTEM_UI_VISIBILITY_CHANGED;
+            }
+
+            if (hasSystemUiListeners != o.hasSystemUiListeners) {
+                hasSystemUiListeners = o.hasSystemUiListeners;
+                changes |= SYSTEM_UI_LISTENER_CHANGED;
+            }
+
+            if (inputFeatures != o.inputFeatures) {
+                inputFeatures = o.inputFeatures;
+                changes |= INPUT_FEATURES_CHANGED;
+            }
+
+            if (userActivityTimeout != o.userActivityTimeout) {
+                userActivityTimeout = o.userActivityTimeout;
+                changes |= USER_ACTIVITY_TIMEOUT_CHANGED;
+            }
+
+            if (!surfaceInsets.equals(o.surfaceInsets)) {
+                surfaceInsets.set(o.surfaceInsets);
+                changes |= SURFACE_INSETS_CHANGED;
+            }
+
+            if (hasManualSurfaceInsets != o.hasManualSurfaceInsets) {
+                hasManualSurfaceInsets = o.hasManualSurfaceInsets;
+                changes |= SURFACE_INSETS_CHANGED;
+            }
+
+            if (receiveInsetsIgnoringZOrder != o.receiveInsetsIgnoringZOrder) {
+                receiveInsetsIgnoringZOrder = o.receiveInsetsIgnoringZOrder;
+                changes |= SURFACE_INSETS_CHANGED;
+            }
+
+            if (preservePreviousSurfaceInsets != o.preservePreviousSurfaceInsets) {
+                preservePreviousSurfaceInsets = o.preservePreviousSurfaceInsets;
+                changes |= SURFACE_INSETS_CHANGED;
+            }
+
+            if (accessibilityIdOfAnchor != o.accessibilityIdOfAnchor) {
+                accessibilityIdOfAnchor = o.accessibilityIdOfAnchor;
+                changes |= ACCESSIBILITY_ANCHOR_CHANGED;
+            }
+
+            if (!Objects.equals(accessibilityTitle, o.accessibilityTitle)
+                    && o.accessibilityTitle != null) {
+                // NOTE: accessibilityTitle only copied if the originator set one.
+                accessibilityTitle = o.accessibilityTitle;
+                changes |= ACCESSIBILITY_TITLE_CHANGED;
+            }
+
+            if (mColorMode != o.mColorMode) {
+                mColorMode = o.mColorMode;
+                changes |= COLOR_MODE_CHANGED;
+            }
+
+            if (preferMinimalPostProcessing != o.preferMinimalPostProcessing) {
+                preferMinimalPostProcessing = o.preferMinimalPostProcessing;
+                changes |= MINIMAL_POST_PROCESSING_PREFERENCE_CHANGED;
+            }
+
+            if (mBlurBehindRadius != o.mBlurBehindRadius) {
+                mBlurBehindRadius = o.mBlurBehindRadius;
+                changes |= BLUR_BEHIND_RADIUS_CHANGED;
+            }
+
+            // This can't change, it's only set at window creation time.
+            hideTimeoutMilliseconds = o.hideTimeoutMilliseconds;
+
+            if (insetsFlags.appearance != o.insetsFlags.appearance) {
+                insetsFlags.appearance = o.insetsFlags.appearance;
+                changes |= INSET_FLAGS_CHANGED;
+            }
+
+            if (insetsFlags.behavior != o.insetsFlags.behavior) {
+                insetsFlags.behavior = o.insetsFlags.behavior;
+                changes |= INSET_FLAGS_CHANGED;
+            }
+
+            if (mFitInsetsTypes != o.mFitInsetsTypes) {
+                mFitInsetsTypes = o.mFitInsetsTypes;
+                changes |= LAYOUT_CHANGED;
+            }
+
+            if (mFitInsetsSides != o.mFitInsetsSides) {
+                mFitInsetsSides = o.mFitInsetsSides;
+                changes |= LAYOUT_CHANGED;
+            }
+
+            if (mFitInsetsIgnoringVisibility != o.mFitInsetsIgnoringVisibility) {
+                mFitInsetsIgnoringVisibility = o.mFitInsetsIgnoringVisibility;
+                changes |= LAYOUT_CHANGED;
+            }
+
+            if (!Arrays.equals(providesInsetsTypes, o.providesInsetsTypes)) {
+                providesInsetsTypes = o.providesInsetsTypes;
+                changes |= LAYOUT_CHANGED;
+            }
+
+            return changes;
+        }
+
+        @Override
+        public String debug(String output) {
+            output += "Contents of " + this + ":";
+            Log.d("Debug", output);
+            output = super.debug("");
+            Log.d("Debug", output);
+            Log.d("Debug", "");
+            Log.d("Debug", "WindowManager.LayoutParams={title=" + mTitle + "}");
+            return "";
+        }
+
+        @Override
+        public String toString() {
+            return toString("");
+        }
+
+        /**
+         * @hide
+         */
+        public void dumpDimensions(StringBuilder sb) {
+            sb.append('(');
+            sb.append(x);
+            sb.append(',');
+            sb.append(y);
+            sb.append(")(");
+            sb.append((width == MATCH_PARENT ? "fill" : (width == WRAP_CONTENT
+                    ? "wrap" : String.valueOf(width))));
+            sb.append('x');
+            sb.append((height == MATCH_PARENT ? "fill" : (height == WRAP_CONTENT
+                    ? "wrap" : String.valueOf(height))));
+            sb.append(")");
+        }
+
+        /**
+         * @hide
+         */
+        public String toString(String prefix) {
+            StringBuilder sb = new StringBuilder(256);
+            sb.append('{');
+            dumpDimensions(sb);
+            if (horizontalMargin != 0) {
+                sb.append(" hm=");
+                sb.append(horizontalMargin);
+            }
+            if (verticalMargin != 0) {
+                sb.append(" vm=");
+                sb.append(verticalMargin);
+            }
+            if (gravity != 0) {
+                sb.append(" gr=");
+                sb.append(Gravity.toString(gravity));
+            }
+            if (softInputMode != 0) {
+                sb.append(" sim={");
+                sb.append(softInputModeToString(softInputMode));
+                sb.append('}');
+            }
+            if (layoutInDisplayCutoutMode != 0) {
+                sb.append(" layoutInDisplayCutoutMode=");
+                sb.append(layoutInDisplayCutoutModeToString(layoutInDisplayCutoutMode));
+            }
+            sb.append(" ty=");
+            sb.append(ViewDebug.intToString(LayoutParams.class, "type", type));
+            if (format != PixelFormat.OPAQUE) {
+                sb.append(" fmt=");
+                sb.append(PixelFormat.formatToString(format));
+            }
+            if (windowAnimations != 0) {
+                sb.append(" wanim=0x");
+                sb.append(Integer.toHexString(windowAnimations));
+            }
+            if (screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
+                sb.append(" or=");
+                sb.append(ActivityInfo.screenOrientationToString(screenOrientation));
+            }
+            if (alpha != 1.0f) {
+                sb.append(" alpha=");
+                sb.append(alpha);
+            }
+            if (screenBrightness != BRIGHTNESS_OVERRIDE_NONE) {
+                sb.append(" sbrt=");
+                sb.append(screenBrightness);
+            }
+            if (buttonBrightness != BRIGHTNESS_OVERRIDE_NONE) {
+                sb.append(" bbrt=");
+                sb.append(buttonBrightness);
+            }
+            if (rotationAnimation != ROTATION_ANIMATION_ROTATE) {
+                sb.append(" rotAnim=");
+                sb.append(rotationAnimationToString(rotationAnimation));
+            }
+            if (preferredRefreshRate != 0) {
+                sb.append(" preferredRefreshRate=");
+                sb.append(preferredRefreshRate);
+            }
+            if (preferredDisplayModeId != 0) {
+                sb.append(" preferredDisplayMode=");
+                sb.append(preferredDisplayModeId);
+            }
+            if (preferredMinDisplayRefreshRate != 0) {
+                sb.append(" preferredMinDisplayRefreshRate=");
+                sb.append(preferredMinDisplayRefreshRate);
+            }
+            if (preferredMaxDisplayRefreshRate != 0) {
+                sb.append(" preferredMaxDisplayRefreshRate=");
+                sb.append(preferredMaxDisplayRefreshRate);
+            }
+            if (hasSystemUiListeners) {
+                sb.append(" sysuil=");
+                sb.append(hasSystemUiListeners);
+            }
+            if (inputFeatures != 0) {
+                sb.append(" if=").append(inputFeatureToString(inputFeatures));
+            }
+            if (userActivityTimeout >= 0) {
+                sb.append(" userActivityTimeout=").append(userActivityTimeout);
+            }
+            if (surfaceInsets.left != 0 || surfaceInsets.top != 0 || surfaceInsets.right != 0 ||
+                    surfaceInsets.bottom != 0 || hasManualSurfaceInsets
+                    || !preservePreviousSurfaceInsets) {
+                sb.append(" surfaceInsets=").append(surfaceInsets);
+                if (hasManualSurfaceInsets) {
+                    sb.append(" (manual)");
+                }
+                if (!preservePreviousSurfaceInsets) {
+                    sb.append(" (!preservePreviousSurfaceInsets)");
+                }
+            }
+            if (receiveInsetsIgnoringZOrder) {
+                sb.append(" receive insets ignoring z-order");
+            }
+            if (mColorMode != COLOR_MODE_DEFAULT) {
+                sb.append(" colorMode=").append(ActivityInfo.colorModeToString(mColorMode));
+            }
+            if (preferMinimalPostProcessing) {
+                sb.append(" preferMinimalPostProcessing=");
+                sb.append(preferMinimalPostProcessing);
+            }
+            if (mBlurBehindRadius != 0) {
+                sb.append(" blurBehindRadius=");
+                sb.append(mBlurBehindRadius);
+            }
+            sb.append(System.lineSeparator());
+            sb.append(prefix).append("  fl=").append(
+                    ViewDebug.flagsToString(LayoutParams.class, "flags", flags));
+            if (privateFlags != 0) {
+                sb.append(System.lineSeparator());
+                sb.append(prefix).append("  pfl=").append(ViewDebug.flagsToString(
+                        LayoutParams.class, "privateFlags", privateFlags));
+            }
+            if (systemUiVisibility != 0) {
+                sb.append(System.lineSeparator());
+                sb.append(prefix).append("  sysui=").append(ViewDebug.flagsToString(
+                        View.class, "mSystemUiVisibility", systemUiVisibility));
+            }
+            if (subtreeSystemUiVisibility != 0) {
+                sb.append(System.lineSeparator());
+                sb.append(prefix).append("  vsysui=").append(ViewDebug.flagsToString(
+                        View.class, "mSystemUiVisibility", subtreeSystemUiVisibility));
+            }
+            if (insetsFlags.appearance != 0) {
+                sb.append(System.lineSeparator());
+                sb.append(prefix).append("  apr=").append(ViewDebug.flagsToString(
+                        InsetsFlags.class, "appearance", insetsFlags.appearance));
+            }
+            if (insetsFlags.behavior != 0) {
+                sb.append(System.lineSeparator());
+                sb.append(prefix).append("  bhv=").append(ViewDebug.flagsToString(
+                        InsetsFlags.class, "behavior", insetsFlags.behavior));
+            }
+            if (mFitInsetsTypes != 0) {
+                sb.append(System.lineSeparator());
+                sb.append(prefix).append("  fitTypes=").append(ViewDebug.flagsToString(
+                        LayoutParams.class, "mFitInsetsTypes", mFitInsetsTypes));
+            }
+            if (mFitInsetsSides != Side.all()) {
+                sb.append(System.lineSeparator());
+                sb.append(prefix).append("  fitSides=").append(ViewDebug.flagsToString(
+                        LayoutParams.class, "mFitInsetsSides", mFitInsetsSides));
+            }
+            if (mFitInsetsIgnoringVisibility) {
+                sb.append(System.lineSeparator());
+                sb.append(prefix).append("  fitIgnoreVis");
+            }
+            if (providesInsetsTypes != null) {
+                sb.append(System.lineSeparator());
+                sb.append(prefix).append("  insetsTypes=");
+                for (int i = 0; i < providesInsetsTypes.length; ++i) {
+                    if (i > 0) sb.append(' ');
+                    sb.append(InsetsState.typeToString(providesInsetsTypes[i]));
+                }
+            }
+
+            sb.append('}');
+            return sb.toString();
+        }
+
+        /**
+         * @hide
+         */
+        public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+            final long token = proto.start(fieldId);
+            proto.write(TYPE, type);
+            proto.write(X, x);
+            proto.write(Y, y);
+            proto.write(WIDTH, width);
+            proto.write(HEIGHT, height);
+            proto.write(HORIZONTAL_MARGIN, horizontalMargin);
+            proto.write(VERTICAL_MARGIN, verticalMargin);
+            proto.write(GRAVITY, gravity);
+            proto.write(SOFT_INPUT_MODE, softInputMode);
+            proto.write(FORMAT, format);
+            proto.write(WINDOW_ANIMATIONS, windowAnimations);
+            proto.write(ALPHA, alpha);
+            proto.write(SCREEN_BRIGHTNESS, screenBrightness);
+            proto.write(BUTTON_BRIGHTNESS, buttonBrightness);
+            proto.write(ROTATION_ANIMATION, rotationAnimation);
+            proto.write(PREFERRED_REFRESH_RATE, preferredRefreshRate);
+            proto.write(WindowLayoutParamsProto.PREFERRED_DISPLAY_MODE_ID, preferredDisplayModeId);
+            proto.write(HAS_SYSTEM_UI_LISTENERS, hasSystemUiListeners);
+            proto.write(INPUT_FEATURE_FLAGS, inputFeatures);
+            proto.write(USER_ACTIVITY_TIMEOUT, userActivityTimeout);
+            proto.write(COLOR_MODE, mColorMode);
+            proto.write(FLAGS, flags);
+            proto.write(PRIVATE_FLAGS, privateFlags);
+            proto.write(SYSTEM_UI_VISIBILITY_FLAGS, systemUiVisibility);
+            proto.write(SUBTREE_SYSTEM_UI_VISIBILITY_FLAGS, subtreeSystemUiVisibility);
+            proto.write(APPEARANCE, insetsFlags.appearance);
+            proto.write(BEHAVIOR, insetsFlags.behavior);
+            proto.write(FIT_INSETS_TYPES, mFitInsetsTypes);
+            proto.write(FIT_INSETS_SIDES, mFitInsetsSides);
+            proto.write(FIT_IGNORE_VISIBILITY, mFitInsetsIgnoringVisibility);
+            proto.end(token);
+        }
+
+        /**
+         * Scale the layout params' coordinates and size.
+         * @hide
+         */
+        public void scale(float scale) {
+            x = (int) (x * scale + 0.5f);
+            y = (int) (y * scale + 0.5f);
+            if (width > 0) {
+                width = (int) (width * scale + 0.5f);
+            }
+            if (height > 0) {
+                height = (int) (height * scale + 0.5f);
+            }
+        }
+
+        /**
+         * Backup the layout parameters used in compatibility mode.
+         * @see LayoutParams#restore()
+         */
+        @UnsupportedAppUsage
+        void backup() {
+            int[] backup = mCompatibilityParamsBackup;
+            if (backup == null) {
+                // we backup 4 elements, x, y, width, height
+                backup = mCompatibilityParamsBackup = new int[4];
+            }
+            backup[0] = x;
+            backup[1] = y;
+            backup[2] = width;
+            backup[3] = height;
+        }
+
+        /**
+         * Restore the layout params' coordinates, size and gravity
+         * @see LayoutParams#backup()
+         */
+        @UnsupportedAppUsage
+        void restore() {
+            int[] backup = mCompatibilityParamsBackup;
+            if (backup != null) {
+                x = backup[0];
+                y = backup[1];
+                width = backup[2];
+                height = backup[3];
+            }
+        }
+
+        private CharSequence mTitle = null;
+
+        /** @hide */
+        @Override
+        protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+            super.encodeProperties(encoder);
+
+            encoder.addProperty("x", x);
+            encoder.addProperty("y", y);
+            encoder.addProperty("horizontalWeight", horizontalWeight);
+            encoder.addProperty("verticalWeight", verticalWeight);
+            encoder.addProperty("type", type);
+            encoder.addProperty("flags", flags);
+        }
+
+        /**
+         * @hide
+         * @return True if the layout parameters will cause the window to cover the full screen;
+         *         false otherwise.
+         */
+        public boolean isFullscreen() {
+            return x == 0 && y == 0
+                    && width == WindowManager.LayoutParams.MATCH_PARENT
+                    && height == WindowManager.LayoutParams.MATCH_PARENT;
+        }
+
+        private static String layoutInDisplayCutoutModeToString(
+                @LayoutInDisplayCutoutMode int mode) {
+            switch (mode) {
+                case LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT:
+                    return "default";
+                case LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS:
+                    return "always";
+                case LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER:
+                    return "never";
+                case LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES:
+                    return "shortEdges";
+                default:
+                    return "unknown(" + mode + ")";
+            }
+        }
+
+        private static String softInputModeToString(@SoftInputModeFlags int softInputMode) {
+            final StringBuilder result = new StringBuilder();
+            final int state = softInputMode & SOFT_INPUT_MASK_STATE;
+            if (state != 0) {
+                result.append("state=");
+                switch (state) {
+                    case SOFT_INPUT_STATE_UNCHANGED:
+                        result.append("unchanged");
+                        break;
+                    case SOFT_INPUT_STATE_HIDDEN:
+                        result.append("hidden");
+                        break;
+                    case SOFT_INPUT_STATE_ALWAYS_HIDDEN:
+                        result.append("always_hidden");
+                        break;
+                    case SOFT_INPUT_STATE_VISIBLE:
+                        result.append("visible");
+                        break;
+                    case SOFT_INPUT_STATE_ALWAYS_VISIBLE:
+                        result.append("always_visible");
+                        break;
+                    default:
+                        result.append(state);
+                        break;
+                }
+                result.append(' ');
+            }
+            final int adjust = softInputMode & SOFT_INPUT_MASK_ADJUST;
+            if (adjust != 0) {
+                result.append("adjust=");
+                switch (adjust) {
+                    case SOFT_INPUT_ADJUST_RESIZE:
+                        result.append("resize");
+                        break;
+                    case SOFT_INPUT_ADJUST_PAN:
+                        result.append("pan");
+                        break;
+                    case SOFT_INPUT_ADJUST_NOTHING:
+                        result.append("nothing");
+                        break;
+                    default:
+                        result.append(adjust);
+                        break;
+                }
+                result.append(' ');
+            }
+            if ((softInputMode & SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
+                result.append("forwardNavigation").append(' ');
+            }
+            result.deleteCharAt(result.length() - 1);
+            return result.toString();
+        }
+
+        private static String rotationAnimationToString(int rotationAnimation) {
+            switch (rotationAnimation) {
+                case ROTATION_ANIMATION_UNSPECIFIED:
+                    return "UNSPECIFIED";
+                case ROTATION_ANIMATION_ROTATE:
+                    return "ROTATE";
+                case ROTATION_ANIMATION_CROSSFADE:
+                    return "CROSSFADE";
+                case ROTATION_ANIMATION_JUMPCUT:
+                    return "JUMPCUT";
+                case ROTATION_ANIMATION_SEAMLESS:
+                    return "SEAMLESS";
+                default:
+                    return Integer.toString(rotationAnimation);
+            }
+        }
+
+        private static String inputFeatureToString(int inputFeature) {
+            switch (inputFeature) {
+                case INPUT_FEATURE_DISABLE_POINTER_GESTURES:
+                    return "DISABLE_POINTER_GESTURES";
+                case INPUT_FEATURE_NO_INPUT_CHANNEL:
+                    return "NO_INPUT_CHANNEL";
+                case INPUT_FEATURE_DISABLE_USER_ACTIVITY:
+                    return "DISABLE_USER_ACTIVITY";
+                default:
+                    return Integer.toString(inputFeature);
+            }
+        }
+    }
+
+    /**
+     * Holds the WM lock for the specified amount of milliseconds.
+     * Intended for use by the tests that need to imitate lock contention.
+     * The token should be obtained by
+     * {@link android.content.pm.PackageManager#getHoldLockToken()}.
+     * @hide
+     */
+    @TestApi
+    default void holdLock(IBinder token, int durationMs) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Used for testing to check if the system supports TaskSnapshot mechanism.
+     * @hide
+     */
+    @TestApi
+    default boolean isTaskSnapshotSupported() {
+        return false;
+    }
+}
diff --git a/android/view/WindowManagerGlobal.java b/android/view/WindowManagerGlobal.java
new file mode 100644
index 0000000..18013e8
--- /dev/null
+++ b/android/view/WindowManagerGlobal.java
@@ -0,0 +1,719 @@
+/*
+ * 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.view;
+
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentCallbacks2;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.res.Configuration;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.util.AndroidRuntimeException;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.inputmethod.InputMethodManager;
+
+import com.android.internal.util.FastPrintWriter;
+
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Provides low-level communication with the system window manager for
+ * operations that are not associated with any particular context.
+ *
+ * This class is only used internally to implement global functions where
+ * the caller already knows the display and relevant compatibility information
+ * for the operation.  For most purposes, you should use {@link WindowManager} instead
+ * since it is bound to a context.
+ *
+ * @see WindowManagerImpl
+ * @hide
+ */
+public final class WindowManagerGlobal {
+    private static final String TAG = "WindowManager";
+
+    private static boolean sUseBLASTAdapter = false;
+
+    /**
+     * The user is navigating with keys (not the touch screen), so
+     * navigational focus should be shown.
+     */
+    public static final int RELAYOUT_RES_IN_TOUCH_MODE = 0x1;
+
+    /**
+     * This is the first time the window is being drawn,
+     * so the client must call drawingFinished() when done
+     */
+    public static final int RELAYOUT_RES_FIRST_TIME = 0x2;
+
+    /**
+     * The window manager has changed the surface from the last call.
+     */
+    public static final int RELAYOUT_RES_SURFACE_CHANGED = 0x4;
+
+    /**
+     * The window is being resized by dragging on the docked divider. The client should render
+     * at (0, 0) and extend its background to the background frame passed into
+     * {@link IWindow#resized}.
+     */
+    public static final int RELAYOUT_RES_DRAG_RESIZING_DOCKED = 0x8;
+
+    /**
+     * The window is being resized by dragging one of the window corners,
+     * in this case the surface would be fullscreen-sized. The client should
+     * render to the actual frame location (instead of (0,curScrollY)).
+     */
+    public static final int RELAYOUT_RES_DRAG_RESIZING_FREEFORM = 0x10;
+
+    /**
+     * The window manager has changed the size of the surface from the last call.
+     */
+    public static final int RELAYOUT_RES_SURFACE_RESIZED = 0x20;
+
+    /**
+     * In multi-window we force show the system bars. Because we don't want that the surface size
+     * changes in this mode, we instead have a flag whether the system bar sizes should always be
+     * consumed, so the app is treated like there is no virtual system bars at all.
+     */
+    public static final int RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS = 0x40;
+
+    /**
+     * This flag indicates the client should not directly submit it's next frame,
+     * but instead should pass it in the postDrawTransaction of
+     * {@link WindowManagerService#finishDrawing}. This is used by the WM
+     * BLASTSyncEngine to synchronize rendering of multiple windows.
+     */
+    public static final int RELAYOUT_RES_BLAST_SYNC = 0x80;
+
+    /**
+     * Flag for relayout: the client will be later giving
+     * internal insets; as a result, the window will not impact other window
+     * layouts until the insets are given.
+     */
+    public static final int RELAYOUT_INSETS_PENDING = 0x1;
+
+    public static final int ADD_FLAG_IN_TOUCH_MODE = 0x1;
+    public static final int ADD_FLAG_APP_VISIBLE = 0x2;
+    public static final int ADD_FLAG_USE_BLAST = 0x8;
+
+    /**
+     * Like {@link #RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS}, but as a "hint" when adding the
+     * window.
+     */
+    public static final int ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS = 0x4;
+
+    public static final int ADD_OKAY = 0;
+    public static final int ADD_BAD_APP_TOKEN = -1;
+    public static final int ADD_BAD_SUBWINDOW_TOKEN = -2;
+    public static final int ADD_NOT_APP_TOKEN = -3;
+    public static final int ADD_APP_EXITING = -4;
+    public static final int ADD_DUPLICATE_ADD = -5;
+    public static final int ADD_STARTING_NOT_NEEDED = -6;
+    public static final int ADD_MULTIPLE_SINGLETON = -7;
+    public static final int ADD_PERMISSION_DENIED = -8;
+    public static final int ADD_INVALID_DISPLAY = -9;
+    public static final int ADD_INVALID_TYPE = -10;
+    public static final int ADD_INVALID_USER = -11;
+
+    @UnsupportedAppUsage
+    private static WindowManagerGlobal sDefaultWindowManager;
+    @UnsupportedAppUsage
+    private static IWindowManager sWindowManagerService;
+    @UnsupportedAppUsage
+    private static IWindowSession sWindowSession;
+
+    @UnsupportedAppUsage
+    private final Object mLock = new Object();
+
+    @UnsupportedAppUsage
+    private final ArrayList<View> mViews = new ArrayList<View>();
+    @UnsupportedAppUsage
+    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
+    @UnsupportedAppUsage
+    private final ArrayList<WindowManager.LayoutParams> mParams =
+            new ArrayList<WindowManager.LayoutParams>();
+    private final ArraySet<View> mDyingViews = new ArraySet<View>();
+
+    private Runnable mSystemPropertyUpdater;
+
+    private WindowManagerGlobal() {
+    }
+
+    @UnsupportedAppUsage
+    public static void initialize() {
+        getWindowManagerService();
+    }
+
+    @UnsupportedAppUsage
+    public static WindowManagerGlobal getInstance() {
+        synchronized (WindowManagerGlobal.class) {
+            if (sDefaultWindowManager == null) {
+                sDefaultWindowManager = new WindowManagerGlobal();
+            }
+            return sDefaultWindowManager;
+        }
+    }
+
+    @UnsupportedAppUsage
+    public static IWindowManager getWindowManagerService() {
+        synchronized (WindowManagerGlobal.class) {
+            if (sWindowManagerService == null) {
+                sWindowManagerService = IWindowManager.Stub.asInterface(
+                        ServiceManager.getService("window"));
+                try {
+                    if (sWindowManagerService != null) {
+                        ValueAnimator.setDurationScale(
+                                sWindowManagerService.getCurrentAnimatorScale());
+                        sUseBLASTAdapter = sWindowManagerService.useBLAST();
+                    }
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+            return sWindowManagerService;
+        }
+    }
+
+    @UnsupportedAppUsage
+    public static IWindowSession getWindowSession() {
+        synchronized (WindowManagerGlobal.class) {
+            if (sWindowSession == null) {
+                try {
+                    // Emulate the legacy behavior.  The global instance of InputMethodManager
+                    // was instantiated here.
+                    // TODO(b/116157766): Remove this hack after cleaning up @UnsupportedAppUsage
+                    InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
+                    IWindowManager windowManager = getWindowManagerService();
+                    sWindowSession = windowManager.openSession(
+                            new IWindowSessionCallback.Stub() {
+                                @Override
+                                public void onAnimatorScaleChanged(float scale) {
+                                    ValueAnimator.setDurationScale(scale);
+                                }
+                            });
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+            return sWindowSession;
+        }
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    public static IWindowSession peekWindowSession() {
+        synchronized (WindowManagerGlobal.class) {
+            return sWindowSession;
+        }
+    }
+
+    /**
+     * Whether or not to use BLAST for ViewRootImpl
+     */
+    public static boolean useBLAST() {
+        return sUseBLASTAdapter;
+    }
+
+    @UnsupportedAppUsage
+    public String[] getViewRootNames() {
+        synchronized (mLock) {
+            final int numRoots = mRoots.size();
+            String[] mViewRoots = new String[numRoots];
+            for (int i = 0; i < numRoots; ++i) {
+                mViewRoots[i] = getWindowName(mRoots.get(i));
+            }
+            return mViewRoots;
+        }
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public ArrayList<ViewRootImpl> getRootViews(IBinder token) {
+        ArrayList<ViewRootImpl> views = new ArrayList<>();
+        synchronized (mLock) {
+            final int numRoots = mRoots.size();
+            for (int i = 0; i < numRoots; ++i) {
+                WindowManager.LayoutParams params = mParams.get(i);
+                if (params.token == null) {
+                    continue;
+                }
+                if (params.token != token) {
+                    boolean isChild = false;
+                    if (params.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW
+                            && params.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
+                        for (int j = 0 ; j < numRoots; ++j) {
+                            View viewj = mViews.get(j);
+                            WindowManager.LayoutParams paramsj = mParams.get(j);
+                            if (params.token == viewj.getWindowToken()
+                                    && paramsj.token == token) {
+                                isChild = true;
+                                break;
+                            }
+                        }
+                    }
+                    if (!isChild) {
+                        continue;
+                    }
+                }
+                views.add(mRoots.get(i));
+            }
+        }
+        return views;
+    }
+
+    /**
+     * @return the list of all views attached to the global window manager
+     */
+    @NonNull
+    public ArrayList<View> getWindowViews() {
+        synchronized (mLock) {
+            return new ArrayList<>(mViews);
+        }
+    }
+
+    public View getWindowView(IBinder windowToken) {
+        synchronized (mLock) {
+            final int numViews = mViews.size();
+            for (int i = 0; i < numViews; ++i) {
+                final View view = mViews.get(i);
+                if (view.getWindowToken() == windowToken) {
+                    return view;
+                }
+            }
+        }
+        return null;
+    }
+
+    @UnsupportedAppUsage
+    public View getRootView(String name) {
+        synchronized (mLock) {
+            for (int i = mRoots.size() - 1; i >= 0; --i) {
+                final ViewRootImpl root = mRoots.get(i);
+                if (name.equals(getWindowName(root))) return root.getView();
+            }
+        }
+
+        return null;
+    }
+
+    public void addView(View view, ViewGroup.LayoutParams params,
+            Display display, Window parentWindow, int userId) {
+        if (view == null) {
+            throw new IllegalArgumentException("view must not be null");
+        }
+        if (display == null) {
+            throw new IllegalArgumentException("display must not be null");
+        }
+        if (!(params instanceof WindowManager.LayoutParams)) {
+            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
+        }
+
+        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
+        if (parentWindow != null) {
+            parentWindow.adjustLayoutParamsForSubWindow(wparams);
+        } else {
+            // If there's no parent, then hardware acceleration for this view is
+            // set from the application's hardware acceleration setting.
+            final Context context = view.getContext();
+            if (context != null
+                    && (context.getApplicationInfo().flags
+                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
+                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+            }
+        }
+
+        ViewRootImpl root;
+        View panelParentView = null;
+
+        synchronized (mLock) {
+            // Start watching for system property changes.
+            if (mSystemPropertyUpdater == null) {
+                mSystemPropertyUpdater = new Runnable() {
+                    @Override public void run() {
+                        synchronized (mLock) {
+                            for (int i = mRoots.size() - 1; i >= 0; --i) {
+                                mRoots.get(i).loadSystemProperties();
+                            }
+                        }
+                    }
+                };
+                SystemProperties.addChangeCallback(mSystemPropertyUpdater);
+            }
+
+            int index = findViewLocked(view, false);
+            if (index >= 0) {
+                if (mDyingViews.contains(view)) {
+                    // Don't wait for MSG_DIE to make it's way through root's queue.
+                    mRoots.get(index).doDie();
+                } else {
+                    throw new IllegalStateException("View " + view
+                            + " has already been added to the window manager.");
+                }
+                // The previous removeView() had not completed executing. Now it has.
+            }
+
+            // If this is a panel window, then find the window it is being
+            // attached to for future reference.
+            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
+                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
+                final int count = mViews.size();
+                for (int i = 0; i < count; i++) {
+                    if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
+                        panelParentView = mViews.get(i);
+                    }
+                }
+            }
+
+            root = new ViewRootImpl(view.getContext(), display);
+
+            view.setLayoutParams(wparams);
+
+            mViews.add(view);
+            mRoots.add(root);
+            mParams.add(wparams);
+
+            // do this last because it fires off messages to start doing things
+            try {
+                root.setView(view, wparams, panelParentView, userId);
+            } catch (RuntimeException e) {
+                // BadTokenException or InvalidDisplayException, clean up.
+                if (index >= 0) {
+                    removeViewLocked(index, true);
+                }
+                throw e;
+            }
+        }
+    }
+
+    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
+        if (view == null) {
+            throw new IllegalArgumentException("view must not be null");
+        }
+        if (!(params instanceof WindowManager.LayoutParams)) {
+            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
+        }
+
+        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
+
+        view.setLayoutParams(wparams);
+
+        synchronized (mLock) {
+            int index = findViewLocked(view, true);
+            ViewRootImpl root = mRoots.get(index);
+            mParams.remove(index);
+            mParams.add(index, wparams);
+            root.setLayoutParams(wparams, false);
+        }
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public void removeView(View view, boolean immediate) {
+        if (view == null) {
+            throw new IllegalArgumentException("view must not be null");
+        }
+
+        synchronized (mLock) {
+            int index = findViewLocked(view, true);
+            View curView = mRoots.get(index).getView();
+            removeViewLocked(index, immediate);
+            if (curView == view) {
+                return;
+            }
+
+            throw new IllegalStateException("Calling with view " + view
+                    + " but the ViewAncestor is attached to " + curView);
+        }
+    }
+
+    /**
+     * Remove all roots with specified token.
+     *
+     * @param token app or window token.
+     * @param who name of caller, used in logs.
+     * @param what type of caller, used in logs.
+     */
+    public void closeAll(IBinder token, String who, String what) {
+        closeAllExceptView(token, null /* view */, who, what);
+    }
+
+    /**
+     * Remove all roots with specified token, except maybe one view.
+     *
+     * @param token app or window token.
+     * @param view view that should be should be preserved along with it's root.
+     *             Pass null if everything should be removed.
+     * @param who name of caller, used in logs.
+     * @param what type of caller, used in logs.
+     */
+    public void closeAllExceptView(IBinder token, View view, String who, String what) {
+        synchronized (mLock) {
+            int count = mViews.size();
+            for (int i = 0; i < count; i++) {
+                if ((view == null || mViews.get(i) != view)
+                        && (token == null || mParams.get(i).token == token)) {
+                    ViewRootImpl root = mRoots.get(i);
+
+                    if (who != null) {
+                        WindowLeaked leak = new WindowLeaked(
+                                what + " " + who + " has leaked window "
+                                + root.getView() + " that was originally added here");
+                        leak.setStackTrace(root.getLocation().getStackTrace());
+                        Log.e(TAG, "", leak);
+                    }
+
+                    removeViewLocked(i, false);
+                }
+            }
+        }
+    }
+
+    private void removeViewLocked(int index, boolean immediate) {
+        ViewRootImpl root = mRoots.get(index);
+        View view = root.getView();
+
+        if (root != null) {
+            root.getImeFocusController().onWindowDismissed();
+        }
+        boolean deferred = root.die(immediate);
+        if (view != null) {
+            view.assignParent(null);
+            if (deferred) {
+                mDyingViews.add(view);
+            }
+        }
+    }
+
+    void doRemoveView(ViewRootImpl root) {
+        boolean allViewsRemoved;
+        synchronized (mLock) {
+            final int index = mRoots.indexOf(root);
+            if (index >= 0) {
+                mRoots.remove(index);
+                mParams.remove(index);
+                final View view = mViews.remove(index);
+                mDyingViews.remove(view);
+            }
+            allViewsRemoved = mRoots.isEmpty();
+        }
+        if (ThreadedRenderer.sTrimForeground) {
+            doTrimForeground();
+        }
+
+        // If we don't have any views anymore in our process, we no longer need the
+        // InsetsAnimationThread to save some resources.
+        if (allViewsRemoved) {
+            InsetsAnimationThread.release();
+        }
+    }
+
+    private int findViewLocked(View view, boolean required) {
+        final int index = mViews.indexOf(view);
+        if (required && index < 0) {
+            throw new IllegalArgumentException("View=" + view + " not attached to window manager");
+        }
+        return index;
+    }
+
+    public static boolean shouldDestroyEglContext(int trimLevel) {
+        // On low-end gfx devices we trim when memory is moderate;
+        // on high-end devices we do this when low.
+        if (trimLevel >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
+            return true;
+        }
+        if (trimLevel >= ComponentCallbacks2.TRIM_MEMORY_MODERATE
+                && !ActivityManager.isHighEndGfx()) {
+            return true;
+        }
+        return false;
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    public void trimMemory(int level) {
+
+        if (shouldDestroyEglContext(level)) {
+            // Destroy all hardware surfaces and resources associated to
+            // known windows
+            synchronized (mLock) {
+                for (int i = mRoots.size() - 1; i >= 0; --i) {
+                    mRoots.get(i).destroyHardwareResources();
+                }
+            }
+            // Force a full memory flush
+            level = ComponentCallbacks2.TRIM_MEMORY_COMPLETE;
+        }
+
+        ThreadedRenderer.trimMemory(level);
+
+        if (ThreadedRenderer.sTrimForeground) {
+            doTrimForeground();
+        }
+    }
+
+    public static void trimForeground() {
+        if (ThreadedRenderer.sTrimForeground) {
+            WindowManagerGlobal wm = WindowManagerGlobal.getInstance();
+            wm.doTrimForeground();
+        }
+    }
+
+    private void doTrimForeground() {
+        boolean hasVisibleWindows = false;
+        synchronized (mLock) {
+            for (int i = mRoots.size() - 1; i >= 0; --i) {
+                final ViewRootImpl root = mRoots.get(i);
+                if (root.mView != null && root.getHostVisibility() == View.VISIBLE
+                        && root.mAttachInfo.mThreadedRenderer != null) {
+                    hasVisibleWindows = true;
+                } else {
+                    root.destroyHardwareResources();
+                }
+            }
+        }
+        if (!hasVisibleWindows) {
+            ThreadedRenderer.trimMemory(
+                    ComponentCallbacks2.TRIM_MEMORY_COMPLETE);
+        }
+    }
+
+    public void dumpGfxInfo(FileDescriptor fd, String[] args) {
+        FileOutputStream fout = new FileOutputStream(fd);
+        PrintWriter pw = new FastPrintWriter(fout);
+        try {
+            synchronized (mLock) {
+                final int count = mViews.size();
+
+                pw.println("Profile data in ms:");
+
+                for (int i = 0; i < count; i++) {
+                    ViewRootImpl root = mRoots.get(i);
+                    String name = getWindowName(root);
+                    pw.printf("\n\t%s (visibility=%d)", name, root.getHostVisibility());
+
+                    ThreadedRenderer renderer =
+                            root.getView().mAttachInfo.mThreadedRenderer;
+                    if (renderer != null) {
+                        renderer.dumpGfxInfo(pw, fd, args);
+                    }
+                }
+
+                pw.println("\nView hierarchy:\n");
+
+                ViewRootImpl.GfxInfo totals = new ViewRootImpl.GfxInfo();
+
+                for (int i = 0; i < count; i++) {
+                    ViewRootImpl root = mRoots.get(i);
+                    ViewRootImpl.GfxInfo info = root.getGfxInfo();
+                    totals.add(info);
+
+                    String name = getWindowName(root);
+                    pw.printf("  %s\n  %d views, %.2f kB of render nodes",
+                            name, info.viewCount, info.renderNodeMemoryUsage / 1024.f);
+                    pw.printf("\n\n");
+                }
+
+                pw.printf("\nTotal %-15s: %d\n", "ViewRootImpl", count);
+                pw.printf("Total %-15s: %d\n", "attached Views", totals.viewCount);
+                pw.printf("Total %-15s: %.2f kB (used) / %.2f kB (capacity)\n\n", "RenderNode",
+                        totals.renderNodeMemoryUsage / 1024.0f,
+                        totals.renderNodeMemoryAllocated / 1024.0f);
+            }
+        } finally {
+            pw.flush();
+        }
+    }
+
+    private static String getWindowName(ViewRootImpl root) {
+        return root.mWindowAttributes.getTitle() + "/" +
+                root.getClass().getName() + '@' + Integer.toHexString(root.hashCode());
+    }
+
+    public void setStoppedState(IBinder token, boolean stopped) {
+        ArrayList<ViewRootImpl> nonCurrentThreadRoots = null;
+        synchronized (mLock) {
+            int count = mViews.size();
+            for (int i = count - 1; i >= 0; i--) {
+                if (token == null || mParams.get(i).token == token) {
+                    ViewRootImpl root = mRoots.get(i);
+                    // Client might remove the view by "stopped" event.
+                    if (root.mThread == Thread.currentThread()) {
+                        root.setWindowStopped(stopped);
+                    } else {
+                        if (nonCurrentThreadRoots == null) {
+                            nonCurrentThreadRoots = new ArrayList<>();
+                        }
+                        nonCurrentThreadRoots.add(root);
+                    }
+                    // Recursively forward stopped state to View's attached
+                    // to this Window rather than the root application token,
+                    // e.g. PopupWindow's.
+                    setStoppedState(root.mAttachInfo.mWindowToken, stopped);
+                }
+            }
+        }
+
+        // Update the stopped state synchronously to ensure the surface won't be used after server
+        // side has destroyed it. This operation should be outside the lock to avoid any potential
+        // paths from setWindowStopped to WindowManagerGlobal which may cause deadlocks.
+        if (nonCurrentThreadRoots != null) {
+            for (int i = nonCurrentThreadRoots.size() - 1; i >= 0; i--) {
+                ViewRootImpl root = nonCurrentThreadRoots.get(i);
+                root.mHandler.runWithScissors(() -> root.setWindowStopped(stopped), 0);
+            }
+        }
+    }
+
+    public void reportNewConfiguration(Configuration config) {
+        synchronized (mLock) {
+            int count = mViews.size();
+            config = new Configuration(config);
+            for (int i=0; i < count; i++) {
+                ViewRootImpl root = mRoots.get(i);
+                root.requestUpdateConfiguration(config);
+            }
+        }
+    }
+
+    /** @hide */
+    public void changeCanvasOpacity(IBinder token, boolean opaque) {
+        if (token == null) {
+            return;
+        }
+        synchronized (mLock) {
+            for (int i = mParams.size() - 1; i >= 0; --i) {
+                if (mParams.get(i).token == token) {
+                    mRoots.get(i).changeCanvasOpacity(opaque);
+                    return;
+                }
+            }
+        }
+    }
+}
+
+final class WindowLeaked extends AndroidRuntimeException {
+    @UnsupportedAppUsage
+    public WindowLeaked(String msg) {
+        super(msg);
+    }
+}
diff --git a/android/view/WindowManagerGlobal_Delegate.java b/android/view/WindowManagerGlobal_Delegate.java
new file mode 100644
index 0000000..2606e55
--- /dev/null
+++ b/android/view/WindowManagerGlobal_Delegate.java
@@ -0,0 +1,43 @@
+/*
+ * 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.view;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of
+ * {@link WindowManagerGlobal}
+ *
+ * Through the layoutlib_create tool, the original  methods of WindowManagerGlobal have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ */
+public class WindowManagerGlobal_Delegate {
+
+    private static IWindowManager sService;
+
+    @LayoutlibDelegate
+    public static IWindowManager getWindowManagerService() {
+        return sService;
+    }
+
+    // ---- internal implementation stuff ----
+
+    public static void setWindowManagerService(IWindowManager service) {
+        sService = service;
+    }
+}
diff --git a/android/view/WindowManagerImpl.java b/android/view/WindowManagerImpl.java
new file mode 100644
index 0000000..36b7165
--- /dev/null
+++ b/android/view/WindowManagerImpl.java
@@ -0,0 +1,174 @@
+/*
+ * 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.view;
+
+import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
+import android.app.ResourcesManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.DisplayMetrics;
+import android.view.Display.Mode;
+
+import com.android.ide.common.rendering.api.ILayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+
+public class WindowManagerImpl implements WindowManager {
+
+    private final Context mContext;
+    private final DisplayMetrics mMetrics;
+    private final Display mDisplay;
+
+    public WindowManagerImpl(Context context, DisplayMetrics metrics) {
+        mContext = context;
+        mMetrics = metrics;
+
+        DisplayInfo info = new DisplayInfo();
+        info.logicalHeight = mMetrics.heightPixels;
+        info.logicalWidth = mMetrics.widthPixels;
+        info.supportedModes = new Mode[] {
+                new Mode(0, mMetrics.widthPixels, mMetrics.heightPixels, 60f)
+        };
+        info.logicalDensityDpi = mMetrics.densityDpi;
+        mDisplay = new Display(null, Display.DEFAULT_DISPLAY, info,
+                DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
+    }
+
+    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
+        Bridge.getLog().fidelityWarning(ILayoutLog.TAG_UNSUPPORTED,
+                "The preview does not support multiple windows.",
+                null, null, null);
+        return this;
+    }
+
+    public WindowManagerImpl createPresentationWindowManager(Context displayContext) {
+        Bridge.getLog().fidelityWarning(ILayoutLog.TAG_UNSUPPORTED,
+                "The preview does not support multiple windows.",
+                null, null, null);
+        return this;
+    }
+
+    /**
+     * Sets the window token to assign when none is specified by the client or
+     * available from the parent window.
+     *
+     * @param token The default token to assign.
+     */
+    public void setDefaultToken(IBinder token) {
+
+    }
+
+    @Override
+    public Display getDefaultDisplay() {
+        return mDisplay;
+    }
+
+
+    @Override
+    public void addView(View arg0, android.view.ViewGroup.LayoutParams arg1) {
+        // pass
+    }
+
+    @Override
+    public void removeView(View arg0) {
+        // pass
+    }
+
+    @Override
+    public void updateViewLayout(View arg0, android.view.ViewGroup.LayoutParams arg1) {
+        // pass
+    }
+
+
+    @Override
+    public void removeViewImmediate(View arg0) {
+        // pass
+    }
+
+    @Override
+    public void requestAppKeyboardShortcuts(
+            KeyboardShortcutsReceiver receiver, int deviceId) {
+    }
+
+    @Override
+    public Region getCurrentImeTouchRegion() {
+        return null;
+    }
+
+    @Override
+    public void setShouldShowWithInsecureKeyguard(int displayId, boolean shouldShow) {
+        // pass
+    }
+
+    @Override
+    public void setShouldShowSystemDecors(int displayId, boolean shouldShow) {
+        // pass
+    }
+
+    @Override
+    public void setDisplayImePolicy(int displayId, int imePolicy) {
+        // pass
+    }
+
+    @Override
+    public WindowMetrics getCurrentWindowMetrics() {
+        final Rect bound = getCurrentBounds(mContext);
+
+        return new WindowMetrics(bound, computeWindowInsets());
+    }
+
+    private static Rect getCurrentBounds(Context context) {
+        synchronized (ResourcesManager.getInstance()) {
+            return context.getResources().getConfiguration().windowConfiguration.getBounds();
+        }
+    }
+
+    @Override
+    public WindowMetrics getMaximumWindowMetrics() {
+        return new WindowMetrics(getMaximumBounds(), computeWindowInsets());
+    }
+
+    private Rect getMaximumBounds() {
+        final Point displaySize = new Point();
+        mDisplay.getRealSize(displaySize);
+        return new Rect(0, 0, displaySize.x, displaySize.y);
+    }
+
+    private WindowInsets computeWindowInsets() {
+        try {
+            final InsetsState insetsState = new InsetsState();
+            final boolean alwaysConsumeSystemBars =
+                    WindowManagerGlobal.getWindowManagerService().getWindowInsets(
+                            new WindowManager.LayoutParams(), mContext.getDisplayId(), insetsState);
+            final Configuration config = mContext.getResources().getConfiguration();
+            final boolean isScreenRound = config.isScreenRound();
+            final int windowingMode = config.windowConfiguration.getWindowingMode();
+            return insetsState.calculateInsets(getCurrentBounds(mContext),
+                    null /* ignoringVisibilityState*/, isScreenRound, alwaysConsumeSystemBars,
+                    SOFT_INPUT_ADJUST_NOTHING, 0, SYSTEM_UI_FLAG_VISIBLE, TYPE_APPLICATION,
+                    windowingMode, null /* typeSideMap */);
+        } catch (RemoteException ignore) {
+        }
+        return null;
+    }
+}
diff --git a/android/view/WindowManagerPolicyConstants.java b/android/view/WindowManagerPolicyConstants.java
new file mode 100644
index 0000000..bbef3e6
--- /dev/null
+++ b/android/view/WindowManagerPolicyConstants.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.os.IInputConstants.POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY;
+
+import android.annotation.IntDef;
+import android.os.PowerManager;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Constants for interfacing with WindowManagerService and WindowManagerPolicyInternal.
+ * @hide
+ */
+public interface WindowManagerPolicyConstants {
+    // Policy flags.  These flags are also defined in frameworks/base/include/ui/Input.h and
+    // frameworks/native/libs/input/android/os/IInputConstants.aidl
+    int FLAG_WAKE = 0x00000001;
+    int FLAG_VIRTUAL = 0x00000002;
+
+    int FLAG_INJECTED_FROM_ACCESSIBILITY = POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY;
+    int FLAG_INJECTED = 0x01000000;
+    int FLAG_TRUSTED = 0x02000000;
+    int FLAG_FILTERED = 0x04000000;
+    int FLAG_DISABLE_KEY_REPEAT = 0x08000000;
+
+    int FLAG_INTERACTIVE = 0x20000000;
+    int FLAG_PASS_TO_USER = 0x40000000;
+
+    // Flags for IActivityTaskManager.keyguardGoingAway()
+    int KEYGUARD_GOING_AWAY_FLAG_TO_SHADE = 1 << 0;
+    int KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS = 1 << 1;
+    int KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER = 1 << 2;
+    int KEYGUARD_GOING_AWAY_FLAG_SUBTLE_WINDOW_ANIMATIONS = 1 << 3;
+
+    // Flags used for indicating whether the internal and/or external input devices
+    // of some type are available.
+    int PRESENCE_INTERNAL = 1 << 0;
+    int PRESENCE_EXTERNAL = 1 << 1;
+
+    // Alternate bars position values
+    int ALT_BAR_UNKNOWN = -1;
+    int ALT_BAR_LEFT = 1 << 0;
+    int ALT_BAR_RIGHT = 1 << 1;
+    int ALT_BAR_BOTTOM = 1 << 2;
+    int ALT_BAR_TOP = 1 << 3;
+
+    // Navigation bar position values
+    int NAV_BAR_INVALID = -1;
+    int NAV_BAR_LEFT = 1 << 0;
+    int NAV_BAR_RIGHT = 1 << 1;
+    int NAV_BAR_BOTTOM = 1 << 2;
+
+    // Navigation bar interaction modes
+    int NAV_BAR_MODE_3BUTTON = 0;
+    int NAV_BAR_MODE_2BUTTON = 1;
+    int NAV_BAR_MODE_GESTURAL = 2;
+
+    // Associated overlays for each nav bar mode
+    String NAV_BAR_MODE_3BUTTON_OVERLAY = "com.android.internal.systemui.navbar.threebutton";
+    String NAV_BAR_MODE_2BUTTON_OVERLAY = "com.android.internal.systemui.navbar.twobutton";
+    String NAV_BAR_MODE_GESTURAL_OVERLAY = "com.android.internal.systemui.navbar.gestural";
+
+    /**
+     * Sticky broadcast of the current HDMI plugged state.
+     */
+    String ACTION_HDMI_PLUGGED = "android.intent.action.HDMI_PLUGGED";
+
+    /**
+     * Extra in {@link #ACTION_HDMI_PLUGGED} indicating the state: true if
+     * plugged in to HDMI, false if not.
+     */
+    String EXTRA_HDMI_PLUGGED_STATE = "state";
+
+    /**
+     * Set to {@code true} when intent was invoked from pressing the home key.
+     * @hide
+     */
+    String EXTRA_FROM_HOME_KEY = "android.intent.extra.FROM_HOME_KEY";
+
+    /**
+     * Extra for the start reason of the HOME intent.
+     * Will be {@link PowerManager#WAKE_REASON_WAKE_KEY} or
+     * {@link PowerManager#WAKE_REASON_POWER_BUTTON} when intent was sent through
+     * {@link PhoneWindowManager#shouldWakeUpWithHomeIntent}.
+     * @hide
+     */
+    String EXTRA_START_REASON = "android.intent.extra.EXTRA_START_REASON";
+
+    // TODO: move this to a more appropriate place.
+    interface PointerEventListener {
+        /**
+         * 1. onPointerEvent will be called on the service.UiThread.
+         * 2. motionEvent will be recycled after onPointerEvent returns so if it is needed later a
+         * copy() must be made and the copy must be recycled.
+         **/
+        void onPointerEvent(MotionEvent motionEvent);
+    }
+
+    @IntDef(prefix = { "OFF_BECAUSE_OF_" }, value = {
+            OFF_BECAUSE_OF_ADMIN,
+            OFF_BECAUSE_OF_USER,
+            OFF_BECAUSE_OF_TIMEOUT,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface OffReason{}
+
+    static @OffReason int translateSleepReasonToOffReason(
+            @PowerManager.GoToSleepReason int reason) {
+        switch (reason) {
+            case PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN:
+                return OFF_BECAUSE_OF_ADMIN;
+            case PowerManager.GO_TO_SLEEP_REASON_TIMEOUT:
+            case PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE:
+                return OFF_BECAUSE_OF_TIMEOUT;
+            default:
+                return OFF_BECAUSE_OF_USER;
+        }
+    }
+
+    /** Screen turned off because of a device admin */
+    int OFF_BECAUSE_OF_ADMIN = 1;
+    /** Screen turned off because of power button */
+    int OFF_BECAUSE_OF_USER = 2;
+    /** Screen turned off because of timeout */
+    int OFF_BECAUSE_OF_TIMEOUT = 3;
+
+    @IntDef(prefix = { "ON_BECAUSE_OF_" }, value = {
+            ON_BECAUSE_OF_USER,
+            ON_BECAUSE_OF_APPLICATION,
+            ON_BECAUSE_OF_UNKNOWN,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface OnReason{}
+
+    /** Convert the on reason to a human readable format */
+    static String onReasonToString(@OnReason int why) {
+        switch (why) {
+            case ON_BECAUSE_OF_USER:
+                return "ON_BECAUSE_OF_USER";
+            case ON_BECAUSE_OF_APPLICATION:
+                return "ON_BECAUSE_OF_APPLICATION";
+            case ON_BECAUSE_OF_UNKNOWN:
+                return "ON_BECAUSE_OF_UNKNOWN";
+            default:
+                return Integer.toString(why);
+        }
+    }
+
+    static @OnReason int translateWakeReasonToOnReason(@PowerManager.WakeReason int reason) {
+        switch (reason) {
+            case PowerManager.WAKE_REASON_POWER_BUTTON:
+            case PowerManager.WAKE_REASON_PLUGGED_IN:
+            case PowerManager.WAKE_REASON_GESTURE:
+            case PowerManager.WAKE_REASON_CAMERA_LAUNCH:
+            case PowerManager.WAKE_REASON_WAKE_KEY:
+            case PowerManager.WAKE_REASON_WAKE_MOTION:
+            case PowerManager.WAKE_REASON_LID:
+                return ON_BECAUSE_OF_USER;
+            case PowerManager.WAKE_REASON_APPLICATION:
+                return ON_BECAUSE_OF_APPLICATION;
+            default:
+                return ON_BECAUSE_OF_UNKNOWN;
+        }
+    }
+
+    /** Screen turned on because of a user-initiated action. */
+    int ON_BECAUSE_OF_USER = 1;
+    /** Screen turned on because of an application request or event */
+    int ON_BECAUSE_OF_APPLICATION = 2;
+    /** Screen turned on for an unknown reason */
+    int ON_BECAUSE_OF_UNKNOWN = 3;
+
+    int APPLICATION_LAYER = 2;
+    int APPLICATION_MEDIA_SUBLAYER = -2;
+    int APPLICATION_MEDIA_OVERLAY_SUBLAYER = -1;
+    int APPLICATION_PANEL_SUBLAYER = 1;
+    int APPLICATION_SUB_PANEL_SUBLAYER = 2;
+    int APPLICATION_ABOVE_SUB_PANEL_SUBLAYER = 3;
+
+    /**
+     * Convert the off reason to a human readable format.
+     */
+    static String offReasonToString(int why) {
+        switch (why) {
+            case OFF_BECAUSE_OF_ADMIN:
+                return "OFF_BECAUSE_OF_ADMIN";
+            case OFF_BECAUSE_OF_USER:
+                return "OFF_BECAUSE_OF_USER";
+            case OFF_BECAUSE_OF_TIMEOUT:
+                return "OFF_BECAUSE_OF_TIMEOUT";
+            default:
+                return Integer.toString(why);
+        }
+    }
+}
diff --git a/android/view/WindowMetrics.java b/android/view/WindowMetrics.java
new file mode 100644
index 0000000..52e4e15
--- /dev/null
+++ b/android/view/WindowMetrics.java
@@ -0,0 +1,81 @@
+/*
+ * 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.view;
+
+import android.annotation.NonNull;
+import android.graphics.Point;
+import android.graphics.Rect;
+
+/**
+ * Metrics about a Window, consisting of the bounds and {@link WindowInsets}.
+ * <p>
+ * This is usually obtained from {@link WindowManager#getCurrentWindowMetrics()} and
+ * {@link WindowManager#getMaximumWindowMetrics()}.
+ *
+ * @see WindowInsets#getInsets(int)
+ * @see WindowManager#getCurrentWindowMetrics()
+ * @see WindowManager#getMaximumWindowMetrics()
+ */
+public final class WindowMetrics {
+    private final @NonNull Rect mBounds;
+    private final @NonNull WindowInsets mWindowInsets;
+
+    public WindowMetrics(@NonNull Rect bounds, @NonNull WindowInsets windowInsets) {
+        mBounds = bounds;
+        mWindowInsets = windowInsets;
+    }
+
+    /**
+     * Returns the bounds of the area associated with this window or visual context.
+     * <p>
+     * <b>Note that the size of the reported bounds can have different size than
+     * {@link Display#getSize(Point)}.</b> This method reports the window size including all system
+     * bar areas, while {@link Display#getSize(Point)} reports the area excluding navigation bars
+     * and display cutout areas. The value reported by {@link Display#getSize(Point)} can be
+     * obtained by using:
+     * <pre class="prettyprint">
+     * final WindowMetrics metrics = windowManager.getCurrentWindowMetrics();
+     * // Gets all excluding insets
+     * final WindowInsets windowInsets = metrics.getWindowInsets();
+     * Insets insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars()
+     *         | WindowInsets.Type.displayCutout());
+     *
+     * int insetsWidth = insets.right + insets.left;
+     * int insetsHeight = insets.top + insets.bottom;
+     *
+     * // Legacy size that Display#getSize reports
+     * final Rect bounds = metrics.getBounds();
+     * final Size legacySize = new Size(bounds.width() - insetsWidth,
+     *         bounds.height() - insetsHeight);
+     * </pre>
+     * </p>
+     *
+     * @return window bounds in pixels.
+     */
+    public @NonNull Rect getBounds() {
+        return mBounds;
+    }
+
+    /**
+     * Returns the {@link WindowInsets} of the area associated with this window or visual context.
+     *
+     * @return the {@link WindowInsets} of the visual area.
+     */
+    public @NonNull WindowInsets getWindowInsets() {
+        return mWindowInsets;
+    }
+}
diff --git a/android/view/WindowlessWindowManager.java b/android/view/WindowlessWindowManager.java
new file mode 100644
index 0000000..ae54f51
--- /dev/null
+++ b/android/view/WindowlessWindowManager.java
@@ -0,0 +1,501 @@
+/*
+ * 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.view;
+
+import android.annotation.Nullable;
+import android.content.res.Configuration;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.IBinder;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.MergedConfiguration;
+import android.window.ClientWindowFrames;
+
+import java.util.HashMap;
+import java.util.Objects;
+
+/**
+* A simplistic implementation of IWindowSession. Rather than managing Surfaces
+* as children of the display, it manages Surfaces as children of a given root.
+*
+* By parcelling the root surface, the app can offer another app content for embedding.
+* @hide
+*/
+public class WindowlessWindowManager implements IWindowSession {
+    private final static String TAG = "WindowlessWindowManager";
+
+    private class State {
+        SurfaceControl mSurfaceControl;
+        WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
+        int mDisplayId;
+        IBinder mInputChannelToken;
+        Region mInputRegion;
+        State(SurfaceControl sc, WindowManager.LayoutParams p, int displayId,
+                IBinder inputChannelToken) {
+            mSurfaceControl = sc;
+            mParams.copyFrom(p);
+            mDisplayId = displayId;
+            mInputChannelToken = inputChannelToken;
+        }
+    };
+
+    /**
+     * Used to store SurfaceControl we've built for clients to
+     * reconfigure them if relayout is called.
+     */
+    final HashMap<IBinder, State> mStateForWindow = new HashMap<IBinder, State>();
+
+    public interface ResizeCompleteCallback {
+        public void finished(SurfaceControl.Transaction completion);
+    }
+
+    final HashMap<IBinder, ResizeCompleteCallback> mResizeCompletionForWindow =
+        new HashMap<IBinder, ResizeCompleteCallback>();
+
+    private final SurfaceSession mSurfaceSession = new SurfaceSession();
+    protected final SurfaceControl mRootSurface;
+    private final Configuration mConfiguration;
+    private final IWindowSession mRealWm;
+    private final IBinder mHostInputToken;
+
+    private int mForceHeight = -1;
+    private int mForceWidth = -1;
+
+    public WindowlessWindowManager(Configuration c, SurfaceControl rootSurface,
+            IBinder hostInputToken) {
+        mRootSurface = rootSurface;
+        mConfiguration = new Configuration(c);
+        mRealWm = WindowManagerGlobal.getWindowSession();
+        mHostInputToken = hostInputToken;
+    }
+
+    protected void setConfiguration(Configuration configuration) {
+        mConfiguration.setTo(configuration);
+    }
+
+    /**
+     * Utility API.
+     */
+    void setCompletionCallback(IBinder window, ResizeCompleteCallback callback) {
+        if (mResizeCompletionForWindow.get(window) != null) {
+            Log.w(TAG, "Unsupported overlapping resizes");
+        }
+        mResizeCompletionForWindow.put(window, callback);
+    }
+
+    protected void setTouchRegion(IBinder window, @Nullable Region region) {
+        State state;
+        synchronized (this) {
+            // Do everything while locked so that we synchronize with relayout. This should be a
+            // very infrequent operation.
+            state = mStateForWindow.get(window);
+            if (state == null) {
+                return;
+            }
+            if (Objects.equals(region, state.mInputRegion)) {
+                return;
+            }
+            state.mInputRegion = region != null ? new Region(region) : null;
+            if (state.mInputChannelToken != null) {
+                try {
+                    mRealWm.updateInputChannel(state.mInputChannelToken, state.mDisplayId,
+                            state.mSurfaceControl, state.mParams.flags, state.mParams.privateFlags,
+                            state.mInputRegion);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Failed to update surface input channel: ", e);
+                }
+            }
+        }
+    }
+
+    protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
+        b.setParent(mRootSurface);
+    }
+
+    /**
+     * IWindowSession implementation.
+     */
+    @Override
+    public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
+            int viewVisibility, int displayId, InsetsState requestedVisibility,
+            InputChannel outInputChannel, InsetsState outInsetsState,
+            InsetsSourceControl[] outActiveControls) {
+        final SurfaceControl.Builder b = new SurfaceControl.Builder(mSurfaceSession)
+                .setFormat(attrs.format)
+                .setBLASTLayer()
+                .setName(attrs.getTitle().toString())
+                .setCallsite("WindowlessWindowManager.addToDisplay");
+        attachToParentSurface(window, b);
+        final SurfaceControl sc = b.build();
+
+        if (((attrs.inputFeatures &
+                WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0)) {
+            try {
+                if(mRealWm instanceof IWindowSession.Stub) {
+                    mRealWm.grantInputChannel(displayId,
+                        new SurfaceControl(sc, "WindowlessWindowManager.addToDisplay"),
+                        window, mHostInputToken, attrs.flags, attrs.privateFlags, attrs.type,
+                        outInputChannel);
+                } else {
+                    mRealWm.grantInputChannel(displayId, sc, window, mHostInputToken, attrs.flags,
+                        attrs.privateFlags, attrs.type, outInputChannel);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to grant input to surface: ", e);
+            }
+        }
+
+        final State state = new State(sc, attrs, displayId,
+                outInputChannel != null ? outInputChannel.getToken() : null);
+        synchronized (this) {
+            mStateForWindow.put(window.asBinder(), state);
+        }
+
+        final int res = WindowManagerGlobal.ADD_OKAY | WindowManagerGlobal.ADD_FLAG_APP_VISIBLE |
+                        WindowManagerGlobal.ADD_FLAG_USE_BLAST;
+
+        // Include whether the window is in touch mode.
+        return isInTouchMode() ? res | WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE : res;
+    }
+
+    /**
+     * IWindowSession implementation. Currently this class doesn't need to support for multi-user.
+     */
+    @Override
+    public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
+            int viewVisibility, int displayId, int userId, InsetsState requestedVisibility,
+            InputChannel outInputChannel, InsetsState outInsetsState,
+            InsetsSourceControl[] outActiveControls) {
+        return addToDisplay(window, attrs, viewVisibility, displayId, requestedVisibility,
+                outInputChannel, outInsetsState, outActiveControls);
+    }
+
+    @Override
+    public int addToDisplayWithoutInputChannel(android.view.IWindow window,
+            android.view.WindowManager.LayoutParams attrs, int viewVisibility, int layerStackId,
+            android.view.InsetsState insetsState) {
+        return 0;
+    }
+
+    @Override
+    public void remove(android.view.IWindow window) throws RemoteException {
+        mRealWm.remove(window);
+        State state;
+        synchronized (this) {
+            state = mStateForWindow.remove(window.asBinder());
+        }
+        if (state == null) {
+            throw new IllegalArgumentException(
+                    "Invalid window token (never added or removed already)");
+        }
+
+        try (SurfaceControl.Transaction t = new SurfaceControl.Transaction()) {
+            t.remove(state.mSurfaceControl).apply();
+        }
+    }
+
+    private boolean isOpaque(WindowManager.LayoutParams attrs) {
+        if (attrs.surfaceInsets != null && attrs.surfaceInsets.left != 0 ||
+                attrs.surfaceInsets.top != 0 || attrs.surfaceInsets.right != 0 ||
+                attrs.surfaceInsets.bottom != 0) {
+            return false;
+        }
+        return !PixelFormat.formatHasAlpha(attrs.format);
+    }
+
+    private boolean isInTouchMode() {
+        try {
+            return WindowManagerGlobal.getWindowSession().getInTouchMode();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to check if the window is in touch mode", e);
+        }
+        return false;
+    }
+
+    /** Access to package members for SystemWindow leashing
+     * @hide
+     */
+    protected IBinder getWindowBinder(View rootView) {
+        final ViewRootImpl root = rootView.getViewRootImpl();
+        if (root == null) {
+            return null;
+        }
+        return root.mWindow.asBinder();
+    }
+
+    /** @hide */
+    @Nullable
+    protected SurfaceControl getSurfaceControl(View rootView) {
+        final ViewRootImpl root = rootView.getViewRootImpl();
+        if (root == null) {
+            return null;
+        }
+        return getSurfaceControl(root.mWindow);
+    }
+
+    /** @hide */
+    @Nullable
+    protected SurfaceControl getSurfaceControl(IWindow window) {
+        final State s = mStateForWindow.get(window.asBinder());
+        if (s == null) {
+            return null;
+        }
+        return s.mSurfaceControl;
+    }
+
+    @Override
+    public int relayout(IWindow window, WindowManager.LayoutParams inAttrs,
+            int requestedWidth, int requestedHeight, int viewFlags, int flags, long frameNumber,
+            ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
+            SurfaceControl outSurfaceControl, InsetsState outInsetsState,
+            InsetsSourceControl[] outActiveControls, Point outSurfaceSize) {
+        final State state;
+        synchronized (this) {
+            state = mStateForWindow.get(window.asBinder());
+        }
+        if (state == null) {
+            throw new IllegalArgumentException(
+                    "Invalid window token (never added or removed already)");
+        }
+        SurfaceControl sc = state.mSurfaceControl;
+        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+
+        int attrChanges = 0;
+        if (inAttrs != null) {
+            attrChanges = state.mParams.copyFrom(inAttrs);
+        }
+        WindowManager.LayoutParams attrs = state.mParams;
+
+        if (viewFlags == View.VISIBLE) {
+            outSurfaceSize.set(getSurfaceWidth(attrs), getSurfaceHeight(attrs));
+            t.setOpaque(sc, isOpaque(attrs)).show(sc).apply();
+            outSurfaceControl.copyFrom(sc, "WindowlessWindowManager.relayout");
+        } else {
+            t.hide(sc).apply();
+            outSurfaceControl.release();
+        }
+        outFrames.frame.set(0, 0, attrs.width, attrs.height);
+        outFrames.displayFrame.set(outFrames.frame);
+
+        mergedConfiguration.setConfiguration(mConfiguration, mConfiguration);
+
+        if ((attrChanges & WindowManager.LayoutParams.FLAGS_CHANGED) != 0
+                && state.mInputChannelToken != null) {
+            try {
+                if(mRealWm instanceof IWindowSession.Stub) {
+                    mRealWm.updateInputChannel(state.mInputChannelToken, state.mDisplayId,
+                            new SurfaceControl(sc, "WindowlessWindowManager.relayout"),
+                            attrs.flags, attrs.privateFlags, state.mInputRegion);
+                } else {
+                    mRealWm.updateInputChannel(state.mInputChannelToken, state.mDisplayId, sc,
+                            attrs.flags, attrs.privateFlags, state.mInputRegion);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to update surface input channel: ", e);
+            }
+        }
+
+        // Include whether the window is in touch mode.
+        return isInTouchMode() ? WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE : 0;
+    }
+
+    @Override
+    public void prepareToReplaceWindows(android.os.IBinder appToken, boolean childrenOnly) {
+    }
+
+    @Override
+    public boolean outOfMemory(android.view.IWindow window) {
+        return false;
+    }
+
+    @Override
+    public void setInsets(android.view.IWindow window, int touchableInsets,
+            android.graphics.Rect contentInsets, android.graphics.Rect visibleInsets,
+            android.graphics.Region touchableRegion) {
+    }
+
+    @Override
+    public void finishDrawing(android.view.IWindow window,
+            android.view.SurfaceControl.Transaction postDrawTransaction) {
+        synchronized (this) {
+            final ResizeCompleteCallback c =
+                mResizeCompletionForWindow.get(window.asBinder());
+            if (c == null) {
+                // No one wanted the callback, but it wasn't necessarily unexpected.
+                postDrawTransaction.apply();
+                return;
+            }
+            c.finished(postDrawTransaction);
+            mResizeCompletionForWindow.remove(window.asBinder());
+        }
+    }
+
+    @Override
+    public void setInTouchMode(boolean showFocus) {
+    }
+
+    @Override
+    public boolean getInTouchMode() {
+        return false;
+    }
+
+    @Override
+    public boolean performHapticFeedback(int effectId, boolean always) {
+        return false;
+    }
+
+    @Override
+    public android.os.IBinder performDrag(android.view.IWindow window, int flags,
+            android.view.SurfaceControl surface, int touchSource, float touchX, float touchY,
+            float thumbCenterX, float thumbCenterY, android.content.ClipData data) {
+        return null;
+    }
+
+    @Override
+    public void reportDropResult(android.view.IWindow window, boolean consumed) {
+    }
+
+    @Override
+    public void cancelDragAndDrop(android.os.IBinder dragToken, boolean skipAnimation) {
+    }
+
+    @Override
+    public void dragRecipientEntered(android.view.IWindow window) {
+    }
+
+    @Override
+    public void dragRecipientExited(android.view.IWindow window) {
+    }
+
+    @Override
+    public void setWallpaperPosition(android.os.IBinder windowToken, float x, float y,
+            float xstep, float ystep) {
+    }
+
+    @Override
+    public void setWallpaperZoomOut(android.os.IBinder windowToken, float zoom) {
+    }
+
+    @Override
+    public void setShouldZoomOutWallpaper(android.os.IBinder windowToken, boolean shouldZoom) {
+    }
+
+    @Override
+    public void wallpaperOffsetsComplete(android.os.IBinder window) {
+    }
+
+    @Override
+    public void setWallpaperDisplayOffset(android.os.IBinder windowToken, int x, int y) {
+    }
+
+    @Override
+    public android.os.Bundle sendWallpaperCommand(android.os.IBinder window,
+            java.lang.String action, int x, int y, int z, android.os.Bundle extras, boolean sync) {
+        return null;
+    }
+
+    @Override
+    public void wallpaperCommandComplete(android.os.IBinder window, android.os.Bundle result) {
+    }
+
+    @Override
+    public void onRectangleOnScreenRequested(android.os.IBinder token,
+            android.graphics.Rect rectangle) {
+    }
+
+    @Override
+    public android.view.IWindowId getWindowId(android.os.IBinder window) {
+        return null;
+    }
+
+    @Override
+    public void pokeDrawLock(android.os.IBinder window) {
+    }
+
+    @Override
+    public boolean startMovingTask(android.view.IWindow window, float startX, float startY) {
+        return false;
+    }
+
+    @Override
+    public void finishMovingTask(android.view.IWindow window) {
+    }
+
+    @Override
+    public void updatePointerIcon(android.view.IWindow window) {
+    }
+
+    @Override
+    public void updateDisplayContentLocation(android.view.IWindow window, int x, int y,
+            int displayId) {
+    }
+
+    @Override
+    public void updateTapExcludeRegion(android.view.IWindow window,
+            android.graphics.Region region) {
+    }
+
+    @Override
+    public void insetsModified(android.view.IWindow window, android.view.InsetsState state) {
+    }
+
+    @Override
+    public void reportSystemGestureExclusionChanged(android.view.IWindow window,
+            java.util.List<android.graphics.Rect> exclusionRects) {
+    }
+
+    @Override
+    public void grantInputChannel(int displayId, SurfaceControl surface, IWindow window,
+            IBinder hostInputToken, int flags, int privateFlags, int type,
+            InputChannel outInputChannel) {
+    }
+
+    @Override
+    public void updateInputChannel(IBinder channelToken, int displayId, SurfaceControl surface,
+            int flags, int privateFlags, Region region) {
+    }
+
+    @Override
+    public android.os.IBinder asBinder() {
+        return null;
+    }
+
+    private int getSurfaceWidth(WindowManager.LayoutParams attrs) {
+      final Rect surfaceInsets = attrs.surfaceInsets;
+      return surfaceInsets != null
+          ? attrs.width + surfaceInsets.left + surfaceInsets.right : attrs.width;
+    }
+    private int getSurfaceHeight(WindowManager.LayoutParams attrs) {
+      final Rect surfaceInsets = attrs.surfaceInsets;
+      return surfaceInsets != null
+          ? attrs.height + surfaceInsets.top + surfaceInsets.bottom : attrs.height;
+    }
+
+    @Override
+    public void grantEmbeddedWindowFocus(IWindow callingWindow, IBinder targetInputToken,
+                                         boolean grantFocus) {
+    }
+
+    @Override
+    public void generateDisplayHash(IWindow window, Rect boundsInWindow, String hashAlgorithm,
+            RemoteCallback callback) {
+    }
+}
diff --git a/android/view/accessibility/AccessibilityCache.java b/android/view/accessibility/AccessibilityCache.java
new file mode 100644
index 0000000..d14dc6e
--- /dev/null
+++ b/android/view/accessibility/AccessibilityCache.java
@@ -0,0 +1,689 @@
+/*
+ * 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.view.accessibility;
+
+import android.os.Build;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.LongArray;
+import android.util.LongSparseArray;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Cache for AccessibilityWindowInfos and AccessibilityNodeInfos.
+ * It is updated when windows change or nodes change.
+ * @hide
+ */
+public class AccessibilityCache {
+
+    private static final String LOG_TAG = "AccessibilityCache";
+
+    private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG) && Build.IS_DEBUGGABLE;
+
+    private static final boolean VERBOSE =
+            Log.isLoggable(LOG_TAG, Log.VERBOSE) && Build.IS_DEBUGGABLE;
+
+    private static final boolean CHECK_INTEGRITY = Build.IS_ENG;
+
+    /**
+     * {@link AccessibilityEvent} types that are critical for the cache to stay up to date
+     *
+     * When adding new event types in {@link #onAccessibilityEvent}, please add it here also, to
+     * make sure that the events are delivered to cache regardless of
+     * {@link android.accessibilityservice.AccessibilityServiceInfo#eventTypes}
+     */
+    public static final int CACHE_CRITICAL_EVENTS_MASK =
+            AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED
+                    | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED
+                    | AccessibilityEvent.TYPE_VIEW_FOCUSED
+                    | AccessibilityEvent.TYPE_VIEW_SELECTED
+                    | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED
+                    | AccessibilityEvent.TYPE_VIEW_CLICKED
+                    | AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED
+                    | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
+                    | AccessibilityEvent.TYPE_VIEW_SCROLLED
+                    | AccessibilityEvent.TYPE_WINDOWS_CHANGED
+                    | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
+
+    private final Object mLock = new Object();
+
+    private final AccessibilityNodeRefresher mAccessibilityNodeRefresher;
+
+    private long mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
+    private long mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
+
+    private int mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+
+    private boolean mIsAllWindowsCached;
+
+    // The SparseArray of all {@link AccessibilityWindowInfo}s on all displays.
+    // The key of outer SparseArray is display ID and the key of inner SparseArray is window ID.
+    private final SparseArray<SparseArray<AccessibilityWindowInfo>> mWindowCacheByDisplay =
+            new SparseArray<>();
+
+    private final SparseArray<LongSparseArray<AccessibilityNodeInfo>> mNodeCache =
+            new SparseArray<>();
+
+    private final SparseArray<AccessibilityWindowInfo> mTempWindowArray =
+            new SparseArray<>();
+
+    public AccessibilityCache(AccessibilityNodeRefresher nodeRefresher) {
+        mAccessibilityNodeRefresher = nodeRefresher;
+    }
+
+    /**
+     * Sets all {@link AccessibilityWindowInfo}s of all displays into the cache.
+     * The key of SparseArray is display ID.
+     *
+     * @param windowsOnAllDisplays The accessibility windows of all displays.
+     */
+    public void setWindowsOnAllDisplays(
+            SparseArray<List<AccessibilityWindowInfo>> windowsOnAllDisplays) {
+        synchronized (mLock) {
+            if (DEBUG) {
+                Log.i(LOG_TAG, "Set windows");
+            }
+            clearWindowCacheLocked();
+            if (windowsOnAllDisplays == null) {
+                return;
+            }
+
+            final int displayCounts = windowsOnAllDisplays.size();
+            for (int i = 0; i < displayCounts; i++) {
+                final List<AccessibilityWindowInfo> windowsOfDisplay =
+                        windowsOnAllDisplays.valueAt(i);
+
+                if (windowsOfDisplay == null) {
+                    continue;
+                }
+
+                final int displayId = windowsOnAllDisplays.keyAt(i);
+                final int windowCount = windowsOfDisplay.size();
+                for (int j = 0; j < windowCount; j++) {
+                    addWindowByDisplayLocked(displayId, windowsOfDisplay.get(j));
+                }
+            }
+            mIsAllWindowsCached = true;
+        }
+    }
+
+    /**
+     * Sets an {@link AccessibilityWindowInfo} into the cache.
+     *
+     * @param window The accessibility window.
+     */
+    public void addWindow(AccessibilityWindowInfo window) {
+        synchronized (mLock) {
+            if (DEBUG) {
+                Log.i(LOG_TAG, "Caching window: " + window.getId() + " at display Id [ "
+                        + window.getDisplayId() + " ]");
+            }
+            addWindowByDisplayLocked(window.getDisplayId(), window);
+        }
+    }
+
+    private void addWindowByDisplayLocked(int displayId, AccessibilityWindowInfo window) {
+        SparseArray<AccessibilityWindowInfo> windows = mWindowCacheByDisplay.get(displayId);
+        if (windows == null) {
+            windows = new SparseArray<>();
+            mWindowCacheByDisplay.put(displayId, windows);
+        }
+        final int windowId = window.getId();
+        windows.put(windowId, new AccessibilityWindowInfo(window));
+    }
+    /**
+     * Notifies the cache that the something in the UI changed. As a result
+     * the cache will either refresh some nodes or evict some nodes.
+     *
+     * Note: any event that ends up affecting the cache should also be present in
+     * {@link #CACHE_CRITICAL_EVENTS_MASK}
+     *
+     * @param event An event.
+     */
+    public void onAccessibilityEvent(AccessibilityEvent event) {
+        AccessibilityNodeInfo nodeToRefresh = null;
+        synchronized (mLock) {
+            if (DEBUG) {
+                Log.i(LOG_TAG, "onAccessibilityEvent(" + event + ")");
+            }
+            final int eventType = event.getEventType();
+            switch (eventType) {
+                case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
+                    if (mAccessibilityFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
+                        removeCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus);
+                    }
+                    mAccessibilityFocus = event.getSourceNodeId();
+                    mAccessibilityFocusedWindow = event.getWindowId();
+                    nodeToRefresh = removeCachedNodeLocked(mAccessibilityFocusedWindow,
+                            mAccessibilityFocus);
+                } break;
+
+                case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
+                    if (mAccessibilityFocus == event.getSourceNodeId()
+                            && mAccessibilityFocusedWindow == event.getWindowId()) {
+                        nodeToRefresh = removeCachedNodeLocked(mAccessibilityFocusedWindow,
+                                mAccessibilityFocus);
+                        mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
+                        mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+                    }
+                } break;
+
+                case AccessibilityEvent.TYPE_VIEW_FOCUSED: {
+                    if (mInputFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
+                        removeCachedNodeLocked(event.getWindowId(), mInputFocus);
+                    }
+                    mInputFocus = event.getSourceNodeId();
+                    nodeToRefresh = removeCachedNodeLocked(event.getWindowId(), mInputFocus);
+                } break;
+
+                case AccessibilityEvent.TYPE_VIEW_SELECTED:
+                case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
+                case AccessibilityEvent.TYPE_VIEW_CLICKED:
+                case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: {
+                    nodeToRefresh = removeCachedNodeLocked(event.getWindowId(),
+                            event.getSourceNodeId());
+                } break;
+
+                case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: {
+                    synchronized (mLock) {
+                        final int windowId = event.getWindowId();
+                        final long sourceId = event.getSourceNodeId();
+                        if ((event.getContentChangeTypes()
+                                & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) != 0) {
+                            clearSubTreeLocked(windowId, sourceId);
+                        } else {
+                            nodeToRefresh = removeCachedNodeLocked(windowId, sourceId);
+                        }
+                    }
+                } break;
+
+                case AccessibilityEvent.TYPE_VIEW_SCROLLED: {
+                    clearSubTreeLocked(event.getWindowId(), event.getSourceNodeId());
+                } break;
+
+                case AccessibilityEvent.TYPE_WINDOWS_CHANGED:
+                    if (event.getWindowChanges()
+                            == AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED) {
+                        // Don't need to clear all cache. Unless the changes are related to
+                        // content, we won't clear all cache here with clear().
+                        clearWindowCacheLocked();
+                        break;
+                    }
+                case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: {
+                    clear();
+                } break;
+            }
+        }
+
+        if (nodeToRefresh != null) {
+            if (DEBUG) {
+                Log.i(LOG_TAG, "Refreshing and re-adding cached node.");
+            }
+            if (mAccessibilityNodeRefresher.refreshNode(nodeToRefresh, true)) {
+                add(nodeToRefresh);
+            }
+        }
+        if (CHECK_INTEGRITY) {
+            checkIntegrity();
+        }
+    }
+
+    private AccessibilityNodeInfo removeCachedNodeLocked(int windowId, long sourceId) {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "Removing cached node.");
+        }
+        LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId);
+        if (nodes == null) {
+            return null;
+        }
+        AccessibilityNodeInfo cachedInfo = nodes.get(sourceId);
+        // If the source is not in the cache - nothing to do.
+        if (cachedInfo == null) {
+            return null;
+        }
+        nodes.remove(sourceId);
+        return cachedInfo;
+    }
+
+    /**
+     * Gets a cached {@link AccessibilityNodeInfo} given the id of the hosting
+     * window and the accessibility id of the node.
+     *
+     * @param windowId The id of the window hosting the node.
+     * @param accessibilityNodeId The info accessibility node id.
+     * @return The cached {@link AccessibilityNodeInfo} or null if such not found.
+     */
+    public AccessibilityNodeInfo getNode(int windowId, long accessibilityNodeId) {
+        synchronized(mLock) {
+            LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId);
+            if (nodes == null) {
+                return null;
+            }
+            AccessibilityNodeInfo info = nodes.get(accessibilityNodeId);
+            if (info != null) {
+                // Return a copy since the client calls to AccessibilityNodeInfo#recycle()
+                // will wipe the data of the cached info.
+                info = new AccessibilityNodeInfo(info);
+            }
+            if (VERBOSE) {
+                Log.i(LOG_TAG, "get(0x" + Long.toHexString(accessibilityNodeId) + ") = " + info);
+            }
+            return info;
+        }
+    }
+
+    /**
+     * Gets all {@link AccessibilityWindowInfo}s of all displays from the cache.
+     *
+     * @return All cached {@link AccessibilityWindowInfo}s of all displays
+     *         or null if such not found. The key of SparseArray is display ID.
+     */
+    public SparseArray<List<AccessibilityWindowInfo>> getWindowsOnAllDisplays() {
+        synchronized (mLock) {
+            if (!mIsAllWindowsCached) {
+                return null;
+            }
+            final SparseArray<List<AccessibilityWindowInfo>> returnWindows = new SparseArray<>();
+            final int displayCounts = mWindowCacheByDisplay.size();
+
+            if (displayCounts > 0) {
+                for (int i = 0; i < displayCounts; i++) {
+                    final int displayId = mWindowCacheByDisplay.keyAt(i);
+                    final SparseArray<AccessibilityWindowInfo> windowsOfDisplay =
+                            mWindowCacheByDisplay.valueAt(i);
+
+                    if (windowsOfDisplay == null) {
+                        continue;
+                    }
+
+                    final int windowCount = windowsOfDisplay.size();
+                    if (windowCount > 0) {
+                        // Careful to return the windows in a decreasing layer order.
+                        SparseArray<AccessibilityWindowInfo> sortedWindows = mTempWindowArray;
+                        sortedWindows.clear();
+
+                        for (int j = 0; j < windowCount; j++) {
+                            AccessibilityWindowInfo window = windowsOfDisplay.valueAt(j);
+                            sortedWindows.put(window.getLayer(), window);
+                        }
+
+                        // It's possible in transient conditions for two windows to share the same
+                        // layer, which results in sortedWindows being smaller than
+                        // mWindowCacheByDisplay
+                        final int sortedWindowCount = sortedWindows.size();
+                        List<AccessibilityWindowInfo> windows =
+                                new ArrayList<>(sortedWindowCount);
+                        for (int j = sortedWindowCount - 1; j >= 0; j--) {
+                            AccessibilityWindowInfo window = sortedWindows.valueAt(j);
+                            windows.add(new AccessibilityWindowInfo(window));
+                            sortedWindows.removeAt(j);
+                        }
+                        returnWindows.put(displayId, windows);
+                    }
+                }
+                return returnWindows;
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Gets an {@link AccessibilityWindowInfo} by windowId.
+     *
+     * @param windowId The id of the window.
+     *
+     * @return The {@link AccessibilityWindowInfo} or null if such not found.
+     */
+    public AccessibilityWindowInfo getWindow(int windowId) {
+        synchronized (mLock) {
+            final int displayCounts = mWindowCacheByDisplay.size();
+            for (int i = 0; i < displayCounts; i++) {
+                final SparseArray<AccessibilityWindowInfo> windowsOfDisplay =
+                        mWindowCacheByDisplay.valueAt(i);
+                if (windowsOfDisplay == null) {
+                    continue;
+                }
+
+                AccessibilityWindowInfo window = windowsOfDisplay.get(windowId);
+                if (window != null) {
+                    return new AccessibilityWindowInfo(window);
+                }
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Caches an {@link AccessibilityNodeInfo}.
+     *
+     * @param info The node to cache.
+     */
+    public void add(AccessibilityNodeInfo info) {
+        synchronized(mLock) {
+            if (VERBOSE) {
+                Log.i(LOG_TAG, "add(" + info + ")");
+            }
+
+            final int windowId = info.getWindowId();
+            LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId);
+            if (nodes == null) {
+                nodes = new LongSparseArray<>();
+                mNodeCache.put(windowId, nodes);
+            }
+
+            final long sourceId = info.getSourceNodeId();
+            AccessibilityNodeInfo oldInfo = nodes.get(sourceId);
+            if (oldInfo != null) {
+                // If the added node is in the cache we have to be careful if
+                // the new one represents a source state where some of the
+                // children have been removed to remove the descendants that
+                // are no longer present.
+                final LongArray newChildrenIds = info.getChildNodeIds();
+
+                final int oldChildCount = oldInfo.getChildCount();
+                for (int i = 0; i < oldChildCount; i++) {
+                    final long oldChildId = oldInfo.getChildId(i);
+                    // If the child is no longer present, remove the sub-tree.
+                    if (newChildrenIds == null || newChildrenIds.indexOf(oldChildId) < 0) {
+                        clearSubTreeLocked(windowId, oldChildId);
+                    }
+                    if (nodes.get(sourceId) == null) {
+                        // We've removed (and thus recycled) this node because it was its own
+                        // ancestor (the app gave us bad data), we can't continue using it.
+                        // Clear the cache for this window and give up on adding the node.
+                        clearNodesForWindowLocked(windowId);
+                        return;
+                    }
+                }
+
+                // Also be careful if the parent has changed since the new
+                // parent may be a predecessor of the old parent which will
+                // add cycles to the cache.
+                final long oldParentId = oldInfo.getParentNodeId();
+                if (info.getParentNodeId() != oldParentId) {
+                    clearSubTreeLocked(windowId, oldParentId);
+                }
+           }
+
+            // Cache a copy since the client calls to AccessibilityNodeInfo#recycle()
+            // will wipe the data of the cached info.
+            AccessibilityNodeInfo clone = new AccessibilityNodeInfo(info);
+            nodes.put(sourceId, clone);
+            if (clone.isAccessibilityFocused()) {
+                if (mAccessibilityFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID
+                        && mAccessibilityFocus != sourceId) {
+                    removeCachedNodeLocked(windowId, mAccessibilityFocus);
+                }
+                mAccessibilityFocus = sourceId;
+                mAccessibilityFocusedWindow = windowId;
+            } else if (mAccessibilityFocus == sourceId) {
+                mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
+                mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+            }
+            if (clone.isFocused()) {
+                mInputFocus = sourceId;
+            }
+        }
+    }
+
+    /**
+     * Clears the cache.
+     */
+    public void clear() {
+        synchronized(mLock) {
+            if (DEBUG) {
+                Log.i(LOG_TAG, "clear()");
+            }
+            clearWindowCacheLocked();
+            final int nodesForWindowCount = mNodeCache.size();
+            for (int i = nodesForWindowCount - 1; i >= 0; i--) {
+                final int windowId = mNodeCache.keyAt(i);
+                clearNodesForWindowLocked(windowId);
+            }
+
+            mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
+            mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
+
+            mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+        }
+    }
+
+    private void clearWindowCacheLocked() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "clearWindowCacheLocked");
+        }
+        final int displayCounts = mWindowCacheByDisplay.size();
+
+        if (displayCounts > 0) {
+            for (int i = displayCounts - 1; i >= 0; i--) {
+                final int displayId = mWindowCacheByDisplay.keyAt(i);
+                final SparseArray<AccessibilityWindowInfo> windows =
+                        mWindowCacheByDisplay.get(displayId);
+                if (windows != null) {
+                    windows.clear();
+                }
+                mWindowCacheByDisplay.remove(displayId);
+            }
+        }
+        mIsAllWindowsCached = false;
+    }
+
+    /**
+     * Clears nodes for the window with the given id
+     */
+    private void clearNodesForWindowLocked(int windowId) {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "clearNodesForWindowLocked(" + windowId + ")");
+        }
+        LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId);
+        if (nodes == null) {
+            return;
+        }
+        mNodeCache.remove(windowId);
+    }
+
+    /**
+     * Clears a subtree rooted at the node with the given id that is
+     * hosted in a given window.
+     *
+     * @param windowId The id of the hosting window.
+     * @param rootNodeId The root id.
+     */
+    private void clearSubTreeLocked(int windowId, long rootNodeId) {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "Clearing cached subtree.");
+        }
+        LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId);
+        if (nodes != null) {
+            clearSubTreeRecursiveLocked(nodes, rootNodeId);
+        }
+    }
+
+    /**
+     * Clears a subtree given a pointer to the root id and the nodes
+     * in the hosting window.
+     *
+     * @param nodes The nodes in the hosting window.
+     * @param rootNodeId The id of the root to evict.
+     *
+     * @return {@code true} if the cache was cleared
+     */
+    private boolean clearSubTreeRecursiveLocked(LongSparseArray<AccessibilityNodeInfo> nodes,
+            long rootNodeId) {
+        AccessibilityNodeInfo current = nodes.get(rootNodeId);
+        if (current == null) {
+            // The node isn't in the cache, but its descendents might be.
+            clear();
+            return true;
+        }
+        nodes.remove(rootNodeId);
+        final int childCount = current.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final long childNodeId = current.getChildId(i);
+            if (clearSubTreeRecursiveLocked(nodes, childNodeId)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Check the integrity of the cache which is nodes from different windows
+     * are not mixed, there is a single active window, there is a single focused
+     * window, for every window there are no duplicates nodes, all nodes for a
+     * window are connected, for every window there is a single input focused
+     * node, and for every window there is a single accessibility focused node.
+     */
+    public void checkIntegrity() {
+        synchronized (mLock) {
+            // Get the root.
+            if (mWindowCacheByDisplay.size() <= 0 && mNodeCache.size() == 0) {
+                return;
+            }
+
+            AccessibilityWindowInfo focusedWindow = null;
+            AccessibilityWindowInfo activeWindow = null;
+
+            final int displayCounts = mWindowCacheByDisplay.size();
+            for (int i = 0; i < displayCounts; i++) {
+                final SparseArray<AccessibilityWindowInfo> windowsOfDisplay =
+                        mWindowCacheByDisplay.valueAt(i);
+
+                if (windowsOfDisplay == null) {
+                    continue;
+                }
+
+                final int windowCount = windowsOfDisplay.size();
+                for (int j = 0; j < windowCount; j++) {
+                    final AccessibilityWindowInfo window = windowsOfDisplay.valueAt(j);
+
+                    // Check for one active window.
+                    if (window.isActive()) {
+                        if (activeWindow != null) {
+                            Log.e(LOG_TAG, "Duplicate active window:" + window);
+                        } else {
+                            activeWindow = window;
+                        }
+                    }
+                    // Check for one focused window.
+                    if (window.isFocused()) {
+                        if (focusedWindow != null) {
+                            Log.e(LOG_TAG, "Duplicate focused window:" + window);
+                        } else {
+                            focusedWindow = window;
+                        }
+                    }
+                }
+            }
+
+            // Traverse the tree and do some checks.
+            AccessibilityNodeInfo accessFocus = null;
+            AccessibilityNodeInfo inputFocus = null;
+
+            final int nodesForWindowCount = mNodeCache.size();
+            for (int i = 0; i < nodesForWindowCount; i++) {
+                LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.valueAt(i);
+                if (nodes.size() <= 0) {
+                    continue;
+                }
+
+                ArraySet<AccessibilityNodeInfo> seen = new ArraySet<>();
+                final int windowId = mNodeCache.keyAt(i);
+
+                final int nodeCount = nodes.size();
+                for (int j = 0; j < nodeCount; j++) {
+                    AccessibilityNodeInfo node = nodes.valueAt(j);
+
+                    // Check for duplicates
+                    if (!seen.add(node)) {
+                        Log.e(LOG_TAG, "Duplicate node: " + node
+                                + " in window:" + windowId);
+                        // Stop now as we potentially found a loop.
+                        continue;
+                    }
+
+                    // Check for one accessibility focus.
+                    if (node.isAccessibilityFocused()) {
+                        if (accessFocus != null) {
+                            Log.e(LOG_TAG, "Duplicate accessibility focus:" + node
+                                    + " in window:" + windowId);
+                        } else {
+                            accessFocus = node;
+                        }
+                    }
+
+                    // Check for one input focus.
+                    if (node.isFocused()) {
+                        if (inputFocus != null) {
+                            Log.e(LOG_TAG, "Duplicate input focus: " + node
+                                    + " in window:" + windowId);
+                        } else {
+                            inputFocus = node;
+                        }
+                    }
+
+                    // The node should be a child of its parent if we have the parent.
+                    AccessibilityNodeInfo nodeParent = nodes.get(node.getParentNodeId());
+                    if (nodeParent != null) {
+                        boolean childOfItsParent = false;
+                        final int childCount = nodeParent.getChildCount();
+                        for (int k = 0; k < childCount; k++) {
+                            AccessibilityNodeInfo child = nodes.get(nodeParent.getChildId(k));
+                            if (child == node) {
+                                childOfItsParent = true;
+                                break;
+                            }
+                        }
+                        if (!childOfItsParent) {
+                            Log.e(LOG_TAG, "Invalid parent-child relation between parent: "
+                                    + nodeParent + " and child: " + node);
+                        }
+                    }
+
+                    // The node should be the parent of its child if we have the child.
+                    final int childCount = node.getChildCount();
+                    for (int k = 0; k < childCount; k++) {
+                        AccessibilityNodeInfo child = nodes.get(node.getChildId(k));
+                        if (child != null) {
+                            AccessibilityNodeInfo parent = nodes.get(child.getParentNodeId());
+                            if (parent != node) {
+                                Log.e(LOG_TAG, "Invalid child-parent relation between child: "
+                                        + node + " and parent: " + nodeParent);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    // Layer of indirection included to break dependency chain for testing
+    public static class AccessibilityNodeRefresher {
+        /** Refresh the given AccessibilityNodeInfo object. */
+        public boolean refreshNode(AccessibilityNodeInfo info, boolean bypassCache) {
+            return info.refresh(null, bypassCache);
+        }
+
+        /** Refresh the given AccessibilityWindowInfo object. */
+        public boolean refreshWindow(AccessibilityWindowInfo info) {
+            return info.refresh();
+        }
+    }
+}
diff --git a/android/view/accessibility/AccessibilityEvent.java b/android/view/accessibility/AccessibilityEvent.java
new file mode 100644
index 0000000..f6d6fde
--- /dev/null
+++ b/android/view/accessibility/AccessibilityEvent.java
@@ -0,0 +1,1499 @@
+/*
+ * 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.view.accessibility;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pools.SynchronizedPool;
+
+import com.android.internal.util.BitUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <p>
+ * This class represents accessibility events that are sent by the system when
+ * something notable happens in the user interface. For example, when a
+ * {@link android.widget.Button} is clicked, a {@link android.view.View} is focused, etc.
+ * </p>
+ * <p>
+ * An accessibility event is fired by an individual view which populates the event with
+ * data for its state and requests from its parent to send the event to interested
+ * parties. The parent can optionally modify or even block the event based on its broader
+ * understanding of the user interface's context.
+ * </p>
+ * <p>
+ * The main purpose of an accessibility event is to communicate changes in the UI to an
+ * {@link android.accessibilityservice.AccessibilityService}. The service may then inspect,
+ * if needed the user interface by examining the View hierarchy, as represented by a tree of
+ * {@link AccessibilityNodeInfo}s (snapshot of a View state)
+ * which can be used for exploring the window content. Note that the privilege for accessing
+ * an event's source, thus the window content, has to be explicitly requested. For more
+ * details refer to {@link android.accessibilityservice.AccessibilityService}. If an
+ * accessibility service has not requested to retrieve the window content the event will
+ * not contain reference to its source. Also for events of type
+ * {@link #TYPE_NOTIFICATION_STATE_CHANGED} the source is never available.
+ * </p>
+ * <p>
+ * This class represents various semantically different accessibility event
+ * types. Each event type has an associated set of related properties. In other
+ * words, each event type is characterized via a subset of the properties exposed
+ * by this class. For each event type there is a corresponding constant defined
+ * in this class. Follows a specification of the event types and their associated properties:
+ * </p>
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about creating and processing AccessibilityEvents, read the
+ * <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a>
+ * developer guide.</p>
+ * </div>
+ * <p>
+ * <b>VIEW TYPES</b></br>
+ * </p>
+ * <p>
+ * <b>View clicked</b> - represents the event of clicking on a {@link android.view.View}
+ * like {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc.</br>
+ * <em>Type:</em>{@link #TYPE_VIEW_CLICKED}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ *   <li>{@link #getEventType()} - The type of the event.</li>
+ *   <li>{@link #getSource()} - The source info (for registered clients).</li>
+ *   <li>{@link #getClassName()} - The class name of the source.</li>
+ *   <li>{@link #getPackageName()} - The package name of the source.</li>
+ *   <li>{@link #getEventTime()}  - The event time.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * <b>View long clicked</b> - represents the event of long clicking on a {@link android.view.View}
+ * like {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc </br>
+ * <em>Type:</em>{@link #TYPE_VIEW_LONG_CLICKED}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ *   <li>{@link #getEventType()} - The type of the event.</li>
+ *   <li>{@link #getSource()} - The source info (for registered clients).</li>
+ *   <li>{@link #getClassName()} - The class name of the source.</li>
+ *   <li>{@link #getPackageName()} - The package name of the source.</li>
+ *   <li>{@link #getEventTime()}  - The event time.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * <b>View selected</b> - represents the event of selecting an item usually in
+ * the context of an {@link android.widget.AdapterView}.</br>
+ * <em>Type:</em> {@link #TYPE_VIEW_SELECTED}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ *   <li>{@link #getEventType()} - The type of the event.</li>
+ *   <li>{@link #getSource()} - The source info (for registered clients).</li>
+ *   <li>{@link #getClassName()} - The class name of the source.</li>
+ *   <li>{@link #getPackageName()} - The package name of the source.</li>
+ *   <li>{@link #getEventTime()}  - The event time.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * <b>View focused</b> - represents the event of focusing a
+ * {@link android.view.View}.</br>
+ * <em>Type:</em> {@link #TYPE_VIEW_FOCUSED}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ *   <li>{@link #getEventType()} - The type of the event.</li>
+ *   <li>{@link #getSource()} - The source info (for registered clients).</li>
+ *   <li>{@link #getClassName()} - The class name of the source.</li>
+ *   <li>{@link #getPackageName()} - The package name of the source.</li>
+ *   <li>{@link #getEventTime()}  - The event time.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * <b>View text changed</b> - represents the event of changing the text of an
+ * {@link android.widget.EditText}.</br>
+ * <em>Type:</em> {@link #TYPE_VIEW_TEXT_CHANGED}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ *   <li>{@link #getEventType()} - The type of the event.</li>
+ *   <li>{@link #getSource()} - The source info (for registered clients).</li>
+ *   <li>{@link #getClassName()} - The class name of the source.</li>
+ *   <li>{@link #getPackageName()} - The package name of the source.</li>
+ *   <li>{@link #getEventTime()}  - The event time.</li>
+ *   <li>{@link #getText()} - The new text of the source.</li>
+ *   <li>{@link #getBeforeText()} - The text of the source before the change.</li>
+ *   <li>{@link #getFromIndex()} - The text change start index.</li>
+ *   <li>{@link #getAddedCount()} - The number of added characters.</li>
+ *   <li>{@link #getRemovedCount()} - The number of removed characters.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * <b>View text selection changed</b> - represents the event of changing the text
+ * selection of an {@link android.widget.EditText}.</br>
+ * <em>Type:</em> {@link #TYPE_VIEW_TEXT_SELECTION_CHANGED} </br>
+ * <em>Properties:</em></br>
+ * <ul>
+ *   <li>{@link #getEventType()} - The type of the event.</li>
+ *   <li>{@link #getSource()} - The source info (for registered clients).</li>
+ *   <li>{@link #getClassName()} - The class name of the source.</li>
+ *   <li>{@link #getPackageName()} - The package name of the source.</li>
+ *   <li>{@link #getEventTime()}  - The event time.</li>
+ * </ul>
+ * </p>
+ * <b>View text traversed at movement granularity</b> - represents the event of traversing the
+ * text of a view at a given granularity. For example, moving to the next word.</br>
+ * <em>Type:</em> {@link #TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY} </br>
+ * <em>Properties:</em></br>
+ * <ul>
+ *   <li>{@link #getEventType()} - The type of the event.</li>
+ *   <li>{@link #getSource()} - The source info (for registered clients).</li>
+ *   <li>{@link #getClassName()} - The class name of the source.</li>
+ *   <li>{@link #getPackageName()} - The package name of the source.</li>
+ *   <li>{@link #getEventTime()}  - The event time.</li>
+ *   <li>{@link #getMovementGranularity()} - Sets the granularity at which a view's text
+ *       was traversed.</li>
+ *   <li>{@link #getText()} -  The text of the source's sub-tree.</li>
+ *   <li>{@link #getFromIndex()} - The start the text that was skipped over in this movement.
+ *       This is the starting point when moving forward through the text, but not when moving
+ *       back.</li>
+ *   <li>{@link #getToIndex()} - The end of the text that was skipped over in this movement.
+ *       This is the ending point when moving forward through the text, but not when moving
+ *       back.</li>
+ *   <li>{@link #getAction()} - Gets traversal action which specifies the direction.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * <b>View scrolled</b> - represents the event of scrolling a view. </br>
+ * <em>Type:</em> {@link #TYPE_VIEW_SCROLLED}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ *   <li>{@link #getEventType()} - The type of the event.</li>
+ *   <li>{@link #getSource()} - The source info (for registered clients).</li>
+ *   <li>{@link #getClassName()} - The class name of the source.</li>
+ *   <li>{@link #getPackageName()} - The package name of the source.</li>
+ *   <li>{@link #getEventTime()}  - The event time.</li>
+ *   <li>{@link #getScrollDeltaX()} - The difference in the horizontal position.</li>
+ *   <li>{@link #getScrollDeltaY()} - The difference in the vertical position.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * <b>TRANSITION TYPES</b></br>
+ * </p>
+ * <p>
+ * <b>Window state changed</b> - represents the event of a change to a section of
+ * the user interface that is visually distinct. Should be sent from either the
+ * root view of a window or from a view that is marked as a pane
+ * {@link android.view.View#setAccessibilityPaneTitle(CharSequence)}. Note that changes
+ * to true windows are represented by {@link #TYPE_WINDOWS_CHANGED}.</br>
+ * <em>Type:</em> {@link #TYPE_WINDOW_STATE_CHANGED}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ *   <li>{@link #getEventType()} - The type of the event.</li>
+ *   <li>{@link #getContentChangeTypes()} - The type of state changes.</li>
+ *   <li>{@link #getSource()} - The source info (for registered clients).</li>
+ *   <li>{@link #getClassName()} - The class name of the source.</li>
+ *   <li>{@link #getPackageName()} - The package name of the source.</li>
+ *   <li>{@link #getEventTime()}  - The event time.</li>
+ *   <li>{@link #getText()} - The text of the source's sub-tree, including the pane titles.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * <b>Window content changed</b> - represents the event of change in the
+ * content of a window. This change can be adding/removing view, changing
+ * a view size, etc.</br>
+ * </p>
+ * <p>
+ * <em>Type:</em> {@link #TYPE_WINDOW_CONTENT_CHANGED}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ *   <li>{@link #getEventType()} - The type of the event.</li>
+ *   <li>{@link #getContentChangeTypes()} - The type of content changes.</li>
+ *   <li>{@link #getSource()} - The source info (for registered clients).</li>
+ *   <li>{@link #getClassName()} - The class name of the source.</li>
+ *   <li>{@link #getPackageName()} - The package name of the source.</li>
+ *   <li>{@link #getEventTime()}  - The event time.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * <b>Windows changed</b> - represents a change in the windows shown on
+ * the screen such as a window appeared, a window disappeared, a window size changed,
+ * a window layer changed, etc. These events should only come from the system, which is responsible
+ * for managing windows. The list of windows is available from
+ * {@link android.accessibilityservice.AccessibilityService#getWindows()}.
+ * For regions of the user interface that are presented as windows but are
+ * controlled by an app's process, use {@link #TYPE_WINDOW_STATE_CHANGED}.</br>
+ * <em>Type:</em> {@link #TYPE_WINDOWS_CHANGED}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ *   <li>{@link #getEventType()} - The type of the event.</li>
+ *   <li>{@link #getEventTime()} - The event time.</li>
+ *   <li>{@link #getWindowChanges()}</li> - The specific change to the source window
+ * </ul>
+ * <em>Note:</em> You can retrieve the {@link AccessibilityWindowInfo} for the window
+ * source of the event by looking through the list returned by
+ * {@link android.accessibilityservice.AccessibilityService#getWindows()} for the window whose ID
+ * matches {@link #getWindowId()}.
+ * </p>
+ * <p>
+ * <b>NOTIFICATION TYPES</b></br>
+ * </p>
+ * <p>
+ * <b>Notification state changed</b> - represents the event showing a transient piece of information
+ * to the user. This information may be a {@link android.app.Notification} or
+ * {@link android.widget.Toast}.</br>
+ * <em>Type:</em> {@link #TYPE_NOTIFICATION_STATE_CHANGED}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ *   <li>{@link #getEventType()} - The type of the event.</li>
+ *   <li>{@link #getClassName()} - The class name of the source.</li>
+ *   <li>{@link #getPackageName()} - The package name of the source.</li>
+ *   <li>{@link #getEventTime()}  - The event time.</li>
+ *   <li>{@link #getParcelableData()} - The posted {@link android.app.Notification}, if
+ *   applicable.</li>
+ *   <li>{@link #getText()} - Displayed text of the {@link android.widget.Toast}, if applicable,
+ *   or may contain text from the {@link android.app.Notification}, although
+ *   {@link #getParcelableData()} is a richer set of data for {@link android.app.Notification}.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * <b>EXPLORATION TYPES</b></br>
+ * </p>
+ * <p>
+ * <b>View hover enter</b> - represents the event of beginning to hover
+ * over a {@link android.view.View}. The hover may be generated via
+ * exploring the screen by touch or via a pointing device.</br>
+ * <em>Type:</em> {@link #TYPE_VIEW_HOVER_ENTER}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ *   <li>{@link #getEventType()} - The type of the event.</li>
+ *   <li>{@link #getSource()} - The source info (for registered clients).</li>
+ *   <li>{@link #getClassName()} - The class name of the source.</li>
+ *   <li>{@link #getPackageName()} - The package name of the source.</li>
+ *   <li>{@link #getEventTime()}  - The event time.</li>
+ * </ul>
+ * </p>
+ * <b>View hover exit</b> - represents the event of stopping to hover
+ * over a {@link android.view.View}. The hover may be generated via
+ * exploring the screen by touch or via a pointing device.</br>
+ * <em>Type:</em> {@link #TYPE_VIEW_HOVER_EXIT}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ *   <li>{@link #getEventType()} - The type of the event.</li>
+ *   <li>{@link #getSource()} - The source info (for registered clients).</li>
+ *   <li>{@link #getClassName()} - The class name of the source.</li>
+ *   <li>{@link #getPackageName()} - The package name of the source.</li>
+ *   <li>{@link #getEventTime()}  - The event time.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * <b>Touch interaction start</b> - represents the event of starting a touch
+ * interaction, which is the user starts touching the screen.</br>
+ * <em>Type:</em> {@link #TYPE_TOUCH_INTERACTION_START}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ *   <li>{@link #getEventType()} - The type of the event.</li>
+ * </ul>
+ * <em>Note:</em> This event is fired only by the system and is not passed to the
+ * view tree to be populated.</br>
+ * </p>
+ * <p>
+ * <b>Touch interaction end</b> - represents the event of ending a touch
+ * interaction, which is the user stops touching the screen.</br>
+ * <em>Type:</em> {@link #TYPE_TOUCH_INTERACTION_END}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ *   <li>{@link #getEventType()} - The type of the event.</li>
+ * </ul>
+ * <em>Note:</em> This event is fired only by the system and is not passed to the
+ * view tree to be populated.</br>
+ * </p>
+ * <p>
+ * <b>Touch exploration gesture start</b> - represents the event of starting a touch
+ * exploring gesture.</br>
+ * <em>Type:</em> {@link #TYPE_TOUCH_EXPLORATION_GESTURE_START}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ *   <li>{@link #getEventType()} - The type of the event.</li>
+ * </ul>
+ * <em>Note:</em> This event is fired only by the system and is not passed to the
+ * view tree to be populated.</br>
+ * </p>
+ * <p>
+ * <b>Touch exploration gesture end</b> - represents the event of ending a touch
+ * exploring gesture.</br>
+ * <em>Type:</em> {@link #TYPE_TOUCH_EXPLORATION_GESTURE_END}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ *   <li>{@link #getEventType()} - The type of the event.</li>
+ * </ul>
+ * <em>Note:</em> This event is fired only by the system and is not passed to the
+ * view tree to be populated.</br>
+ * </p>
+ * <p>
+ * <b>Touch gesture detection start</b> - represents the event of starting a user
+ * gesture detection.</br>
+ * <em>Type:</em> {@link #TYPE_GESTURE_DETECTION_START}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ *   <li>{@link #getEventType()} - The type of the event.</li>
+ * </ul>
+ * <em>Note:</em> This event is fired only by the system and is not passed to the
+ * view tree to be populated.</br>
+ * </p>
+ * <p>
+ * <b>Touch gesture detection end</b> - represents the event of ending a user
+ * gesture detection.</br>
+ * <em>Type:</em> {@link #TYPE_GESTURE_DETECTION_END}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ *   <li>{@link #getEventType()} - The type of the event.</li>
+ * </ul>
+ * <em>Note:</em> This event is fired only by the system and is not passed to the
+ * view tree to be populated.</br>
+ * </p>
+ * <p>
+ * <b>MISCELLANEOUS TYPES</b></br>
+ * </p>
+ * <p>
+ * <b>Announcement</b> - represents the event of an application requesting a screen reader to make
+ * an announcement. Because the event carries no semantic meaning, this event is appropriate only
+ * in exceptional situations where additional screen reader output is needed but other types of
+ * accessibility services do not need to be aware of the change.</br>
+ * <em>Type:</em> {@link #TYPE_ANNOUNCEMENT}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ *   <li>{@link #getEventType()} - The type of the event.</li>
+ *   <li>{@link #getSource()} - The source info (for registered clients).</li>
+ *   <li>{@link #getClassName()} - The class name of the source.</li>
+ *   <li>{@link #getPackageName()} - The package name of the source.</li>
+ *   <li>{@link #getEventTime()}  - The event time.</li>
+ *   <li>{@link #getText()} - The text of the announcement.</li>
+ * </ul>
+ * </p>
+ *
+ * @see android.view.accessibility.AccessibilityManager
+ * @see android.accessibilityservice.AccessibilityService
+ * @see AccessibilityNodeInfo
+ */
+public final class AccessibilityEvent extends AccessibilityRecord implements Parcelable {
+    private static final String LOG_TAG = "AccessibilityEvent";
+
+    private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG) && Build.IS_DEBUGGABLE;
+
+    /** @hide */
+    public static final boolean DEBUG_ORIGIN = false;
+
+    /**
+     * Invalid selection/focus position.
+     *
+     * @see #getCurrentItemIndex()
+     */
+    public static final int INVALID_POSITION = -1;
+
+    /**
+     * Maximum length of the text fields.
+     *
+     * @see #getBeforeText()
+     * @see #getText()
+     * </br>
+     * Note: This constant is no longer needed since there
+     *       is no limit on the length of text that is contained
+     *       in an accessibility event anymore.
+     */
+    @Deprecated
+    public static final int MAX_TEXT_LENGTH = 500;
+
+    /**
+     * Represents the event of clicking on a {@link android.view.View} like
+     * {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc.
+     */
+    public static final int TYPE_VIEW_CLICKED = 0x00000001;
+
+    /**
+     * Represents the event of long clicking on a {@link android.view.View} like
+     * {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc.
+     */
+    public static final int TYPE_VIEW_LONG_CLICKED = 0x00000002;
+
+    /**
+     * Represents the event of selecting an item usually in the context of an
+     * {@link android.widget.AdapterView}.
+     */
+    public static final int TYPE_VIEW_SELECTED = 0x00000004;
+
+    /**
+     * Represents the event of setting input focus of a {@link android.view.View}.
+     */
+    public static final int TYPE_VIEW_FOCUSED = 0x00000008;
+
+    /**
+     * Represents the event of changing the text of an {@link android.widget.EditText}.
+     */
+    public static final int TYPE_VIEW_TEXT_CHANGED = 0x00000010;
+
+    /**
+     * Represents the event of a change to a visually distinct section of the user interface.
+     * These events should only be dispatched from {@link android.view.View}s that have
+     * accessibility pane titles, and replaces {@link #TYPE_WINDOW_CONTENT_CHANGED} for those
+     * sources. Details about the change are available from {@link #getContentChangeTypes()}.
+     */
+    public static final int TYPE_WINDOW_STATE_CHANGED = 0x00000020;
+
+    /**
+     * Represents the event showing a {@link android.app.Notification}.
+     */
+    public static final int TYPE_NOTIFICATION_STATE_CHANGED = 0x00000040;
+
+    /**
+     * Represents the event of a hover enter over a {@link android.view.View}.
+     */
+    public static final int TYPE_VIEW_HOVER_ENTER = 0x00000080;
+
+    /**
+     * Represents the event of a hover exit over a {@link android.view.View}.
+     */
+    public static final int TYPE_VIEW_HOVER_EXIT = 0x00000100;
+
+    /**
+     * Represents the event of starting a touch exploration gesture.
+     */
+    public static final int TYPE_TOUCH_EXPLORATION_GESTURE_START = 0x00000200;
+
+    /**
+     * Represents the event of ending a touch exploration gesture.
+     */
+    public static final int TYPE_TOUCH_EXPLORATION_GESTURE_END = 0x00000400;
+
+    /**
+     * Represents the event of changing the content of a window and more
+     * specifically the sub-tree rooted at the event's source.
+     */
+    public static final int TYPE_WINDOW_CONTENT_CHANGED = 0x00000800;
+
+    /**
+     * Represents the event of scrolling a view. This event type is generally not sent directly.
+     * @see android.view.View#onScrollChanged(int, int, int, int)
+     */
+    public static final int TYPE_VIEW_SCROLLED = 0x00001000;
+
+    /**
+     * Represents the event of changing the selection in an {@link android.widget.EditText}.
+     */
+    public static final int TYPE_VIEW_TEXT_SELECTION_CHANGED = 0x00002000;
+
+    /**
+     * Represents the event of an application making an announcement.
+     */
+    public static final int TYPE_ANNOUNCEMENT = 0x00004000;
+
+    /**
+     * Represents the event of gaining accessibility focus.
+     */
+    public static final int TYPE_VIEW_ACCESSIBILITY_FOCUSED = 0x00008000;
+
+    /**
+     * Represents the event of clearing accessibility focus.
+     */
+    public static final int TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED = 0x00010000;
+
+    /**
+     * Represents the event of traversing the text of a view at a given movement granularity.
+     */
+    public static final int TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY = 0x00020000;
+
+    /**
+     * Represents the event of beginning gesture detection.
+     */
+    public static final int TYPE_GESTURE_DETECTION_START = 0x00040000;
+
+    /**
+     * Represents the event of ending gesture detection.
+     */
+    public static final int TYPE_GESTURE_DETECTION_END = 0x00080000;
+
+    /**
+     * Represents the event of the user starting to touch the screen.
+     */
+    public static final int TYPE_TOUCH_INTERACTION_START = 0x00100000;
+
+    /**
+     * Represents the event of the user ending to touch the screen.
+     */
+    public static final int TYPE_TOUCH_INTERACTION_END = 0x00200000;
+
+    /**
+     * Represents the event change in the system windows shown on the screen. This event type should
+     * only be dispatched by the system.
+     */
+    public static final int TYPE_WINDOWS_CHANGED = 0x00400000;
+
+    /**
+     * Represents the event of a context click on a {@link android.view.View}.
+     */
+    public static final int TYPE_VIEW_CONTEXT_CLICKED = 0x00800000;
+
+    /**
+     * Represents the event of the assistant currently reading the users screen context.
+     */
+    public static final int TYPE_ASSIST_READING_CONTEXT = 0x01000000;
+
+    /**
+     * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
+     * The type of change is not defined.
+     */
+    public static final int CONTENT_CHANGE_TYPE_UNDEFINED = 0x00000000;
+
+    /**
+     * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
+     * One or more content changes occurred in the the subtree rooted at the source node,
+     * or the subtree's structure changed when a node was added or removed.
+     */
+    public static final int CONTENT_CHANGE_TYPE_SUBTREE = 0x00000001;
+
+    /**
+     * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
+     * The node's text changed.
+     */
+    public static final int CONTENT_CHANGE_TYPE_TEXT = 0x00000002;
+
+    /**
+     * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
+     * The node's content description changed.
+     */
+    public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000004;
+
+    /**
+     * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event:
+     * The node's pane title changed.
+     */
+    public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 0x00000008;
+
+    /**
+     * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event:
+     * The node has a pane title, and either just appeared or just was assigned a title when it
+     * had none before.
+     */
+    public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 0x00000010;
+
+    /**
+     * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event:
+     * Can mean one of two slightly different things. The primary meaning is that the node has
+     * a pane title, and was removed from the node hierarchy. It will also be sent if the pane
+     * title is set to {@code null} after it contained a title.
+     * No source will be returned if the node is no longer on the screen. To make the change more
+     * clear for the user, the first entry in {@link #getText()} will return the value that would
+     * have been returned by {@code getSource().getPaneTitle()}.
+     */
+    public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 0x00000020;
+
+    /**
+     * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
+     * state description of the node as returned by
+     * {@link AccessibilityNodeInfo#getStateDescription} changed. If part of the state description
+     * changes, the changed part can be put into event text. For example, if state description
+     * changed from "on, wifi signal full" to "on, wifi three bars", "wifi three bars" can be put
+     * into the event text.
+     */
+    public static final int CONTENT_CHANGE_TYPE_STATE_DESCRIPTION = 0x00000040;
+
+    /**
+     * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+     * The window was added.
+     */
+    public static final int WINDOWS_CHANGE_ADDED = 0x00000001;
+
+    /**
+     * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+     * A window was removed.
+     */
+    public static final int WINDOWS_CHANGE_REMOVED = 0x00000002;
+
+    /**
+     * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+     * The window's title changed.
+     */
+    public static final int WINDOWS_CHANGE_TITLE = 0x00000004;
+
+    /**
+     * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+     * The window's bounds changed.
+     * <p>
+     * Starting in {@link android.os.Build.VERSION_CODES#R R}, this event implies the window's
+     * region changed. It's also possible that region changed but bounds doesn't.
+     * </p>
+     */
+    public static final int WINDOWS_CHANGE_BOUNDS = 0x00000008;
+
+    /**
+     * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+     * The window's layer changed.
+     */
+    public static final int WINDOWS_CHANGE_LAYER = 0x00000010;
+
+    /**
+     * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+     * The window's {@link AccessibilityWindowInfo#isActive()} changed.
+     */
+    public static final int WINDOWS_CHANGE_ACTIVE = 0x00000020;
+
+    /**
+     * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+     * The window's {@link AccessibilityWindowInfo#isFocused()} changed.
+     */
+    public static final int WINDOWS_CHANGE_FOCUSED = 0x00000040;
+
+    /**
+     * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+     * The window's {@link AccessibilityWindowInfo#isAccessibilityFocused()} changed.
+     */
+    public static final int WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED = 0x00000080;
+
+    /**
+     * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+     * The window's parent changed.
+     */
+    public static final int WINDOWS_CHANGE_PARENT = 0x00000100;
+
+    /**
+     * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+     * The window's children changed.
+     */
+    public static final int WINDOWS_CHANGE_CHILDREN = 0x00000200;
+
+    /**
+     * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+     * The window either entered or exited picture-in-picture mode.
+     */
+    public static final int WINDOWS_CHANGE_PIP = 0x00000400;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "WINDOWS_CHANGE_" }, value = {
+            WINDOWS_CHANGE_ADDED,
+            WINDOWS_CHANGE_REMOVED,
+            WINDOWS_CHANGE_TITLE,
+            WINDOWS_CHANGE_BOUNDS,
+            WINDOWS_CHANGE_LAYER,
+            WINDOWS_CHANGE_ACTIVE,
+            WINDOWS_CHANGE_FOCUSED,
+            WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED,
+            WINDOWS_CHANGE_PARENT,
+            WINDOWS_CHANGE_CHILDREN,
+            WINDOWS_CHANGE_PIP
+    })
+    public @interface WindowsChangeTypes {}
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "CONTENT_CHANGE_TYPE_" },
+            value = {
+                    CONTENT_CHANGE_TYPE_UNDEFINED,
+                    CONTENT_CHANGE_TYPE_SUBTREE,
+                    CONTENT_CHANGE_TYPE_TEXT,
+                    CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION,
+                    CONTENT_CHANGE_TYPE_STATE_DESCRIPTION,
+                    CONTENT_CHANGE_TYPE_PANE_TITLE,
+                    CONTENT_CHANGE_TYPE_PANE_APPEARED,
+                    CONTENT_CHANGE_TYPE_PANE_DISAPPEARED
+            })
+    public @interface ContentChangeTypes {}
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "TYPE_" }, value = {
+            TYPE_VIEW_CLICKED,
+            TYPE_VIEW_LONG_CLICKED,
+            TYPE_VIEW_SELECTED,
+            TYPE_VIEW_FOCUSED,
+            TYPE_VIEW_TEXT_CHANGED,
+            TYPE_WINDOW_STATE_CHANGED,
+            TYPE_NOTIFICATION_STATE_CHANGED,
+            TYPE_VIEW_HOVER_ENTER,
+            TYPE_VIEW_HOVER_EXIT,
+            TYPE_TOUCH_EXPLORATION_GESTURE_START,
+            TYPE_TOUCH_EXPLORATION_GESTURE_END,
+            TYPE_WINDOW_CONTENT_CHANGED,
+            TYPE_VIEW_SCROLLED,
+            TYPE_VIEW_TEXT_SELECTION_CHANGED,
+            TYPE_ANNOUNCEMENT,
+            TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+            TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED,
+            TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
+            TYPE_GESTURE_DETECTION_START,
+            TYPE_GESTURE_DETECTION_END,
+            TYPE_TOUCH_INTERACTION_START,
+            TYPE_TOUCH_INTERACTION_END,
+            TYPE_WINDOWS_CHANGED,
+            TYPE_VIEW_CONTEXT_CLICKED,
+            TYPE_ASSIST_READING_CONTEXT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface EventType {}
+
+    /**
+     * Mask for {@link AccessibilityEvent} all types.
+     *
+     * @see #TYPE_VIEW_CLICKED
+     * @see #TYPE_VIEW_LONG_CLICKED
+     * @see #TYPE_VIEW_SELECTED
+     * @see #TYPE_VIEW_FOCUSED
+     * @see #TYPE_VIEW_TEXT_CHANGED
+     * @see #TYPE_WINDOW_STATE_CHANGED
+     * @see #TYPE_NOTIFICATION_STATE_CHANGED
+     * @see #TYPE_VIEW_HOVER_ENTER
+     * @see #TYPE_VIEW_HOVER_EXIT
+     * @see #TYPE_TOUCH_EXPLORATION_GESTURE_START
+     * @see #TYPE_TOUCH_EXPLORATION_GESTURE_END
+     * @see #TYPE_WINDOW_CONTENT_CHANGED
+     * @see #TYPE_VIEW_SCROLLED
+     * @see #TYPE_VIEW_TEXT_SELECTION_CHANGED
+     * @see #TYPE_ANNOUNCEMENT
+     * @see #TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+     * @see #TYPE_GESTURE_DETECTION_START
+     * @see #TYPE_GESTURE_DETECTION_END
+     * @see #TYPE_TOUCH_INTERACTION_START
+     * @see #TYPE_TOUCH_INTERACTION_END
+     * @see #TYPE_WINDOWS_CHANGED
+     * @see #TYPE_VIEW_CONTEXT_CLICKED
+     */
+    public static final int TYPES_ALL_MASK = 0xFFFFFFFF;
+
+    private static final int MAX_POOL_SIZE = 10;
+    private static final SynchronizedPool<AccessibilityEvent> sPool =
+            new SynchronizedPool<>(MAX_POOL_SIZE);
+
+    @UnsupportedAppUsage
+    private @EventType int mEventType;
+    private CharSequence mPackageName;
+    private long mEventTime;
+    int mMovementGranularity;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    int mAction;
+    int mContentChangeTypes;
+    int mWindowChangeTypes;
+
+    /**
+     * The stack trace describing where this event originated from on the app side.
+     * Only populated if {@link #DEBUG_ORIGIN} is enabled
+     * Can be inspected(e.g. printed) from an
+     * {@link android.accessibilityservice.AccessibilityService} to trace where particular events
+     * are being dispatched from.
+     *
+     * @hide
+     */
+    public StackTraceElement[] originStackTrace = null;
+
+    private ArrayList<AccessibilityRecord> mRecords;
+
+    /**
+     * Creates a new {@link AccessibilityEvent}.
+     */
+    public AccessibilityEvent() {
+        if (DEBUG_ORIGIN) originStackTrace = Thread.currentThread().getStackTrace();
+    }
+
+
+    /**
+     * Creates a new {@link AccessibilityEvent} with the given <code>eventType</code>.
+     *
+     * @param eventType The event type.
+     */
+    public AccessibilityEvent(int eventType) {
+        mEventType = eventType;
+        if (DEBUG_ORIGIN) originStackTrace = Thread.currentThread().getStackTrace();
+    }
+
+    /**
+     * Copy constructor. Creates a new {@link AccessibilityEvent}, and this instance is initialized
+     * from the given <code>event</code>.
+     *
+     * @param event The other event.
+     */
+    public AccessibilityEvent(@NonNull AccessibilityEvent event) {
+        init(event);
+    }
+
+    /**
+     * Initialize an event from another one.
+     *
+     * @param event The event to initialize from.
+     */
+    void init(AccessibilityEvent event) {
+        super.init(event);
+        mEventType = event.mEventType;
+        mMovementGranularity = event.mMovementGranularity;
+        mAction = event.mAction;
+        mContentChangeTypes = event.mContentChangeTypes;
+        mWindowChangeTypes = event.mWindowChangeTypes;
+        mEventTime = event.mEventTime;
+        mPackageName = event.mPackageName;
+        if (event.mRecords != null) {
+            final int recordCount = event.mRecords.size();
+            mRecords = new ArrayList<>(recordCount);
+            for (int i = 0; i < recordCount; i++) {
+                final AccessibilityRecord record = event.mRecords.get(i);
+                final AccessibilityRecord recordClone = new AccessibilityRecord(record);
+                mRecords.add(recordClone);
+            }
+        }
+        if (DEBUG_ORIGIN) originStackTrace = event.originStackTrace;
+    }
+
+    /**
+     * Sets if this instance is sealed.
+     *
+     * @param sealed Whether is sealed.
+     *
+     * @hide
+     */
+    @Override
+    public void setSealed(boolean sealed) {
+        super.setSealed(sealed);
+        final List<AccessibilityRecord> records = mRecords;
+        if (records != null) {
+            final int recordCount = records.size();
+            for (int i = 0; i < recordCount; i++) {
+                AccessibilityRecord record = records.get(i);
+                record.setSealed(sealed);
+            }
+        }
+    }
+
+    /**
+     * Gets the number of records contained in the event.
+     *
+     * @return The number of records.
+     */
+    public int getRecordCount() {
+        return mRecords == null ? 0 : mRecords.size();
+    }
+
+    /**
+     * Appends an {@link AccessibilityRecord} to the end of event records.
+     *
+     * @param record The record to append.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void appendRecord(AccessibilityRecord record) {
+        enforceNotSealed();
+        if (mRecords == null) {
+            mRecords = new ArrayList<AccessibilityRecord>();
+        }
+        mRecords.add(record);
+    }
+
+    /**
+     * Gets the record at a given index.
+     *
+     * @param index The index.
+     * @return The record at the specified index.
+     */
+    public AccessibilityRecord getRecord(int index) {
+        if (mRecords == null) {
+            throw new IndexOutOfBoundsException("Invalid index " + index + ", size is 0");
+        }
+        return mRecords.get(index);
+    }
+
+    /**
+     * Gets the event type.
+     *
+     * @return The event type.
+     */
+    public @EventType int getEventType() {
+        return mEventType;
+    }
+
+    /**
+     * Gets the bit mask of change types signaled by a
+     * {@link #TYPE_WINDOW_CONTENT_CHANGED} event or {@link #TYPE_WINDOW_STATE_CHANGED}. A single
+     * event may represent multiple change types.
+     *
+     * @return The bit mask of change types. One or more of:
+     *         <ul>
+     *         <li>{@link #CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION}
+     *         <li>{@link #CONTENT_CHANGE_TYPE_STATE_DESCRIPTION}
+     *         <li>{@link #CONTENT_CHANGE_TYPE_SUBTREE}
+     *         <li>{@link #CONTENT_CHANGE_TYPE_TEXT}
+     *         <li>{@link #CONTENT_CHANGE_TYPE_PANE_TITLE}
+     *         <li>{@link #CONTENT_CHANGE_TYPE_UNDEFINED}
+     *         <li>{@link #CONTENT_CHANGE_TYPE_PANE_APPEARED}
+     *         <li>{@link #CONTENT_CHANGE_TYPE_PANE_DISAPPEARED}
+     *         </ul>
+     */
+    @ContentChangeTypes
+    public int getContentChangeTypes() {
+        return mContentChangeTypes;
+    }
+
+    private static String contentChangeTypesToString(int types) {
+        return BitUtils.flagsToString(types, AccessibilityEvent::singleContentChangeTypeToString);
+    }
+
+    private static String singleContentChangeTypeToString(int type) {
+        switch (type) {
+            case CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION:
+                return "CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION";
+            case CONTENT_CHANGE_TYPE_STATE_DESCRIPTION:
+                return "CONTENT_CHANGE_TYPE_STATE_DESCRIPTION";
+            case CONTENT_CHANGE_TYPE_SUBTREE: return "CONTENT_CHANGE_TYPE_SUBTREE";
+            case CONTENT_CHANGE_TYPE_TEXT: return "CONTENT_CHANGE_TYPE_TEXT";
+            case CONTENT_CHANGE_TYPE_PANE_TITLE: return "CONTENT_CHANGE_TYPE_PANE_TITLE";
+            case CONTENT_CHANGE_TYPE_UNDEFINED: return "CONTENT_CHANGE_TYPE_UNDEFINED";
+            case CONTENT_CHANGE_TYPE_PANE_APPEARED: return "CONTENT_CHANGE_TYPE_PANE_APPEARED";
+            case CONTENT_CHANGE_TYPE_PANE_DISAPPEARED:
+                return "CONTENT_CHANGE_TYPE_PANE_DISAPPEARED";
+            default: return Integer.toHexString(type);
+        }
+    }
+
+    /**
+     * Sets the bit mask of node tree changes signaled by an
+     * {@link #TYPE_WINDOW_CONTENT_CHANGED} event.
+     *
+     * @param changeTypes The bit mask of change types.
+     * @throws IllegalStateException If called from an AccessibilityService.
+     * @see #getContentChangeTypes()
+     */
+    public void setContentChangeTypes(@ContentChangeTypes int changeTypes) {
+        enforceNotSealed();
+        mContentChangeTypes = changeTypes;
+    }
+
+    /**
+     * Get the bit mask of change types signaled by a {@link #TYPE_WINDOWS_CHANGED} event. A
+     * single event may represent multiple change types.
+     *
+     * @return The bit mask of change types.
+     */
+    @WindowsChangeTypes
+    public int getWindowChanges() {
+        return mWindowChangeTypes;
+    }
+
+    /** @hide  */
+    public void setWindowChanges(@WindowsChangeTypes int changes) {
+        mWindowChangeTypes = changes;
+    }
+
+    private static String windowChangeTypesToString(@WindowsChangeTypes int types) {
+        return BitUtils.flagsToString(types, AccessibilityEvent::singleWindowChangeTypeToString);
+    }
+
+    private static String singleWindowChangeTypeToString(int type) {
+        switch (type) {
+            case WINDOWS_CHANGE_ADDED: return "WINDOWS_CHANGE_ADDED";
+            case WINDOWS_CHANGE_REMOVED: return "WINDOWS_CHANGE_REMOVED";
+            case WINDOWS_CHANGE_TITLE: return "WINDOWS_CHANGE_TITLE";
+            case WINDOWS_CHANGE_BOUNDS: return "WINDOWS_CHANGE_BOUNDS";
+            case WINDOWS_CHANGE_LAYER: return "WINDOWS_CHANGE_LAYER";
+            case WINDOWS_CHANGE_ACTIVE: return "WINDOWS_CHANGE_ACTIVE";
+            case WINDOWS_CHANGE_FOCUSED: return "WINDOWS_CHANGE_FOCUSED";
+            case WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED:
+                return "WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED";
+            case WINDOWS_CHANGE_PARENT: return "WINDOWS_CHANGE_PARENT";
+            case WINDOWS_CHANGE_CHILDREN: return "WINDOWS_CHANGE_CHILDREN";
+            case WINDOWS_CHANGE_PIP: return "WINDOWS_CHANGE_PIP";
+            default: return Integer.toHexString(type);
+        }
+    }
+
+    /**
+     * Sets the event type.
+     *
+     * @param eventType The event type.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setEventType(@EventType int eventType) {
+        enforceNotSealed();
+        mEventType = eventType;
+    }
+
+    /**
+     * Gets the time in which this event was sent.
+     *
+     * @return The event time.
+     */
+    public long getEventTime() {
+        return mEventTime;
+    }
+
+    /**
+     * Sets the time in which this event was sent.
+     *
+     * @param eventTime The event time.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setEventTime(long eventTime) {
+        enforceNotSealed();
+        mEventTime = eventTime;
+    }
+
+    /**
+     * Gets the package name of the source.
+     *
+     * @return The package name.
+     */
+    public CharSequence getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * Sets the package name of the source.
+     *
+     * @param packageName The package name.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setPackageName(CharSequence packageName) {
+        enforceNotSealed();
+        mPackageName = packageName;
+    }
+
+    /**
+     * Sets the movement granularity that was traversed.
+     *
+     * @param granularity The granularity.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setMovementGranularity(int granularity) {
+        enforceNotSealed();
+        mMovementGranularity = granularity;
+    }
+
+    /**
+     * Gets the movement granularity that was traversed.
+     *
+     * @return The granularity.
+     */
+    public int getMovementGranularity() {
+        return mMovementGranularity;
+    }
+
+    /**
+     * Sets the performed action that triggered this event.
+     * <p>
+     * Valid actions are defined in {@link AccessibilityNodeInfo}:
+     * <ul>
+     * <li>{@link AccessibilityNodeInfo#ACTION_ACCESSIBILITY_FOCUS}
+     * <li>{@link AccessibilityNodeInfo#ACTION_CLEAR_ACCESSIBILITY_FOCUS}
+     * <li>{@link AccessibilityNodeInfo#ACTION_CLEAR_FOCUS}
+     * <li>{@link AccessibilityNodeInfo#ACTION_CLEAR_SELECTION}
+     * <li>{@link AccessibilityNodeInfo#ACTION_CLICK}
+     * <li>etc.
+     * </ul>
+     *
+     * @param action The action.
+     * @throws IllegalStateException If called from an AccessibilityService.
+     * @see AccessibilityNodeInfo#performAction(int)
+     */
+    public void setAction(int action) {
+        enforceNotSealed();
+        mAction = action;
+    }
+
+    /**
+     * Gets the performed action that triggered this event.
+     *
+     * @return The action.
+     */
+    public int getAction() {
+        return mAction;
+    }
+
+    /**
+     * Convenience method to obtain a {@link #TYPE_WINDOWS_CHANGED} event for a specific window and
+     * change set.
+     *
+     * @param windowId The ID of the window that changed
+     * @param windowChangeTypes The changes to populate
+     * @return An instance of a TYPE_WINDOWS_CHANGED, populated with the requested fields and with
+     *         importantForAccessibility set to {@code true}.
+     *
+     * @hide
+     */
+    public static AccessibilityEvent obtainWindowsChangedEvent(
+            int windowId, int windowChangeTypes) {
+        final AccessibilityEvent event = AccessibilityEvent.obtain(TYPE_WINDOWS_CHANGED);
+        event.setWindowId(windowId);
+        event.setWindowChanges(windowChangeTypes);
+        event.setImportantForAccessibility(true);
+        return event;
+    }
+
+    /**
+     * Returns a cached instance if such is available or a new one is
+     * instantiated with its type property set.
+     *
+     * <p>In most situations object pooling is not beneficial. Create a new instance using the
+     * constructor {@link #AccessibilityEvent(int)} instead.
+     *
+     * @param eventType The event type.
+     * @return An instance.
+     */
+    public static AccessibilityEvent obtain(int eventType) {
+        AccessibilityEvent event = AccessibilityEvent.obtain();
+        event.setEventType(eventType);
+        return event;
+    }
+
+    /**
+     * Returns a cached instance if such is available or a new one is
+     * created. The returned instance is initialized from the given
+     * <code>event</code>.
+     *
+     * <p>In most situations object pooling is not beneficial. Create a new instance using the
+     * constructor {@link #AccessibilityEvent(AccessibilityEvent)} instead.
+     *
+     * @param event The other event.
+     * @return An instance.
+     */
+    public static AccessibilityEvent obtain(AccessibilityEvent event) {
+        AccessibilityEvent eventClone = AccessibilityEvent.obtain();
+        eventClone.init(event);
+        return eventClone;
+    }
+
+    /**
+     * Returns a cached instance if such is available or a new one is
+     * instantiated.
+     *
+     * <p>In most situations object pooling is not beneficial. Create a new instance using the
+     * constructor {@link #AccessibilityEvent()} instead.
+     *
+     * @return An instance.
+     */
+    public static AccessibilityEvent obtain() {
+        AccessibilityEvent event = sPool.acquire();
+        if (event == null) event = new AccessibilityEvent();
+        if (DEBUG_ORIGIN) event.originStackTrace = Thread.currentThread().getStackTrace();
+        return event;
+    }
+
+    /**
+     * Recycles an instance back to be reused.
+     * <p>
+     *   <b>Note: You must not touch the object after calling this function.</b>
+     * </p>
+     *
+     * <p>In most situations object pooling is not beneficial, and recycling is not necessary.
+     *
+     * @throws IllegalStateException If the event is already recycled.
+     */
+    @Override
+    public void recycle() {
+        clear();
+        sPool.release(this);
+    }
+
+    /**
+     * Clears the state of this instance.
+     *
+     * @hide
+     */
+    @Override
+    protected void clear() {
+        super.clear();
+        mEventType = 0;
+        mMovementGranularity = 0;
+        mAction = 0;
+        mContentChangeTypes = 0;
+        mWindowChangeTypes = 0;
+        mPackageName = null;
+        mEventTime = 0;
+        if (mRecords != null) {
+            while (!mRecords.isEmpty()) {
+                AccessibilityRecord record = mRecords.remove(0);
+                record.recycle();
+            }
+        }
+        if (DEBUG_ORIGIN) originStackTrace = null;
+    }
+
+    /**
+     * Creates a new instance from a {@link Parcel}.
+     *
+     * @param parcel A parcel containing the state of a {@link AccessibilityEvent}.
+     */
+    public void initFromParcel(Parcel parcel) {
+        mSealed = (parcel.readInt() == 1);
+        mEventType = parcel.readInt();
+        mMovementGranularity = parcel.readInt();
+        mAction = parcel.readInt();
+        mContentChangeTypes = parcel.readInt();
+        mWindowChangeTypes = parcel.readInt();
+        mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+        mEventTime = parcel.readLong();
+        mConnectionId = parcel.readInt();
+        readAccessibilityRecordFromParcel(this, parcel);
+
+        // Read the records.
+        final int recordCount = parcel.readInt();
+        if (recordCount > 0) {
+            mRecords = new ArrayList<>(recordCount);
+            for (int i = 0; i < recordCount; i++) {
+                AccessibilityRecord record = AccessibilityRecord.obtain();
+                readAccessibilityRecordFromParcel(record, parcel);
+                record.mConnectionId = mConnectionId;
+                mRecords.add(record);
+            }
+        }
+
+        if (DEBUG_ORIGIN) {
+            originStackTrace = new StackTraceElement[parcel.readInt()];
+            for (int i = 0; i < originStackTrace.length; i++) {
+                originStackTrace[i] = new StackTraceElement(
+                        parcel.readString(),
+                        parcel.readString(),
+                        parcel.readString(),
+                        parcel.readInt());
+            }
+        }
+    }
+
+    /**
+     * Reads an {@link AccessibilityRecord} from a parcel.
+     *
+     * @param record The record to initialize.
+     * @param parcel The parcel to read from.
+     */
+    private void readAccessibilityRecordFromParcel(AccessibilityRecord record,
+            Parcel parcel) {
+        record.mBooleanProperties = parcel.readInt();
+        record.mCurrentItemIndex = parcel.readInt();
+        record.mItemCount = parcel.readInt();
+        record.mFromIndex = parcel.readInt();
+        record.mToIndex = parcel.readInt();
+        record.mScrollX = parcel.readInt();
+        record.mScrollY =  parcel.readInt();
+        record.mScrollDeltaX =  parcel.readInt();
+        record.mScrollDeltaY =  parcel.readInt();
+        record.mMaxScrollX = parcel.readInt();
+        record.mMaxScrollY =  parcel.readInt();
+        record.mAddedCount = parcel.readInt();
+        record.mRemovedCount = parcel.readInt();
+        record.mClassName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+        record.mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+        record.mBeforeText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+        record.mParcelableData = parcel.readParcelable(null);
+        parcel.readList(record.mText, null);
+        record.mSourceWindowId = parcel.readInt();
+        record.mSourceNodeId = parcel.readLong();
+        record.mSealed = (parcel.readInt() == 1);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeInt(isSealed() ? 1 : 0);
+        parcel.writeInt(mEventType);
+        parcel.writeInt(mMovementGranularity);
+        parcel.writeInt(mAction);
+        parcel.writeInt(mContentChangeTypes);
+        parcel.writeInt(mWindowChangeTypes);
+        TextUtils.writeToParcel(mPackageName, parcel, 0);
+        parcel.writeLong(mEventTime);
+        parcel.writeInt(mConnectionId);
+        writeAccessibilityRecordToParcel(this, parcel, flags);
+
+        // Write the records.
+        final int recordCount = getRecordCount();
+        parcel.writeInt(recordCount);
+        for (int i = 0; i < recordCount; i++) {
+            AccessibilityRecord record = mRecords.get(i);
+            writeAccessibilityRecordToParcel(record, parcel, flags);
+        }
+
+        if (DEBUG_ORIGIN) {
+            if (originStackTrace == null) originStackTrace = Thread.currentThread().getStackTrace();
+            parcel.writeInt(originStackTrace.length);
+            for (StackTraceElement element : originStackTrace) {
+                parcel.writeString(element.getClassName());
+                parcel.writeString(element.getMethodName());
+                parcel.writeString(element.getFileName());
+                parcel.writeInt(element.getLineNumber());
+            }
+        }
+    }
+
+    /**
+     * Writes an {@link AccessibilityRecord} to a parcel.
+     *
+     * @param record The record to write.
+     * @param parcel The parcel to which to write.
+     */
+    private void writeAccessibilityRecordToParcel(AccessibilityRecord record, Parcel parcel,
+            int flags) {
+        parcel.writeInt(record.mBooleanProperties);
+        parcel.writeInt(record.mCurrentItemIndex);
+        parcel.writeInt(record.mItemCount);
+        parcel.writeInt(record.mFromIndex);
+        parcel.writeInt(record.mToIndex);
+        parcel.writeInt(record.mScrollX);
+        parcel.writeInt(record.mScrollY);
+        parcel.writeInt(record.mScrollDeltaX);
+        parcel.writeInt(record.mScrollDeltaY);
+        parcel.writeInt(record.mMaxScrollX);
+        parcel.writeInt(record.mMaxScrollY);
+        parcel.writeInt(record.mAddedCount);
+        parcel.writeInt(record.mRemovedCount);
+        TextUtils.writeToParcel(record.mClassName, parcel, flags);
+        TextUtils.writeToParcel(record.mContentDescription, parcel, flags);
+        TextUtils.writeToParcel(record.mBeforeText, parcel, flags);
+        parcel.writeParcelable(record.mParcelableData, flags);
+        parcel.writeList(record.mText);
+        parcel.writeInt(record.mSourceWindowId);
+        parcel.writeLong(record.mSourceNodeId);
+        parcel.writeInt(record.mSealed ? 1 : 0);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("EventType: ").append(eventTypeToString(mEventType));
+        builder.append("; EventTime: ").append(mEventTime);
+        builder.append("; PackageName: ").append(mPackageName);
+        if (!DEBUG_CONCISE_TOSTRING || mMovementGranularity != 0) {
+            builder.append("; MovementGranularity: ").append(mMovementGranularity);
+        }
+        if (!DEBUG_CONCISE_TOSTRING || mAction != 0) {
+            builder.append("; Action: ").append(mAction);
+        }
+        if (!DEBUG_CONCISE_TOSTRING || mContentChangeTypes != 0) {
+            builder.append("; ContentChangeTypes: ").append(
+                    contentChangeTypesToString(mContentChangeTypes));
+        }
+        if (!DEBUG_CONCISE_TOSTRING || mWindowChangeTypes != 0) {
+            builder.append("; WindowChangeTypes: ").append(
+                    windowChangeTypesToString(mWindowChangeTypes));
+        }
+        super.appendTo(builder);
+        if (DEBUG || DEBUG_CONCISE_TOSTRING) {
+            if (!DEBUG_CONCISE_TOSTRING) {
+                builder.append("\n");
+            }
+            if (DEBUG) {
+                builder.append("; SourceWindowId: 0x").append(Long.toHexString(mSourceWindowId));
+                builder.append("; SourceNodeId: 0x").append(Long.toHexString(mSourceNodeId));
+            }
+            for (int i = 0; i < getRecordCount(); i++) {
+                builder.append("  Record ").append(i).append(":");
+                getRecord(i).appendTo(builder).append("\n");
+            }
+        } else {
+            builder.append("; recordCount: ").append(getRecordCount());
+        }
+        return builder.toString();
+    }
+
+    /**
+     * Returns the string representation of an event type. For example,
+     * {@link #TYPE_VIEW_CLICKED} is represented by the string TYPE_VIEW_CLICKED.
+     *
+     * @param eventType The event type
+     * @return The string representation.
+     */
+    public static String eventTypeToString(int eventType) {
+        if (eventType == TYPES_ALL_MASK) {
+            return "TYPES_ALL_MASK";
+        }
+        StringBuilder builder = new StringBuilder();
+        int eventTypeCount = 0;
+        while (eventType != 0) {
+            final int eventTypeFlag = 1 << Integer.numberOfTrailingZeros(eventType);
+            eventType &= ~eventTypeFlag;
+
+            if (eventTypeCount > 0) {
+                builder.append(", ");
+            }
+            builder.append(singleEventTypeToString(eventTypeFlag));
+
+            eventTypeCount++;
+        }
+        if (eventTypeCount > 1) {
+            builder.insert(0, '[');
+            builder.append(']');
+        }
+        return builder.toString();
+    }
+
+    private static String singleEventTypeToString(int eventType) {
+        switch (eventType) {
+            case TYPE_VIEW_CLICKED: return "TYPE_VIEW_CLICKED";
+            case TYPE_VIEW_LONG_CLICKED: return "TYPE_VIEW_LONG_CLICKED";
+            case TYPE_VIEW_SELECTED: return "TYPE_VIEW_SELECTED";
+            case TYPE_VIEW_FOCUSED: return "TYPE_VIEW_FOCUSED";
+            case TYPE_VIEW_TEXT_CHANGED: return "TYPE_VIEW_TEXT_CHANGED";
+            case TYPE_WINDOW_STATE_CHANGED: return "TYPE_WINDOW_STATE_CHANGED";
+            case TYPE_VIEW_HOVER_ENTER: return "TYPE_VIEW_HOVER_ENTER";
+            case TYPE_VIEW_HOVER_EXIT: return "TYPE_VIEW_HOVER_EXIT";
+            case TYPE_NOTIFICATION_STATE_CHANGED: return "TYPE_NOTIFICATION_STATE_CHANGED";
+            case TYPE_TOUCH_EXPLORATION_GESTURE_START: {
+                return "TYPE_TOUCH_EXPLORATION_GESTURE_START";
+            }
+            case TYPE_TOUCH_EXPLORATION_GESTURE_END: return "TYPE_TOUCH_EXPLORATION_GESTURE_END";
+            case TYPE_WINDOW_CONTENT_CHANGED: return "TYPE_WINDOW_CONTENT_CHANGED";
+            case TYPE_VIEW_TEXT_SELECTION_CHANGED: return "TYPE_VIEW_TEXT_SELECTION_CHANGED";
+            case TYPE_VIEW_SCROLLED: return "TYPE_VIEW_SCROLLED";
+            case TYPE_ANNOUNCEMENT: return "TYPE_ANNOUNCEMENT";
+            case TYPE_VIEW_ACCESSIBILITY_FOCUSED: return "TYPE_VIEW_ACCESSIBILITY_FOCUSED";
+            case TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
+                return "TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED";
+            }
+            case TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY: {
+                return "TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY";
+            }
+            case TYPE_GESTURE_DETECTION_START: return "TYPE_GESTURE_DETECTION_START";
+            case TYPE_GESTURE_DETECTION_END: return "TYPE_GESTURE_DETECTION_END";
+            case TYPE_TOUCH_INTERACTION_START: return "TYPE_TOUCH_INTERACTION_START";
+            case TYPE_TOUCH_INTERACTION_END: return "TYPE_TOUCH_INTERACTION_END";
+            case TYPE_WINDOWS_CHANGED: return "TYPE_WINDOWS_CHANGED";
+            case TYPE_VIEW_CONTEXT_CLICKED: return "TYPE_VIEW_CONTEXT_CLICKED";
+            case TYPE_ASSIST_READING_CONTEXT: return "TYPE_ASSIST_READING_CONTEXT";
+            default: return Integer.toHexString(eventType);
+        }
+    }
+
+    /**
+     * @see Parcelable.Creator
+     */
+    public static final @android.annotation.NonNull Parcelable.Creator<AccessibilityEvent> CREATOR =
+            new Parcelable.Creator<AccessibilityEvent>() {
+        public AccessibilityEvent createFromParcel(Parcel parcel) {
+            AccessibilityEvent event = AccessibilityEvent.obtain();
+            event.initFromParcel(parcel);
+            return event;
+        }
+
+        public AccessibilityEvent[] newArray(int size) {
+            return new AccessibilityEvent[size];
+        }
+    };
+}
diff --git a/android/view/accessibility/AccessibilityEventSource.java b/android/view/accessibility/AccessibilityEventSource.java
new file mode 100644
index 0000000..525ba9e
--- /dev/null
+++ b/android/view/accessibility/AccessibilityEventSource.java
@@ -0,0 +1,61 @@
+/*
+ * 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.view.accessibility;
+
+/**
+ * This interface is implemented by classes source of {@link AccessibilityEvent}s.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about making applications accessible, read the
+ * <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a>
+ * developer guide.</p>
+ * </div>
+ */
+public interface AccessibilityEventSource {
+
+    /**
+     * Handles the request for sending an {@link AccessibilityEvent} given
+     * the event type. The method must first check if accessibility is on
+     * via calling {@link AccessibilityManager#isEnabled() AccessibilityManager.isEnabled()},
+     * obtain an {@link AccessibilityEvent} from the event pool through calling
+     * {@link AccessibilityEvent#obtain(int) AccessibilityEvent.obtain(int)}, populate the
+     * event, and send it for dispatch via calling
+     * {@link AccessibilityManager#sendAccessibilityEvent(AccessibilityEvent)
+     * AccessibilityManager.sendAccessibilityEvent(AccessibilityEvent)}.
+     *
+     * @see AccessibilityEvent
+     * @see AccessibilityManager
+     *
+     * @param eventType The event type.
+     */
+    public void sendAccessibilityEvent(int eventType);
+
+    /**
+     * Handles the request for sending an {@link AccessibilityEvent}. The
+     * method does not guarantee to check if accessibility is on before
+     * sending the event for dispatch. It is responsibility of the caller
+     * to do the check via calling {@link AccessibilityManager#isEnabled()
+     * AccessibilityManager.isEnabled()}.
+     *
+     * @see AccessibilityEvent
+     * @see AccessibilityManager
+     *
+     * @param event The event.
+     */
+    public void sendAccessibilityEventUnchecked(AccessibilityEvent event);
+}
diff --git a/android/view/accessibility/AccessibilityInteractionClient.java b/android/view/accessibility/AccessibilityInteractionClient.java
new file mode 100644
index 0000000..dd81dd9
--- /dev/null
+++ b/android/view/accessibility/AccessibilityInteractionClient.java
@@ -0,0 +1,1245 @@
+/*
+ ** Copyright 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.view.accessibility;
+
+import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.SparseArray;
+import android.util.SparseLongArray;
+import android.view.Display;
+import android.view.ViewConfiguration;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * This class is a singleton that performs accessibility interaction
+ * which is it queries remote view hierarchies about snapshots of their
+ * views as well requests from these hierarchies to perform certain
+ * actions on their views.
+ *
+ * Rationale: The content retrieval APIs are synchronous from a client's
+ *     perspective but internally they are asynchronous. The client thread
+ *     calls into the system requesting an action and providing a callback
+ *     to receive the result after which it waits up to a timeout for that
+ *     result. The system enforces security and the delegates the request
+ *     to a given view hierarchy where a message is posted (from a binder
+ *     thread) describing what to be performed by the main UI thread the
+ *     result of which it delivered via the mentioned callback. However,
+ *     the blocked client thread and the main UI thread of the target view
+ *     hierarchy can be the same thread, for example an accessibility service
+ *     and an activity run in the same process, thus they are executed on the
+ *     same main thread. In such a case the retrieval will fail since the UI
+ *     thread that has to process the message describing the work to be done
+ *     is blocked waiting for a result is has to compute! To avoid this scenario
+ *     when making a call the client also passes its process and thread ids so
+ *     the accessed view hierarchy can detect if the client making the request
+ *     is running in its main UI thread. In such a case the view hierarchy,
+ *     specifically the binder thread performing the IPC to it, does not post a
+ *     message to be run on the UI thread but passes it to the singleton
+ *     interaction client through which all interactions occur and the latter is
+ *     responsible to execute the message before starting to wait for the
+ *     asynchronous result delivered via the callback. In this case the expected
+ *     result is already received so no waiting is performed.
+ *
+ * @hide
+ */
+public final class AccessibilityInteractionClient
+        extends IAccessibilityInteractionConnectionCallback.Stub {
+
+    public static final int NO_ID = -1;
+
+    public static final String CALL_STACK = "call_stack";
+
+    private static final String LOG_TAG = "AccessibilityInteractionClient";
+
+    private static final boolean DEBUG = false;
+
+    private static final boolean CHECK_INTEGRITY = true;
+
+    private static final long TIMEOUT_INTERACTION_MILLIS = 5000;
+
+    private static final long DISABLE_PREFETCHING_FOR_SCROLLING_MILLIS =
+            (long) (ViewConfiguration.getSendRecurringAccessibilityEventsInterval() * 1.5);
+
+    private static final Object sStaticLock = new Object();
+
+    private static final LongSparseArray<AccessibilityInteractionClient> sClients =
+        new LongSparseArray<>();
+
+    private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache =
+            new SparseArray<>();
+
+    /** List of timestamps which indicate the latest time an a11y service receives a scroll event
+        from a window, mapping from windowId -> timestamp. */
+    private static final SparseLongArray sScrollingWindows = new SparseLongArray();
+
+    private static AccessibilityCache sAccessibilityCache =
+            new AccessibilityCache(new AccessibilityCache.AccessibilityNodeRefresher());
+
+    private final AtomicInteger mInteractionIdCounter = new AtomicInteger();
+
+    private final Object mInstanceLock = new Object();
+
+    private final AccessibilityManager  mAccessibilityManager;
+
+    private volatile int mInteractionId = -1;
+    private volatile int mCallingUid = Process.INVALID_UID;
+
+    private AccessibilityNodeInfo mFindAccessibilityNodeInfoResult;
+
+    private List<AccessibilityNodeInfo> mFindAccessibilityNodeInfosResult;
+
+    private boolean mPerformAccessibilityActionResult;
+
+    private Message mSameThreadMessage;
+
+    private int mInteractionIdWaitingForPrefetchResult = -1;
+    private int mConnectionIdWaitingForPrefetchResult;
+    private String[] mPackageNamesForNextPrefetchResult;
+
+    /**
+     * @return The client for the current thread.
+     */
+    @UnsupportedAppUsage()
+    public static AccessibilityInteractionClient getInstance() {
+        final long threadId = Thread.currentThread().getId();
+        return getInstanceForThread(threadId);
+    }
+
+    /**
+     * <strong>Note:</strong> We keep one instance per interrogating thread since
+     * the instance contains state which can lead to undesired thread interleavings.
+     * We do not have a thread local variable since other threads should be able to
+     * look up the correct client knowing a thread id. See ViewRootImpl for details.
+     *
+     * @return The client for a given <code>threadId</code>.
+     */
+    public static AccessibilityInteractionClient getInstanceForThread(long threadId) {
+        synchronized (sStaticLock) {
+            AccessibilityInteractionClient client = sClients.get(threadId);
+            if (client == null) {
+                client = new AccessibilityInteractionClient();
+                sClients.put(threadId, client);
+            }
+            return client;
+        }
+    }
+
+    /**
+     * @return The client for the current thread.
+     */
+    public static AccessibilityInteractionClient getInstance(Context context) {
+        final long threadId = Thread.currentThread().getId();
+        if (context != null) {
+            return getInstanceForThread(threadId, context);
+        }
+        return getInstanceForThread(threadId);
+    }
+
+    /**
+     * <strong>Note:</strong> We keep one instance per interrogating thread since
+     * the instance contains state which can lead to undesired thread interleavings.
+     * We do not have a thread local variable since other threads should be able to
+     * look up the correct client knowing a thread id. See ViewRootImpl for details.
+     *
+     * @return The client for a given <code>threadId</code>.
+     */
+    public static AccessibilityInteractionClient getInstanceForThread(
+            long threadId, Context context) {
+        synchronized (sStaticLock) {
+            AccessibilityInteractionClient client = sClients.get(threadId);
+            if (client == null) {
+                client = new AccessibilityInteractionClient(context);
+                sClients.put(threadId, client);
+            }
+            return client;
+        }
+    }
+
+    /**
+     * Gets a cached accessibility service connection.
+     *
+     * @param connectionId The connection id.
+     * @return The cached connection if such.
+     */
+    public static IAccessibilityServiceConnection getConnection(int connectionId) {
+        synchronized (sConnectionCache) {
+            return sConnectionCache.get(connectionId);
+        }
+    }
+
+    /**
+     * Adds a cached accessibility service connection.
+     *
+     * @param connectionId The connection id.
+     * @param connection The connection.
+     */
+    public static void addConnection(int connectionId, IAccessibilityServiceConnection connection) {
+        synchronized (sConnectionCache) {
+            sConnectionCache.put(connectionId, connection);
+        }
+    }
+
+    /**
+     * Removes a cached accessibility service connection.
+     *
+     * @param connectionId The connection id.
+     */
+    public static void removeConnection(int connectionId) {
+        synchronized (sConnectionCache) {
+            sConnectionCache.remove(connectionId);
+        }
+    }
+
+    /**
+     * This method is only for testing. Replacing the cache is a generally terrible idea, but
+     * tests need to be able to verify this class's interactions with the cache
+     */
+    @VisibleForTesting
+    public static void setCache(AccessibilityCache cache) {
+        sAccessibilityCache = cache;
+    }
+
+    private AccessibilityInteractionClient() {
+        /* reducing constructor visibility */
+        mAccessibilityManager = null;
+    }
+
+    private AccessibilityInteractionClient(Context context) {
+        mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
+    }
+
+    /**
+     * Sets the message to be processed if the interacted view hierarchy
+     * and the interacting client are running in the same thread.
+     *
+     * @param message The message.
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public void setSameThreadMessage(Message message) {
+        synchronized (mInstanceLock) {
+            mSameThreadMessage = message;
+            mInstanceLock.notifyAll();
+        }
+    }
+
+    /**
+     * Gets the root {@link AccessibilityNodeInfo} in the currently active window.
+     *
+     * @param connectionId The id of a connection for interacting with the system.
+     * @return The root {@link AccessibilityNodeInfo} if found, null otherwise.
+     */
+    public AccessibilityNodeInfo getRootInActiveWindow(int connectionId) {
+        return findAccessibilityNodeInfoByAccessibilityId(connectionId,
+                AccessibilityWindowInfo.ACTIVE_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID,
+                false, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS, null);
+    }
+
+    /**
+     * Gets the info for a window.
+     *
+     * @param connectionId The id of a connection for interacting with the system.
+     * @param accessibilityWindowId A unique window id. Use
+     *     {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID}
+     *     to query the currently active window.
+     * @return The {@link AccessibilityWindowInfo}.
+     */
+    public AccessibilityWindowInfo getWindow(int connectionId, int accessibilityWindowId) {
+        return getWindow(connectionId, accessibilityWindowId, /* bypassCache */ false);
+    }
+
+    /**
+     * Gets the info for a window.
+     *
+     * @param connectionId The id of a connection for interacting with the system.
+     * @param accessibilityWindowId A unique window id. Use
+     *     {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID}
+     *     to query the currently active window.
+     * @param bypassCache Whether to bypass the cache.
+     * @return The {@link AccessibilityWindowInfo}.
+     */
+    public AccessibilityWindowInfo getWindow(int connectionId, int accessibilityWindowId,
+            boolean bypassCache) {
+        try {
+            IAccessibilityServiceConnection connection = getConnection(connectionId);
+            if (connection != null) {
+                AccessibilityWindowInfo window;
+                if (!bypassCache) {
+                    window = sAccessibilityCache.getWindow(accessibilityWindowId);
+                    if (window != null) {
+                        if (DEBUG) {
+                            Log.i(LOG_TAG, "Window cache hit");
+                        }
+                        return window;
+                    }
+                    if (DEBUG) {
+                        Log.i(LOG_TAG, "Window cache miss");
+                    }
+                }
+                final long identityToken = Binder.clearCallingIdentity();
+                try {
+                    window = connection.getWindow(accessibilityWindowId);
+                } finally {
+                    Binder.restoreCallingIdentity(identityToken);
+                }
+                if (window != null) {
+                    if (!bypassCache) {
+                        sAccessibilityCache.addWindow(window);
+                    }
+                    return window;
+                }
+            } else {
+                if (DEBUG) {
+                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+                }
+            }
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while calling remote getWindow", re);
+        }
+        return null;
+    }
+
+    /**
+     * Gets the info for all windows of the default display.
+     *
+     * @param connectionId The id of a connection for interacting with the system.
+     * @return The {@link AccessibilityWindowInfo} list.
+     */
+    public List<AccessibilityWindowInfo> getWindows(int connectionId) {
+        final SparseArray<List<AccessibilityWindowInfo>> windows =
+                getWindowsOnAllDisplays(connectionId);
+        if (windows.size() > 0) {
+            return windows.valueAt(Display.DEFAULT_DISPLAY);
+        }
+        return Collections.emptyList();
+    }
+
+    /**
+     * Gets the info for all windows of all displays.
+     *
+     * @param connectionId The id of a connection for interacting with the system.
+     * @return The SparseArray of {@link AccessibilityWindowInfo} list.
+     *         The key of SparseArray is display ID.
+     */
+    public SparseArray<List<AccessibilityWindowInfo>> getWindowsOnAllDisplays(int connectionId) {
+        try {
+            IAccessibilityServiceConnection connection = getConnection(connectionId);
+            if (connection != null) {
+                SparseArray<List<AccessibilityWindowInfo>> windows =
+                        sAccessibilityCache.getWindowsOnAllDisplays();
+                if (windows != null) {
+                    if (DEBUG) {
+                        Log.i(LOG_TAG, "Windows cache hit");
+                    }
+                    return windows;
+                }
+                if (DEBUG) {
+                    Log.i(LOG_TAG, "Windows cache miss");
+                }
+                final long identityToken = Binder.clearCallingIdentity();
+                try {
+                    windows = connection.getWindows();
+                } finally {
+                    Binder.restoreCallingIdentity(identityToken);
+                }
+                if (windows != null) {
+                    sAccessibilityCache.setWindowsOnAllDisplays(windows);
+                    return windows;
+                }
+            } else {
+                if (DEBUG) {
+                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+                }
+            }
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while calling remote getWindowsOnAllDisplays", re);
+        }
+
+        final SparseArray<List<AccessibilityWindowInfo>> emptyWindows = new SparseArray<>();
+        return emptyWindows;
+    }
+
+
+    /**
+     * Finds an {@link AccessibilityNodeInfo} by accessibility id and given leash token instead of
+     * window id. This method is used to find the leashed node on the embedded view hierarchy.
+     *
+     * @param connectionId The id of a connection for interacting with the system.
+     * @param leashToken The token of the embedded hierarchy.
+     * @param accessibilityNodeId A unique view id or virtual descendant id from
+     *     where to start the search. Use
+     *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+     *     to start from the root.
+     * @param bypassCache Whether to bypass the cache while looking for the node.
+     * @param prefetchFlags flags to guide prefetching.
+     * @param arguments Optional action arguments.
+     * @return An {@link AccessibilityNodeInfo} if found, null otherwise.
+     */
+    public @Nullable AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(
+            int connectionId, @NonNull IBinder leashToken, long accessibilityNodeId,
+            boolean bypassCache, int prefetchFlags, Bundle arguments) {
+        if (leashToken == null) {
+            return null;
+        }
+        int windowId = -1;
+        try {
+            IAccessibilityServiceConnection connection = getConnection(connectionId);
+            if (connection != null) {
+                windowId = connection.getWindowIdForLeashToken(leashToken);
+            } else {
+                if (DEBUG) {
+                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+                }
+            }
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while calling remote getWindowIdForLeashToken", re);
+        }
+        if (windowId == -1) {
+            return null;
+        }
+        return findAccessibilityNodeInfoByAccessibilityId(connectionId, windowId,
+                accessibilityNodeId, bypassCache, prefetchFlags, arguments);
+    }
+
+    /**
+     * Finds an {@link AccessibilityNodeInfo} by accessibility id.
+     *
+     * @param connectionId The id of a connection for interacting with the system.
+     * @param accessibilityWindowId A unique window id. Use
+     *     {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID}
+     *     to query the currently active window.
+     * @param accessibilityNodeId A unique view id or virtual descendant id from
+     *     where to start the search. Use
+     *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+     *     to start from the root.
+     * @param bypassCache Whether to bypass the cache while looking for the node.
+     * @param prefetchFlags flags to guide prefetching.
+     * @return An {@link AccessibilityNodeInfo} if found, null otherwise.
+     */
+    public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(int connectionId,
+            int accessibilityWindowId, long accessibilityNodeId, boolean bypassCache,
+            int prefetchFlags, Bundle arguments) {
+        if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0
+                && (prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) == 0) {
+            throw new IllegalArgumentException("FLAG_PREFETCH_SIBLINGS"
+                + " requires FLAG_PREFETCH_PREDECESSORS");
+        }
+        try {
+            IAccessibilityServiceConnection connection = getConnection(connectionId);
+            if (connection != null) {
+                if (!bypassCache) {
+                    AccessibilityNodeInfo cachedInfo = sAccessibilityCache.getNode(
+                            accessibilityWindowId, accessibilityNodeId);
+                    if (cachedInfo != null) {
+                        if (DEBUG) {
+                            Log.i(LOG_TAG, "Node cache hit for "
+                                    + idToString(accessibilityWindowId, accessibilityNodeId));
+                        }
+                        return cachedInfo;
+                    }
+                    if (DEBUG) {
+                        Log.i(LOG_TAG, "Node cache miss for "
+                                + idToString(accessibilityWindowId, accessibilityNodeId));
+                    }
+                } else {
+                    // No need to prefech nodes in bypass cache case.
+                    prefetchFlags &= ~AccessibilityNodeInfo.FLAG_PREFETCH_MASK;
+                }
+                // Skip prefetching if window is scrolling.
+                if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_MASK) != 0
+                        && isWindowScrolling(accessibilityWindowId)) {
+                    prefetchFlags &= ~AccessibilityNodeInfo.FLAG_PREFETCH_MASK;
+                }
+                final int interactionId = mInteractionIdCounter.getAndIncrement();
+                final String[] packageNames;
+                final long identityToken = Binder.clearCallingIdentity();
+                try {
+                    packageNames = connection.findAccessibilityNodeInfoByAccessibilityId(
+                            accessibilityWindowId, accessibilityNodeId, interactionId, this,
+                            prefetchFlags, Thread.currentThread().getId(), arguments);
+                } finally {
+                    Binder.restoreCallingIdentity(identityToken);
+                }
+                if (packageNames != null) {
+                    AccessibilityNodeInfo info =
+                            getFindAccessibilityNodeInfoResultAndClear(interactionId);
+                    if (mAccessibilityManager != null
+                            && mAccessibilityManager.isAccessibilityTracingEnabled()) {
+                        logTrace(connection, "findAccessibilityNodeInfoByAccessibilityId",
+                                "InteractionId:" + interactionId + ";Result: " + info
+                                        + ";connectionId=" + connectionId
+                                        + ";accessibilityWindowId="
+                                        + accessibilityWindowId + ";accessibilityNodeId="
+                                        + accessibilityNodeId + ";bypassCache=" + bypassCache
+                                        + ";prefetchFlags=" + prefetchFlags
+                                        + ";arguments=" + arguments);
+                    }
+                    if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_MASK) != 0
+                            && info != null) {
+                        setInteractionWaitingForPrefetchResult(interactionId, connectionId,
+                                packageNames);
+                    }
+                    finalizeAndCacheAccessibilityNodeInfo(info, connectionId,
+                            bypassCache, packageNames);
+                    return info;
+                }
+            } else {
+                if (DEBUG) {
+                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+                }
+            }
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while calling remote"
+                    + " findAccessibilityNodeInfoByAccessibilityId", re);
+        }
+        return null;
+    }
+
+    private void setInteractionWaitingForPrefetchResult(int interactionId, int connectionId,
+            String[] packageNames) {
+        synchronized (mInstanceLock) {
+            mInteractionIdWaitingForPrefetchResult = interactionId;
+            mConnectionIdWaitingForPrefetchResult = connectionId;
+            mPackageNamesForNextPrefetchResult = packageNames;
+        }
+    }
+
+    private static String idToString(int accessibilityWindowId, long accessibilityNodeId) {
+        return accessibilityWindowId + "/"
+                + AccessibilityNodeInfo.idToString(accessibilityNodeId);
+    }
+
+    /**
+     * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed in
+     * the window whose id is specified and starts from the node whose accessibility
+     * id is specified.
+     *
+     * @param connectionId The id of a connection for interacting with the system.
+     * @param accessibilityWindowId A unique window id. Use
+     *     {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID}
+     *     to query the currently active window.
+     * @param accessibilityNodeId A unique view id or virtual descendant id from
+     *     where to start the search. Use
+     *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+     *     to start from the root.
+     * @param viewId The fully qualified resource name of the view id to find.
+     * @return An list of {@link AccessibilityNodeInfo} if found, empty list otherwise.
+     */
+    public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(int connectionId,
+            int accessibilityWindowId, long accessibilityNodeId, String viewId) {
+        try {
+            IAccessibilityServiceConnection connection = getConnection(connectionId);
+            if (connection != null) {
+                final int interactionId = mInteractionIdCounter.getAndIncrement();
+                final String[] packageNames;
+                final long identityToken = Binder.clearCallingIdentity();
+                try {
+                    packageNames = connection.findAccessibilityNodeInfosByViewId(
+                            accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this,
+                            Thread.currentThread().getId());
+                } finally {
+                    Binder.restoreCallingIdentity(identityToken);
+                }
+
+                if (packageNames != null) {
+                    List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
+                            interactionId);
+                    if (mAccessibilityManager != null
+                            && mAccessibilityManager.isAccessibilityTracingEnabled()) {
+                        logTrace(connection, "findAccessibilityNodeInfosByViewId", "InteractionId="
+                                + interactionId + ":Result: " + infos + ";connectionId="
+                                + connectionId + ";accessibilityWindowId=" + accessibilityWindowId
+                                + ";accessibilityNodeId=" + accessibilityNodeId + ";viewId="
+                                + viewId);
+                    }
+                    if (infos != null) {
+                        finalizeAndCacheAccessibilityNodeInfos(infos, connectionId,
+                                false, packageNames);
+                        return infos;
+                    }
+                }
+            } else {
+                if (DEBUG) {
+                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+                }
+            }
+        } catch (RemoteException re) {
+            Log.w(LOG_TAG, "Error while calling remote"
+                    + " findAccessibilityNodeInfoByViewIdInActiveWindow", re);
+        }
+        return Collections.emptyList();
+    }
+
+    /**
+     * Finds {@link AccessibilityNodeInfo}s by View text. The match is case
+     * insensitive containment. The search is performed in the window whose
+     * id is specified and starts from the node whose accessibility id is
+     * specified.
+     *
+     * @param connectionId The id of a connection for interacting with the system.
+     * @param accessibilityWindowId A unique window id. Use
+     *     {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID}
+     *     to query the currently active window.
+     * @param accessibilityNodeId A unique view id or virtual descendant id from
+     *     where to start the search. Use
+     *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+     *     to start from the root.
+     * @param text The searched text.
+     * @return A list of found {@link AccessibilityNodeInfo}s.
+     */
+    public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(int connectionId,
+            int accessibilityWindowId, long accessibilityNodeId, String text) {
+        try {
+            IAccessibilityServiceConnection connection = getConnection(connectionId);
+            if (connection != null) {
+                final int interactionId = mInteractionIdCounter.getAndIncrement();
+                final String[] packageNames;
+                final long identityToken = Binder.clearCallingIdentity();
+                try {
+                    packageNames = connection.findAccessibilityNodeInfosByText(
+                            accessibilityWindowId, accessibilityNodeId, text, interactionId, this,
+                            Thread.currentThread().getId());
+                } finally {
+                    Binder.restoreCallingIdentity(identityToken);
+                }
+
+                if (packageNames != null) {
+                    List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
+                            interactionId);
+                    if (mAccessibilityManager != null
+                            && mAccessibilityManager.isAccessibilityTracingEnabled()) {
+                        logTrace(connection, "findAccessibilityNodeInfosByText", "InteractionId="
+                                + interactionId + ":Result: " + infos + ";connectionId="
+                                + connectionId + ";accessibilityWindowId=" + accessibilityWindowId
+                                + ";accessibilityNodeId=" + accessibilityNodeId + ";text=" + text);
+                    }
+                    if (infos != null) {
+                        finalizeAndCacheAccessibilityNodeInfos(infos, connectionId,
+                                false, packageNames);
+                        return infos;
+                    }
+                }
+            } else {
+                if (DEBUG) {
+                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+                }
+            }
+        } catch (RemoteException re) {
+            Log.w(LOG_TAG, "Error while calling remote"
+                    + " findAccessibilityNodeInfosByViewText", re);
+        }
+        return Collections.emptyList();
+    }
+
+    /**
+     * Finds the {@link android.view.accessibility.AccessibilityNodeInfo} that has the
+     * specified focus type. The search is performed in the window whose id is specified
+     * and starts from the node whose accessibility id is specified.
+     *
+     * @param connectionId The id of a connection for interacting with the system.
+     * @param accessibilityWindowId A unique window id. Use
+     *     {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID}
+     *     to query the currently active window.
+     * @param accessibilityNodeId A unique view id or virtual descendant id from
+     *     where to start the search. Use
+     *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+     *     to start from the root.
+     * @param focusType The focus type.
+     * @return The accessibility focused {@link AccessibilityNodeInfo}.
+     */
+    public AccessibilityNodeInfo findFocus(int connectionId, int accessibilityWindowId,
+            long accessibilityNodeId, int focusType) {
+        try {
+            IAccessibilityServiceConnection connection = getConnection(connectionId);
+            if (connection != null) {
+                final int interactionId = mInteractionIdCounter.getAndIncrement();
+                final String[] packageNames;
+                final long identityToken = Binder.clearCallingIdentity();
+                try {
+                    packageNames = connection.findFocus(accessibilityWindowId,
+                            accessibilityNodeId, focusType, interactionId, this,
+                            Thread.currentThread().getId());
+                } finally {
+                    Binder.restoreCallingIdentity(identityToken);
+                }
+
+                if (packageNames != null) {
+                    AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
+                            interactionId);
+                    if (mAccessibilityManager != null
+                            && mAccessibilityManager.isAccessibilityTracingEnabled()) {
+                        logTrace(connection, "findFocus", "InteractionId=" + interactionId
+                                + ":Result: " + info + ";connectionId=" + connectionId
+                                + ";accessibilityWindowId=" + accessibilityWindowId
+                                + ";accessibilityNodeId=" + accessibilityNodeId + ";focusType="
+                                + focusType);
+                    }
+                    finalizeAndCacheAccessibilityNodeInfo(info, connectionId, false, packageNames);
+                    return info;
+                }
+            } else {
+                if (DEBUG) {
+                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+                }
+            }
+        } catch (RemoteException re) {
+            Log.w(LOG_TAG, "Error while calling remote findFocus", re);
+        }
+        return null;
+    }
+
+    /**
+     * Finds the accessibility focused {@link android.view.accessibility.AccessibilityNodeInfo}.
+     * The search is performed in the window whose id is specified and starts from the
+     * node whose accessibility id is specified.
+     *
+     * @param connectionId The id of a connection for interacting with the system.
+     * @param accessibilityWindowId A unique window id. Use
+     *     {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID}
+     *     to query the currently active window.
+     * @param accessibilityNodeId A unique view id or virtual descendant id from
+     *     where to start the search. Use
+     *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+     *     to start from the root.
+     * @param direction The direction in which to search for focusable.
+     * @return The accessibility focused {@link AccessibilityNodeInfo}.
+     */
+    public AccessibilityNodeInfo focusSearch(int connectionId, int accessibilityWindowId,
+            long accessibilityNodeId, int direction) {
+        try {
+            IAccessibilityServiceConnection connection = getConnection(connectionId);
+            if (connection != null) {
+                final int interactionId = mInteractionIdCounter.getAndIncrement();
+                final String[] packageNames;
+                final long identityToken = Binder.clearCallingIdentity();
+                try {
+                    packageNames = connection.focusSearch(accessibilityWindowId,
+                            accessibilityNodeId, direction, interactionId, this,
+                            Thread.currentThread().getId());
+                } finally {
+                    Binder.restoreCallingIdentity(identityToken);
+                }
+
+                if (packageNames != null) {
+                    AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
+                            interactionId);
+                    finalizeAndCacheAccessibilityNodeInfo(info, connectionId, false, packageNames);
+                    if (mAccessibilityManager != null
+                            && mAccessibilityManager.isAccessibilityTracingEnabled()) {
+                        logTrace(connection, "focusSearch", "InteractionId=" + interactionId
+                                + ":Result: " + info + ";connectionId=" + connectionId
+                                + ";accessibilityWindowId=" + accessibilityWindowId
+                                + ";accessibilityNodeId=" + accessibilityNodeId + ";direction="
+                                + direction);
+                    }
+                    return info;
+                }
+            } else {
+                if (DEBUG) {
+                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+                }
+            }
+        } catch (RemoteException re) {
+            Log.w(LOG_TAG, "Error while calling remote accessibilityFocusSearch", re);
+        }
+        return null;
+    }
+
+    /**
+     * Performs an accessibility action on an {@link AccessibilityNodeInfo}.
+     *
+     * @param connectionId The id of a connection for interacting with the system.
+     * @param accessibilityWindowId A unique window id. Use
+     *     {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID}
+     *     to query the currently active window.
+     * @param accessibilityNodeId A unique view id or virtual descendant id from
+     *     where to start the search. Use
+     *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+     *     to start from the root.
+     * @param action The action to perform.
+     * @param arguments Optional action arguments.
+     * @return Whether the action was performed.
+     */
+    public boolean performAccessibilityAction(int connectionId, int accessibilityWindowId,
+            long accessibilityNodeId, int action, Bundle arguments) {
+        try {
+            IAccessibilityServiceConnection connection = getConnection(connectionId);
+            if (connection != null) {
+                final int interactionId = mInteractionIdCounter.getAndIncrement();
+                final boolean success;
+                final long identityToken = Binder.clearCallingIdentity();
+                try {
+                    success = connection.performAccessibilityAction(
+                            accessibilityWindowId, accessibilityNodeId, action, arguments,
+                            interactionId, this, Thread.currentThread().getId());
+                } finally {
+                    Binder.restoreCallingIdentity(identityToken);
+                }
+
+                if (success) {
+                    final boolean result =
+                            getPerformAccessibilityActionResultAndClear(interactionId);
+                    if (mAccessibilityManager != null
+                            && mAccessibilityManager.isAccessibilityTracingEnabled()) {
+                        logTrace(connection, "performAccessibilityAction", "InteractionId="
+                                + interactionId + ":Result: " + result + ";connectionId="
+                                + connectionId + ";accessibilityWindowId=" + accessibilityWindowId
+                                + ";accessibilityNodeId=" + accessibilityNodeId + ";action="
+                                + action + ";arguments=" + arguments);
+                    }
+                    return result;
+                }
+            } else {
+                if (DEBUG) {
+                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+                }
+            }
+        } catch (RemoteException re) {
+            Log.w(LOG_TAG, "Error while calling remote performAccessibilityAction", re);
+        }
+        return false;
+    }
+
+    /**
+     * Clears the accessibility cache.
+     */
+    @UnsupportedAppUsage()
+    public void clearCache() {
+        sAccessibilityCache.clear();
+    }
+
+    public void onAccessibilityEvent(AccessibilityEvent event) {
+        switch (event.getEventType()) {
+            case AccessibilityEvent.TYPE_VIEW_SCROLLED:
+                updateScrollingWindow(event.getWindowId(), SystemClock.uptimeMillis());
+                break;
+            case AccessibilityEvent.TYPE_WINDOWS_CHANGED:
+                if (event.getWindowChanges() == AccessibilityEvent.WINDOWS_CHANGE_REMOVED) {
+                    deleteScrollingWindow(event.getWindowId());
+                }
+                break;
+            default:
+                break;
+        }
+        sAccessibilityCache.onAccessibilityEvent(event);
+    }
+
+    /**
+     * Gets the the result of an async request that returns an {@link AccessibilityNodeInfo}.
+     *
+     * @param interactionId The interaction id to match the result with the request.
+     * @return The result {@link AccessibilityNodeInfo}.
+     */
+    private AccessibilityNodeInfo getFindAccessibilityNodeInfoResultAndClear(int interactionId) {
+        synchronized (mInstanceLock) {
+            final boolean success = waitForResultTimedLocked(interactionId);
+            AccessibilityNodeInfo result = success ? mFindAccessibilityNodeInfoResult : null;
+            clearResultLocked();
+            return result;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info,
+                int interactionId) {
+        synchronized (mInstanceLock) {
+            if (interactionId > mInteractionId) {
+                mFindAccessibilityNodeInfoResult = info;
+                mInteractionId = interactionId;
+                mCallingUid = Binder.getCallingUid();
+            }
+            mInstanceLock.notifyAll();
+        }
+    }
+
+    /**
+     * Gets the the result of an async request that returns {@link AccessibilityNodeInfo}s.
+     *
+     * @param interactionId The interaction id to match the result with the request.
+     * @return The result {@link AccessibilityNodeInfo}s.
+     */
+    private List<AccessibilityNodeInfo> getFindAccessibilityNodeInfosResultAndClear(
+                int interactionId) {
+        synchronized (mInstanceLock) {
+            final boolean success = waitForResultTimedLocked(interactionId);
+            final List<AccessibilityNodeInfo> result;
+            if (success) {
+                result = mFindAccessibilityNodeInfosResult;
+            } else {
+                result = Collections.emptyList();
+            }
+            clearResultLocked();
+            if (Build.IS_DEBUGGABLE && CHECK_INTEGRITY) {
+                checkFindAccessibilityNodeInfoResultIntegrity(result);
+            }
+            return result;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos,
+                int interactionId) {
+        synchronized (mInstanceLock) {
+            if (interactionId > mInteractionId) {
+                if (infos != null) {
+                    // If the call is not an IPC, i.e. it is made from the same process, we need to
+                    // instantiate new result list to avoid passing internal instances to clients.
+                    final boolean isIpcCall = (Binder.getCallingPid() != Process.myPid());
+                    if (!isIpcCall) {
+                        mFindAccessibilityNodeInfosResult = new ArrayList<>(infos);
+                    } else {
+                        mFindAccessibilityNodeInfosResult = infos;
+                    }
+                } else {
+                    mFindAccessibilityNodeInfosResult = Collections.emptyList();
+                }
+                mInteractionId = interactionId;
+                mCallingUid = Binder.getCallingUid();
+            }
+            mInstanceLock.notifyAll();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setPrefetchAccessibilityNodeInfoResult(@NonNull List<AccessibilityNodeInfo> infos,
+                                                       int interactionId) {
+        int interactionIdWaitingForPrefetchResultCopy = -1;
+        int connectionIdWaitingForPrefetchResultCopy = -1;
+        String[] packageNamesForNextPrefetchResultCopy = null;
+
+        if (infos.isEmpty()) {
+            return;
+        }
+
+        synchronized (mInstanceLock) {
+            if (mInteractionIdWaitingForPrefetchResult == interactionId) {
+                interactionIdWaitingForPrefetchResultCopy = mInteractionIdWaitingForPrefetchResult;
+                connectionIdWaitingForPrefetchResultCopy =
+                        mConnectionIdWaitingForPrefetchResult;
+                if (mPackageNamesForNextPrefetchResult != null) {
+                    packageNamesForNextPrefetchResultCopy =
+                            new String[mPackageNamesForNextPrefetchResult.length];
+                    for (int i = 0; i < mPackageNamesForNextPrefetchResult.length; i++) {
+                        packageNamesForNextPrefetchResultCopy[i] =
+                                mPackageNamesForNextPrefetchResult[i];
+                    }
+                }
+            }
+        }
+
+        if (interactionIdWaitingForPrefetchResultCopy == interactionId) {
+            finalizeAndCacheAccessibilityNodeInfos(
+                    infos, connectionIdWaitingForPrefetchResultCopy, false,
+                    packageNamesForNextPrefetchResultCopy);
+            if (mAccessibilityManager != null
+                    && mAccessibilityManager.isAccessibilityTracingEnabled()) {
+                logTrace(getConnection(connectionIdWaitingForPrefetchResultCopy),
+                        "setPrefetchAccessibilityNodeInfoResult",
+                        "InteractionId:" + interactionId + ";Result: " + infos
+                                + ";connectionId=" + connectionIdWaitingForPrefetchResultCopy,
+                        Binder.getCallingUid());
+            }
+        } else if (DEBUG) {
+            Log.w(LOG_TAG, "Prefetching for interaction with id " + interactionId + " dropped "
+                    + infos.size() + " nodes");
+        }
+    }
+
+    /**
+     * Gets the result of a request to perform an accessibility action.
+     *
+     * @param interactionId The interaction id to match the result with the request.
+     * @return Whether the action was performed.
+     */
+    private boolean getPerformAccessibilityActionResultAndClear(int interactionId) {
+        synchronized (mInstanceLock) {
+            final boolean success = waitForResultTimedLocked(interactionId);
+            final boolean result = success ? mPerformAccessibilityActionResult : false;
+            clearResultLocked();
+            return result;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setPerformAccessibilityActionResult(boolean succeeded, int interactionId) {
+        synchronized (mInstanceLock) {
+            if (interactionId > mInteractionId) {
+                mPerformAccessibilityActionResult = succeeded;
+                mInteractionId = interactionId;
+                mCallingUid = Binder.getCallingUid();
+            }
+            mInstanceLock.notifyAll();
+        }
+    }
+
+    /**
+     * Clears the result state.
+     */
+    private void clearResultLocked() {
+        mInteractionId = -1;
+        mFindAccessibilityNodeInfoResult = null;
+        mFindAccessibilityNodeInfosResult = null;
+        mPerformAccessibilityActionResult = false;
+    }
+
+    /**
+     * Waits up to a given bound for a result of a request and returns it.
+     *
+     * @param interactionId The interaction id to match the result with the request.
+     * @return Whether the result was received.
+     */
+    private boolean waitForResultTimedLocked(int interactionId) {
+        long waitTimeMillis = TIMEOUT_INTERACTION_MILLIS;
+        final long startTimeMillis = SystemClock.uptimeMillis();
+        while (true) {
+            try {
+                Message sameProcessMessage = getSameProcessMessageAndClear();
+                if (sameProcessMessage != null) {
+                    sameProcessMessage.getTarget().handleMessage(sameProcessMessage);
+                }
+
+                if (mInteractionId == interactionId) {
+                    return true;
+                }
+                if (mInteractionId > interactionId) {
+                    return false;
+                }
+                final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+                waitTimeMillis = TIMEOUT_INTERACTION_MILLIS - elapsedTimeMillis;
+                if (waitTimeMillis <= 0) {
+                    return false;
+                }
+                mInstanceLock.wait(waitTimeMillis);
+            } catch (InterruptedException ie) {
+                /* ignore */
+            }
+        }
+    }
+
+    /**
+     * Finalize an {@link AccessibilityNodeInfo} before passing it to the client.
+     *
+     * @param info The info.
+     * @param connectionId The id of the connection to the system.
+     * @param bypassCache Whether or not to bypass the cache. The node is added to the cache if
+     *                    this value is {@code false}
+     * @param packageNames The valid package names a node can come from.
+     */
+    private void finalizeAndCacheAccessibilityNodeInfo(AccessibilityNodeInfo info,
+            int connectionId, boolean bypassCache, String[] packageNames) {
+        if (info != null) {
+            info.setConnectionId(connectionId);
+            // Empty array means any package name is Okay
+            if (!ArrayUtils.isEmpty(packageNames)) {
+                CharSequence packageName = info.getPackageName();
+                if (packageName == null
+                        || !ArrayUtils.contains(packageNames, packageName.toString())) {
+                    // If the node package not one of the valid ones, pick the top one - this
+                    // is one of the packages running in the introspected UID.
+                    info.setPackageName(packageNames[0]);
+                }
+            }
+            info.setSealed(true);
+            if (!bypassCache) {
+                sAccessibilityCache.add(info);
+            }
+        }
+    }
+
+    /**
+     * Finalize {@link AccessibilityNodeInfo}s before passing them to the client.
+     *
+     * @param infos The {@link AccessibilityNodeInfo}s.
+     * @param connectionId The id of the connection to the system.
+     * @param bypassCache Whether or not to bypass the cache. The nodes are added to the cache if
+     *                    this value is {@code false}
+     * @param packageNames The valid package names a node can come from.
+     */
+    private void finalizeAndCacheAccessibilityNodeInfos(List<AccessibilityNodeInfo> infos,
+            int connectionId, boolean bypassCache, String[] packageNames) {
+        if (infos != null) {
+            final int infosCount = infos.size();
+            for (int i = 0; i < infosCount; i++) {
+                AccessibilityNodeInfo info = infos.get(i);
+                finalizeAndCacheAccessibilityNodeInfo(info, connectionId,
+                        bypassCache, packageNames);
+            }
+        }
+    }
+
+    /**
+     * Gets the message stored if the interacted and interacting
+     * threads are the same.
+     *
+     * @return The message.
+     */
+    private Message getSameProcessMessageAndClear() {
+        synchronized (mInstanceLock) {
+            Message result = mSameThreadMessage;
+            mSameThreadMessage = null;
+            return result;
+        }
+    }
+
+    /**
+     * Checks whether the infos are a fully connected tree with no duplicates.
+     *
+     * @param infos The result list to check.
+     */
+    private void checkFindAccessibilityNodeInfoResultIntegrity(List<AccessibilityNodeInfo> infos) {
+        if (infos.size() == 0) {
+            return;
+        }
+        // Find the root node.
+        AccessibilityNodeInfo root = infos.get(0);
+        final int infoCount = infos.size();
+        for (int i = 1; i < infoCount; i++) {
+            for (int j = i; j < infoCount; j++) {
+                AccessibilityNodeInfo candidate = infos.get(j);
+                if (root.getParentNodeId() == candidate.getSourceNodeId()) {
+                    root = candidate;
+                    break;
+                }
+            }
+        }
+        if (root == null) {
+            Log.e(LOG_TAG, "No root.");
+        }
+        // Check for duplicates.
+        HashSet<AccessibilityNodeInfo> seen = new HashSet<>();
+        Queue<AccessibilityNodeInfo> fringe = new LinkedList<>();
+        fringe.add(root);
+        while (!fringe.isEmpty()) {
+            AccessibilityNodeInfo current = fringe.poll();
+            if (!seen.add(current)) {
+                Log.e(LOG_TAG, "Duplicate node.");
+                return;
+            }
+            final int childCount = current.getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                final long childId = current.getChildId(i);
+                for (int j = 0; j < infoCount; j++) {
+                    AccessibilityNodeInfo child = infos.get(j);
+                    if (child.getSourceNodeId() == childId) {
+                        fringe.add(child);
+                    }
+                }
+            }
+        }
+        final int disconnectedCount = infos.size() - seen.size();
+        if (disconnectedCount > 0) {
+            Log.e(LOG_TAG, disconnectedCount + " Disconnected nodes.");
+        }
+    }
+
+    /**
+     * Update scroll event timestamp of a given window.
+     *
+     * @param windowId The window id.
+     * @param uptimeMillis Device uptime millis.
+     */
+    private void updateScrollingWindow(int windowId, long uptimeMillis) {
+        synchronized (sScrollingWindows) {
+            sScrollingWindows.put(windowId, uptimeMillis);
+        }
+    }
+
+    /**
+     * Remove a window from the scrolling windows list.
+     *
+     * @param windowId The window id.
+     */
+    private void deleteScrollingWindow(int windowId) {
+        synchronized (sScrollingWindows) {
+            sScrollingWindows.delete(windowId);
+        }
+    }
+
+    /**
+     * Whether or not the window is scrolling.
+     *
+     * @param windowId
+     * @return true if it's scrolling.
+     */
+    private boolean isWindowScrolling(int windowId) {
+        synchronized (sScrollingWindows) {
+            final long latestScrollingTime = sScrollingWindows.get(windowId);
+            if (latestScrollingTime == 0) {
+                return false;
+            }
+            final long currentUptime = SystemClock.uptimeMillis();
+            if (currentUptime > (latestScrollingTime + DISABLE_PREFETCHING_FOR_SCROLLING_MILLIS)) {
+                sScrollingWindows.delete(windowId);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private void logTrace(
+            IAccessibilityServiceConnection connection, String method, String params,
+            int callingUid) {
+        try {
+            Bundle b = new Bundle();
+            ArrayList<StackTraceElement> callStack = new ArrayList<StackTraceElement>(
+                    Arrays.asList(Thread.currentThread().getStackTrace()));
+            b.putSerializable(CALL_STACK, callStack);
+            connection.logTrace(SystemClock.elapsedRealtimeNanos(),
+                    LOG_TAG + ".callback for " + method, params, Process.myPid(),
+                    Thread.currentThread().getId(), callingUid, b);
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG, "Failed to log trace. " + e);
+        }
+    }
+
+    private void logTrace(
+            IAccessibilityServiceConnection connection, String method, String params) {
+        logTrace(connection, method, params, mCallingUid);
+    }
+}
diff --git a/android/view/accessibility/AccessibilityManager.java b/android/view/accessibility/AccessibilityManager.java
new file mode 100644
index 0000000..4454f29
--- /dev/null
+++ b/android/view/accessibility/AccessibilityManager.java
@@ -0,0 +1,371 @@
+/*
+ * 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.view.accessibility;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.AccessibilityServiceInfo.FeedbackType;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.ServiceInfo;
+import android.os.Handler;
+import android.view.IWindow;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent.EventType;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * System level service that serves as an event dispatch for {@link AccessibilityEvent}s.
+ * Such events are generated when something notable happens in the user interface,
+ * for example an {@link android.app.Activity} starts, the focus or selection of a
+ * {@link android.view.View} changes etc. Parties interested in handling accessibility
+ * events implement and register an accessibility service which extends
+ * {@code android.accessibilityservice.AccessibilityService}.
+ *
+ * @see AccessibilityEvent
+ * @see android.content.Context#getSystemService
+ */
+@SuppressWarnings("UnusedDeclaration")
+public final class AccessibilityManager {
+
+    private static AccessibilityManager sInstance = new AccessibilityManager(null, null, 0);
+
+
+    /**
+     * Listener for the accessibility state.
+     */
+    public interface AccessibilityStateChangeListener {
+
+        /**
+         * Called back on change in the accessibility state.
+         *
+         * @param enabled Whether accessibility is enabled.
+         */
+        public void onAccessibilityStateChanged(boolean enabled);
+    }
+
+    /**
+     * Listener for the system touch exploration state. To listen for changes to
+     * the touch exploration state on the device, implement this interface and
+     * register it with the system by calling
+     * {@link #addTouchExplorationStateChangeListener}.
+     */
+    public interface TouchExplorationStateChangeListener {
+
+        /**
+         * Called when the touch exploration enabled state changes.
+         *
+         * @param enabled Whether touch exploration is enabled.
+         */
+        public void onTouchExplorationStateChanged(boolean enabled);
+    }
+
+    /**
+     * Listener for the system high text contrast state. To listen for changes to
+     * the high text contrast state on the device, implement this interface and
+     * register it with the system by calling
+     * {@link #addHighTextContrastStateChangeListener}.
+     */
+    public interface HighTextContrastChangeListener {
+
+        /**
+         * Called when the high text contrast enabled state changes.
+         *
+         * @param enabled Whether high text contrast is enabled.
+         */
+        public void onHighTextContrastStateChanged(boolean enabled);
+    }
+
+    /**
+     * Policy to inject behavior into the accessibility manager.
+     *
+     * @hide
+     */
+    public interface AccessibilityPolicy {
+        /**
+         * Checks whether accessibility is enabled.
+         *
+         * @param accessibilityEnabled Whether the accessibility layer is enabled.
+         * @return whether accessibility is enabled.
+         */
+        boolean isEnabled(boolean accessibilityEnabled);
+
+        /**
+         * Notifies the policy for an accessibility event.
+         *
+         * @param event The event.
+         * @param accessibilityEnabled Whether the accessibility layer is enabled.
+         * @param relevantEventTypes The events relevant events.
+         * @return The event to dispatch or null.
+         */
+        @Nullable AccessibilityEvent onAccessibilityEvent(@NonNull AccessibilityEvent event,
+                boolean accessibilityEnabled, @EventType int relevantEventTypes);
+
+        /**
+         * Gets the list of relevant events.
+         *
+         * @param relevantEventTypes The relevant events.
+         * @return The relevant events to report.
+         */
+        @EventType int getRelevantEventTypes(@EventType int relevantEventTypes);
+
+        /**
+         * Gets the list of installed services to report.
+         *
+         * @param installedService The installed services.
+         * @return The services to report.
+         */
+        @NonNull List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(
+                @Nullable List<AccessibilityServiceInfo> installedService);
+
+        /**
+         * Gets the list of enabled accessibility services.
+         *
+         * @param feedbackTypeFlags The feedback type to query for.
+         * @param enabledService The enabled services.
+         * @return The services to report.
+         */
+        @Nullable List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
+                @FeedbackType int feedbackTypeFlags,
+                @Nullable List<AccessibilityServiceInfo> enabledService);
+    }
+
+    private final IAccessibilityManagerClient.Stub mClient =
+            new IAccessibilityManagerClient.Stub() {
+                public void setState(int state) {
+                }
+
+                public void notifyServicesStateChanged(long updatedUiTimeout) {
+                }
+
+                public void setRelevantEventTypes(int eventTypes) {
+                }
+
+                public void setFocusAppearance(int strokeWidth, int color) {
+                }
+            };
+
+    /**
+     * Get an AccessibilityManager instance (create one if necessary).
+     *
+     */
+    public static AccessibilityManager getInstance(Context context) {
+        return sInstance;
+    }
+
+    /**
+     * Create an instance.
+     *
+     * @param context A {@link Context}.
+     */
+    public AccessibilityManager(Context context, IAccessibilityManager service, int userId) {
+    }
+
+    public IAccessibilityManagerClient getClient() {
+        return mClient;
+    }
+
+    /**
+     * Returns if the {@link AccessibilityManager} is enabled.
+     *
+     * @return True if this {@link AccessibilityManager} is enabled, false otherwise.
+     */
+    public boolean isEnabled() {
+        return false;
+    }
+
+    /**
+     * Returns if the touch exploration in the system is enabled.
+     *
+     * @return True if touch exploration is enabled, false otherwise.
+     */
+    public boolean isTouchExplorationEnabled() {
+        return true;
+    }
+
+    /**
+     * Returns if the high text contrast in the system is enabled.
+     * <p>
+     * <strong>Note:</strong> You need to query this only if you application is
+     * doing its own rendering and does not rely on the platform rendering pipeline.
+     * </p>
+     *
+     */
+    public boolean isHighTextContrastEnabled() {
+        return false;
+    }
+
+    /**
+     * Sends an {@link AccessibilityEvent}.
+     */
+    public void sendAccessibilityEvent(AccessibilityEvent event) {
+    }
+
+    /**
+     * Returns whether there are observers registered for this event type. If
+     * this method returns false you shuold not generate events of this type
+     * to conserve resources.
+     *
+     * @param type The event type.
+     * @return Whether the event is being observed.
+     */
+    public boolean isObservedEventType(@AccessibilityEvent.EventType int type) {
+        return false;
+    }
+
+    /**
+     * Requests interruption of the accessibility feedback from all accessibility services.
+     */
+    public void interrupt() {
+    }
+
+    /**
+     * Returns the {@link ServiceInfo}s of the installed accessibility services.
+     *
+     * @return An unmodifiable list with {@link ServiceInfo}s.
+     */
+    @Deprecated
+    public List<ServiceInfo> getAccessibilityServiceList() {
+        return Collections.emptyList();
+    }
+
+    public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Returns the {@link AccessibilityServiceInfo}s of the enabled accessibility services
+     * for a given feedback type.
+     *
+     * @param feedbackTypeFlags The feedback type flags.
+     * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
+     *
+     * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE
+     * @see AccessibilityServiceInfo#FEEDBACK_GENERIC
+     * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
+     * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
+     * @see AccessibilityServiceInfo#FEEDBACK_VISUAL
+     */
+    public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
+            int feedbackTypeFlags) {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Registers an {@link AccessibilityStateChangeListener} for changes in
+     * the global accessibility state of the system.
+     *
+     * @param listener The listener.
+     * @return True if successfully registered.
+     */
+    public boolean addAccessibilityStateChangeListener(
+            AccessibilityStateChangeListener listener) {
+        return true;
+    }
+
+    /**
+     * Registers an {@link AccessibilityStateChangeListener} for changes in
+     * the global accessibility state of the system. If the listener has already been registered,
+     * the handler used to call it back is updated.
+     *
+     * @param listener The listener.
+     * @param handler The handler on which the listener should be called back, or {@code null}
+     *                for a callback on the process's main handler.
+     */
+    public void addAccessibilityStateChangeListener(
+            @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {}
+
+    public boolean removeAccessibilityStateChangeListener(
+            AccessibilityStateChangeListener listener) {
+        return true;
+    }
+
+    /**
+     * Registers a {@link TouchExplorationStateChangeListener} for changes in
+     * the global touch exploration state of the system.
+     *
+     * @param listener The listener.
+     * @return True if successfully registered.
+     */
+    public boolean addTouchExplorationStateChangeListener(
+            @NonNull TouchExplorationStateChangeListener listener) {
+        return true;
+    }
+
+    /**
+     * Registers an {@link TouchExplorationStateChangeListener} for changes in
+     * the global touch exploration state of the system. If the listener has already been
+     * registered, the handler used to call it back is updated.
+     *
+     * @param listener The listener.
+     * @param handler The handler on which the listener should be called back, or {@code null}
+     *                for a callback on the process's main handler.
+     */
+    public void addTouchExplorationStateChangeListener(
+            @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {}
+
+    /**
+     * Unregisters a {@link TouchExplorationStateChangeListener}.
+     *
+     * @param listener The listener.
+     * @return True if successfully unregistered.
+     */
+    public boolean removeTouchExplorationStateChangeListener(
+            @NonNull TouchExplorationStateChangeListener listener) {
+        return true;
+    }
+
+    /**
+     * Registers a {@link HighTextContrastChangeListener} for changes in
+     * the global high text contrast state of the system.
+     *
+     * @param listener The listener.
+     *
+     * @hide
+     */
+    public void addHighTextContrastStateChangeListener(
+            @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {}
+
+    /**
+     * Unregisters a {@link HighTextContrastChangeListener}.
+     *
+     * @param listener The listener.
+     *
+     * @hide
+     */
+    public void removeHighTextContrastStateChangeListener(
+            @NonNull HighTextContrastChangeListener listener) {}
+
+    /**
+     * Sets the current state and notifies listeners, if necessary.
+     *
+     * @param stateFlags The state flags.
+     */
+    private void setStateLocked(int stateFlags) {
+    }
+
+    public int addAccessibilityInteractionConnection(IWindow windowToken,
+            IAccessibilityInteractionConnection connection) {
+        return View.NO_ID;
+    }
+
+    public void removeAccessibilityInteractionConnection(IWindow windowToken) {
+    }
+
+}
diff --git a/android/view/accessibility/AccessibilityNodeIdManager.java b/android/view/accessibility/AccessibilityNodeIdManager.java
new file mode 100644
index 0000000..e377117
--- /dev/null
+++ b/android/view/accessibility/AccessibilityNodeIdManager.java
@@ -0,0 +1,61 @@
+/*
+ * 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.view.accessibility;
+
+import android.view.View;
+
+/**
+ * Class that replaces the original AccessibilityNodeIdManager with a no-op version. This avoids
+ * the accidental leaking of views referenced by the originalin layoutlib.
+ */
+public final class AccessibilityNodeIdManager {
+    private static AccessibilityNodeIdManager sIdManager = new AccessibilityNodeIdManager();
+
+
+    public static synchronized AccessibilityNodeIdManager getInstance() {
+        return sIdManager;
+    }
+
+    private AccessibilityNodeIdManager() {
+    }
+
+    /**
+     * Register view to be kept track of by the accessibility system.
+     * Must be paired with unregisterView, otherwise this will leak.
+     * @param view The view to be registered.
+     * @param id The accessibilityViewId of the view.
+     */
+    public void registerViewWithId(View view, int id) {
+    }
+
+    /**
+     * Unregister view, accessibility won't keep track of this view after this call.
+     * @param id The id returned from registerView when the view as first associated.
+     */
+    public void unregisterViewWithId(int id) {
+    }
+
+    /**
+     * Accessibility uses this to find the view in the hierarchy.
+     * @param id The accessibility view id.
+     * @return The view.
+     */
+    public View findView(int id) {
+       return null;
+    }
+}
+
diff --git a/android/view/accessibility/AccessibilityNodeInfo.java b/android/view/accessibility/AccessibilityNodeInfo.java
new file mode 100644
index 0000000..085eb81
--- /dev/null
+++ b/android/view/accessibility/AccessibilityNodeInfo.java
@@ -0,0 +1,5974 @@
+/*
+ * 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.view.accessibility;
+
+import static com.android.internal.util.BitUtils.bitAt;
+import static com.android.internal.util.BitUtils.isBitSet;
+
+import static java.util.Collections.EMPTY_LIST;
+
+import android.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.InputType;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.style.AccessibilityClickableSpan;
+import android.text.style.AccessibilityReplacementSpan;
+import android.text.style.AccessibilityURLSpan;
+import android.text.style.ClickableSpan;
+import android.text.style.ReplacementSpan;
+import android.text.style.URLSpan;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.LongArray;
+import android.util.Pools.SynchronizedPool;
+import android.util.Size;
+import android.util.TypedValue;
+import android.view.SurfaceView;
+import android.view.TouchDelegate;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.android.internal.R;
+import com.android.internal.util.CollectionUtils;
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * This class represents a node of the window content as well as actions that
+ * can be requested from its source. From the point of view of an
+ * {@link android.accessibilityservice.AccessibilityService} a window's content is
+ * presented as a tree of accessibility node infos, which may or may not map one-to-one
+ * to the view hierarchy. In other words, a custom view is free to report itself as
+ * a tree of accessibility node info.
+ * </p>
+ * <p>
+ * Once an accessibility node info is delivered to an accessibility service it is
+ * made immutable and calling a state mutation method generates an error.
+ * </p>
+ * <p>
+ * Please refer to {@link android.accessibilityservice.AccessibilityService} for
+ * details about how to obtain a handle to window content as a tree of accessibility
+ * node info as well as details about the security model.
+ * </p>
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about making applications accessible, read the
+ * <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a>
+ * developer guide.</p>
+ * </div>
+ *
+ * @see android.accessibilityservice.AccessibilityService
+ * @see AccessibilityEvent
+ * @see AccessibilityManager
+ */
+public class AccessibilityNodeInfo implements Parcelable {
+
+    private static final String TAG = "AccessibilityNodeInfo";
+
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG) && Build.IS_DEBUGGABLE;
+
+    /** @hide */
+    public static final int UNDEFINED_CONNECTION_ID = -1;
+
+    /** @hide */
+    public static final int UNDEFINED_SELECTION_INDEX = -1;
+
+    /** @hide */
+    public static final int UNDEFINED_ITEM_ID = Integer.MAX_VALUE;
+
+    /** @hide */
+    public static final int ROOT_ITEM_ID = Integer.MAX_VALUE - 1;
+
+    /** @hide */
+    public static final int LEASHED_ITEM_ID = Integer.MAX_VALUE - 2;
+
+    /** @hide */
+    public static final long UNDEFINED_NODE_ID = makeNodeId(UNDEFINED_ITEM_ID, UNDEFINED_ITEM_ID);
+
+    /** @hide */
+    public static final long ROOT_NODE_ID = makeNodeId(ROOT_ITEM_ID,
+            AccessibilityNodeProvider.HOST_VIEW_ID);
+
+    /** @hide */
+    public static final long LEASHED_NODE_ID = makeNodeId(LEASHED_ITEM_ID,
+            AccessibilityNodeProvider.HOST_VIEW_ID);
+
+    /** @hide */
+    public static final int FLAG_PREFETCH_PREDECESSORS = 0x00000001;
+
+    /** @hide */
+    public static final int FLAG_PREFETCH_SIBLINGS = 0x00000002;
+
+    /** @hide */
+    public static final int FLAG_PREFETCH_DESCENDANTS = 0x00000004;
+
+    /** @hide */
+    public static final int FLAG_PREFETCH_MASK = 0x00000007;
+
+    /** @hide */
+    public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000008;
+
+    /** @hide */
+    public static final int FLAG_REPORT_VIEW_IDS = 0x00000010;
+
+    // Actions.
+
+    /**
+     * Action that gives input focus to the node.
+     */
+    public static final int ACTION_FOCUS =  0x00000001;
+
+    /**
+     * Action that clears input focus of the node.
+     */
+    public static final int ACTION_CLEAR_FOCUS = 0x00000002;
+
+    /**
+     * Action that selects the node.
+     */
+    public static final int ACTION_SELECT = 0x00000004;
+
+    /**
+     * Action that deselects the node.
+     */
+    public static final int ACTION_CLEAR_SELECTION = 0x00000008;
+
+    /**
+     * Action that clicks on the node info.
+     *
+     * See {@link AccessibilityAction#ACTION_CLICK}
+     */
+    public static final int ACTION_CLICK = 0x00000010;
+
+    /**
+     * Action that long clicks on the node.
+     *
+     * <p>It does not support coordinate information for anchoring.</p>
+     */
+    public static final int ACTION_LONG_CLICK = 0x00000020;
+
+    /**
+     * Action that gives accessibility focus to the node.
+     */
+    public static final int ACTION_ACCESSIBILITY_FOCUS = 0x00000040;
+
+    /**
+     * Action that clears accessibility focus of the node.
+     */
+    public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 0x00000080;
+
+    /**
+     * Action that requests to go to the next entity in this node's text
+     * at a given movement granularity. For example, move to the next character,
+     * word, etc.
+     * <p>
+     * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<,
+     * {@link #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}<br>
+     * <strong>Example:</strong> Move to the previous character and do not extend selection.
+     * <code><pre><p>
+     *   Bundle arguments = new Bundle();
+     *   arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+     *           AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+     *   arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN,
+     *           false);
+     *   info.performAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+     * </code></pre></p>
+     * </p>
+     *
+     * @see #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+     * @see #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+     *
+     * @see #setMovementGranularities(int)
+     * @see #getMovementGranularities()
+     *
+     * @see #MOVEMENT_GRANULARITY_CHARACTER
+     * @see #MOVEMENT_GRANULARITY_WORD
+     * @see #MOVEMENT_GRANULARITY_LINE
+     * @see #MOVEMENT_GRANULARITY_PARAGRAPH
+     * @see #MOVEMENT_GRANULARITY_PAGE
+     */
+    public static final int ACTION_NEXT_AT_MOVEMENT_GRANULARITY = 0x00000100;
+
+    /**
+     * Action that requests to go to the previous entity in this node's text
+     * at a given movement granularity. For example, move to the next character,
+     * word, etc.
+     * <p>
+     * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<,
+     * {@link #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}<br>
+     * <strong>Example:</strong> Move to the next character and do not extend selection.
+     * <code><pre><p>
+     *   Bundle arguments = new Bundle();
+     *   arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+     *           AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+     *   arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN,
+     *           false);
+     *   info.performAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
+     *           arguments);
+     * </code></pre></p>
+     * </p>
+     *
+     * @see #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+     * @see #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+     *
+     * @see #setMovementGranularities(int)
+     * @see #getMovementGranularities()
+     *
+     * @see #MOVEMENT_GRANULARITY_CHARACTER
+     * @see #MOVEMENT_GRANULARITY_WORD
+     * @see #MOVEMENT_GRANULARITY_LINE
+     * @see #MOVEMENT_GRANULARITY_PARAGRAPH
+     * @see #MOVEMENT_GRANULARITY_PAGE
+     */
+    public static final int ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY = 0x00000200;
+
+    /**
+     * Action to move to the next HTML element of a given type. For example, move
+     * to the BUTTON, INPUT, TABLE, etc.
+     * <p>
+     * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_HTML_ELEMENT_STRING}<br>
+     * <strong>Example:</strong>
+     * <code><pre><p>
+     *   Bundle arguments = new Bundle();
+     *   arguments.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "BUTTON");
+     *   info.performAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, arguments);
+     * </code></pre></p>
+     * </p>
+     */
+    public static final int ACTION_NEXT_HTML_ELEMENT = 0x00000400;
+
+    /**
+     * Action to move to the previous HTML element of a given type. For example, move
+     * to the BUTTON, INPUT, TABLE, etc.
+     * <p>
+     * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_HTML_ELEMENT_STRING}<br>
+     * <strong>Example:</strong>
+     * <code><pre><p>
+     *   Bundle arguments = new Bundle();
+     *   arguments.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "BUTTON");
+     *   info.performAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT, arguments);
+     * </code></pre></p>
+     * </p>
+     */
+    public static final int ACTION_PREVIOUS_HTML_ELEMENT = 0x00000800;
+
+    /**
+     * Action to scroll the node content forward.
+     */
+    public static final int ACTION_SCROLL_FORWARD = 0x00001000;
+
+    /**
+     * Action to scroll the node content backward.
+     */
+    public static final int ACTION_SCROLL_BACKWARD = 0x00002000;
+
+    /**
+     * Action to copy the current selection to the clipboard.
+     */
+    public static final int ACTION_COPY = 0x00004000;
+
+    /**
+     * Action to paste the current clipboard content.
+     */
+    public static final int ACTION_PASTE = 0x00008000;
+
+    /**
+     * Action to cut the current selection and place it to the clipboard.
+     */
+    public static final int ACTION_CUT = 0x00010000;
+
+    /**
+     * Action to set the selection. Performing this action with no arguments
+     * clears the selection.
+     * <p>
+     * <strong>Arguments:</strong>
+     * {@link #ACTION_ARGUMENT_SELECTION_START_INT},
+     * {@link #ACTION_ARGUMENT_SELECTION_END_INT}<br>
+     * <strong>Example:</strong>
+     * <code><pre><p>
+     *   Bundle arguments = new Bundle();
+     *   arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 1);
+     *   arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 2);
+     *   info.performAction(AccessibilityNodeInfo.ACTION_SET_SELECTION, arguments);
+     * </code></pre></p>
+     * </p>
+     *
+     * @see #ACTION_ARGUMENT_SELECTION_START_INT
+     * @see #ACTION_ARGUMENT_SELECTION_END_INT
+     */
+    public static final int ACTION_SET_SELECTION = 0x00020000;
+
+    /**
+     * Action to expand an expandable node.
+     */
+    public static final int ACTION_EXPAND = 0x00040000;
+
+    /**
+     * Action to collapse an expandable node.
+     */
+    public static final int ACTION_COLLAPSE = 0x00080000;
+
+    /**
+     * Action to dismiss a dismissable node.
+     */
+    public static final int ACTION_DISMISS = 0x00100000;
+
+    /**
+     * Action that sets the text of the node. Performing the action without argument, using <code>
+     * null</code> or empty {@link CharSequence} will clear the text. This action will also put the
+     * cursor at the end of text.
+     * <p>
+     * <strong>Arguments:</strong>
+     * {@link #ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE}<br>
+     * <strong>Example:</strong>
+     * <code><pre><p>
+     *   Bundle arguments = new Bundle();
+     *   arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,
+     *       "android");
+     *   info.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
+     * </code></pre></p>
+     */
+    public static final int ACTION_SET_TEXT = 0x00200000;
+
+    /** @hide */
+    public static final int LAST_LEGACY_STANDARD_ACTION = ACTION_SET_TEXT;
+
+    /**
+     * Mask to see if the value is larger than the largest ACTION_ constant
+     */
+    private static final int ACTION_TYPE_MASK = 0xFF000000;
+
+    // Action arguments
+
+    /**
+     * Argument for which movement granularity to be used when traversing the node text.
+     * <p>
+     * <strong>Type:</strong> int<br>
+     * <strong>Actions:</strong>
+     * <ul>
+     *     <li>{@link AccessibilityAction#ACTION_NEXT_AT_MOVEMENT_GRANULARITY}</li>
+     *     <li>{@link AccessibilityAction#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY}</li>
+     * </ul>
+     * </p>
+     *
+     * @see AccessibilityAction#ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+     * @see AccessibilityAction#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+     */
+    public static final String ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT =
+            "ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT";
+
+    /**
+     * Argument for which HTML element to get moving to the next/previous HTML element.
+     * <p>
+     * <strong>Type:</strong> String<br>
+     * <strong>Actions:</strong>
+     * <ul>
+     *     <li>{@link AccessibilityAction#ACTION_NEXT_HTML_ELEMENT}</li>
+     *     <li>{@link AccessibilityAction#ACTION_PREVIOUS_HTML_ELEMENT}</li>
+     * </ul>
+     * </p>
+     *
+     * @see AccessibilityAction#ACTION_NEXT_HTML_ELEMENT
+     * @see AccessibilityAction#ACTION_PREVIOUS_HTML_ELEMENT
+     */
+    public static final String ACTION_ARGUMENT_HTML_ELEMENT_STRING =
+            "ACTION_ARGUMENT_HTML_ELEMENT_STRING";
+
+    /**
+     * Argument for whether when moving at granularity to extend the selection
+     * or to move it otherwise.
+     * <p>
+     * <strong>Type:</strong> boolean<br>
+     * <strong>Actions:</strong>
+     * <ul>
+     *     <li>{@link AccessibilityAction#ACTION_NEXT_AT_MOVEMENT_GRANULARITY}</li>
+     *     <li>{@link AccessibilityAction#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY}</li>
+     * </ul>
+     *
+     * @see AccessibilityAction#ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+     * @see AccessibilityAction#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+     */
+    public static final String ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN =
+            "ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN";
+
+    /**
+     * Argument for specifying the selection start.
+     * <p>
+     * <strong>Type:</strong> int<br>
+     * <strong>Actions:</strong>
+     * <ul>
+     *     <li>{@link AccessibilityAction#ACTION_SET_SELECTION}</li>
+     * </ul>
+     *
+     * @see AccessibilityAction#ACTION_SET_SELECTION
+     */
+    public static final String ACTION_ARGUMENT_SELECTION_START_INT =
+            "ACTION_ARGUMENT_SELECTION_START_INT";
+
+    /**
+     * Argument for specifying the selection end.
+     * <p>
+     * <strong>Type:</strong> int<br>
+     * <strong>Actions:</strong>
+     * <ul>
+     *     <li>{@link AccessibilityAction#ACTION_SET_SELECTION}</li>
+     * </ul>
+     *
+     * @see AccessibilityAction#ACTION_SET_SELECTION
+     */
+    public static final String ACTION_ARGUMENT_SELECTION_END_INT =
+            "ACTION_ARGUMENT_SELECTION_END_INT";
+
+    /**
+     * Argument for specifying the text content to set.
+     * <p>
+     * <strong>Type:</strong> CharSequence<br>
+     * <strong>Actions:</strong>
+     * <ul>
+     *     <li>{@link AccessibilityAction#ACTION_SET_TEXT}</li>
+     * </ul>
+     *
+     * @see AccessibilityAction#ACTION_SET_TEXT
+     */
+    public static final String ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE =
+            "ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE";
+
+    /**
+     * Argument for specifying the collection row to make visible on screen.
+     * <p>
+     * <strong>Type:</strong> int<br>
+     * <strong>Actions:</strong>
+     * <ul>
+     *     <li>{@link AccessibilityAction#ACTION_SCROLL_TO_POSITION}</li>
+     * </ul>
+     *
+     * @see AccessibilityAction#ACTION_SCROLL_TO_POSITION
+     */
+    public static final String ACTION_ARGUMENT_ROW_INT =
+            "android.view.accessibility.action.ARGUMENT_ROW_INT";
+
+    /**
+     * Argument for specifying the collection column to make visible on screen.
+     * <p>
+     * <strong>Type:</strong> int<br>
+     * <strong>Actions:</strong>
+     * <ul>
+     *     <li>{@link AccessibilityAction#ACTION_SCROLL_TO_POSITION}</li>
+     * </ul>
+     *
+     * @see AccessibilityAction#ACTION_SCROLL_TO_POSITION
+     */
+    public static final String ACTION_ARGUMENT_COLUMN_INT =
+            "android.view.accessibility.action.ARGUMENT_COLUMN_INT";
+
+    /**
+     * Argument for specifying the progress value to set.
+     * <p>
+     * <strong>Type:</strong> float<br>
+     * <strong>Actions:</strong>
+     * <ul>
+     *     <li>{@link AccessibilityAction#ACTION_SET_PROGRESS}</li>
+     * </ul>
+     *
+     * @see AccessibilityAction#ACTION_SET_PROGRESS
+     */
+    public static final String ACTION_ARGUMENT_PROGRESS_VALUE =
+            "android.view.accessibility.action.ARGUMENT_PROGRESS_VALUE";
+
+    /**
+     * Argument for specifying the x coordinate to which to move a window.
+     * <p>
+     * <strong>Type:</strong> int<br>
+     * <strong>Actions:</strong>
+     * <ul>
+     *     <li>{@link AccessibilityAction#ACTION_MOVE_WINDOW}</li>
+     * </ul>
+     *
+     * @see AccessibilityAction#ACTION_MOVE_WINDOW
+     */
+    public static final String ACTION_ARGUMENT_MOVE_WINDOW_X =
+            "ACTION_ARGUMENT_MOVE_WINDOW_X";
+
+    /**
+     * Argument for specifying the y coordinate to which to move a window.
+     * <p>
+     * <strong>Type:</strong> int<br>
+     * <strong>Actions:</strong>
+     * <ul>
+     *     <li>{@link AccessibilityAction#ACTION_MOVE_WINDOW}</li>
+     * </ul>
+     *
+     * @see AccessibilityAction#ACTION_MOVE_WINDOW
+     */
+    public static final String ACTION_ARGUMENT_MOVE_WINDOW_Y =
+            "ACTION_ARGUMENT_MOVE_WINDOW_Y";
+
+    /**
+     * Argument to pass the {@link AccessibilityClickableSpan}.
+     * For use with R.id.accessibilityActionClickOnClickableSpan
+     * @hide
+     */
+    public static final String ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN =
+            "android.view.accessibility.action.ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN";
+
+    /**
+     * Argument to represent the duration in milliseconds to press and hold a node.
+     * <p>
+     * <strong>Type:</strong> int<br>
+     * <strong>Actions:</strong>
+     * <ul>
+     *     <li>{@link AccessibilityAction#ACTION_PRESS_AND_HOLD}</li>
+     * </ul>
+     *
+     * @see AccessibilityAction#ACTION_PRESS_AND_HOLD
+     */
+    public static final String ACTION_ARGUMENT_PRESS_AND_HOLD_DURATION_MILLIS_INT =
+            "android.view.accessibility.action.ARGUMENT_PRESS_AND_HOLD_DURATION_MILLIS_INT";
+
+    // Focus types
+
+    /**
+     * The input focus.
+     */
+    public static final int FOCUS_INPUT = 1;
+
+    /**
+     * The accessibility focus.
+     */
+    public static final int FOCUS_ACCESSIBILITY = 2;
+
+    // Movement granularities
+
+    /**
+     * Movement granularity bit for traversing the text of a node by character.
+     */
+    public static final int MOVEMENT_GRANULARITY_CHARACTER = 0x00000001;
+
+    /**
+     * Movement granularity bit for traversing the text of a node by word.
+     */
+    public static final int MOVEMENT_GRANULARITY_WORD = 0x00000002;
+
+    /**
+     * Movement granularity bit for traversing the text of a node by line.
+     */
+    public static final int MOVEMENT_GRANULARITY_LINE = 0x00000004;
+
+    /**
+     * Movement granularity bit for traversing the text of a node by paragraph.
+     */
+    public static final int MOVEMENT_GRANULARITY_PARAGRAPH = 0x00000008;
+
+    /**
+     * Movement granularity bit for traversing the text of a node by page.
+     */
+    public static final int MOVEMENT_GRANULARITY_PAGE = 0x00000010;
+
+    /**
+     * Key used to request and locate extra data for text character location. This key requests that
+     * an array of {@link android.graphics.RectF}s be added to the extras. This request is made with
+     * {@link #refreshWithExtraData(String, Bundle)}. The arguments taken by this request are two
+     * integers: {@link #EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX} and
+     * {@link #EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH}. The starting index must be valid
+     * inside the CharSequence returned by {@link #getText()}, and the length must be positive.
+     * <p>
+     * The data can be retrieved from the {@code Bundle} returned by {@link #getExtras()} using this
+     * string as a key for {@link Bundle#getParcelableArray(String)}. The
+     * {@link android.graphics.RectF} will be null for characters that either do not exist or are
+     * off the screen.
+     *
+     * {@see #refreshWithExtraData(String, Bundle)}
+     */
+    public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY =
+            "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_KEY";
+
+    /**
+     * Integer argument specifying the start index of the requested text location data. Must be
+     * valid inside the CharSequence returned by {@link #getText()}.
+     *
+     * @see #EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
+     */
+    public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX =
+            "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX";
+
+    /**
+     * Integer argument specifying the end index of the requested text location data. Must be
+     * positive and no larger than {@link #EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH}.
+     *
+     * @see #EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
+     */
+    public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH =
+            "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH";
+
+    /**
+     * The maximum allowed length of the requested text location data.
+     */
+    public static final int EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_MAX_LENGTH = 20000;
+
+    /**
+     * Key used to request extra data for the rendering information.
+     * The key requests that a {@link AccessibilityNodeInfo.ExtraRenderingInfo} be added to this
+     * info. This request is made with {@link #refreshWithExtraData(String, Bundle)} without
+     * argument.
+     * <p>
+     * The data can be retrieved from the {@link ExtraRenderingInfo} returned by
+     * {@link #getExtraRenderingInfo()} using {@link ExtraRenderingInfo#getLayoutSize},
+     * {@link ExtraRenderingInfo#getTextSizeInPx()} and
+     * {@link ExtraRenderingInfo#getTextSizeUnit()}. For layout params, it is supported by both
+     * {@link TextView} and {@link ViewGroup}. For text size and unit, it is only supported by
+     * {@link TextView}.
+     *
+     * @see #refreshWithExtraData(String, Bundle)
+     */
+    public static final String EXTRA_DATA_RENDERING_INFO_KEY =
+            "android.view.accessibility.extra.DATA_RENDERING_INFO_KEY";
+
+    /** @hide */
+    public static final String EXTRA_DATA_REQUESTED_KEY =
+            "android.view.accessibility.AccessibilityNodeInfo.extra_data_requested";
+
+    // Boolean attributes.
+
+    private static final int BOOLEAN_PROPERTY_CHECKABLE = 0x00000001;
+
+    private static final int BOOLEAN_PROPERTY_CHECKED = 0x00000002;
+
+    private static final int BOOLEAN_PROPERTY_FOCUSABLE = 0x00000004;
+
+    private static final int BOOLEAN_PROPERTY_FOCUSED = 0x00000008;
+
+    private static final int BOOLEAN_PROPERTY_SELECTED = 0x00000010;
+
+    private static final int BOOLEAN_PROPERTY_CLICKABLE = 0x00000020;
+
+    private static final int BOOLEAN_PROPERTY_LONG_CLICKABLE = 0x00000040;
+
+    private static final int BOOLEAN_PROPERTY_ENABLED = 0x00000080;
+
+    private static final int BOOLEAN_PROPERTY_PASSWORD = 0x00000100;
+
+    private static final int BOOLEAN_PROPERTY_SCROLLABLE = 0x00000200;
+
+    private static final int BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED = 0x00000400;
+
+    private static final int BOOLEAN_PROPERTY_VISIBLE_TO_USER = 0x00000800;
+
+    private static final int BOOLEAN_PROPERTY_EDITABLE = 0x00001000;
+
+    private static final int BOOLEAN_PROPERTY_OPENS_POPUP = 0x00002000;
+
+    private static final int BOOLEAN_PROPERTY_DISMISSABLE = 0x00004000;
+
+    private static final int BOOLEAN_PROPERTY_MULTI_LINE = 0x00008000;
+
+    private static final int BOOLEAN_PROPERTY_CONTENT_INVALID = 0x00010000;
+
+    private static final int BOOLEAN_PROPERTY_CONTEXT_CLICKABLE = 0x00020000;
+
+    private static final int BOOLEAN_PROPERTY_IMPORTANCE = 0x0040000;
+
+    private static final int BOOLEAN_PROPERTY_SCREEN_READER_FOCUSABLE = 0x0080000;
+
+    private static final int BOOLEAN_PROPERTY_IS_SHOWING_HINT = 0x0100000;
+
+    private static final int BOOLEAN_PROPERTY_IS_HEADING = 0x0200000;
+
+    private static final int BOOLEAN_PROPERTY_IS_TEXT_ENTRY_KEY = 0x0400000;
+
+    /**
+     * Bits that provide the id of a virtual descendant of a view.
+     */
+    private static final long VIRTUAL_DESCENDANT_ID_MASK = 0xffffffff00000000L;
+    /**
+     * Bit shift of {@link #VIRTUAL_DESCENDANT_ID_MASK} to get to the id for a
+     * virtual descendant of a view. Such a descendant does not exist in the view
+     * hierarchy and is only reported via the accessibility APIs.
+     */
+    private static final int VIRTUAL_DESCENDANT_ID_SHIFT = 32;
+
+    // TODO(b/129300068): Remove sNumInstancesInUse.
+    private static AtomicInteger sNumInstancesInUse;
+
+    /**
+     * Gets the accessibility view id which identifies a View in the view three.
+     *
+     * @param accessibilityNodeId The id of an {@link AccessibilityNodeInfo}.
+     * @return The accessibility view id part of the node id.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public static int getAccessibilityViewId(long accessibilityNodeId) {
+        return (int) accessibilityNodeId;
+    }
+
+    /**
+     * Gets the virtual descendant id which identifies an imaginary view in a
+     * containing View.
+     *
+     * @param accessibilityNodeId The id of an {@link AccessibilityNodeInfo}.
+     * @return The virtual view id part of the node id.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public static int getVirtualDescendantId(long accessibilityNodeId) {
+        return (int) ((accessibilityNodeId & VIRTUAL_DESCENDANT_ID_MASK)
+                >> VIRTUAL_DESCENDANT_ID_SHIFT);
+    }
+
+    /**
+     * Makes a node id by shifting the <code>virtualDescendantId</code>
+     * by {@link #VIRTUAL_DESCENDANT_ID_SHIFT} and taking
+     * the bitwise or with the <code>accessibilityViewId</code>.
+     *
+     * @param accessibilityViewId A View accessibility id.
+     * @param virtualDescendantId A virtual descendant id.
+     * @return The node id.
+     *
+     * @hide
+     */
+    public static long makeNodeId(int accessibilityViewId, int virtualDescendantId) {
+        return (((long) virtualDescendantId) << VIRTUAL_DESCENDANT_ID_SHIFT) | accessibilityViewId;
+    }
+
+    // Housekeeping.
+    private static final int MAX_POOL_SIZE = 50;
+    private static final SynchronizedPool<AccessibilityNodeInfo> sPool =
+            new SynchronizedPool<>(MAX_POOL_SIZE);
+
+    private static final AccessibilityNodeInfo DEFAULT = new AccessibilityNodeInfo();
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private boolean mSealed;
+
+    // Data.
+    private int mWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+    @UnsupportedAppUsage
+    private long mSourceNodeId = UNDEFINED_NODE_ID;
+    private long mParentNodeId = UNDEFINED_NODE_ID;
+    private long mLabelForId = UNDEFINED_NODE_ID;
+    private long mLabeledById = UNDEFINED_NODE_ID;
+    private long mTraversalBefore = UNDEFINED_NODE_ID;
+    private long mTraversalAfter = UNDEFINED_NODE_ID;
+
+    private int mBooleanProperties;
+    private final Rect mBoundsInParent = new Rect();
+    private final Rect mBoundsInScreen = new Rect();
+    private int mDrawingOrderInParent;
+
+    private CharSequence mPackageName;
+    private CharSequence mClassName;
+    // Hidden, unparceled value used to hold the original value passed to setText
+    private CharSequence mOriginalText;
+    private CharSequence mText;
+    private CharSequence mHintText;
+    private CharSequence mError;
+    private CharSequence mPaneTitle;
+    private CharSequence mStateDescription;
+    private CharSequence mContentDescription;
+    private CharSequence mTooltipText;
+    private String mViewIdResourceName;
+    private ArrayList<String> mExtraDataKeys;
+
+    @UnsupportedAppUsage
+    private LongArray mChildNodeIds;
+    private ArrayList<AccessibilityAction> mActions;
+
+    private int mMaxTextLength = -1;
+    private int mMovementGranularities;
+
+    private int mTextSelectionStart = UNDEFINED_SELECTION_INDEX;
+    private int mTextSelectionEnd = UNDEFINED_SELECTION_INDEX;
+    private int mInputType = InputType.TYPE_NULL;
+    private int mLiveRegion = View.ACCESSIBILITY_LIVE_REGION_NONE;
+
+    private Bundle mExtras;
+
+    private int mConnectionId = UNDEFINED_CONNECTION_ID;
+
+    private RangeInfo mRangeInfo;
+    private CollectionInfo mCollectionInfo;
+    private CollectionItemInfo mCollectionItemInfo;
+
+    private TouchDelegateInfo mTouchDelegateInfo;
+
+    private ExtraRenderingInfo mExtraRenderingInfo;
+
+    private IBinder mLeashedChild;
+    private IBinder mLeashedParent;
+    private long mLeashedParentNodeId = UNDEFINED_NODE_ID;
+
+    /**
+     * Creates a new {@link AccessibilityNodeInfo}.
+     */
+    public AccessibilityNodeInfo() {
+    }
+
+    /**
+     * Creates a new {@link AccessibilityNodeInfo} with the given <code>source</code>.
+     *
+     * @param source The source view.
+     */
+    public AccessibilityNodeInfo(@NonNull View source) {
+        setSource(source);
+    }
+
+    /**
+     * Creates a new {@link AccessibilityNodeInfo} with the given <code>source</code>.
+     *
+     * @param root The root of the virtual subtree.
+     * @param virtualDescendantId The id of the virtual descendant.
+     */
+    public AccessibilityNodeInfo(@NonNull View root, int virtualDescendantId) {
+        setSource(root, virtualDescendantId);
+    }
+
+    /**
+     * Copy constructor. Creates a new {@link AccessibilityNodeInfo}, and this new instance is
+     * initialized from the given <code>info</code>.
+     *
+     * @param info The other info.
+     */
+    public AccessibilityNodeInfo(@NonNull AccessibilityNodeInfo info) {
+        init(info, false /* usePoolingInfo */);
+    }
+
+    /**
+     * Sets the source.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param source The info source.
+     */
+    public void setSource(View source) {
+        setSource(source, AccessibilityNodeProvider.HOST_VIEW_ID);
+    }
+
+    /**
+     * Sets the source to be a virtual descendant of the given <code>root</code>.
+     * If <code>virtualDescendantId</code> is {@link View#NO_ID} the root
+     * is set as the source.
+     * <p>
+     * A virtual descendant is an imaginary View that is reported as a part of the view
+     * hierarchy for accessibility purposes. This enables custom views that draw complex
+     * content to report themselves as a tree of virtual views, thus conveying their
+     * logical structure.
+     * </p>
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param root The root of the virtual subtree.
+     * @param virtualDescendantId The id of the virtual descendant.
+     */
+    public void setSource(View root, int virtualDescendantId) {
+        enforceNotSealed();
+        mWindowId = (root != null) ? root.getAccessibilityWindowId() : UNDEFINED_ITEM_ID;
+        final int rootAccessibilityViewId =
+            (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+        mSourceNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+    }
+
+    /**
+     * Find the view that has the specified focus type. The search starts from
+     * the view represented by this node info.
+     *
+     * <p>
+     * <strong>Note:</strong> If this view hierarchy has a {@link SurfaceView} embedding another
+     * view hierarchy via {@link SurfaceView#setChildSurfacePackage}, there is a limitation that
+     * this API won't be able to find the node for the view on the embedded view hierarchy. It's
+     * because views don't know about the embedded hierarchies. Instead, you could traverse all
+     * the children to find the node. Or, use {@link AccessibilityService#findFocus(int)} for
+     * {@link #FOCUS_ACCESSIBILITY} only since it has no such limitation.
+     * </p>
+     *
+     * @param focus The focus to find. One of {@link #FOCUS_INPUT} or
+     *         {@link #FOCUS_ACCESSIBILITY}.
+     * @return The node info of the focused view or null.
+     *
+     * @see #FOCUS_INPUT
+     * @see #FOCUS_ACCESSIBILITY
+     */
+    public AccessibilityNodeInfo findFocus(int focus) {
+        enforceSealed();
+        enforceValidFocusType(focus);
+        if (!canPerformRequestOverConnection(mConnectionId, mWindowId, mSourceNodeId)) {
+            return null;
+        }
+        return AccessibilityInteractionClient.getInstance().findFocus(mConnectionId, mWindowId,
+                mSourceNodeId, focus);
+    }
+
+    /**
+     * Searches for the nearest view in the specified direction that can take
+     * the input focus.
+     *
+     * <p>
+     * <strong>Note:</strong> If this view hierarchy has a {@link SurfaceView} embedding another
+     * view hierarchy via {@link SurfaceView#setChildSurfacePackage}, there is a limitation that
+     * this API won't be able to find the node for the view in the specified direction on the
+     * embedded view hierarchy. It's because views don't know about the embedded hierarchies.
+     * Instead, you could traverse all the children to find the node.
+     * </p>
+     *
+     * @param direction The direction. Can be one of:
+     *     {@link View#FOCUS_DOWN},
+     *     {@link View#FOCUS_UP},
+     *     {@link View#FOCUS_LEFT},
+     *     {@link View#FOCUS_RIGHT},
+     *     {@link View#FOCUS_FORWARD},
+     *     {@link View#FOCUS_BACKWARD}.
+     *
+     * @return The node info for the view that can take accessibility focus.
+     */
+    public AccessibilityNodeInfo focusSearch(int direction) {
+        enforceSealed();
+        enforceValidFocusDirection(direction);
+        if (!canPerformRequestOverConnection(mConnectionId, mWindowId, mSourceNodeId)) {
+            return null;
+        }
+        return AccessibilityInteractionClient.getInstance().focusSearch(mConnectionId, mWindowId,
+                mSourceNodeId, direction);
+    }
+
+    /**
+     * Gets the id of the window from which the info comes from.
+     *
+     * @return The window id.
+     */
+    public int getWindowId() {
+        return mWindowId;
+    }
+
+    /**
+     * Refreshes this info with the latest state of the view it represents.
+     * <p>
+     * <strong>Note:</strong> If this method returns false this info is obsolete
+     * since it represents a view that is no longer in the view tree and should
+     * be recycled.
+     * </p>
+     *
+     * @param bypassCache Whether to bypass the cache.
+     * @return Whether the refresh succeeded.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public boolean refresh(Bundle arguments, boolean bypassCache) {
+        enforceSealed();
+        if (!canPerformRequestOverConnection(mConnectionId, mWindowId, mSourceNodeId)) {
+            return false;
+        }
+        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        AccessibilityNodeInfo refreshedInfo = client.findAccessibilityNodeInfoByAccessibilityId(
+                mConnectionId, mWindowId, mSourceNodeId, bypassCache, 0, arguments);
+        if (refreshedInfo == null) {
+            return false;
+        }
+        // Hard-to-reproduce bugs seem to be due to some tools recycling a node on another
+        // thread. If that happens, the init will re-seal the node, which then is in a bad state
+        // when it is obtained. Enforce sealing again before we init to fail when a node has been
+        // recycled during a refresh to catch such errors earlier.
+        enforceSealed();
+        init(refreshedInfo, true /* usePoolingInfo */);
+        refreshedInfo.recycle();
+        return true;
+    }
+
+    /**
+     * Refreshes this info with the latest state of the view it represents.
+     *
+     * @return {@code true} if the refresh succeeded. {@code false} if the {@link View} represented
+     * by this node is no longer in the view tree (and thus this node is obsolete and should be
+     * recycled).
+     */
+    public boolean refresh() {
+        return refresh(null, true);
+    }
+
+    /**
+     * Refreshes this info with the latest state of the view it represents, and request new
+     * data be added by the View.
+     *
+     * @param extraDataKey The extra data requested. Data that must be requested
+     *                     with this mechanism is generally expensive to retrieve, so should only be
+     *                     requested when needed. See
+     *                     {@link #EXTRA_DATA_RENDERING_INFO_KEY},
+     *                     {@link #EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY},
+     *                     {@link #getAvailableExtraData()} and {@link #getExtraRenderingInfo()}.
+     * @param args A bundle of arguments for the request. These depend on the particular request.
+     *
+     * @return {@code true} if the refresh succeeded. {@code false} if the {@link View} represented
+     * by this node is no longer in the view tree (and thus this node is obsolete and should be
+     * recycled).
+     */
+    public boolean refreshWithExtraData(String extraDataKey, Bundle args) {
+        // limits the text location length to make sure the rectangle array allocation avoids
+        // the binder transaction failure and OOM crash.
+        if (args.getInt(EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1)
+                > EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_MAX_LENGTH) {
+            args.putInt(EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH,
+                    EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_MAX_LENGTH);
+        }
+
+        args.putString(EXTRA_DATA_REQUESTED_KEY, extraDataKey);
+        return refresh(args, true);
+    }
+
+    /**
+     * Returns the array containing the IDs of this node's children.
+     *
+     * @hide
+     */
+    public LongArray getChildNodeIds() {
+        return mChildNodeIds;
+    }
+
+    /**
+     * Returns the id of the child at the specified index.
+     *
+     * @throws IndexOutOfBoundsException when index &lt; 0 || index &gt;=
+     *             getChildCount()
+     * @hide
+     */
+    public long getChildId(int index) {
+        if (mChildNodeIds == null) {
+            throw new IndexOutOfBoundsException();
+        }
+        return mChildNodeIds.get(index);
+    }
+
+    /**
+     * Gets the number of children.
+     *
+     * @return The child count.
+     */
+    public int getChildCount() {
+        return mChildNodeIds == null ? 0 : mChildNodeIds.size();
+    }
+
+    /**
+     * Get the child at given index.
+     * <p>
+     *   <strong>Note:</strong> It is a client responsibility to recycle the
+     *     received info by calling {@link AccessibilityNodeInfo#recycle()}
+     *     to avoid creating of multiple instances.
+     * </p>
+     *
+     * @param index The child index.
+     * @return The child node.
+     *
+     * @throws IllegalStateException If called outside of an AccessibilityService.
+     *
+     */
+    public AccessibilityNodeInfo getChild(int index) {
+        enforceSealed();
+        if (mChildNodeIds == null) {
+            return null;
+        }
+        if (!canPerformRequestOverConnection(mConnectionId, mWindowId, mSourceNodeId)) {
+            return null;
+        }
+        final long childId = mChildNodeIds.get(index);
+        final AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        if (mLeashedChild != null && childId == LEASHED_NODE_ID) {
+            return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mLeashedChild,
+                    ROOT_NODE_ID, false, FLAG_PREFETCH_DESCENDANTS, null);
+        }
+
+        return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mWindowId,
+                childId, false, FLAG_PREFETCH_DESCENDANTS, null);
+    }
+
+    /**
+     * Adds a child.
+     * <p>
+     * <strong>Note:</strong> Cannot be called from an
+     * {@link android.accessibilityservice.AccessibilityService}.
+     * This class is made immutable before being delivered to an AccessibilityService.
+     * Note that a view cannot be made its own child.
+     * </p>
+     *
+     * @param child The child.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void addChild(View child) {
+        addChildInternal(child, AccessibilityNodeProvider.HOST_VIEW_ID, true);
+    }
+
+    /**
+     * Adds a view root from leashed content as a child. This method is used to embedded another
+     * view hierarchy.
+     * <p>
+     * <strong>Note:</strong> Only one leashed child is permitted.
+     * </p>
+     * <p>
+     * <strong>Note:</strong> Cannot be called from an
+     * {@link android.accessibilityservice.AccessibilityService}.
+     * This class is made immutable before being delivered to an AccessibilityService.
+     * Note that a view cannot be made its own child.
+     * </p>
+     *
+     * @param token The token to which a view root is added.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     * @hide
+     */
+    @TestApi
+    public void addChild(@NonNull IBinder token) {
+        enforceNotSealed();
+        if (token == null) {
+            return;
+        }
+        if (mChildNodeIds == null) {
+            mChildNodeIds = new LongArray();
+        }
+
+        mLeashedChild = token;
+        // Checking uniqueness.
+        // Since only one leashed child is permitted, skip adding ID if the ID already exists.
+        if (mChildNodeIds.indexOf(LEASHED_NODE_ID) >= 0) {
+            return;
+        }
+        mChildNodeIds.add(LEASHED_NODE_ID);
+    }
+
+    /**
+     * Unchecked version of {@link #addChild(View)} that does not verify
+     * uniqueness. For framework use only.
+     *
+     * @hide
+     */
+    public void addChildUnchecked(View child) {
+        addChildInternal(child, AccessibilityNodeProvider.HOST_VIEW_ID, false);
+    }
+
+    /**
+     * Removes a child. If the child was not previously added to the node,
+     * calling this method has no effect.
+     * <p>
+     * <strong>Note:</strong> Cannot be called from an
+     * {@link android.accessibilityservice.AccessibilityService}.
+     * This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param child The child.
+     * @return true if the child was present
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public boolean removeChild(View child) {
+        return removeChild(child, AccessibilityNodeProvider.HOST_VIEW_ID);
+    }
+
+    /**
+     * Removes a leashed child. If the child was not previously added to the node,
+     * calling this method has no effect.
+     * <p>
+     * <strong>Note:</strong> Cannot be called from an
+     * {@link android.accessibilityservice.AccessibilityService}.
+     * This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param token The token of the leashed child
+     * @return true if the child was present
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     * @hide
+     */
+    public boolean removeChild(IBinder token) {
+        enforceNotSealed();
+        if (mChildNodeIds == null || mLeashedChild == null) {
+            return false;
+        }
+        if (!mLeashedChild.equals(token)) {
+            return false;
+        }
+        final int index = mChildNodeIds.indexOf(LEASHED_NODE_ID);
+        mLeashedChild = null;
+        if (index < 0) {
+            return false;
+        }
+        mChildNodeIds.remove(index);
+        return true;
+    }
+
+    /**
+     * Adds a virtual child which is a descendant of the given <code>root</code>.
+     * If <code>virtualDescendantId</code> is {@link View#NO_ID} the root
+     * is added as a child.
+     * <p>
+     * A virtual descendant is an imaginary View that is reported as a part of the view
+     * hierarchy for accessibility purposes. This enables custom views that draw complex
+     * content to report them selves as a tree of virtual views, thus conveying their
+     * logical structure.
+     * Note that a view cannot be made its own child.
+     * </p>
+     *
+     * @param root The root of the virtual subtree.
+     * @param virtualDescendantId The id of the virtual child.
+     */
+    public void addChild(View root, int virtualDescendantId) {
+        addChildInternal(root, virtualDescendantId, true);
+    }
+
+    private void addChildInternal(View root, int virtualDescendantId, boolean checked) {
+        enforceNotSealed();
+        if (mChildNodeIds == null) {
+            mChildNodeIds = new LongArray();
+        }
+        final int rootAccessibilityViewId =
+            (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+        final long childNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+        if (childNodeId == mSourceNodeId) {
+            Log.e(TAG, "Rejecting attempt to make a View its own child");
+            return;
+        }
+
+        // If we're checking uniqueness and the ID already exists, abort.
+        if (checked && mChildNodeIds.indexOf(childNodeId) >= 0) {
+            return;
+        }
+        mChildNodeIds.add(childNodeId);
+    }
+
+    /**
+     * Removes a virtual child which is a descendant of the given
+     * <code>root</code>. If the child was not previously added to the node,
+     * calling this method has no effect.
+     *
+     * @param root The root of the virtual subtree.
+     * @param virtualDescendantId The id of the virtual child.
+     * @return true if the child was present
+     * @see #addChild(View, int)
+     */
+    public boolean removeChild(View root, int virtualDescendantId) {
+        enforceNotSealed();
+        final LongArray childIds = mChildNodeIds;
+        if (childIds == null) {
+            return false;
+        }
+        final int rootAccessibilityViewId =
+                (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+        final long childNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+        final int index = childIds.indexOf(childNodeId);
+        if (index < 0) {
+            return false;
+        }
+        childIds.remove(index);
+        return true;
+    }
+
+    /**
+     * Gets the actions that can be performed on the node.
+     */
+    public List<AccessibilityAction> getActionList() {
+        return CollectionUtils.emptyIfNull(mActions);
+    }
+
+    /**
+     * Gets the actions that can be performed on the node.
+     *
+     * @return The bit mask of with actions.
+     *
+     * @see AccessibilityNodeInfo#ACTION_FOCUS
+     * @see AccessibilityNodeInfo#ACTION_CLEAR_FOCUS
+     * @see AccessibilityNodeInfo#ACTION_SELECT
+     * @see AccessibilityNodeInfo#ACTION_CLEAR_SELECTION
+     * @see AccessibilityNodeInfo#ACTION_ACCESSIBILITY_FOCUS
+     * @see AccessibilityNodeInfo#ACTION_CLEAR_ACCESSIBILITY_FOCUS
+     * @see AccessibilityNodeInfo#ACTION_CLICK
+     * @see AccessibilityNodeInfo#ACTION_LONG_CLICK
+     * @see AccessibilityNodeInfo#ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+     * @see AccessibilityNodeInfo#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+     * @see AccessibilityNodeInfo#ACTION_NEXT_HTML_ELEMENT
+     * @see AccessibilityNodeInfo#ACTION_PREVIOUS_HTML_ELEMENT
+     * @see AccessibilityNodeInfo#ACTION_SCROLL_FORWARD
+     * @see AccessibilityNodeInfo#ACTION_SCROLL_BACKWARD
+     *
+     * @deprecated Use {@link #getActionList()}.
+     */
+    @Deprecated
+    public int getActions() {
+        int returnValue = 0;
+
+        if (mActions == null) {
+            return returnValue;
+        }
+
+        final int actionSize = mActions.size();
+        for (int i = 0; i < actionSize; i++) {
+            int actionId = mActions.get(i).getId();
+            if (actionId <= LAST_LEGACY_STANDARD_ACTION) {
+                returnValue |= actionId;
+            }
+        }
+
+        return returnValue;
+    }
+
+    /**
+     * Adds an action that can be performed on the node.
+     * <p>
+     * To add a standard action use the static constants on {@link AccessibilityAction}.
+     * To add a custom action create a new {@link AccessibilityAction} by passing in a
+     * resource id from your application as the action id and an optional label that
+     * describes the action. To override one of the standard actions use as the action
+     * id of a standard action id such as {@link #ACTION_CLICK} and an optional label that
+     * describes the action.
+     * </p>
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param action The action.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void addAction(AccessibilityAction action) {
+        enforceNotSealed();
+
+        addActionUnchecked(action);
+    }
+
+    private void addActionUnchecked(AccessibilityAction action) {
+        if (action == null) {
+            return;
+        }
+
+        if (mActions == null) {
+            mActions = new ArrayList<>();
+        }
+
+        mActions.remove(action);
+        mActions.add(action);
+    }
+
+    /**
+     * Adds an action that can be performed on the node.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param action The action.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     * @throws IllegalArgumentException If the argument is not one of the standard actions.
+     *
+     * @deprecated This has been deprecated for {@link #addAction(AccessibilityAction)}
+     */
+    @Deprecated
+    public void addAction(int action) {
+        enforceNotSealed();
+
+        if ((action & ACTION_TYPE_MASK) != 0) {
+            throw new IllegalArgumentException("Action is not a combination of the standard " +
+                    "actions: " + action);
+        }
+
+        addStandardActions(action);
+    }
+
+    /**
+     * Removes an action that can be performed on the node. If the action was
+     * not already added to the node, calling this method has no effect.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param action The action to be removed.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     * @deprecated Use {@link #removeAction(AccessibilityAction)}
+     */
+    @Deprecated
+    public void removeAction(int action) {
+        enforceNotSealed();
+
+        removeAction(getActionSingleton(action));
+    }
+
+    /**
+     * Removes an action that can be performed on the node. If the action was
+     * not already added to the node, calling this method has no effect.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param action The action to be removed.
+     * @return The action removed from the list of actions.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public boolean removeAction(AccessibilityAction action) {
+        enforceNotSealed();
+
+        if (mActions == null || action == null) {
+            return false;
+        }
+
+        return mActions.remove(action);
+    }
+
+    /**
+     * Removes all actions.
+     *
+     * @hide
+     */
+    public void removeAllActions() {
+        if (mActions != null) {
+            mActions.clear();
+        }
+    }
+
+    /**
+     * Gets the node before which this one is visited during traversal. A screen-reader
+     * must visit the content of this node before the content of the one it precedes.
+     *
+     * @return The succeeding node if such or <code>null</code>.
+     *
+     * @see #setTraversalBefore(android.view.View)
+     * @see #setTraversalBefore(android.view.View, int)
+     */
+    public AccessibilityNodeInfo getTraversalBefore() {
+        enforceSealed();
+        return getNodeForAccessibilityId(mConnectionId, mWindowId, mTraversalBefore);
+    }
+
+    /**
+     * Sets the view before whose node this one should be visited during traversal. A
+     * screen-reader must visit the content of this node before the content of the one
+     * it precedes.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param view The view providing the preceding node.
+     *
+     * @see #getTraversalBefore()
+     */
+    public void setTraversalBefore(View view) {
+        setTraversalBefore(view, AccessibilityNodeProvider.HOST_VIEW_ID);
+    }
+
+    /**
+     * Sets the node before which this one is visited during traversal. A screen-reader
+     * must visit the content of this node before the content of the one it precedes.
+     * The successor is a virtual descendant of the given <code>root</code>. If
+     * <code>virtualDescendantId</code> equals to {@link View#NO_ID} the root is set
+     * as the successor.
+     * <p>
+     * A virtual descendant is an imaginary View that is reported as a part of the view
+     * hierarchy for accessibility purposes. This enables custom views that draw complex
+     * content to report them selves as a tree of virtual views, thus conveying their
+     * logical structure.
+     * </p>
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param root The root of the virtual subtree.
+     * @param virtualDescendantId The id of the virtual descendant.
+     */
+    public void setTraversalBefore(View root, int virtualDescendantId) {
+        enforceNotSealed();
+        final int rootAccessibilityViewId = (root != null)
+                ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+        mTraversalBefore = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+    }
+
+    /**
+     * Gets the node after which this one is visited in accessibility traversal.
+     * A screen-reader must visit the content of the other node before the content
+     * of this one.
+     *
+     * @return The succeeding node if such or <code>null</code>.
+     *
+     * @see #setTraversalAfter(android.view.View)
+     * @see #setTraversalAfter(android.view.View, int)
+     */
+    public AccessibilityNodeInfo getTraversalAfter() {
+        enforceSealed();
+        return getNodeForAccessibilityId(mConnectionId, mWindowId, mTraversalAfter);
+    }
+
+    /**
+     * Sets the view whose node is visited after this one in accessibility traversal.
+     * A screen-reader must visit the content of the other node before the content
+     * of this one.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param view The previous view.
+     *
+     * @see #getTraversalAfter()
+     */
+    public void setTraversalAfter(View view) {
+        setTraversalAfter(view, AccessibilityNodeProvider.HOST_VIEW_ID);
+    }
+
+    /**
+     * Sets the node after which this one is visited in accessibility traversal.
+     * A screen-reader must visit the content of the other node before the content
+     * of this one. If <code>virtualDescendantId</code> equals to {@link View#NO_ID}
+     * the root is set as the predecessor.
+     * <p>
+     * A virtual descendant is an imaginary View that is reported as a part of the view
+     * hierarchy for accessibility purposes. This enables custom views that draw complex
+     * content to report them selves as a tree of virtual views, thus conveying their
+     * logical structure.
+     * </p>
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param root The root of the virtual subtree.
+     * @param virtualDescendantId The id of the virtual descendant.
+     */
+    public void setTraversalAfter(View root, int virtualDescendantId) {
+        enforceNotSealed();
+        final int rootAccessibilityViewId = (root != null)
+                ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+        mTraversalAfter = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+    }
+
+    /**
+     * Get the extra data available for this node.
+     * <p>
+     * Some data that is useful for some accessibility services is expensive to compute, and would
+     * place undue overhead on apps to compute all the time. That data can be requested with
+     * {@link #refreshWithExtraData(String, Bundle)}.
+     *
+     * @return An unmodifiable list of keys corresponding to extra data that can be requested.
+     * @see #EXTRA_DATA_RENDERING_INFO_KEY
+     * @see #EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
+     */
+    public List<String> getAvailableExtraData() {
+        if (mExtraDataKeys != null) {
+            return Collections.unmodifiableList(mExtraDataKeys);
+        } else {
+            return EMPTY_LIST;
+        }
+    }
+
+    /**
+     * Set the extra data available for this node.
+     * <p>
+     * <strong>Note:</strong> When a {@code View} passes in a non-empty list, it promises that
+     * it will populate the node's extras with corresponding pieces of information in
+     * {@link View#addExtraDataToAccessibilityNodeInfo(AccessibilityNodeInfo, String, Bundle)}.
+     * <p>
+     * <strong>Note:</strong> Cannot be called from an
+     * {@link android.accessibilityservice.AccessibilityService}.
+     * This class is made immutable before being delivered to an AccessibilityService.
+     *
+     * @param extraDataKeys A list of types of extra data that are available.
+     * @see #getAvailableExtraData()
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setAvailableExtraData(List<String> extraDataKeys) {
+        enforceNotSealed();
+        mExtraDataKeys = new ArrayList<>(extraDataKeys);
+    }
+
+    /**
+     * Sets the maximum text length, or -1 for no limit.
+     * <p>
+     * Typically used to indicate that an editable text field has a limit on
+     * the number of characters entered.
+     * <p>
+     * <strong>Note:</strong> Cannot be called from an
+     * {@link android.accessibilityservice.AccessibilityService}.
+     * This class is made immutable before being delivered to an AccessibilityService.
+     *
+     * @param max The maximum text length.
+     * @see #getMaxTextLength()
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setMaxTextLength(int max) {
+        enforceNotSealed();
+        mMaxTextLength = max;
+    }
+
+    /**
+     * Returns the maximum text length for this node.
+     *
+     * @return The maximum text length, or -1 for no limit.
+     * @see #setMaxTextLength(int)
+     */
+    public int getMaxTextLength() {
+        return mMaxTextLength;
+    }
+
+    /**
+     * Sets the movement granularities for traversing the text of this node.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param granularities The bit mask with granularities.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setMovementGranularities(int granularities) {
+        enforceNotSealed();
+        mMovementGranularities = granularities;
+    }
+
+    /**
+     * Gets the movement granularities for traversing the text of this node.
+     *
+     * @return The bit mask with granularities.
+     */
+    public int getMovementGranularities() {
+        return mMovementGranularities;
+    }
+
+    /**
+     * Performs an action on the node.
+     * <p>
+     *   <strong>Note:</strong> An action can be performed only if the request is made
+     *   from an {@link android.accessibilityservice.AccessibilityService}.
+     * </p>
+     *
+     * @param action The action to perform.
+     * @return True if the action was performed.
+     *
+     * @throws IllegalStateException If called outside of an AccessibilityService.
+     */
+    public boolean performAction(int action) {
+        enforceSealed();
+        if (!canPerformRequestOverConnection(mConnectionId, mWindowId, mSourceNodeId)) {
+            return false;
+        }
+        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        Bundle arguments = null;
+        if (mExtras != null) {
+            arguments = mExtras;
+        }
+        return client.performAccessibilityAction(mConnectionId, mWindowId, mSourceNodeId,
+                action, arguments);
+    }
+
+    /**
+     * Performs an action on the node.
+     * <p>
+     *   <strong>Note:</strong> An action can be performed only if the request is made
+     *   from an {@link android.accessibilityservice.AccessibilityService}.
+     * </p>
+     *
+     * @param action The action to perform.
+     * @param arguments A bundle with additional arguments.
+     * @return True if the action was performed.
+     *
+     * @throws IllegalStateException If called outside of an AccessibilityService.
+     */
+    public boolean performAction(int action, Bundle arguments) {
+        enforceSealed();
+        if (!canPerformRequestOverConnection(mConnectionId, mWindowId, mSourceNodeId)) {
+            return false;
+        }
+        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        return client.performAccessibilityAction(mConnectionId, mWindowId, mSourceNodeId,
+                action, arguments);
+    }
+
+    /**
+     * Finds {@link AccessibilityNodeInfo}s by text. The match is case
+     * insensitive containment. The search is relative to this info i.e.
+     * this info is the root of the traversed tree.
+     *
+     * <p>
+     *   <strong>Note:</strong> It is a client responsibility to recycle the
+     *     received info by calling {@link AccessibilityNodeInfo#recycle()}
+     *     to avoid creating of multiple instances.
+     * </p>
+     * <p>
+     * <strong>Note:</strong> If this view hierarchy has a {@link SurfaceView} embedding another
+     * view hierarchy via {@link SurfaceView#setChildSurfacePackage}, there is a limitation that
+     * this API won't be able to find the node for the view on the embedded view hierarchy. It's
+     * because views don't know about the embedded hierarchies. Instead, you could traverse all
+     * the children to find the node.
+     * </p>
+     *
+     * @param text The searched text.
+     * @return A list of node info.
+     */
+    public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text) {
+        enforceSealed();
+        if (!canPerformRequestOverConnection(mConnectionId, mWindowId, mSourceNodeId)) {
+            return Collections.emptyList();
+        }
+        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        return client.findAccessibilityNodeInfosByText(mConnectionId, mWindowId, mSourceNodeId,
+                text);
+    }
+
+    /**
+     * Finds {@link AccessibilityNodeInfo}s by the fully qualified view id's resource
+     * name where a fully qualified id is of the from "package:id/id_resource_name".
+     * For example, if the target application's package is "foo.bar" and the id
+     * resource name is "baz", the fully qualified resource id is "foo.bar:id/baz".
+     *
+     * <p>
+     *   <strong>Note:</strong> It is a client responsibility to recycle the
+     *     received info by calling {@link AccessibilityNodeInfo#recycle()}
+     *     to avoid creating of multiple instances.
+     * </p>
+     * <p>
+     *   <strong>Note:</strong> The primary usage of this API is for UI test automation
+     *   and in order to report the fully qualified view id if an {@link AccessibilityNodeInfo}
+     *   the client has to set the {@link AccessibilityServiceInfo#FLAG_REPORT_VIEW_IDS}
+     *   flag when configuring the {@link android.accessibilityservice.AccessibilityService}.
+     * </p>
+     * <p>
+     * <strong>Note:</strong> If this view hierarchy has a {@link SurfaceView} embedding another
+     * view hierarchy via {@link SurfaceView#setChildSurfacePackage}, there is a limitation that
+     * this API won't be able to find the node for the view on the embedded view hierarchy. It's
+     * because views don't know about the embedded hierarchies. Instead, you could traverse all
+     * the children to find the node.
+     * </p>
+     *
+     * @param viewId The fully qualified resource name of the view id to find.
+     * @return A list of node info.
+     */
+    public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(@NonNull String viewId) {
+        enforceSealed();
+        if (viewId == null) {
+            Log.e(TAG, "returns empty list due to null viewId.");
+            return Collections.emptyList();
+        }
+        if (!canPerformRequestOverConnection(mConnectionId, mWindowId, mSourceNodeId)) {
+            return Collections.emptyList();
+        }
+        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        return client.findAccessibilityNodeInfosByViewId(mConnectionId, mWindowId, mSourceNodeId,
+                viewId);
+    }
+
+    /**
+     * Gets the window to which this node belongs.
+     *
+     * @return The window.
+     *
+     * @see android.accessibilityservice.AccessibilityService#getWindows()
+     */
+    public AccessibilityWindowInfo getWindow() {
+        enforceSealed();
+        if (!canPerformRequestOverConnection(mConnectionId, mWindowId, mSourceNodeId)) {
+            return null;
+        }
+        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        return client.getWindow(mConnectionId, mWindowId);
+    }
+
+    /**
+     * Gets the parent.
+     * <p>
+     *   <strong>Note:</strong> It is a client responsibility to recycle the
+     *     received info by calling {@link AccessibilityNodeInfo#recycle()}
+     *     to avoid creating of multiple instances.
+     * </p>
+     *
+     * @return The parent.
+     */
+    public AccessibilityNodeInfo getParent() {
+        enforceSealed();
+        if (mLeashedParent != null && mLeashedParentNodeId != UNDEFINED_NODE_ID) {
+            return getNodeForAccessibilityId(mConnectionId, mLeashedParent, mLeashedParentNodeId);
+        }
+        return getNodeForAccessibilityId(mConnectionId, mWindowId, mParentNodeId);
+    }
+
+    /**
+     * @return The parent node id.
+     *
+     * @hide
+     */
+    public long getParentNodeId() {
+        return mParentNodeId;
+    }
+
+    /**
+     * Sets the parent.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param parent The parent.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setParent(View parent) {
+        setParent(parent, AccessibilityNodeProvider.HOST_VIEW_ID);
+    }
+
+    /**
+     * Sets the parent to be a virtual descendant of the given <code>root</code>.
+     * If <code>virtualDescendantId</code> equals to {@link View#NO_ID} the root
+     * is set as the parent.
+     * <p>
+     * A virtual descendant is an imaginary View that is reported as a part of the view
+     * hierarchy for accessibility purposes. This enables custom views that draw complex
+     * content to report them selves as a tree of virtual views, thus conveying their
+     * logical structure.
+     * </p>
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param root The root of the virtual subtree.
+     * @param virtualDescendantId The id of the virtual descendant.
+     */
+    public void setParent(View root, int virtualDescendantId) {
+        enforceNotSealed();
+        final int rootAccessibilityViewId =
+            (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+        mParentNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+    }
+
+    /**
+     * Gets the node bounds in the viewParent's coordinates.
+     * {@link #getParent()} does not represent the source's viewParent.
+     * Instead it represents the result of {@link View#getParentForAccessibility()},
+     * which returns the closest ancestor where {@link View#isImportantForAccessibility()} is true.
+     * So this method is not reliable.
+     * <p>
+     * When magnification is enabled, the bounds in parent are also scaled up by magnification
+     * scale. For example, it returns Rect(20, 20, 200, 200) for original bounds
+     * Rect(10, 10, 100, 100), when the magnification scale is 2.
+     * <p/>
+     *
+     * @param outBounds The output node bounds.
+     * @deprecated Use {@link #getBoundsInScreen(Rect)} instead.
+     *
+     */
+    @Deprecated
+    public void getBoundsInParent(Rect outBounds) {
+        outBounds.set(mBoundsInParent.left, mBoundsInParent.top,
+                mBoundsInParent.right, mBoundsInParent.bottom);
+    }
+
+    /**
+     * Sets the node bounds in the viewParent's coordinates.
+     * {@link #getParent()} does not represent the source's viewParent.
+     * Instead it represents the result of {@link View#getParentForAccessibility()},
+     * which returns the closest ancestor where {@link View#isImportantForAccessibility()} is true.
+     * So this method is not reliable.
+     *
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param bounds The node bounds.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     * @deprecated Accessibility services should not care about these bounds.
+     */
+    @Deprecated
+    public void setBoundsInParent(Rect bounds) {
+        enforceNotSealed();
+        mBoundsInParent.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
+    }
+
+    /**
+     * Gets the node bounds in screen coordinates.
+     * <p>
+     * When magnification is enabled, the bounds in screen are scaled up by magnification scale
+     * and the positions are also adjusted according to the offset of magnification viewport.
+     * For example, it returns Rect(-180, -180, 0, 0) for original bounds Rect(10, 10, 100, 100),
+     * when the magnification scale is 2 and offsets for X and Y are both 200.
+     * <p/>
+     *
+     * @param outBounds The output node bounds.
+     */
+    public void getBoundsInScreen(Rect outBounds) {
+        outBounds.set(mBoundsInScreen.left, mBoundsInScreen.top,
+                mBoundsInScreen.right, mBoundsInScreen.bottom);
+    }
+
+    /**
+     * Returns the actual rect containing the node bounds in screen coordinates.
+     *
+     * @hide Not safe to expose outside the framework.
+     */
+    public Rect getBoundsInScreen() {
+        return mBoundsInScreen;
+    }
+
+    /**
+     * Sets the node bounds in screen coordinates.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param bounds The node bounds.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setBoundsInScreen(Rect bounds) {
+        enforceNotSealed();
+        mBoundsInScreen.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
+    }
+
+    /**
+     * Gets whether this node is checkable.
+     *
+     * @return True if the node is checkable.
+     */
+    public boolean isCheckable() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_CHECKABLE);
+    }
+
+    /**
+     * Sets whether this node is checkable.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param checkable True if the node is checkable.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setCheckable(boolean checkable) {
+        setBooleanProperty(BOOLEAN_PROPERTY_CHECKABLE, checkable);
+    }
+
+    /**
+     * Gets whether this node is checked.
+     *
+     * @return True if the node is checked.
+     */
+    public boolean isChecked() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_CHECKED);
+    }
+
+    /**
+     * Sets whether this node is checked.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param checked True if the node is checked.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setChecked(boolean checked) {
+        setBooleanProperty(BOOLEAN_PROPERTY_CHECKED, checked);
+    }
+
+    /**
+     * Gets whether this node is focusable.
+     *
+     * @return True if the node is focusable.
+     */
+    public boolean isFocusable() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_FOCUSABLE);
+    }
+
+    /**
+     * Sets whether this node is focusable.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param focusable True if the node is focusable.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setFocusable(boolean focusable) {
+        setBooleanProperty(BOOLEAN_PROPERTY_FOCUSABLE, focusable);
+    }
+
+    /**
+     * Gets whether this node is focused.
+     *
+     * @return True if the node is focused.
+     */
+    public boolean isFocused() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_FOCUSED);
+    }
+
+    /**
+     * Sets whether this node is focused.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param focused True if the node is focused.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setFocused(boolean focused) {
+        setBooleanProperty(BOOLEAN_PROPERTY_FOCUSED, focused);
+    }
+
+    /**
+     * Gets whether this node is visible to the user.
+     * <p>
+     * Between {@link Build.VERSION_CODES#JELLY_BEAN API 16} and
+     * {@link Build.VERSION_CODES#Q API 29}, this method may incorrectly return false when
+     * magnification is enabled. On other versions, a node is considered visible even if it is not
+     * on the screen because magnification is active.
+     * </p>
+     *
+     * @return Whether the node is visible to the user.
+     */
+    public boolean isVisibleToUser() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_VISIBLE_TO_USER);
+    }
+
+    /**
+     * Sets whether this node is visible to the user.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param visibleToUser Whether the node is visible to the user.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setVisibleToUser(boolean visibleToUser) {
+        setBooleanProperty(BOOLEAN_PROPERTY_VISIBLE_TO_USER, visibleToUser);
+    }
+
+    /**
+     * Gets whether this node is accessibility focused.
+     *
+     * @return True if the node is accessibility focused.
+     */
+    public boolean isAccessibilityFocused() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED);
+    }
+
+    /**
+     * Sets whether this node is accessibility focused.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param focused True if the node is accessibility focused.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setAccessibilityFocused(boolean focused) {
+        setBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED, focused);
+    }
+
+    /**
+     * Gets whether this node is selected.
+     *
+     * @return True if the node is selected.
+     */
+    public boolean isSelected() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_SELECTED);
+    }
+
+    /**
+     * Sets whether this node is selected.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param selected True if the node is selected.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setSelected(boolean selected) {
+        setBooleanProperty(BOOLEAN_PROPERTY_SELECTED, selected);
+    }
+
+    /**
+     * Gets whether this node is clickable.
+     *
+     * @return True if the node is clickable.
+     */
+    public boolean isClickable() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_CLICKABLE);
+    }
+
+    /**
+     * Sets whether this node is clickable.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param clickable True if the node is clickable.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setClickable(boolean clickable) {
+        setBooleanProperty(BOOLEAN_PROPERTY_CLICKABLE, clickable);
+    }
+
+    /**
+     * Gets whether this node is long clickable.
+     *
+     * @return True if the node is long clickable.
+     */
+    public boolean isLongClickable() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_LONG_CLICKABLE);
+    }
+
+    /**
+     * Sets whether this node is long clickable.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param longClickable True if the node is long clickable.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setLongClickable(boolean longClickable) {
+        setBooleanProperty(BOOLEAN_PROPERTY_LONG_CLICKABLE, longClickable);
+    }
+
+    /**
+     * Gets whether this node is enabled.
+     *
+     * @return True if the node is enabled.
+     */
+    public boolean isEnabled() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_ENABLED);
+    }
+
+    /**
+     * Sets whether this node is enabled.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param enabled True if the node is enabled.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setEnabled(boolean enabled) {
+        setBooleanProperty(BOOLEAN_PROPERTY_ENABLED, enabled);
+    }
+
+    /**
+     * Gets whether this node is a password.
+     *
+     * @return True if the node is a password.
+     */
+    public boolean isPassword() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_PASSWORD);
+    }
+
+    /**
+     * Sets whether this node is a password.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param password True if the node is a password.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setPassword(boolean password) {
+        setBooleanProperty(BOOLEAN_PROPERTY_PASSWORD, password);
+    }
+
+    /**
+     * Gets if the node is scrollable.
+     *
+     * @return True if the node is scrollable, false otherwise.
+     */
+    public boolean isScrollable() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_SCROLLABLE);
+    }
+
+    /**
+     * Sets if the node is scrollable.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param scrollable True if the node is scrollable, false otherwise.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setScrollable(boolean scrollable) {
+        setBooleanProperty(BOOLEAN_PROPERTY_SCROLLABLE, scrollable);
+    }
+
+    /**
+     * Gets if the node is editable.
+     *
+     * @return True if the node is editable, false otherwise.
+     */
+    public boolean isEditable() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_EDITABLE);
+    }
+
+    /**
+     * Sets whether this node is editable.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param editable True if the node is editable.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setEditable(boolean editable) {
+        setBooleanProperty(BOOLEAN_PROPERTY_EDITABLE, editable);
+    }
+
+    /**
+     * If this node represents a visually distinct region of the screen that may update separately
+     * from the rest of the window, it is considered a pane. Set the pane title to indicate that
+     * the node is a pane, and to provide a title for it.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     * @param paneTitle The title of the pane represented by this node.
+     */
+    public void setPaneTitle(@Nullable CharSequence paneTitle) {
+        enforceNotSealed();
+        mPaneTitle = (paneTitle == null)
+                ? null : paneTitle.subSequence(0, paneTitle.length());
+    }
+
+    /**
+     * Get the title of the pane represented by this node.
+     *
+     * @return The title of the pane represented by this node, or {@code null} if this node does
+     *         not represent a pane.
+     */
+    public @Nullable CharSequence getPaneTitle() {
+        return mPaneTitle;
+    }
+
+    /**
+     * Get the drawing order of the view corresponding it this node.
+     * <p>
+     * Drawing order is determined only within the node's parent, so this index is only relative
+     * to its siblings.
+     * <p>
+     * In some cases, the drawing order is essentially simultaneous, so it is possible for two
+     * siblings to return the same value. It is also possible that values will be skipped.
+     *
+     * @return The drawing position of the view corresponding to this node relative to its siblings.
+     */
+    public int getDrawingOrder() {
+        return mDrawingOrderInParent;
+    }
+
+    /**
+     * Set the drawing order of the view corresponding it this node.
+     *
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     * @param drawingOrderInParent
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setDrawingOrder(int drawingOrderInParent) {
+        enforceNotSealed();
+        mDrawingOrderInParent = drawingOrderInParent;
+    }
+
+    /**
+     * Gets the collection info if the node is a collection. A collection
+     * child is always a collection item.
+     *
+     * @return The collection info.
+     */
+    public CollectionInfo getCollectionInfo() {
+        return mCollectionInfo;
+    }
+
+    /**
+     * Sets the collection info if the node is a collection. A collection
+     * child is always a collection item.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param collectionInfo The collection info.
+     */
+    public void setCollectionInfo(CollectionInfo collectionInfo) {
+        enforceNotSealed();
+        mCollectionInfo = collectionInfo;
+    }
+
+    /**
+     * Gets the collection item info if the node is a collection item. A collection
+     * item is always a child of a collection.
+     *
+     * @return The collection item info.
+     */
+    public CollectionItemInfo getCollectionItemInfo() {
+        return mCollectionItemInfo;
+    }
+
+    /**
+     * Sets the collection item info if the node is a collection item. A collection
+     * item is always a child of a collection.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     */
+    public void setCollectionItemInfo(CollectionItemInfo collectionItemInfo) {
+        enforceNotSealed();
+        mCollectionItemInfo = collectionItemInfo;
+    }
+
+    /**
+     * Gets the range info if this node is a range.
+     *
+     * @return The range.
+     */
+    public RangeInfo getRangeInfo() {
+        return mRangeInfo;
+    }
+
+    /**
+     * Sets the range info if this node is a range.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param rangeInfo The range info.
+     */
+    public void setRangeInfo(RangeInfo rangeInfo) {
+        enforceNotSealed();
+        mRangeInfo = rangeInfo;
+    }
+
+    /**
+     * Gets the {@link ExtraRenderingInfo extra rendering info} if the node is meant to be
+     * refreshed with extra data to examine rendering related accessibility issues.
+     *
+     * @return The {@link ExtraRenderingInfo extra rendering info}.
+     *
+     * @see #EXTRA_DATA_RENDERING_INFO_KEY
+     * @see #refreshWithExtraData(String, Bundle)
+     */
+    @Nullable
+    public ExtraRenderingInfo getExtraRenderingInfo() {
+        return mExtraRenderingInfo;
+    }
+
+    /**
+     * Sets the extra rendering info, <code>extraRenderingInfo<code/>, if the node is meant to be
+     * refreshed with extra data.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param extraRenderingInfo The {@link ExtraRenderingInfo extra rendering info}.
+     * @hide
+     */
+    public void setExtraRenderingInfo(@NonNull ExtraRenderingInfo extraRenderingInfo) {
+        enforceNotSealed();
+        mExtraRenderingInfo = extraRenderingInfo;
+    }
+
+    /**
+     * Gets if the content of this node is invalid. For example,
+     * a date is not well-formed.
+     *
+     * @return If the node content is invalid.
+     */
+    public boolean isContentInvalid() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_CONTENT_INVALID);
+    }
+
+    /**
+     * Sets if the content of this node is invalid. For example,
+     * a date is not well-formed.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param contentInvalid If the node content is invalid.
+     */
+    public void setContentInvalid(boolean contentInvalid) {
+        setBooleanProperty(BOOLEAN_PROPERTY_CONTENT_INVALID, contentInvalid);
+    }
+
+    /**
+     * Gets whether this node is context clickable.
+     *
+     * @return True if the node is context clickable.
+     */
+    public boolean isContextClickable() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_CONTEXT_CLICKABLE);
+    }
+
+    /**
+     * Sets whether this node is context clickable.
+     * <p>
+     * <strong>Note:</strong> Cannot be called from an
+     * {@link android.accessibilityservice.AccessibilityService}. This class is made immutable
+     * before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param contextClickable True if the node is context clickable.
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setContextClickable(boolean contextClickable) {
+        setBooleanProperty(BOOLEAN_PROPERTY_CONTEXT_CLICKABLE, contextClickable);
+    }
+
+    /**
+     * Gets the node's live region mode.
+     * <p>
+     * A live region is a node that contains information that is important for
+     * the user and when it changes the user should be notified. For example,
+     * in a login screen with a TextView that displays an "incorrect password"
+     * notification, that view should be marked as a live region with mode
+     * {@link View#ACCESSIBILITY_LIVE_REGION_POLITE}.
+     * <p>
+     * It is the responsibility of the accessibility service to monitor
+     * {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} events indicating
+     * changes to live region nodes and their children.
+     *
+     * @return The live region mode, or
+     *         {@link View#ACCESSIBILITY_LIVE_REGION_NONE} if the view is not a
+     *         live region.
+     * @see android.view.View#getAccessibilityLiveRegion()
+     */
+    public int getLiveRegion() {
+        return mLiveRegion;
+    }
+
+    /**
+     * Sets the node's live region mode.
+     * <p>
+     * <strong>Note:</strong> Cannot be called from an
+     * {@link android.accessibilityservice.AccessibilityService}. This class is
+     * made immutable before being delivered to an AccessibilityService.
+     *
+     * @param mode The live region mode, or
+     *        {@link View#ACCESSIBILITY_LIVE_REGION_NONE} if the view is not a
+     *        live region.
+     * @see android.view.View#setAccessibilityLiveRegion(int)
+     */
+    public void setLiveRegion(int mode) {
+        enforceNotSealed();
+        mLiveRegion = mode;
+    }
+
+    /**
+     * Gets if the node is a multi line editable text.
+     *
+     * @return True if the node is multi line.
+     */
+    public boolean isMultiLine() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_MULTI_LINE);
+    }
+
+    /**
+     * Sets if the node is a multi line editable text.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param multiLine True if the node is multi line.
+     */
+    public void setMultiLine(boolean multiLine) {
+        setBooleanProperty(BOOLEAN_PROPERTY_MULTI_LINE, multiLine);
+    }
+
+    /**
+     * Gets if this node opens a popup or a dialog.
+     *
+     * @return If the the node opens a popup.
+     */
+    public boolean canOpenPopup() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_OPENS_POPUP);
+    }
+
+    /**
+     * Sets if this node opens a popup or a dialog.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param opensPopup If the the node opens a popup.
+     */
+    public void setCanOpenPopup(boolean opensPopup) {
+        enforceNotSealed();
+        setBooleanProperty(BOOLEAN_PROPERTY_OPENS_POPUP, opensPopup);
+    }
+
+    /**
+     * Gets if the node can be dismissed.
+     *
+     * @return If the node can be dismissed.
+     */
+    public boolean isDismissable() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_DISMISSABLE);
+    }
+
+    /**
+     * Sets if the node can be dismissed.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param dismissable If the node can be dismissed.
+     */
+    public void setDismissable(boolean dismissable) {
+        setBooleanProperty(BOOLEAN_PROPERTY_DISMISSABLE, dismissable);
+    }
+
+    /**
+     * Returns whether the node originates from a view considered important for accessibility.
+     *
+     * @return {@code true} if the node originates from a view considered important for
+     *         accessibility, {@code false} otherwise
+     *
+     * @see View#isImportantForAccessibility()
+     */
+    public boolean isImportantForAccessibility() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_IMPORTANCE);
+    }
+
+    /**
+     * Sets whether the node is considered important for accessibility.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param important {@code true} if the node is considered important for accessibility,
+     *                  {@code false} otherwise
+     */
+    public void setImportantForAccessibility(boolean important) {
+        setBooleanProperty(BOOLEAN_PROPERTY_IMPORTANCE, important);
+    }
+
+    /**
+     * Returns whether the node is explicitly marked as a focusable unit by a screen reader. Note
+     * that {@code false} indicates that it is not explicitly marked, not that the node is not
+     * a focusable unit. Screen readers should generally use other signals, such as
+     * {@link #isFocusable()}, or the presence of text in a node, to determine what should receive
+     * focus.
+     *
+     * @return {@code true} if the node is specifically marked as a focusable unit for screen
+     *         readers, {@code false} otherwise.
+     *
+     * @see View#isScreenReaderFocusable()
+     */
+    public boolean isScreenReaderFocusable() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_SCREEN_READER_FOCUSABLE);
+    }
+
+    /**
+     * Sets whether the node should be considered a focusable unit by a screen reader.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param screenReaderFocusable {@code true} if the node is a focusable unit for screen readers,
+     *                              {@code false} otherwise.
+     */
+    public void setScreenReaderFocusable(boolean screenReaderFocusable) {
+        setBooleanProperty(BOOLEAN_PROPERTY_SCREEN_READER_FOCUSABLE, screenReaderFocusable);
+    }
+
+    /**
+     * Returns whether the node's text represents a hint for the user to enter text. It should only
+     * be {@code true} if the node has editable text.
+     *
+     * @return {@code true} if the text in the node represents a hint to the user, {@code false}
+     * otherwise.
+     */
+    public boolean isShowingHintText() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_IS_SHOWING_HINT);
+    }
+
+    /**
+     * Sets whether the node's text represents a hint for the user to enter text. It should only
+     * be {@code true} if the node has editable text.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param showingHintText {@code true} if the text in the node represents a hint to the user,
+     * {@code false} otherwise.
+     */
+    public void setShowingHintText(boolean showingHintText) {
+        setBooleanProperty(BOOLEAN_PROPERTY_IS_SHOWING_HINT, showingHintText);
+    }
+
+    /**
+     * Returns whether node represents a heading.
+     * <p><strong>Note:</strong> Returns {@code true} if either {@link #setHeading(boolean)}
+     * marks this node as a heading or if the node has a {@link CollectionItemInfo} that marks
+     * it as such, to accomodate apps that use the now-deprecated API.</p>
+     *
+     * @return {@code true} if the node is a heading, {@code false} otherwise.
+     */
+    public boolean isHeading() {
+        if (getBooleanProperty(BOOLEAN_PROPERTY_IS_HEADING)) return true;
+        CollectionItemInfo itemInfo = getCollectionItemInfo();
+        return ((itemInfo != null) && itemInfo.mHeading);
+    }
+
+    /**
+     * Sets whether the node represents a heading.
+     *
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param isHeading {@code true} if the node is a heading, {@code false} otherwise.
+     */
+    public void setHeading(boolean isHeading) {
+        setBooleanProperty(BOOLEAN_PROPERTY_IS_HEADING, isHeading);
+    }
+
+    /**
+     * Returns whether node represents a text entry key that is part of a keyboard or keypad.
+     *
+     * @return {@code true} if the node is a text entry key., {@code false} otherwise.
+     */
+    public boolean isTextEntryKey() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_IS_TEXT_ENTRY_KEY);
+    }
+
+    /**
+     * Sets whether the node represents a text entry key that is part of a keyboard or keypad.
+     *
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param isTextEntryKey {@code true} if the node is a text entry key, {@code false} otherwise.
+     */
+    public void setTextEntryKey(boolean isTextEntryKey) {
+        setBooleanProperty(BOOLEAN_PROPERTY_IS_TEXT_ENTRY_KEY, isTextEntryKey);
+    }
+
+    /**
+     * Gets the package this node comes from.
+     *
+     * @return The package name.
+     */
+    public CharSequence getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * Sets the package this node comes from.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param packageName The package name.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setPackageName(CharSequence packageName) {
+        enforceNotSealed();
+        mPackageName = packageName;
+    }
+
+    /**
+     * Gets the class this node comes from.
+     *
+     * @return The class name.
+     */
+    public CharSequence getClassName() {
+        return mClassName;
+    }
+
+    /**
+     * Sets the class this node comes from.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param className The class name.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setClassName(CharSequence className) {
+        enforceNotSealed();
+        mClassName = className;
+    }
+
+    /**
+     * Gets the text of this node.
+     * <p>
+     *   <strong>Note:</strong> If the text contains {@link ClickableSpan}s or {@link URLSpan}s,
+     *   these spans will have been replaced with ones whose {@link ClickableSpan#onClick(View)}
+     *   can be called from an {@link AccessibilityService}. When called from a service, the
+     *   {@link View} argument is ignored and the corresponding span will be found on the view that
+     *   this {@code AccessibilityNodeInfo} represents and called with that view as its argument.
+     *   <p>
+     *   This treatment of {@link ClickableSpan}s means that the text returned from this method may
+     *   different slightly one passed to {@link #setText(CharSequence)}, although they will be
+     *   equivalent according to {@link TextUtils#equals(CharSequence, CharSequence)}. The
+     *   {@link ClickableSpan#onClick(View)} of any spans, however, will generally not work outside
+     *   of an accessibility service.
+     * </p>
+     *
+     * @return The text.
+     */
+    public CharSequence getText() {
+        // Attach this node to any spans that need it
+        if (mText instanceof Spanned) {
+            Spanned spanned = (Spanned) mText;
+            AccessibilityClickableSpan[] clickableSpans =
+                    spanned.getSpans(0, mText.length(), AccessibilityClickableSpan.class);
+            for (int i = 0; i < clickableSpans.length; i++) {
+                clickableSpans[i].copyConnectionDataFrom(this);
+            }
+            AccessibilityURLSpan[] urlSpans =
+                    spanned.getSpans(0, mText.length(), AccessibilityURLSpan.class);
+            for (int i = 0; i < urlSpans.length; i++) {
+                urlSpans[i].copyConnectionDataFrom(this);
+            }
+        }
+        return mText;
+    }
+
+    /**
+     * Get the text passed to setText before any changes to the spans.
+     * @hide
+     */
+    public CharSequence getOriginalText() {
+        return mOriginalText;
+    }
+
+    /**
+     * Sets the text of this node.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param text The text.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setText(CharSequence text) {
+        enforceNotSealed();
+        mOriginalText = text;
+        if (text instanceof Spanned) {
+            CharSequence tmpText = text;
+            tmpText = replaceClickableSpan(tmpText);
+            tmpText = replaceReplacementSpan(tmpText);
+            mText = tmpText;
+            return;
+        }
+        mText = (text == null) ? null : text.subSequence(0, text.length());
+    }
+
+    /**
+     * Replaces any ClickableSpan in the given {@code text} with placeholders.
+     *
+     * @param text The text.
+     *
+     * @return The spannable with ClickableSpan replacement.
+     */
+    private CharSequence replaceClickableSpan(CharSequence text) {
+        ClickableSpan[] clickableSpans =
+                ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class);
+        Spannable spannable = new SpannableStringBuilder(text);
+        if (clickableSpans.length == 0) {
+            return text;
+        }
+        for (int i = 0; i < clickableSpans.length; i++) {
+            ClickableSpan span = clickableSpans[i];
+            if ((span instanceof AccessibilityClickableSpan)
+                    || (span instanceof AccessibilityURLSpan)) {
+                // We've already done enough
+                break;
+            }
+            int spanToReplaceStart = spannable.getSpanStart(span);
+            int spanToReplaceEnd = spannable.getSpanEnd(span);
+            int spanToReplaceFlags = spannable.getSpanFlags(span);
+            spannable.removeSpan(span);
+            ClickableSpan replacementSpan = (span instanceof URLSpan)
+                    ? new AccessibilityURLSpan((URLSpan) span)
+                    : new AccessibilityClickableSpan(span.getId());
+            spannable.setSpan(replacementSpan, spanToReplaceStart, spanToReplaceEnd,
+                    spanToReplaceFlags);
+        }
+        return spannable;
+    }
+
+    /**
+     * Replaces any ReplacementSpan in the given {@code text} if the object has content description.
+     *
+     * @param text The text.
+     *
+     * @return The spannable with ReplacementSpan replacement.
+     */
+    private CharSequence replaceReplacementSpan(CharSequence text) {
+        ReplacementSpan[] replacementSpans =
+                ((Spanned) text).getSpans(0, text.length(), ReplacementSpan.class);
+        SpannableStringBuilder spannable = new SpannableStringBuilder(text);
+        if (replacementSpans.length == 0) {
+            return text;
+        }
+        for (int i = 0; i < replacementSpans.length; i++) {
+            ReplacementSpan span = replacementSpans[i];
+            CharSequence replacementText = span.getContentDescription();
+            if (span instanceof AccessibilityReplacementSpan) {
+                // We've already done enough
+                break;
+            }
+            if (replacementText == null) {
+                continue;
+            }
+            int spanToReplaceStart = spannable.getSpanStart(span);
+            int spanToReplaceEnd = spannable.getSpanEnd(span);
+            int spanToReplaceFlags = spannable.getSpanFlags(span);
+            spannable.removeSpan(span);
+            ReplacementSpan replacementSpan = new AccessibilityReplacementSpan(replacementText);
+            spannable.setSpan(replacementSpan, spanToReplaceStart, spanToReplaceEnd,
+                    spanToReplaceFlags);
+        }
+        return spannable;
+    }
+
+    /**
+     * Gets the hint text of this node. Only applies to nodes where text can be entered.
+     *
+     * @return The hint text.
+     */
+    public CharSequence getHintText() {
+        return mHintText;
+    }
+
+    /**
+     * Sets the hint text of this node. Only applies to nodes where text can be entered.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param hintText The hint text for this mode.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setHintText(CharSequence hintText) {
+        enforceNotSealed();
+        mHintText = (hintText == null) ? null : hintText.subSequence(0, hintText.length());
+    }
+
+    /**
+     * Sets the error text of this node.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param error The error text.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setError(CharSequence error) {
+        enforceNotSealed();
+        mError = (error == null) ? null : error.subSequence(0, error.length());
+    }
+
+    /**
+     * Gets the error text of this node.
+     *
+     * @return The error text.
+     */
+    public CharSequence getError() {
+        return mError;
+    }
+
+    /**
+     * Get the state description of this node.
+     *
+     * @return the state description
+     */
+    public @Nullable CharSequence getStateDescription() {
+        return mStateDescription;
+    }
+
+    /**
+     * Gets the content description of this node.
+     *
+     * @return The content description.
+     */
+    public CharSequence getContentDescription() {
+        return mContentDescription;
+    }
+
+
+    /**
+     * Sets the state description of this node.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param stateDescription the state description of this node.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setStateDescription(@Nullable CharSequence stateDescription) {
+        enforceNotSealed();
+        mStateDescription = (stateDescription == null) ? null
+                : stateDescription.subSequence(0, stateDescription.length());
+    }
+
+    /**
+     * Sets the content description of this node.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param contentDescription The content description.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setContentDescription(CharSequence contentDescription) {
+        enforceNotSealed();
+        mContentDescription = (contentDescription == null) ? null
+                : contentDescription.subSequence(0, contentDescription.length());
+    }
+
+    /**
+     * Gets the tooltip text of this node.
+     *
+     * @return The tooltip text.
+     */
+    @Nullable
+    public CharSequence getTooltipText() {
+        return mTooltipText;
+    }
+
+    /**
+     * Sets the tooltip text of this node.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param tooltipText The tooltip text.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setTooltipText(@Nullable CharSequence tooltipText) {
+        enforceNotSealed();
+        mTooltipText = (tooltipText == null) ? null
+                : tooltipText.subSequence(0, tooltipText.length());
+    }
+
+    /**
+     * Sets the view for which the view represented by this info serves as a
+     * label for accessibility purposes.
+     *
+     * @param labeled The view for which this info serves as a label.
+     */
+    public void setLabelFor(View labeled) {
+        setLabelFor(labeled, AccessibilityNodeProvider.HOST_VIEW_ID);
+    }
+
+    /**
+     * Sets the view for which the view represented by this info serves as a
+     * label for accessibility purposes. If <code>virtualDescendantId</code>
+     * is {@link View#NO_ID} the root is set as the labeled.
+     * <p>
+     * A virtual descendant is an imaginary View that is reported as a part of the view
+     * hierarchy for accessibility purposes. This enables custom views that draw complex
+     * content to report themselves as a tree of virtual views, thus conveying their
+     * logical structure.
+     * </p>
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param root The root whose virtual descendant serves as a label.
+     * @param virtualDescendantId The id of the virtual descendant.
+     */
+    public void setLabelFor(View root, int virtualDescendantId) {
+        enforceNotSealed();
+        final int rootAccessibilityViewId = (root != null)
+                ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+        mLabelForId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+    }
+
+    /**
+     * Gets the node info for which the view represented by this info serves as
+     * a label for accessibility purposes.
+     * <p>
+     *   <strong>Note:</strong> It is a client responsibility to recycle the
+     *     received info by calling {@link AccessibilityNodeInfo#recycle()}
+     *     to avoid creating of multiple instances.
+     * </p>
+     *
+     * @return The labeled info.
+     */
+    public AccessibilityNodeInfo getLabelFor() {
+        enforceSealed();
+        return getNodeForAccessibilityId(mConnectionId, mWindowId, mLabelForId);
+    }
+
+    /**
+     * Sets the view which serves as the label of the view represented by
+     * this info for accessibility purposes.
+     *
+     * @param label The view that labels this node's source.
+     */
+    public void setLabeledBy(View label) {
+        setLabeledBy(label, AccessibilityNodeProvider.HOST_VIEW_ID);
+    }
+
+    /**
+     * Sets the view which serves as the label of the view represented by
+     * this info for accessibility purposes. If <code>virtualDescendantId</code>
+     * is {@link View#NO_ID} the root is set as the label.
+     * <p>
+     * A virtual descendant is an imaginary View that is reported as a part of the view
+     * hierarchy for accessibility purposes. This enables custom views that draw complex
+     * content to report themselves as a tree of virtual views, thus conveying their
+     * logical structure.
+     * </p>
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param root The root whose virtual descendant labels this node's source.
+     * @param virtualDescendantId The id of the virtual descendant.
+     */
+    public void setLabeledBy(View root, int virtualDescendantId) {
+        enforceNotSealed();
+        final int rootAccessibilityViewId = (root != null)
+                ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+        mLabeledById = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+    }
+
+    /**
+     * Gets the node info which serves as the label of the view represented by
+     * this info for accessibility purposes.
+     * <p>
+     *   <strong>Note:</strong> It is a client responsibility to recycle the
+     *     received info by calling {@link AccessibilityNodeInfo#recycle()}
+     *     to avoid creating of multiple instances.
+     * </p>
+     *
+     * @return The label.
+     */
+    public AccessibilityNodeInfo getLabeledBy() {
+        enforceSealed();
+        return getNodeForAccessibilityId(mConnectionId, mWindowId, mLabeledById);
+    }
+
+    /**
+     * Sets the fully qualified resource name of the source view's id.
+     *
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param viewIdResName The id resource name.
+     */
+    public void setViewIdResourceName(String viewIdResName) {
+        enforceNotSealed();
+        mViewIdResourceName = viewIdResName;
+    }
+
+    /**
+     * Gets the fully qualified resource name of the source view's id.
+     *
+     * <p>
+     *   <strong>Note:</strong> The primary usage of this API is for UI test automation
+     *   and in order to report the source view id of an {@link AccessibilityNodeInfo} the
+     *   client has to set the {@link AccessibilityServiceInfo#FLAG_REPORT_VIEW_IDS}
+     *   flag when configuring the {@link android.accessibilityservice.AccessibilityService}.
+     * </p>
+
+     * @return The id resource name.
+     */
+    public String getViewIdResourceName() {
+        return mViewIdResourceName;
+    }
+
+    /**
+     * Gets the text selection start or the cursor position.
+     * <p>
+     * If no text is selected, both this method and
+     * {@link AccessibilityNodeInfo#getTextSelectionEnd()} return the same value:
+     * the current location of the cursor.
+     * </p>
+     *
+     * @return The text selection start, the cursor location if there is no selection, or -1 if
+     *         there is no text selection and no cursor.
+     */
+    public int getTextSelectionStart() {
+        return mTextSelectionStart;
+    }
+
+    /**
+     * Gets the text selection end if text is selected.
+     * <p>
+     * If no text is selected, both this method and
+     * {@link AccessibilityNodeInfo#getTextSelectionStart()} return the same value:
+     * the current location of the cursor.
+     * </p>
+     *
+     * @return The text selection end, the cursor location if there is no selection, or -1 if
+     *         there is no text selection and no cursor.
+     */
+    public int getTextSelectionEnd() {
+        return mTextSelectionEnd;
+    }
+
+    /**
+     * Sets the text selection start and end.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param start The text selection start.
+     * @param end The text selection end.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setTextSelection(int start, int end) {
+        enforceNotSealed();
+        mTextSelectionStart = start;
+        mTextSelectionEnd = end;
+    }
+
+    /**
+     * Gets the input type of the source as defined by {@link InputType}.
+     *
+     * @return The input type.
+     */
+    public int getInputType() {
+        return mInputType;
+    }
+
+    /**
+     * Sets the input type of the source as defined by {@link InputType}.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an
+     *   AccessibilityService.
+     * </p>
+     *
+     * @param inputType The input type.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setInputType(int inputType) {
+        enforceNotSealed();
+        mInputType = inputType;
+    }
+
+    /**
+     * Gets an optional bundle with extra data. The bundle
+     * is lazily created and never <code>null</code>.
+     * <p>
+     * <strong>Note:</strong> It is recommended to use the package
+     * name of your application as a prefix for the keys to avoid
+     * collisions which may confuse an accessibility service if the
+     * same key has different meaning when emitted from different
+     * applications.
+     * </p>
+     *
+     * @return The bundle.
+     */
+    public Bundle getExtras() {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        return mExtras;
+    }
+
+    /**
+     * Check if a node has an extras bundle
+     * @hide
+     */
+    public boolean hasExtras() {
+        return mExtras != null;
+    }
+
+    /**
+     * Get the {@link TouchDelegateInfo} for touch delegate behavior with the represented view.
+     * It is possible for the same node to be pointed to by several regions. Use
+     * {@link TouchDelegateInfo#getRegionAt(int)} to get touch delegate target {@link Region}, and
+     * {@link TouchDelegateInfo#getTargetForRegion(Region)} for {@link AccessibilityNodeInfo} from
+     * the given region.
+     *
+     * @return {@link TouchDelegateInfo} or {@code null} if there are no touch delegates.
+     */
+    @Nullable
+    public TouchDelegateInfo getTouchDelegateInfo() {
+        if (mTouchDelegateInfo != null) {
+            mTouchDelegateInfo.setConnectionId(mConnectionId);
+            mTouchDelegateInfo.setWindowId(mWindowId);
+        }
+        return mTouchDelegateInfo;
+    }
+
+    /**
+     * Set touch delegate info if the represented view has a {@link TouchDelegate}.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an
+     *   AccessibilityService.
+     * </p>
+     *
+     * @param delegatedInfo {@link TouchDelegateInfo} returned from
+     *         {@link TouchDelegate#getTouchDelegateInfo()}.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setTouchDelegateInfo(@NonNull TouchDelegateInfo delegatedInfo) {
+        enforceNotSealed();
+        mTouchDelegateInfo = delegatedInfo;
+    }
+
+    /**
+     * Gets the value of a boolean property.
+     *
+     * @param property The property.
+     * @return The value.
+     */
+    private boolean getBooleanProperty(int property) {
+        return (mBooleanProperties & property) != 0;
+    }
+
+    /**
+     * Sets a boolean property.
+     *
+     * @param property The property.
+     * @param value The value.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    private void setBooleanProperty(int property, boolean value) {
+        enforceNotSealed();
+        if (value) {
+            mBooleanProperties |= property;
+        } else {
+            mBooleanProperties &= ~property;
+        }
+    }
+
+    /**
+     * Sets the unique id of the IAccessibilityServiceConnection over which
+     * this instance can send requests to the system.
+     *
+     * @param connectionId The connection id.
+     *
+     * @hide
+     */
+    public void setConnectionId(int connectionId) {
+        enforceNotSealed();
+        mConnectionId = connectionId;
+    }
+
+    /**
+     * Get the connection ID.
+     *
+     * @return The connection id
+     *
+     * @hide
+     */
+    public int getConnectionId() {
+        return mConnectionId;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Sets the id of the source node.
+     *
+     * @param sourceId The id.
+     * @param windowId The window id.
+     *
+     * @hide
+     */
+    public void setSourceNodeId(long sourceId, int windowId) {
+        enforceNotSealed();
+        mSourceNodeId = sourceId;
+        mWindowId = windowId;
+    }
+
+    /**
+     * Gets the id of the source node.
+     *
+     * @return The id.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @TestApi
+    public long getSourceNodeId() {
+        return mSourceNodeId;
+    }
+
+    /**
+     * Sets the token and node id of the leashed parent.
+     *
+     * @param token The token.
+     * @param viewId The accessibility view id.
+     * @hide
+     */
+    @TestApi
+    public void setLeashedParent(@Nullable IBinder token, int viewId) {
+        enforceNotSealed();
+        mLeashedParent = token;
+        mLeashedParentNodeId = makeNodeId(viewId, AccessibilityNodeProvider.HOST_VIEW_ID);
+    }
+
+    /**
+     * Gets the token of the leashed parent.
+     *
+     * @return The token.
+     * @hide
+     */
+    public @Nullable IBinder getLeashedParent() {
+        return mLeashedParent;
+    }
+
+    /**
+     * Gets the node id of the leashed parent.
+     *
+     * @return The accessibility node id.
+     * @hide
+     */
+    public long getLeashedParentNodeId() {
+        return mLeashedParentNodeId;
+    }
+
+    /**
+     * Sets if this instance is sealed.
+     *
+     * @param sealed Whether is sealed.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void setSealed(boolean sealed) {
+        mSealed = sealed;
+    }
+
+    /**
+     * Gets if this instance is sealed.
+     *
+     * @return Whether is sealed.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public boolean isSealed() {
+        return mSealed;
+    }
+
+    /**
+     * Enforces that this instance is sealed.
+     *
+     * @throws IllegalStateException If this instance is not sealed.
+     *
+     * @hide
+     */
+    protected void enforceSealed() {
+        if (!isSealed()) {
+            throw new IllegalStateException("Cannot perform this "
+                    + "action on a not sealed instance.");
+        }
+    }
+
+    private void enforceValidFocusDirection(int direction) {
+        switch (direction) {
+            case View.FOCUS_DOWN:
+            case View.FOCUS_UP:
+            case View.FOCUS_LEFT:
+            case View.FOCUS_RIGHT:
+            case View.FOCUS_FORWARD:
+            case View.FOCUS_BACKWARD:
+                return;
+            default:
+                throw new IllegalArgumentException("Unknown direction: " + direction);
+        }
+    }
+
+    private void enforceValidFocusType(int focusType) {
+        switch (focusType) {
+            case FOCUS_INPUT:
+            case FOCUS_ACCESSIBILITY:
+                return;
+            default:
+                throw new IllegalArgumentException("Unknown focus type: " + focusType);
+        }
+    }
+
+    /**
+     * Enforces that this instance is not sealed.
+     *
+     * @throws IllegalStateException If this instance is sealed.
+     *
+     * @hide
+     */
+    protected void enforceNotSealed() {
+        if (isSealed()) {
+            throw new IllegalStateException("Cannot perform this "
+                    + "action on a sealed instance.");
+        }
+    }
+
+    /**
+     * Returns a cached instance if such is available otherwise a new one
+     * and sets the source.
+     *
+     * <p>In most situations object pooling is not beneficial. Create a new instance using the
+     * constructor {@link #AccessibilityNodeInfo(View)} instead.
+     *
+     * @param source The source view.
+     * @return An instance.
+     *
+     * @see #setSource(View)
+     */
+    public static AccessibilityNodeInfo obtain(View source) {
+        AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+        info.setSource(source);
+        return info;
+    }
+
+    /**
+     * Returns a cached instance if such is available otherwise a new one
+     * and sets the source.
+     *
+     * <p>In most situations object pooling is not beneficial. Create a new instance using the
+     * constructor {@link #AccessibilityNodeInfo(View, int)} instead.
+     *
+     * @param root The root of the virtual subtree.
+     * @param virtualDescendantId The id of the virtual descendant.
+     * @return An instance.
+     *
+     * @see #setSource(View, int)
+     */
+    public static AccessibilityNodeInfo obtain(View root, int virtualDescendantId) {
+        AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+        info.setSource(root, virtualDescendantId);
+        return info;
+    }
+
+    /**
+     * Returns a cached instance if such is available otherwise a new one.
+     *
+     * <p>In most situations object pooling is not beneficial. Create a new instance using the
+     * constructor {@link #AccessibilityNodeInfo()} instead.
+     *
+     * @return An instance.
+     */
+    public static AccessibilityNodeInfo obtain() {
+        AccessibilityNodeInfo info = sPool.acquire();
+        if (sNumInstancesInUse != null) {
+            sNumInstancesInUse.incrementAndGet();
+        }
+        return (info != null) ? info : new AccessibilityNodeInfo();
+    }
+
+    /**
+     * Returns a cached instance if such is available or a new one is
+     * create. The returned instance is initialized from the given
+     * <code>info</code>.
+     *
+     * <p>In most situations object pooling is not beneficial. Create a new instance using the
+     * constructor {@link #AccessibilityNodeInfo(AccessibilityNodeInfo)} instead.
+     *
+     * @param info The other info.
+     * @return An instance.
+     */
+    public static AccessibilityNodeInfo obtain(AccessibilityNodeInfo info) {
+        AccessibilityNodeInfo infoClone = AccessibilityNodeInfo.obtain();
+        infoClone.init(info, true /* usePoolingInfo */);
+        return infoClone;
+    }
+
+    /**
+     * Return an instance back to be reused.
+     * <p>
+     * <strong>Note:</strong> You must not touch the object after calling this function.
+     *
+     * <p>In most situations object pooling is not beneficial, and recycling is not necessary.
+     *
+     * @throws IllegalStateException If the info is already recycled.
+     */
+    public void recycle() {
+        clear();
+        sPool.release(this);
+        if (sNumInstancesInUse != null) {
+            sNumInstancesInUse.decrementAndGet();
+        }
+    }
+
+    /**
+     * Specify a counter that will be incremented on obtain() and decremented on recycle()
+     *
+     * @hide
+     */
+    @TestApi
+    public static void setNumInstancesInUseCounter(AtomicInteger counter) {
+        sNumInstancesInUse = counter;
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     *   <strong>Note:</strong> After the instance is written to a parcel it
+     *      is recycled. You must not touch the object after calling this function.
+     * </p>
+     */
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        writeToParcelNoRecycle(parcel, flags);
+        // Since instances of this class are fetched via synchronous i.e. blocking
+        // calls in IPCs we always recycle as soon as the instance is marshaled.
+        recycle();
+    }
+
+    /** @hide */
+    @TestApi
+    public void writeToParcelNoRecycle(Parcel parcel, int flags) {
+        // Write bit set of indices of fields with values differing from default
+        long nonDefaultFields = 0;
+        int fieldIndex = 0; // index of the current field
+        if (isSealed() != DEFAULT.isSealed()) nonDefaultFields |= bitAt(fieldIndex);
+        fieldIndex++;
+        if (mSourceNodeId != DEFAULT.mSourceNodeId) nonDefaultFields |= bitAt(fieldIndex);
+        fieldIndex++;
+        if (mWindowId != DEFAULT.mWindowId) nonDefaultFields |= bitAt(fieldIndex);
+        fieldIndex++;
+        if (mParentNodeId != DEFAULT.mParentNodeId) nonDefaultFields |= bitAt(fieldIndex);
+        fieldIndex++;
+        if (mLabelForId != DEFAULT.mLabelForId) nonDefaultFields |= bitAt(fieldIndex);
+        fieldIndex++;
+        if (mLabeledById != DEFAULT.mLabeledById) nonDefaultFields |= bitAt(fieldIndex);
+        fieldIndex++;
+        if (mTraversalBefore != DEFAULT.mTraversalBefore) nonDefaultFields |= bitAt(fieldIndex);
+        fieldIndex++;
+        if (mTraversalAfter != DEFAULT.mTraversalAfter) nonDefaultFields |= bitAt(fieldIndex);
+        fieldIndex++;
+        if (mConnectionId != DEFAULT.mConnectionId) nonDefaultFields |= bitAt(fieldIndex);
+        fieldIndex++;
+        if (!LongArray.elementsEqual(mChildNodeIds, DEFAULT.mChildNodeIds)) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
+        if (!Objects.equals(mBoundsInParent, DEFAULT.mBoundsInParent)) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
+        if (!Objects.equals(mBoundsInScreen, DEFAULT.mBoundsInScreen)) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
+        if (!Objects.equals(mActions, DEFAULT.mActions)) nonDefaultFields |= bitAt(fieldIndex);
+        fieldIndex++;
+        if (mMaxTextLength != DEFAULT.mMaxTextLength) nonDefaultFields |= bitAt(fieldIndex);
+        fieldIndex++;
+        if (mMovementGranularities != DEFAULT.mMovementGranularities) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
+        if (mBooleanProperties != DEFAULT.mBooleanProperties) nonDefaultFields |= bitAt(fieldIndex);
+        fieldIndex++;
+        if (!Objects.equals(mPackageName, DEFAULT.mPackageName)) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
+        if (!Objects.equals(mClassName, DEFAULT.mClassName)) nonDefaultFields |= bitAt(fieldIndex);
+        fieldIndex++;
+        if (!Objects.equals(mText, DEFAULT.mText)) nonDefaultFields |= bitAt(fieldIndex);
+        fieldIndex++;
+        if (!Objects.equals(mHintText, DEFAULT.mHintText)) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
+        if (!Objects.equals(mError, DEFAULT.mError)) nonDefaultFields |= bitAt(fieldIndex);
+        fieldIndex++;
+        if (!Objects.equals(mStateDescription, DEFAULT.mStateDescription)) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
+        if (!Objects.equals(mContentDescription, DEFAULT.mContentDescription)) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
+        if (!Objects.equals(mPaneTitle, DEFAULT.mPaneTitle)) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
+        if (!Objects.equals(mTooltipText, DEFAULT.mTooltipText)) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
+        if (!Objects.equals(mViewIdResourceName, DEFAULT.mViewIdResourceName)) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
+        if (mTextSelectionStart != DEFAULT.mTextSelectionStart) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
+        if (mTextSelectionEnd != DEFAULT.mTextSelectionEnd) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
+        if (mInputType != DEFAULT.mInputType) nonDefaultFields |= bitAt(fieldIndex);
+        fieldIndex++;
+        if (mLiveRegion != DEFAULT.mLiveRegion) nonDefaultFields |= bitAt(fieldIndex);
+        fieldIndex++;
+        if (mDrawingOrderInParent != DEFAULT.mDrawingOrderInParent) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
+        if (!Objects.equals(mExtraDataKeys, DEFAULT.mExtraDataKeys)) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
+        if (!Objects.equals(mExtras, DEFAULT.mExtras)) nonDefaultFields |= bitAt(fieldIndex);
+        fieldIndex++;
+        if (!Objects.equals(mRangeInfo, DEFAULT.mRangeInfo)) nonDefaultFields |= bitAt(fieldIndex);
+        fieldIndex++;
+        if (!Objects.equals(mCollectionInfo, DEFAULT.mCollectionInfo)) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
+        if (!Objects.equals(mCollectionItemInfo, DEFAULT.mCollectionItemInfo)) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
+        if (!Objects.equals(mTouchDelegateInfo, DEFAULT.mTouchDelegateInfo)) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
+        if (!Objects.equals(mExtraRenderingInfo, DEFAULT.mExtraRenderingInfo)) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
+        if (mLeashedChild != DEFAULT.mLeashedChild) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
+        if (mLeashedParent != DEFAULT.mLeashedParent) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
+        if (mLeashedParentNodeId != DEFAULT.mLeashedParentNodeId) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        int totalFields = fieldIndex;
+        parcel.writeLong(nonDefaultFields);
+
+        fieldIndex = 0;
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(isSealed() ? 1 : 0);
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mSourceNodeId);
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mWindowId);
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mParentNodeId);
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mLabelForId);
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mLabeledById);
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalBefore);
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalAfter);
+
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mConnectionId);
+
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            final LongArray childIds = mChildNodeIds;
+            if (childIds == null) {
+                parcel.writeInt(0);
+            } else {
+                final int childIdsSize = childIds.size();
+                parcel.writeInt(childIdsSize);
+                for (int i = 0; i < childIdsSize; i++) {
+                    parcel.writeLong(childIds.get(i));
+                }
+            }
+        }
+
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            parcel.writeInt(mBoundsInParent.top);
+            parcel.writeInt(mBoundsInParent.bottom);
+            parcel.writeInt(mBoundsInParent.left);
+            parcel.writeInt(mBoundsInParent.right);
+        }
+
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            parcel.writeInt(mBoundsInScreen.top);
+            parcel.writeInt(mBoundsInScreen.bottom);
+            parcel.writeInt(mBoundsInScreen.left);
+            parcel.writeInt(mBoundsInScreen.right);
+        }
+
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            if (mActions != null && !mActions.isEmpty()) {
+                final int actionCount = mActions.size();
+
+                int nonStandardActionCount = 0;
+                long defaultStandardActions = 0;
+                for (int i = 0; i < actionCount; i++) {
+                    AccessibilityAction action = mActions.get(i);
+                    if (isDefaultStandardAction(action)) {
+                        defaultStandardActions |= action.mSerializationFlag;
+                    } else {
+                        nonStandardActionCount++;
+                    }
+                }
+                parcel.writeLong(defaultStandardActions);
+
+                parcel.writeInt(nonStandardActionCount);
+                for (int i = 0; i < actionCount; i++) {
+                    AccessibilityAction action = mActions.get(i);
+                    if (!isDefaultStandardAction(action)) {
+                        action.writeToParcel(parcel, flags);
+                    }
+                }
+            } else {
+                parcel.writeLong(0);
+                parcel.writeInt(0);
+            }
+        }
+
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mMaxTextLength);
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mMovementGranularities);
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mBooleanProperties);
+
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mPackageName);
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mClassName);
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mText);
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mHintText);
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mError);
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mStateDescription);
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            parcel.writeCharSequence(mContentDescription);
+        }
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mPaneTitle);
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mTooltipText);
+
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeString(mViewIdResourceName);
+
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mTextSelectionStart);
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mTextSelectionEnd);
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mInputType);
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mLiveRegion);
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mDrawingOrderInParent);
+
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeStringList(mExtraDataKeys);
+
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeBundle(mExtras);
+
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            parcel.writeInt(mRangeInfo.getType());
+            parcel.writeFloat(mRangeInfo.getMin());
+            parcel.writeFloat(mRangeInfo.getMax());
+            parcel.writeFloat(mRangeInfo.getCurrent());
+        }
+
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            parcel.writeInt(mCollectionInfo.getRowCount());
+            parcel.writeInt(mCollectionInfo.getColumnCount());
+            parcel.writeInt(mCollectionInfo.isHierarchical() ? 1 : 0);
+            parcel.writeInt(mCollectionInfo.getSelectionMode());
+        }
+
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            parcel.writeInt(mCollectionItemInfo.getRowIndex());
+            parcel.writeInt(mCollectionItemInfo.getRowSpan());
+            parcel.writeInt(mCollectionItemInfo.getColumnIndex());
+            parcel.writeInt(mCollectionItemInfo.getColumnSpan());
+            parcel.writeInt(mCollectionItemInfo.isHeading() ? 1 : 0);
+            parcel.writeInt(mCollectionItemInfo.isSelected() ? 1 : 0);
+        }
+
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            mTouchDelegateInfo.writeToParcel(parcel, flags);
+        }
+
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            parcel.writeValue(mExtraRenderingInfo.getLayoutSize());
+            parcel.writeFloat(mExtraRenderingInfo.getTextSizeInPx());
+            parcel.writeInt(mExtraRenderingInfo.getTextSizeUnit());
+        }
+
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            parcel.writeStrongBinder(mLeashedChild);
+        }
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            parcel.writeStrongBinder(mLeashedParent);
+        }
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            parcel.writeLong(mLeashedParentNodeId);
+        }
+
+        if (DEBUG) {
+            fieldIndex--;
+            if (totalFields != fieldIndex) {
+                throw new IllegalStateException("Number of fields mismatch: " + totalFields
+                        + " vs " + fieldIndex);
+            }
+        }
+    }
+
+    /**
+     * Initializes this instance from another one.
+     *
+     * @param other The other instance.
+     * @param usePoolingInfos whether using pooled object internally or not
+     */
+    private void init(AccessibilityNodeInfo other, boolean usePoolingInfos) {
+        mSealed = other.mSealed;
+        mSourceNodeId = other.mSourceNodeId;
+        mParentNodeId = other.mParentNodeId;
+        mLabelForId = other.mLabelForId;
+        mLabeledById = other.mLabeledById;
+        mTraversalBefore = other.mTraversalBefore;
+        mTraversalAfter = other.mTraversalAfter;
+        mWindowId = other.mWindowId;
+        mConnectionId = other.mConnectionId;
+        mBoundsInParent.set(other.mBoundsInParent);
+        mBoundsInScreen.set(other.mBoundsInScreen);
+        mPackageName = other.mPackageName;
+        mClassName = other.mClassName;
+        mText = other.mText;
+        mOriginalText = other.mOriginalText;
+        mHintText = other.mHintText;
+        mError = other.mError;
+        mStateDescription = other.mStateDescription;
+        mContentDescription = other.mContentDescription;
+        mPaneTitle = other.mPaneTitle;
+        mTooltipText = other.mTooltipText;
+        mViewIdResourceName = other.mViewIdResourceName;
+
+        if (mActions != null) mActions.clear();
+        final ArrayList<AccessibilityAction> otherActions = other.mActions;
+        if (otherActions != null && otherActions.size() > 0) {
+            if (mActions == null) {
+                mActions = new ArrayList(otherActions);
+            } else {
+                mActions.addAll(other.mActions);
+            }
+        }
+
+        mBooleanProperties = other.mBooleanProperties;
+        mMaxTextLength = other.mMaxTextLength;
+        mMovementGranularities = other.mMovementGranularities;
+
+
+        if (mChildNodeIds != null) mChildNodeIds.clear();
+        final LongArray otherChildNodeIds = other.mChildNodeIds;
+        if (otherChildNodeIds != null && otherChildNodeIds.size() > 0) {
+            if (mChildNodeIds == null) {
+                mChildNodeIds = otherChildNodeIds.clone();
+            } else {
+                mChildNodeIds.addAll(otherChildNodeIds);
+            }
+        }
+
+        mTextSelectionStart = other.mTextSelectionStart;
+        mTextSelectionEnd = other.mTextSelectionEnd;
+        mInputType = other.mInputType;
+        mLiveRegion = other.mLiveRegion;
+        mDrawingOrderInParent = other.mDrawingOrderInParent;
+
+        mExtraDataKeys = other.mExtraDataKeys;
+
+        mExtras = other.mExtras != null ? new Bundle(other.mExtras) : null;
+
+        if (usePoolingInfos) {
+            initPoolingInfos(other);
+        } else {
+            initCopyInfos(other);
+        }
+
+        final TouchDelegateInfo otherInfo = other.mTouchDelegateInfo;
+        mTouchDelegateInfo = (otherInfo != null)
+                ? new TouchDelegateInfo(otherInfo.mTargetMap, true) : null;
+
+        mLeashedChild = other.mLeashedChild;
+        mLeashedParent = other.mLeashedParent;
+        mLeashedParentNodeId = other.mLeashedParentNodeId;
+    }
+
+    private void initPoolingInfos(AccessibilityNodeInfo other) {
+        if (mRangeInfo != null) mRangeInfo.recycle();
+        mRangeInfo = (other.mRangeInfo != null)
+                ? RangeInfo.obtain(other.mRangeInfo) : null;
+        if (mCollectionInfo != null) mCollectionInfo.recycle();
+        mCollectionInfo = (other.mCollectionInfo != null)
+                ? CollectionInfo.obtain(other.mCollectionInfo) : null;
+        if (mCollectionItemInfo != null) mCollectionItemInfo.recycle();
+        mCollectionItemInfo =  (other.mCollectionItemInfo != null)
+                ? CollectionItemInfo.obtain(other.mCollectionItemInfo) : null;
+        if (mExtraRenderingInfo != null) mExtraRenderingInfo.recycle();
+        mExtraRenderingInfo = (other.mExtraRenderingInfo != null)
+                ? ExtraRenderingInfo.obtain(other.mExtraRenderingInfo) : null;
+    }
+
+    private void initCopyInfos(AccessibilityNodeInfo other) {
+        RangeInfo ri = other.mRangeInfo;
+        mRangeInfo = (ri == null) ? null
+                : new RangeInfo(ri.mType, ri.mMin, ri.mMax, ri.mCurrent);
+        CollectionInfo ci = other.mCollectionInfo;
+        mCollectionInfo = (ci == null) ? null
+                : new CollectionInfo(ci.mRowCount, ci.mColumnCount,
+                                     ci.mHierarchical, ci.mSelectionMode);
+        CollectionItemInfo cii = other.mCollectionItemInfo;
+        mCollectionItemInfo = (cii == null)  ? null
+                : new CollectionItemInfo(cii.mRowIndex, cii.mRowSpan, cii.mColumnIndex,
+                                         cii.mColumnSpan, cii.mHeading, cii.mSelected);
+        ExtraRenderingInfo ti = other.mExtraRenderingInfo;
+        mExtraRenderingInfo = (ti == null) ? null
+                : new ExtraRenderingInfo(ti);
+    }
+
+    /**
+     * Creates a new instance from a {@link Parcel}.
+     *
+     * @param parcel A parcel containing the state of a {@link AccessibilityNodeInfo}.
+     */
+    private void initFromParcel(Parcel parcel) {
+        // Bit mask of non-default-valued field indices
+        long nonDefaultFields = parcel.readLong();
+        int fieldIndex = 0;
+        final boolean sealed = isBitSet(nonDefaultFields, fieldIndex++)
+                ? (parcel.readInt() == 1)
+                : DEFAULT.mSealed;
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mSourceNodeId = parcel.readLong();
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mWindowId = parcel.readInt();
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mParentNodeId = parcel.readLong();
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mLabelForId = parcel.readLong();
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mLabeledById = parcel.readLong();
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalBefore = parcel.readLong();
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalAfter = parcel.readLong();
+
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mConnectionId = parcel.readInt();
+
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            final int childrenSize = parcel.readInt();
+            if (childrenSize <= 0) {
+                mChildNodeIds = null;
+            } else {
+                mChildNodeIds = new LongArray(childrenSize);
+                for (int i = 0; i < childrenSize; i++) {
+                    final long childId = parcel.readLong();
+                    mChildNodeIds.add(childId);
+                }
+            }
+        }
+
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            mBoundsInParent.top = parcel.readInt();
+            mBoundsInParent.bottom = parcel.readInt();
+            mBoundsInParent.left = parcel.readInt();
+            mBoundsInParent.right = parcel.readInt();
+        }
+
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            mBoundsInScreen.top = parcel.readInt();
+            mBoundsInScreen.bottom = parcel.readInt();
+            mBoundsInScreen.left = parcel.readInt();
+            mBoundsInScreen.right = parcel.readInt();
+        }
+
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            final long standardActions = parcel.readLong();
+            addStandardActions(standardActions);
+            final int nonStandardActionCount = parcel.readInt();
+            for (int i = 0; i < nonStandardActionCount; i++) {
+                final AccessibilityAction action =
+                        AccessibilityAction.CREATOR.createFromParcel(parcel);
+                addActionUnchecked(action);
+            }
+        }
+
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mMaxTextLength = parcel.readInt();
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mMovementGranularities = parcel.readInt();
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mBooleanProperties = parcel.readInt();
+
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mPackageName = parcel.readCharSequence();
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mClassName = parcel.readCharSequence();
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mText = parcel.readCharSequence();
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mHintText = parcel.readCharSequence();
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mError = parcel.readCharSequence();
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mStateDescription = parcel.readCharSequence();
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            mContentDescription = parcel.readCharSequence();
+        }
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mPaneTitle = parcel.readCharSequence();
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mTooltipText = parcel.readCharSequence();
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mViewIdResourceName = parcel.readString();
+
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mTextSelectionStart = parcel.readInt();
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mTextSelectionEnd = parcel.readInt();
+
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mInputType = parcel.readInt();
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mLiveRegion = parcel.readInt();
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mDrawingOrderInParent = parcel.readInt();
+
+        mExtraDataKeys = isBitSet(nonDefaultFields, fieldIndex++)
+                ? parcel.createStringArrayList()
+                : null;
+
+        mExtras = isBitSet(nonDefaultFields, fieldIndex++)
+                ? parcel.readBundle()
+                : null;
+
+        if (mRangeInfo != null) mRangeInfo.recycle();
+        mRangeInfo = isBitSet(nonDefaultFields, fieldIndex++)
+                ? RangeInfo.obtain(
+                        parcel.readInt(),
+                        parcel.readFloat(),
+                        parcel.readFloat(),
+                        parcel.readFloat())
+                : null;
+
+        if (mCollectionInfo != null) mCollectionInfo.recycle();
+        mCollectionInfo = isBitSet(nonDefaultFields, fieldIndex++)
+                ? CollectionInfo.obtain(
+                        parcel.readInt(),
+                        parcel.readInt(),
+                        parcel.readInt() == 1,
+                        parcel.readInt())
+                : null;
+
+        if (mCollectionItemInfo != null) mCollectionItemInfo.recycle();
+        mCollectionItemInfo = isBitSet(nonDefaultFields, fieldIndex++)
+                ? CollectionItemInfo.obtain(
+                        parcel.readInt(),
+                        parcel.readInt(),
+                        parcel.readInt(),
+                        parcel.readInt(),
+                        parcel.readInt() == 1,
+                        parcel.readInt() == 1)
+                : null;
+
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            mTouchDelegateInfo = TouchDelegateInfo.CREATOR.createFromParcel(parcel);
+        }
+
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            if (mExtraRenderingInfo != null) mExtraRenderingInfo.recycle();
+            mExtraRenderingInfo = ExtraRenderingInfo.obtain();
+            mExtraRenderingInfo.mLayoutSize = (Size) parcel.readValue(null);
+            mExtraRenderingInfo.mTextSizeInPx = parcel.readFloat();
+            mExtraRenderingInfo.mTextSizeUnit = parcel.readInt();
+        }
+
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            mLeashedChild = parcel.readStrongBinder();
+        }
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            mLeashedParent = parcel.readStrongBinder();
+        }
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            mLeashedParentNodeId = parcel.readLong();
+        }
+
+        mSealed = sealed;
+    }
+
+    /**
+     * Clears the state of this instance.
+     */
+    private void clear() {
+        init(DEFAULT, true /* usePoolingInfo */);
+    }
+
+    private static boolean isDefaultStandardAction(AccessibilityAction action) {
+        return (action.mSerializationFlag != -1L) && TextUtils.isEmpty(action.getLabel());
+    }
+
+    private static AccessibilityAction getActionSingleton(int actionId) {
+        final int actions = AccessibilityAction.sStandardActions.size();
+        for (int i = 0; i < actions; i++) {
+            AccessibilityAction currentAction = AccessibilityAction.sStandardActions.valueAt(i);
+            if (actionId == currentAction.getId()) {
+                return currentAction;
+            }
+        }
+
+        return null;
+    }
+
+    private static AccessibilityAction getActionSingletonBySerializationFlag(long flag) {
+        final int actions = AccessibilityAction.sStandardActions.size();
+        for (int i = 0; i < actions; i++) {
+            AccessibilityAction currentAction = AccessibilityAction.sStandardActions.valueAt(i);
+            if (flag == currentAction.mSerializationFlag) {
+                return currentAction;
+            }
+        }
+
+        return null;
+    }
+
+    private void addStandardActions(long serializationIdMask) {
+        long remainingIds = serializationIdMask;
+        while (remainingIds > 0) {
+            final long id = 1L << Long.numberOfTrailingZeros(remainingIds);
+            remainingIds &= ~id;
+            AccessibilityAction action = getActionSingletonBySerializationFlag(id);
+            addAction(action);
+        }
+    }
+
+    /**
+     * Gets the human readable action symbolic name.
+     *
+     * @param action The action.
+     * @return The symbolic name.
+     */
+    private static String getActionSymbolicName(int action) {
+        switch (action) {
+            case ACTION_FOCUS:
+                return "ACTION_FOCUS";
+            case ACTION_CLEAR_FOCUS:
+                return "ACTION_CLEAR_FOCUS";
+            case ACTION_SELECT:
+                return "ACTION_SELECT";
+            case ACTION_CLEAR_SELECTION:
+                return "ACTION_CLEAR_SELECTION";
+            case ACTION_CLICK:
+                return "ACTION_CLICK";
+            case ACTION_LONG_CLICK:
+                return "ACTION_LONG_CLICK";
+            case ACTION_ACCESSIBILITY_FOCUS:
+                return "ACTION_ACCESSIBILITY_FOCUS";
+            case ACTION_CLEAR_ACCESSIBILITY_FOCUS:
+                return "ACTION_CLEAR_ACCESSIBILITY_FOCUS";
+            case ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
+                return "ACTION_NEXT_AT_MOVEMENT_GRANULARITY";
+            case ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY:
+                return "ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY";
+            case ACTION_NEXT_HTML_ELEMENT:
+                return "ACTION_NEXT_HTML_ELEMENT";
+            case ACTION_PREVIOUS_HTML_ELEMENT:
+                return "ACTION_PREVIOUS_HTML_ELEMENT";
+            case ACTION_SCROLL_FORWARD:
+                return "ACTION_SCROLL_FORWARD";
+            case ACTION_SCROLL_BACKWARD:
+                return "ACTION_SCROLL_BACKWARD";
+            case ACTION_CUT:
+                return "ACTION_CUT";
+            case ACTION_COPY:
+                return "ACTION_COPY";
+            case ACTION_PASTE:
+                return "ACTION_PASTE";
+            case ACTION_SET_SELECTION:
+                return "ACTION_SET_SELECTION";
+            case ACTION_EXPAND:
+                return "ACTION_EXPAND";
+            case ACTION_COLLAPSE:
+                return "ACTION_COLLAPSE";
+            case ACTION_DISMISS:
+                return "ACTION_DISMISS";
+            case ACTION_SET_TEXT:
+                return "ACTION_SET_TEXT";
+            case R.id.accessibilityActionShowOnScreen:
+                return "ACTION_SHOW_ON_SCREEN";
+            case R.id.accessibilityActionScrollToPosition:
+                return "ACTION_SCROLL_TO_POSITION";
+            case R.id.accessibilityActionScrollUp:
+                return "ACTION_SCROLL_UP";
+            case R.id.accessibilityActionScrollLeft:
+                return "ACTION_SCROLL_LEFT";
+            case R.id.accessibilityActionScrollDown:
+                return "ACTION_SCROLL_DOWN";
+            case R.id.accessibilityActionScrollRight:
+                return "ACTION_SCROLL_RIGHT";
+            case R.id.accessibilityActionPageDown:
+                return "ACTION_PAGE_DOWN";
+            case R.id.accessibilityActionPageUp:
+                return "ACTION_PAGE_UP";
+            case R.id.accessibilityActionPageLeft:
+                return "ACTION_PAGE_LEFT";
+            case R.id.accessibilityActionPageRight:
+                return "ACTION_PAGE_RIGHT";
+            case R.id.accessibilityActionSetProgress:
+                return "ACTION_SET_PROGRESS";
+            case R.id.accessibilityActionContextClick:
+                return "ACTION_CONTEXT_CLICK";
+            case R.id.accessibilityActionShowTooltip:
+                return "ACTION_SHOW_TOOLTIP";
+            case R.id.accessibilityActionHideTooltip:
+                return "ACTION_HIDE_TOOLTIP";
+            case R.id.accessibilityActionPressAndHold:
+                return "ACTION_PRESS_AND_HOLD";
+            case R.id.accessibilityActionImeEnter:
+                return "ACTION_IME_ENTER";
+            default:
+                return "ACTION_UNKNOWN";
+        }
+    }
+
+    /**
+     * Gets the human readable movement granularity symbolic name.
+     *
+     * @param granularity The granularity.
+     * @return The symbolic name.
+     */
+    private static String getMovementGranularitySymbolicName(int granularity) {
+        switch (granularity) {
+            case MOVEMENT_GRANULARITY_CHARACTER:
+                return "MOVEMENT_GRANULARITY_CHARACTER";
+            case MOVEMENT_GRANULARITY_WORD:
+                return "MOVEMENT_GRANULARITY_WORD";
+            case MOVEMENT_GRANULARITY_LINE:
+                return "MOVEMENT_GRANULARITY_LINE";
+            case MOVEMENT_GRANULARITY_PARAGRAPH:
+                return "MOVEMENT_GRANULARITY_PARAGRAPH";
+            case MOVEMENT_GRANULARITY_PAGE:
+                return "MOVEMENT_GRANULARITY_PAGE";
+            default:
+                throw new IllegalArgumentException("Unknown movement granularity: " + granularity);
+        }
+    }
+
+    private static boolean canPerformRequestOverConnection(int connectionId,
+            int windowId, long accessibilityNodeId) {
+        return ((windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID)
+                && (getAccessibilityViewId(accessibilityNodeId) != UNDEFINED_ITEM_ID)
+                && (connectionId != UNDEFINED_CONNECTION_ID));
+    }
+
+    @Override
+    public boolean equals(@Nullable Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (object == null) {
+            return false;
+        }
+        if (getClass() != object.getClass()) {
+            return false;
+        }
+        AccessibilityNodeInfo other = (AccessibilityNodeInfo) object;
+        if (mSourceNodeId != other.mSourceNodeId) {
+            return false;
+        }
+        if (mWindowId != other.mWindowId) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + getAccessibilityViewId(mSourceNodeId);
+        result = prime * result + getVirtualDescendantId(mSourceNodeId);
+        result = prime * result + mWindowId;
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append(super.toString());
+
+        if (DEBUG) {
+            builder.append("; sourceNodeId: 0x").append(Long.toHexString(mSourceNodeId));
+            builder.append("; windowId: 0x").append(Long.toHexString(mWindowId));
+            builder.append("; accessibilityViewId: 0x")
+                    .append(Long.toHexString(getAccessibilityViewId(mSourceNodeId)));
+            builder.append("; virtualDescendantId: 0x")
+                    .append(Long.toHexString(getVirtualDescendantId(mSourceNodeId)));
+            builder.append("; mParentNodeId: 0x").append(Long.toHexString(mParentNodeId));
+            builder.append("; traversalBefore: 0x").append(Long.toHexString(mTraversalBefore));
+            builder.append("; traversalAfter: 0x").append(Long.toHexString(mTraversalAfter));
+
+            int granularities = mMovementGranularities;
+            builder.append("; MovementGranularities: [");
+            while (granularities != 0) {
+                final int granularity = 1 << Integer.numberOfTrailingZeros(granularities);
+                granularities &= ~granularity;
+                builder.append(getMovementGranularitySymbolicName(granularity));
+                if (granularities != 0) {
+                    builder.append(", ");
+                }
+            }
+            builder.append("]");
+
+            builder.append("; childAccessibilityIds: [");
+            final LongArray childIds = mChildNodeIds;
+            if (childIds != null) {
+                for (int i = 0, count = childIds.size(); i < count; i++) {
+                    builder.append("0x").append(Long.toHexString(childIds.get(i)));
+                    if (i < count - 1) {
+                        builder.append(", ");
+                    }
+                }
+            }
+            builder.append("]");
+        }
+
+        builder.append("; boundsInParent: ").append(mBoundsInParent);
+        builder.append("; boundsInScreen: ").append(mBoundsInScreen);
+
+        builder.append("; packageName: ").append(mPackageName);
+        builder.append("; className: ").append(mClassName);
+        builder.append("; text: ").append(mText);
+        builder.append("; error: ").append(mError);
+        builder.append("; maxTextLength: ").append(mMaxTextLength);
+        builder.append("; stateDescription: ").append(mStateDescription);
+        builder.append("; contentDescription: ").append(mContentDescription);
+        builder.append("; tooltipText: ").append(mTooltipText);
+        builder.append("; viewIdResName: ").append(mViewIdResourceName);
+
+        builder.append("; checkable: ").append(isCheckable());
+        builder.append("; checked: ").append(isChecked());
+        builder.append("; focusable: ").append(isFocusable());
+        builder.append("; focused: ").append(isFocused());
+        builder.append("; selected: ").append(isSelected());
+        builder.append("; clickable: ").append(isClickable());
+        builder.append("; longClickable: ").append(isLongClickable());
+        builder.append("; contextClickable: ").append(isContextClickable());
+        builder.append("; enabled: ").append(isEnabled());
+        builder.append("; password: ").append(isPassword());
+        builder.append("; scrollable: ").append(isScrollable());
+        builder.append("; importantForAccessibility: ").append(isImportantForAccessibility());
+        builder.append("; visible: ").append(isVisibleToUser());
+        builder.append("; actions: ").append(mActions);
+
+        return builder.toString();
+    }
+
+    private static AccessibilityNodeInfo getNodeForAccessibilityId(int connectionId,
+            int windowId, long accessibilityId) {
+        if (!canPerformRequestOverConnection(connectionId, windowId, accessibilityId)) {
+            return null;
+        }
+        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        return client.findAccessibilityNodeInfoByAccessibilityId(connectionId,
+                windowId, accessibilityId, false, FLAG_PREFETCH_PREDECESSORS
+                        | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS, null);
+    }
+
+    private static AccessibilityNodeInfo getNodeForAccessibilityId(int connectionId,
+            IBinder leashToken, long accessibilityId) {
+        if (!((leashToken != null)
+                && (getAccessibilityViewId(accessibilityId) != UNDEFINED_ITEM_ID)
+                && (connectionId != UNDEFINED_CONNECTION_ID))) {
+            return null;
+        }
+        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        return client.findAccessibilityNodeInfoByAccessibilityId(connectionId,
+                leashToken, accessibilityId, false, FLAG_PREFETCH_PREDECESSORS
+                        | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS, null);
+    }
+
+    /** @hide */
+    public static String idToString(long accessibilityId) {
+        int accessibilityViewId = getAccessibilityViewId(accessibilityId);
+        int virtualDescendantId = getVirtualDescendantId(accessibilityId);
+        return virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID
+                ? idItemToString(accessibilityViewId)
+                : idItemToString(accessibilityViewId) + ":" + idItemToString(virtualDescendantId);
+    }
+
+    private static String idItemToString(int item) {
+        switch (item) {
+            case ROOT_ITEM_ID: return "ROOT";
+            case UNDEFINED_ITEM_ID: return "UNDEFINED";
+            case AccessibilityNodeProvider.HOST_VIEW_ID: return "HOST";
+            default: return "" + item;
+        }
+    }
+
+    /**
+     * A class defining an action that can be performed on an {@link AccessibilityNodeInfo}.
+     * Each action has a unique id that is mandatory and optional data.
+     * <p>
+     * There are three categories of actions:
+     * <ul>
+     * <li><strong>Standard actions</strong> - These are actions that are reported and
+     * handled by the standard UI widgets in the platform. For each standard action
+     * there is a static constant defined in this class, e.g. {@link #ACTION_FOCUS}.
+     * These actions will have {@code null} labels.
+     * </li>
+     * <li><strong>Custom actions action</strong> - These are actions that are reported
+     * and handled by custom widgets. i.e. ones that are not part of the UI toolkit. For
+     * example, an application may define a custom action for clearing the user history.
+     * </li>
+     * <li><strong>Overriden standard actions</strong> - These are actions that override
+     * standard actions to customize them. For example, an app may add a label to the
+     * standard {@link #ACTION_CLICK} action to indicate to the user that this action clears
+     * browsing history.
+     * </ul>
+     * </p>
+     * <p>
+     * Actions are typically added to an {@link AccessibilityNodeInfo} by using
+     * {@link AccessibilityNodeInfo#addAction(AccessibilityAction)} within
+     * {@link View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)} and are performed
+     * within {@link View#performAccessibilityAction(int, Bundle)}.
+     * </p>
+     * <p class="note">
+     * <strong>Note:</strong> Views which support these actions should invoke
+     * {@link View#setImportantForAccessibility(int)} with
+     * {@link View#IMPORTANT_FOR_ACCESSIBILITY_YES} to ensure an {@link AccessibilityService}
+     * can discover the set of supported actions.
+     * </p>
+     */
+    public static final class AccessibilityAction implements Parcelable {
+
+        /** @hide */
+        public static final ArraySet<AccessibilityAction> sStandardActions = new ArraySet<>();
+
+        /**
+         * Action that gives input focus to the node.
+         */
+        public static final AccessibilityAction ACTION_FOCUS =
+                new AccessibilityAction(AccessibilityNodeInfo.ACTION_FOCUS);
+
+        /**
+         * Action that clears input focus of the node.
+         */
+        public static final AccessibilityAction ACTION_CLEAR_FOCUS =
+                new AccessibilityAction(AccessibilityNodeInfo.ACTION_CLEAR_FOCUS);
+
+        /**
+         *  Action that selects the node.
+         */
+        public static final AccessibilityAction ACTION_SELECT =
+                new AccessibilityAction(AccessibilityNodeInfo.ACTION_SELECT);
+
+        /**
+         * Action that deselects the node.
+         */
+        public static final AccessibilityAction ACTION_CLEAR_SELECTION =
+                new AccessibilityAction(AccessibilityNodeInfo.ACTION_CLEAR_SELECTION);
+
+        /**
+         * Action that clicks on the node info.
+         */
+        public static final AccessibilityAction ACTION_CLICK =
+                new AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK);
+
+        /**
+         * Action that long clicks on the node.
+         */
+        public static final AccessibilityAction ACTION_LONG_CLICK =
+                new AccessibilityAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
+
+        /**
+         * Action that gives accessibility focus to the node.
+         */
+        public static final AccessibilityAction ACTION_ACCESSIBILITY_FOCUS =
+                new AccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+
+        /**
+         * Action that clears accessibility focus of the node.
+         */
+        public static final AccessibilityAction ACTION_CLEAR_ACCESSIBILITY_FOCUS =
+                new AccessibilityAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
+
+        /**
+         * Action that requests to go to the next entity in this node's text
+         * at a given movement granularity. For example, move to the next character,
+         * word, etc.
+         * <p>
+         * <strong>Arguments:</strong>
+         * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+         *  AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT},
+         * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+         *  AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}<br>
+         * <strong>Example:</strong> Move to the previous character and do not extend selection.
+         * <code><pre><p>
+         *   Bundle arguments = new Bundle();
+         *   arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+         *           AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+         *   arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN,
+         *           false);
+         *   info.performAction(AccessibilityAction.ACTION_NEXT_AT_MOVEMENT_GRANULARITY.getId(),
+         *           arguments);
+         * </code></pre></p>
+         * </p>
+         *
+         * @see AccessibilityNodeInfo#ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+         *  AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+         * @see AccessibilityNodeInfo#ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+         *  AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+         *
+         * @see AccessibilityNodeInfo#setMovementGranularities(int)
+         *  AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+         * @see AccessibilityNodeInfo#getMovementGranularities()
+         *  AccessibilityNodeInfo.getMovementGranularities()
+         *
+         * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_CHARACTER
+         *  AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
+         * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_WORD
+         *  AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
+         * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_LINE
+         *  AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
+         * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_PARAGRAPH
+         *  AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
+         * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_PAGE
+         *  AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE
+         */
+        public static final AccessibilityAction ACTION_NEXT_AT_MOVEMENT_GRANULARITY =
+                new AccessibilityAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
+
+        /**
+         * Action that requests to go to the previous entity in this node's text
+         * at a given movement granularity. For example, move to the next character,
+         * word, etc.
+         * <p>
+         * <strong>Arguments:</strong>
+         * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+         *  AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT},
+         * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+         *  AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}<br>
+         * <strong>Example:</strong> Move to the next character and do not extend selection.
+         * <code><pre><p>
+         *   Bundle arguments = new Bundle();
+         *   arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+         *           AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+         *   arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN,
+         *           false);
+         *   info.performAction(AccessibilityAction.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY.getId(),
+         *           arguments);
+         * </code></pre></p>
+         * </p>
+         *
+         * @see AccessibilityNodeInfo#ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+         *  AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+         * @see AccessibilityNodeInfo#ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+         *  AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+         *
+         * @see AccessibilityNodeInfo#setMovementGranularities(int)
+         *   AccessibilityNodeInfo.setMovementGranularities(int)
+         * @see AccessibilityNodeInfo#getMovementGranularities()
+         *  AccessibilityNodeInfo.getMovementGranularities()
+         *
+         * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_CHARACTER
+         *  AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
+         * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_WORD
+         *  AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
+         * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_LINE
+         *  AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
+         * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_PARAGRAPH
+         *  AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
+         * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_PAGE
+         *  AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE
+         */
+        public static final AccessibilityAction ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY =
+                new AccessibilityAction(
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
+
+        /**
+         * Action to move to the next HTML element of a given type. For example, move
+         * to the BUTTON, INPUT, TABLE, etc.
+         * <p>
+         * <strong>Arguments:</strong>
+         * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_HTML_ELEMENT_STRING
+         *  AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING}<br>
+         * <strong>Example:</strong>
+         * <code><pre><p>
+         *   Bundle arguments = new Bundle();
+         *   arguments.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "BUTTON");
+         *   info.performAction(AccessibilityAction.ACTION_NEXT_HTML_ELEMENT.getId(), arguments);
+         * </code></pre></p>
+         * </p>
+         */
+        public static final AccessibilityAction ACTION_NEXT_HTML_ELEMENT =
+                new AccessibilityAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT);
+
+        /**
+         * Action to move to the previous HTML element of a given type. For example, move
+         * to the BUTTON, INPUT, TABLE, etc.
+         * <p>
+         * <strong>Arguments:</strong>
+         * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_HTML_ELEMENT_STRING
+         *  AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING}<br>
+         * <strong>Example:</strong>
+         * <code><pre><p>
+         *   Bundle arguments = new Bundle();
+         *   arguments.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "BUTTON");
+         *   info.performAction(AccessibilityAction.ACTION_PREVIOUS_HTML_ELEMENT.getId(), arguments);
+         * </code></pre></p>
+         * </p>
+         */
+        public static final AccessibilityAction ACTION_PREVIOUS_HTML_ELEMENT =
+                new AccessibilityAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT);
+
+        /**
+         * Action to scroll the node content forward.
+         */
+        public static final AccessibilityAction ACTION_SCROLL_FORWARD =
+                new AccessibilityAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+
+        /**
+         * Action to scroll the node content backward.
+         */
+        public static final AccessibilityAction ACTION_SCROLL_BACKWARD =
+                new AccessibilityAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+
+        /**
+         * Action to copy the current selection to the clipboard.
+         */
+        public static final AccessibilityAction ACTION_COPY =
+                new AccessibilityAction(AccessibilityNodeInfo.ACTION_COPY);
+
+        /**
+         * Action to paste the current clipboard content.
+         */
+        public static final AccessibilityAction ACTION_PASTE =
+                new AccessibilityAction(AccessibilityNodeInfo.ACTION_PASTE);
+
+        /**
+         * Action to cut the current selection and place it to the clipboard.
+         */
+        public static final AccessibilityAction ACTION_CUT =
+                new AccessibilityAction(AccessibilityNodeInfo.ACTION_CUT);
+
+        /**
+         * Action to set the selection. Performing this action with no arguments
+         * clears the selection.
+         * <p>
+         * <strong>Arguments:</strong>
+         * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_SELECTION_START_INT
+         *  AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT},
+         * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_SELECTION_END_INT
+         *  AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT}<br>
+         * <strong>Example:</strong>
+         * <code><pre><p>
+         *   Bundle arguments = new Bundle();
+         *   arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 1);
+         *   arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 2);
+         *   info.performAction(AccessibilityAction.ACTION_SET_SELECTION.getId(), arguments);
+         * </code></pre></p>
+         * </p>
+         *
+         * @see AccessibilityNodeInfo#ACTION_ARGUMENT_SELECTION_START_INT
+         *  AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT
+         * @see AccessibilityNodeInfo#ACTION_ARGUMENT_SELECTION_END_INT
+         *  AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT
+         */
+        public static final AccessibilityAction ACTION_SET_SELECTION =
+                new AccessibilityAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
+
+        /**
+         * Action to expand an expandable node.
+         */
+        public static final AccessibilityAction ACTION_EXPAND =
+                new AccessibilityAction(AccessibilityNodeInfo.ACTION_EXPAND);
+
+        /**
+         * Action to collapse an expandable node.
+         */
+        public static final AccessibilityAction ACTION_COLLAPSE =
+                new AccessibilityAction(AccessibilityNodeInfo.ACTION_COLLAPSE);
+
+        /**
+         * Action to dismiss a dismissable node.
+         */
+        public static final AccessibilityAction ACTION_DISMISS =
+                new AccessibilityAction(AccessibilityNodeInfo.ACTION_DISMISS);
+
+        /**
+         * Action that sets the text of the node. Performing the action without argument,
+         * using <code> null</code> or empty {@link CharSequence} will clear the text. This
+         * action will also put the cursor at the end of text.
+         * <p>
+         * <strong>Arguments:</strong>
+         * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE
+         *  AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE}<br>
+         * <strong>Example:</strong>
+         * <code><pre><p>
+         *   Bundle arguments = new Bundle();
+         *   arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,
+         *       "android");
+         *   info.performAction(AccessibilityAction.ACTION_SET_TEXT.getId(), arguments);
+         * </code></pre></p>
+         */
+        public static final AccessibilityAction ACTION_SET_TEXT =
+                new AccessibilityAction(AccessibilityNodeInfo.ACTION_SET_TEXT);
+
+        /**
+         * Action that requests the node make its bounding rectangle visible
+         * on the screen, scrolling if necessary just enough.
+         *
+         * @see View#requestRectangleOnScreen(Rect)
+         */
+        public static final AccessibilityAction ACTION_SHOW_ON_SCREEN =
+                new AccessibilityAction(R.id.accessibilityActionShowOnScreen);
+
+        /**
+         * Action that scrolls the node to make the specified collection
+         * position visible on screen.
+         * <p>
+         * <strong>Arguments:</strong>
+         * <ul>
+         *     <li>{@link AccessibilityNodeInfo#ACTION_ARGUMENT_ROW_INT}</li>
+         *     <li>{@link AccessibilityNodeInfo#ACTION_ARGUMENT_COLUMN_INT}</li>
+         * <ul>
+         *
+         * @see AccessibilityNodeInfo#getCollectionInfo()
+         */
+        public static final AccessibilityAction ACTION_SCROLL_TO_POSITION =
+                new AccessibilityAction(R.id.accessibilityActionScrollToPosition);
+
+        /**
+         * Action to scroll the node content up.
+         */
+        public static final AccessibilityAction ACTION_SCROLL_UP =
+                new AccessibilityAction(R.id.accessibilityActionScrollUp);
+
+        /**
+         * Action to scroll the node content left.
+         */
+        public static final AccessibilityAction ACTION_SCROLL_LEFT =
+                new AccessibilityAction(R.id.accessibilityActionScrollLeft);
+
+        /**
+         * Action to scroll the node content down.
+         */
+        public static final AccessibilityAction ACTION_SCROLL_DOWN =
+                new AccessibilityAction(R.id.accessibilityActionScrollDown);
+
+        /**
+         * Action to scroll the node content right.
+         */
+        public static final AccessibilityAction ACTION_SCROLL_RIGHT =
+                new AccessibilityAction(R.id.accessibilityActionScrollRight);
+
+        /**
+         * Action to move to the page above.
+         */
+        public static final AccessibilityAction ACTION_PAGE_UP =
+                new AccessibilityAction(R.id.accessibilityActionPageUp);
+
+        /**
+         * Action to move to the page below.
+         */
+        public static final AccessibilityAction ACTION_PAGE_DOWN =
+                new AccessibilityAction(R.id.accessibilityActionPageDown);
+
+        /**
+         * Action to move to the page left.
+         */
+        public static final AccessibilityAction ACTION_PAGE_LEFT =
+                new AccessibilityAction(R.id.accessibilityActionPageLeft);
+
+        /**
+         * Action to move to the page right.
+         */
+        public static final AccessibilityAction ACTION_PAGE_RIGHT =
+                new AccessibilityAction(R.id.accessibilityActionPageRight);
+
+        /**
+         * Action that context clicks the node.
+         */
+        public static final AccessibilityAction ACTION_CONTEXT_CLICK =
+                new AccessibilityAction(R.id.accessibilityActionContextClick);
+
+        /**
+         * Action that sets progress between {@link  RangeInfo#getMin() RangeInfo.getMin()} and
+         * {@link  RangeInfo#getMax() RangeInfo.getMax()}. It should use the same value type as
+         * {@link RangeInfo#getType() RangeInfo.getType()}
+         * <p>
+         * <strong>Arguments:</strong>
+         * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_PROGRESS_VALUE}
+         *
+         * @see RangeInfo
+         */
+        public static final AccessibilityAction ACTION_SET_PROGRESS =
+                new AccessibilityAction(R.id.accessibilityActionSetProgress);
+
+        /**
+         * Action to move a window to a new location.
+         * <p>
+         * <strong>Arguments:</strong>
+         * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_MOVE_WINDOW_X}
+         * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_MOVE_WINDOW_Y}
+         */
+        public static final AccessibilityAction ACTION_MOVE_WINDOW =
+                new AccessibilityAction(R.id.accessibilityActionMoveWindow);
+
+        /**
+         * Action to show a tooltip. A node should expose this action only for views with tooltip
+         * text that but are not currently showing a tooltip.
+         */
+        public static final AccessibilityAction ACTION_SHOW_TOOLTIP =
+                new AccessibilityAction(R.id.accessibilityActionShowTooltip);
+
+        /**
+         * Action to hide a tooltip. A node should expose this action only for views that are
+         * currently showing a tooltip.
+         */
+        public static final AccessibilityAction ACTION_HIDE_TOOLTIP =
+                new AccessibilityAction(R.id.accessibilityActionHideTooltip);
+
+        /**
+         * Action that presses and holds a node.
+         * <p>
+         * This action is for nodes that have distinct behavior that depends on how long a press is
+         * held. Nodes having a single action for long press should use {@link #ACTION_LONG_CLICK}
+         *  instead of this action, and nodes should not expose both actions.
+         * <p>
+         * When calling {@code performAction(ACTION_PRESS_AND_HOLD, bundle}, use
+         * {@link #ACTION_ARGUMENT_PRESS_AND_HOLD_DURATION_MILLIS_INT} to specify how long the
+         * node is pressed. The first time an accessibility service performs ACTION_PRES_AND_HOLD
+         * on a node, it must specify 0 as ACTION_ARGUMENT_PRESS_AND_HOLD, so the application is
+         * notified that the held state has started. To ensure reasonable behavior, the values
+         * must be increased incrementally and may not exceed 10,000. UIs requested
+         * to hold for times outside of this range should ignore the action.
+         * <p>
+         * The total time the element is held could be specified by an accessibility user up-front,
+         * or may depend on what happens on the UI as the user continues to request the hold.
+         * <p>
+         *   <strong>Note:</strong> The time between dispatching the action and it arriving in the
+         *     UI process is not guaranteed. It is possible on a busy system for the time to expire
+         *     unexpectedly. For the case of holding down a key for a repeating action, a delayed
+         *     arrival should be benign. Please do not use this sort of action in cases where such
+         *     delays will lead to unexpected UI behavior.
+         * <p>
+         */
+        @NonNull public static final AccessibilityAction ACTION_PRESS_AND_HOLD =
+                new AccessibilityAction(R.id.accessibilityActionPressAndHold);
+
+        /**
+         * Action to send an ime actionId which is from
+         * {@link android.view.inputmethod.EditorInfo#actionId}. This ime actionId sets by
+         * {@link TextView#setImeActionLabel(CharSequence, int)}, or it would be
+         * {@link android.view.inputmethod.EditorInfo#IME_ACTION_UNSPECIFIED} if no specific
+         * actionId has set. A node should expose this action only for views that are currently
+         * with input focus and editable.
+         */
+        @NonNull public static final AccessibilityAction ACTION_IME_ENTER =
+                new AccessibilityAction(R.id.accessibilityActionImeEnter);
+
+        private final int mActionId;
+        private final CharSequence mLabel;
+
+        /** @hide */
+        public long mSerializationFlag = -1L;
+
+        /**
+         * Creates a new AccessibilityAction. For adding a standard action without a specific label,
+         * use the static constants.
+         *
+         * You can also override the description for one the standard actions. Below is an example
+         * how to override the standard click action by adding a custom label:
+         * <pre>
+         *   AccessibilityAction action = new AccessibilityAction(
+         *           AccessibilityAction.ACTION_CLICK.getId(), getLocalizedLabel());
+         *   node.addAction(action);
+         * </pre>
+         *
+         * @param actionId The id for this action. This should either be one of the
+         *                 standard actions or a specific action for your app. In that case it is
+         *                 required to use a resource identifier.
+         * @param label The label for the new AccessibilityAction.
+         */
+        public AccessibilityAction(int actionId, @Nullable CharSequence label) {
+            mActionId = actionId;
+            mLabel = label;
+        }
+
+        /**
+         * Constructor for a {@link #sStandardActions standard} action
+         */
+        private AccessibilityAction(int standardActionId) {
+            this(standardActionId, null);
+
+            mSerializationFlag = bitAt(sStandardActions.size());
+            sStandardActions.add(this);
+        }
+
+        /**
+         * Gets the id for this action.
+         *
+         * @return The action id.
+         */
+        public int getId() {
+            return mActionId;
+        }
+
+        /**
+         * Gets the label for this action. Its purpose is to describe the
+         * action to user.
+         *
+         * @return The label.
+         */
+        public CharSequence getLabel() {
+            return mLabel;
+        }
+
+        @Override
+        public int hashCode() {
+            return mActionId;
+        }
+
+        @Override
+        public boolean equals(@Nullable Object other) {
+            if (other == null) {
+                return false;
+            }
+
+            if (other == this) {
+                return true;
+            }
+
+            if (getClass() != other.getClass()) {
+                return false;
+            }
+
+            return mActionId == ((AccessibilityAction)other).mActionId;
+        }
+
+        @Override
+        public String toString() {
+            return "AccessibilityAction: " + getActionSymbolicName(mActionId) + " - " + mLabel;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        /**
+         * Write data into a parcel.
+         */
+        public void writeToParcel(@NonNull Parcel out, int flags) {
+            out.writeInt(mActionId);
+            out.writeCharSequence(mLabel);
+        }
+
+        public static final @NonNull Parcelable.Creator<AccessibilityAction> CREATOR =
+                new Parcelable.Creator<AccessibilityAction>() {
+                    public AccessibilityAction createFromParcel(Parcel in) {
+                        return new AccessibilityAction(in);
+                    }
+
+                    public AccessibilityAction[] newArray(int size) {
+                        return new AccessibilityAction[size];
+                    }
+                };
+
+        private AccessibilityAction(Parcel in) {
+            mActionId = in.readInt();
+            mLabel = in.readCharSequence();
+        }
+    }
+
+    /**
+     * Class with information if a node is a range. Use
+     * {@link RangeInfo#obtain(int, float, float, float)} to get an instance. Recycling is
+     * handled by the {@link AccessibilityNodeInfo} to which this object is attached.
+     */
+    public static final class RangeInfo {
+        private static final int MAX_POOL_SIZE = 10;
+
+        /** Range type: integer. */
+        public static final int RANGE_TYPE_INT = 0;
+        /** Range type: float. */
+        public static final int RANGE_TYPE_FLOAT = 1;
+        /** Range type: percent with values from zero to one hundred. */
+        public static final int RANGE_TYPE_PERCENT = 2;
+
+        private static final SynchronizedPool<RangeInfo> sPool =
+                new SynchronizedPool<AccessibilityNodeInfo.RangeInfo>(MAX_POOL_SIZE);
+
+        private int mType;
+        private float mMin;
+        private float mMax;
+        private float mCurrent;
+
+        /**
+         * Obtains a pooled instance that is a clone of another one.
+         *
+         * <p>In most situations object pooling is not beneficial. Create a new instance using the
+         * constructor {@link AccessibilityNodeInfo.RangeInfo#RangeInfo(int,
+         * float, float, float)} instead.
+         *
+         * @param other The instance to clone.
+         *
+         * @hide
+         */
+        public static RangeInfo obtain(RangeInfo other) {
+            return obtain(other.mType, other.mMin, other.mMax, other.mCurrent);
+        }
+
+        /**
+         * Obtains a pooled instance.
+         *
+         * <p>In most situations object pooling is not beneficial. Create a new instance using the
+         * constructor {@link AccessibilityNodeInfo.RangeInfo#RangeInfo(int,
+         * float, float, float)} instead.
+         *
+         * @param type The type of the range.
+         * @param min The minimum value. Use {@code Float.NEGATIVE_INFINITY} if the range has no
+         *            minimum.
+         * @param max The maximum value. Use {@code Float.POSITIVE_INFINITY} if the range has no
+         *            maximum.
+         * @param current The current value.
+         */
+        public static RangeInfo obtain(int type, float min, float max, float current) {
+            RangeInfo info = sPool.acquire();
+            if (info == null) {
+                return new RangeInfo(type, min, max, current);
+            }
+
+            info.mType = type;
+            info.mMin = min;
+            info.mMax = max;
+            info.mCurrent = current;
+            return info;
+        }
+
+        /**
+         * Creates a new range.
+         *
+         * @param type The type of the range.
+         * @param min The minimum value. Use {@code Float.NEGATIVE_INFINITY} if the range has no
+         *            minimum.
+         * @param max The maximum value. Use {@code Float.POSITIVE_INFINITY} if the range has no
+         *            maximum.
+         * @param current The current value.
+         */
+        public RangeInfo(int type, float min, float max, float current) {
+            mType = type;
+            mMin = min;
+            mMax = max;
+            mCurrent = current;
+        }
+
+        /**
+         * Gets the range type.
+         *
+         * @return The range type.
+         *
+         * @see #RANGE_TYPE_INT
+         * @see #RANGE_TYPE_FLOAT
+         * @see #RANGE_TYPE_PERCENT
+         */
+        public int getType() {
+            return mType;
+        }
+
+        /**
+         * Gets the minimum value.
+         *
+         * @return The minimum value, or {@code Float.NEGATIVE_INFINITY} if no minimum exists.
+         */
+        public float getMin() {
+            return mMin;
+        }
+
+        /**
+         * Gets the maximum value.
+         *
+         * @return The maximum value, or {@code Float.POSITIVE_INFINITY} if no maximum exists.
+         */
+        public float getMax() {
+            return mMax;
+        }
+
+        /**
+         * Gets the current value.
+         *
+         * @return The current value.
+         */
+        public float getCurrent() {
+            return mCurrent;
+        }
+
+        /**
+         * Recycles this instance.
+         *
+         * <p>In most situations object pooling is not beneficial, and recycling is not necessary.
+         */
+        void recycle() {
+            clear();
+            sPool.release(this);
+        }
+
+        private void clear() {
+            mType = 0;
+            mMin = 0;
+            mMax = 0;
+            mCurrent = 0;
+        }
+    }
+
+    /**
+     * Class with information if a node is a collection. Use
+     * {@link CollectionInfo#obtain(int, int, boolean)} to get an instance. Recycling is
+     * handled by the {@link AccessibilityNodeInfo} to which this object is attached.
+     * <p>
+     * A collection of items has rows and columns and may be hierarchical.
+     * For example, a horizontal list is a collection with one column, as
+     * many rows as the list items, and is not hierarchical; A table is a
+     * collection with several rows, several columns, and is not hierarchical;
+     * A vertical tree is a hierarchical collection with one column and
+     * as many rows as the first level children.
+     * </p>
+     */
+    public static final class CollectionInfo {
+        /** Selection mode where items are not selectable. */
+        public static final int SELECTION_MODE_NONE = 0;
+
+        /** Selection mode where a single item may be selected. */
+        public static final int SELECTION_MODE_SINGLE = 1;
+
+        /** Selection mode where multiple items may be selected. */
+        public static final int SELECTION_MODE_MULTIPLE = 2;
+
+        private static final int MAX_POOL_SIZE = 20;
+
+        private static final SynchronizedPool<CollectionInfo> sPool =
+                new SynchronizedPool<>(MAX_POOL_SIZE);
+
+        private int mRowCount;
+        private int mColumnCount;
+        private boolean mHierarchical;
+        private int mSelectionMode;
+
+        /**
+         * Obtains a pooled instance that is a clone of another one.
+         *
+         * <p>In most situations object pooling is not beneficial. Create a new instance using the
+         * constructor {@link
+         * AccessibilityNodeInfo.CollectionInfo#CollectionInfo} instead.
+         *
+         * @param other The instance to clone.
+         * @hide
+         */
+        public static CollectionInfo obtain(CollectionInfo other) {
+            return CollectionInfo.obtain(other.mRowCount, other.mColumnCount, other.mHierarchical,
+                    other.mSelectionMode);
+        }
+
+        /**
+         * Obtains a pooled instance.
+         *
+         * <p>In most situations object pooling is not beneficial. Create a new instance using the
+         * constructor {@link
+         * AccessibilityNodeInfo.CollectionInfo#CollectionInfo(int, int,
+         * boolean)} instead.
+         *
+         * @param rowCount The number of rows, or -1 if count is unknown.
+         * @param columnCount The number of columns, or -1 if count is unknown.
+         * @param hierarchical Whether the collection is hierarchical.
+         */
+        public static CollectionInfo obtain(int rowCount, int columnCount,
+                boolean hierarchical) {
+            return obtain(rowCount, columnCount, hierarchical, SELECTION_MODE_NONE);
+        }
+
+        /**
+         * Obtains a pooled instance.
+         *
+         * <p>In most situations object pooling is not beneficial. Create a new instance using the
+         * constructor {@link
+         * AccessibilityNodeInfo.CollectionInfo#CollectionInfo(int, int,
+         * boolean, int)} instead.
+         *
+         * @param rowCount The number of rows.
+         * @param columnCount The number of columns.
+         * @param hierarchical Whether the collection is hierarchical.
+         * @param selectionMode The collection's selection mode, one of:
+         *            <ul>
+         *            <li>{@link #SELECTION_MODE_NONE}
+         *            <li>{@link #SELECTION_MODE_SINGLE}
+         *            <li>{@link #SELECTION_MODE_MULTIPLE}
+         *            </ul>
+         */
+        public static CollectionInfo obtain(int rowCount, int columnCount,
+                boolean hierarchical, int selectionMode) {
+           final CollectionInfo info = sPool.acquire();
+            if (info == null) {
+                return new CollectionInfo(rowCount, columnCount, hierarchical, selectionMode);
+            }
+
+            info.mRowCount = rowCount;
+            info.mColumnCount = columnCount;
+            info.mHierarchical = hierarchical;
+            info.mSelectionMode = selectionMode;
+            return info;
+        }
+
+        /**
+         * Creates a new instance.
+         *
+         * @param rowCount The number of rows.
+         * @param columnCount The number of columns.
+         * @param hierarchical Whether the collection is hierarchical.
+         */
+        public CollectionInfo(int rowCount, int columnCount, boolean hierarchical) {
+            this(rowCount, columnCount, hierarchical, SELECTION_MODE_NONE);
+        }
+
+        /**
+         * Creates a new instance.
+         *
+         * @param rowCount The number of rows.
+         * @param columnCount The number of columns.
+         * @param hierarchical Whether the collection is hierarchical.
+         * @param selectionMode The collection's selection mode.
+         */
+        public CollectionInfo(int rowCount, int columnCount, boolean hierarchical,
+                int selectionMode) {
+            mRowCount = rowCount;
+            mColumnCount = columnCount;
+            mHierarchical = hierarchical;
+            mSelectionMode = selectionMode;
+        }
+
+        /**
+         * Gets the number of rows.
+         *
+         * @return The row count, or -1 if count is unknown.
+         */
+        public int getRowCount() {
+            return mRowCount;
+        }
+
+        /**
+         * Gets the number of columns.
+         *
+         * @return The column count, or -1 if count is unknown.
+         */
+        public int getColumnCount() {
+            return mColumnCount;
+        }
+
+        /**
+         * Gets if the collection is a hierarchically ordered.
+         *
+         * @return Whether the collection is hierarchical.
+         */
+        public boolean isHierarchical() {
+            return mHierarchical;
+        }
+
+        /**
+         * Gets the collection's selection mode.
+         *
+         * @return The collection's selection mode, one of:
+         *         <ul>
+         *         <li>{@link #SELECTION_MODE_NONE}
+         *         <li>{@link #SELECTION_MODE_SINGLE}
+         *         <li>{@link #SELECTION_MODE_MULTIPLE}
+         *         </ul>
+         */
+        public int getSelectionMode() {
+            return mSelectionMode;
+        }
+
+        /**
+         * Recycles this instance.
+         *
+         * <p>In most situations object pooling is not beneficial, and recycling is not necessary.
+         */
+        void recycle() {
+            clear();
+            sPool.release(this);
+        }
+
+        private void clear() {
+            mRowCount = 0;
+            mColumnCount = 0;
+            mHierarchical = false;
+            mSelectionMode = SELECTION_MODE_NONE;
+        }
+    }
+
+    /**
+     * Class with information if a node is a collection item. Use
+     * {@link CollectionItemInfo#obtain(int, int, int, int, boolean)}
+     * to get an instance. Recycling is handled by the {@link AccessibilityNodeInfo} to which this
+     * object is attached.
+     * <p>
+     * A collection item is contained in a collection, it starts at
+     * a given row and column in the collection, and spans one or
+     * more rows and columns. For example, a header of two related
+     * table columns starts at the first row and the first column,
+     * spans one row and two columns.
+     * </p>
+     */
+    public static final class CollectionItemInfo {
+        private static final int MAX_POOL_SIZE = 20;
+
+        private static final SynchronizedPool<CollectionItemInfo> sPool =
+                new SynchronizedPool<>(MAX_POOL_SIZE);
+
+        /**
+         * Obtains a pooled instance that is a clone of another one.
+         *
+         * <p>In most situations object pooling is not beneficial. Create a new instance using the
+         * constructor {@link
+         * AccessibilityNodeInfo.CollectionItemInfo#CollectionItemInfo}
+         * instead.
+         *
+         * @param other The instance to clone.
+         * @hide
+         */
+        public static CollectionItemInfo obtain(CollectionItemInfo other) {
+            return CollectionItemInfo.obtain(other.mRowIndex, other.mRowSpan, other.mColumnIndex,
+                    other.mColumnSpan, other.mHeading, other.mSelected);
+        }
+
+        /**
+         * Obtains a pooled instance.
+         *
+         * <p>In most situations object pooling is not beneficial. Create a new instance using the
+         * constructor {@link
+         * AccessibilityNodeInfo.CollectionItemInfo#CollectionItemInfo(int,
+         * int, int, int, boolean)} instead.
+         *
+         * @param rowIndex The row index at which the item is located.
+         * @param rowSpan The number of rows the item spans.
+         * @param columnIndex The column index at which the item is located.
+         * @param columnSpan The number of columns the item spans.
+         * @param heading Whether the item is a heading. (Prefer
+         *                {@link AccessibilityNodeInfo#setHeading(boolean)}).
+         */
+        public static CollectionItemInfo obtain(int rowIndex, int rowSpan,
+                int columnIndex, int columnSpan, boolean heading) {
+            return obtain(rowIndex, rowSpan, columnIndex, columnSpan, heading, false);
+        }
+
+        /**
+         * Obtains a pooled instance.
+         *
+         * <p>In most situations object pooling is not beneficial. Creates a new instance using the
+         * constructor {@link
+         * AccessibilityNodeInfo.CollectionItemInfo#CollectionItemInfo(int,
+         * int, int, int, boolean, boolean)} instead.
+         *
+         * @param rowIndex The row index at which the item is located.
+         * @param rowSpan The number of rows the item spans.
+         * @param columnIndex The column index at which the item is located.
+         * @param columnSpan The number of columns the item spans.
+         * @param heading Whether the item is a heading. (Prefer
+         *                {@link AccessibilityNodeInfo#setHeading(boolean)})
+         * @param selected Whether the item is selected.
+         */
+        public static CollectionItemInfo obtain(int rowIndex, int rowSpan,
+                int columnIndex, int columnSpan, boolean heading, boolean selected) {
+            final CollectionItemInfo info = sPool.acquire();
+            if (info == null) {
+                return new CollectionItemInfo(
+                        rowIndex, rowSpan, columnIndex, columnSpan, heading, selected);
+            }
+
+            info.mRowIndex = rowIndex;
+            info.mRowSpan = rowSpan;
+            info.mColumnIndex = columnIndex;
+            info.mColumnSpan = columnSpan;
+            info.mHeading = heading;
+            info.mSelected = selected;
+            return info;
+        }
+
+        private boolean mHeading;
+        private int mColumnIndex;
+        private int mRowIndex;
+        private int mColumnSpan;
+        private int mRowSpan;
+        private boolean mSelected;
+
+        /**
+         * Creates a new instance.
+         *
+         * @param rowIndex The row index at which the item is located.
+         * @param rowSpan The number of rows the item spans.
+         * @param columnIndex The column index at which the item is located.
+         * @param columnSpan The number of columns the item spans.
+         * @param heading Whether the item is a heading.
+         */
+        public CollectionItemInfo(int rowIndex, int rowSpan, int columnIndex, int columnSpan,
+                boolean heading) {
+            this(rowIndex, rowSpan, columnIndex, columnSpan, heading, false);
+        }
+
+        /**
+         * Creates a new instance.
+         *
+         * @param rowIndex The row index at which the item is located.
+         * @param rowSpan The number of rows the item spans.
+         * @param columnIndex The column index at which the item is located.
+         * @param columnSpan The number of columns the item spans.
+         * @param heading Whether the item is a heading.
+         * @param selected Whether the item is selected.
+         */
+        public CollectionItemInfo(int rowIndex, int rowSpan, int columnIndex, int columnSpan,
+                boolean heading, boolean selected) {
+            mRowIndex = rowIndex;
+            mRowSpan = rowSpan;
+            mColumnIndex = columnIndex;
+            mColumnSpan = columnSpan;
+            mHeading = heading;
+            mSelected = selected;
+        }
+
+        /**
+         * Gets the column index at which the item is located.
+         *
+         * @return The column index.
+         */
+        public int getColumnIndex() {
+            return mColumnIndex;
+        }
+
+        /**
+         * Gets the row index at which the item is located.
+         *
+         * @return The row index.
+         */
+        public int getRowIndex() {
+            return mRowIndex;
+        }
+
+        /**
+         * Gets the number of columns the item spans.
+         *
+         * @return The column span.
+         */
+        public int getColumnSpan() {
+            return mColumnSpan;
+        }
+
+        /**
+         * Gets the number of rows the item spans.
+         *
+         * @return The row span.
+         */
+        public int getRowSpan() {
+            return mRowSpan;
+        }
+
+        /**
+         * Gets if the collection item is a heading. For example, section
+         * heading, table header, etc.
+         *
+         * @return If the item is a heading.
+         * @deprecated Use {@link AccessibilityNodeInfo#isHeading()}
+         */
+        public boolean isHeading() {
+            return mHeading;
+        }
+
+        /**
+         * Gets if the collection item is selected.
+         *
+         * @return If the item is selected.
+         */
+        public boolean isSelected() {
+            return mSelected;
+        }
+
+        /**
+         * Recycles this instance.
+         *
+         * <p>In most situations object pooling is not beneficial, and recycling is not necessary.
+         */
+        void recycle() {
+            clear();
+            sPool.release(this);
+        }
+
+        private void clear() {
+            mColumnIndex = 0;
+            mColumnSpan = 0;
+            mRowIndex = 0;
+            mRowSpan = 0;
+            mHeading = false;
+            mSelected = false;
+        }
+    }
+
+    /**
+     * Class with information of touch delegated views and regions from {@link TouchDelegate} for
+     * the {@link AccessibilityNodeInfo}.
+     *
+     * @see AccessibilityNodeInfo#setTouchDelegateInfo(TouchDelegateInfo)
+     */
+    public static final class TouchDelegateInfo implements Parcelable {
+        private ArrayMap<Region, Long> mTargetMap;
+        // Two ids are initialized lazily in AccessibilityNodeInfo#getTouchDelegateInfo
+        private int mConnectionId;
+        private int mWindowId;
+
+        /**
+         * Create a new instance of {@link TouchDelegateInfo}.
+         *
+         * @param targetMap A map from regions (in view coordinates) to delegated views.
+         * @throws IllegalArgumentException if targetMap is empty or {@code null} in
+         * Regions or Views.
+         */
+        public TouchDelegateInfo(@NonNull Map<Region, View> targetMap) {
+            Preconditions.checkArgument(!targetMap.isEmpty()
+                    && !targetMap.containsKey(null) && !targetMap.containsValue(null));
+            mTargetMap = new ArrayMap<>(targetMap.size());
+            for (final Region region : targetMap.keySet()) {
+                final View view = targetMap.get(region);
+                mTargetMap.put(region, (long) view.getAccessibilityViewId());
+            }
+        }
+
+        /**
+         * Create a new instance from target map.
+         *
+         * @param targetMap A map from regions (in view coordinates) to delegated views'
+         *                  accessibility id.
+         * @param doCopy True if shallow copy targetMap.
+         * @throws IllegalArgumentException if targetMap is empty or {@code null} in
+         * Regions or Views.
+         */
+        TouchDelegateInfo(@NonNull ArrayMap<Region, Long> targetMap, boolean doCopy) {
+            Preconditions.checkArgument(!targetMap.isEmpty()
+                    && !targetMap.containsKey(null) && !targetMap.containsValue(null));
+            if (doCopy) {
+                mTargetMap = new ArrayMap<>(targetMap.size());
+                mTargetMap.putAll(targetMap);
+            } else {
+                mTargetMap = targetMap;
+            }
+        }
+
+        /**
+         * Set the connection ID.
+         *
+         * @param connectionId The connection id.
+         */
+        private void setConnectionId(int connectionId) {
+            mConnectionId = connectionId;
+        }
+
+        /**
+         * Set the window ID.
+         *
+         * @param windowId The window id.
+         */
+        private void setWindowId(int windowId) {
+            mWindowId = windowId;
+        }
+
+        /**
+         * Returns the number of touch delegate target region.
+         *
+         * @return Number of touch delegate target region.
+         */
+        public int getRegionCount() {
+            return mTargetMap.size();
+        }
+
+        /**
+         * Return the {@link Region} at the given index in the {@link TouchDelegateInfo}.
+         *
+         * @param index The desired index, must be between 0 and {@link #getRegionCount()}-1.
+         * @return Returns the {@link Region} stored at the given index.
+         */
+        @NonNull
+        public Region getRegionAt(int index) {
+            return mTargetMap.keyAt(index);
+        }
+
+        /**
+         * Return the target {@link AccessibilityNodeInfo} for the given {@link Region}.
+         * <p>
+         *   <strong>Note:</strong> This api can only be called from {@link AccessibilityService}.
+         * </p>
+         * <p>
+         *   <strong>Note:</strong> It is a client responsibility to recycle the
+         *     received info by calling {@link AccessibilityNodeInfo#recycle()}
+         *     to avoid creating of multiple instances.
+         * </p>
+         *
+         * @param region The region retrieved from {@link #getRegionAt(int)}.
+         * @return The target node associates with the given region.
+         */
+        @Nullable
+        public AccessibilityNodeInfo getTargetForRegion(@NonNull Region region) {
+            return getNodeForAccessibilityId(mConnectionId, mWindowId, mTargetMap.get(region));
+        }
+
+        /**
+         * Return the accessibility id of target node.
+         *
+         * @param region The region retrieved from {@link #getRegionAt(int)}.
+         * @return The accessibility id of target node.
+         *
+         * @hide
+         */
+        @TestApi
+        public long getAccessibilityIdForRegion(@NonNull Region region) {
+            return mTargetMap.get(region);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mTargetMap.size());
+            for (int i = 0; i < mTargetMap.size(); i++) {
+                final Region region = mTargetMap.keyAt(i);
+                final Long accessibilityId = mTargetMap.valueAt(i);
+                region.writeToParcel(dest, flags);
+                dest.writeLong(accessibilityId);
+            }
+        }
+
+        /**
+         * @see android.os.Parcelable.Creator
+         */
+        public static final @android.annotation.NonNull Parcelable.Creator<TouchDelegateInfo> CREATOR =
+                new Parcelable.Creator<TouchDelegateInfo>() {
+            @Override
+            public TouchDelegateInfo createFromParcel(Parcel parcel) {
+                final int size = parcel.readInt();
+                if (size == 0) {
+                    return null;
+                }
+                final ArrayMap<Region, Long> targetMap = new ArrayMap<>(size);
+                for (int i = 0; i < size; i++) {
+                    final Region region = Region.CREATOR.createFromParcel(parcel);
+                    final long accessibilityId = parcel.readLong();
+                    targetMap.put(region, accessibilityId);
+                }
+                final TouchDelegateInfo touchDelegateInfo = new TouchDelegateInfo(
+                        targetMap, false);
+                return touchDelegateInfo;
+            }
+
+            @Override
+            public TouchDelegateInfo[] newArray(int size) {
+                return new TouchDelegateInfo[size];
+            }
+        };
+    }
+
+    /**
+     * Class with information of a view useful to evaluate accessibility needs. Developers can
+     * refresh the node with the key {@link #EXTRA_DATA_RENDERING_INFO_KEY} to fetch the text size
+     * and unit if it is {@link TextView} and the height and the width of layout params from
+     * {@link ViewGroup} or {@link TextView}.
+     *
+     * @see #EXTRA_DATA_RENDERING_INFO_KEY
+     * @see #refreshWithExtraData(String, Bundle)
+     */
+    public static final class ExtraRenderingInfo {
+        private static final int UNDEFINED_VALUE = -1;
+        private static final int MAX_POOL_SIZE = 20;
+        private static final SynchronizedPool<ExtraRenderingInfo> sPool =
+                new SynchronizedPool<>(MAX_POOL_SIZE);
+
+        private Size mLayoutSize;
+        private float mTextSizeInPx = UNDEFINED_VALUE;
+        private int mTextSizeUnit = UNDEFINED_VALUE;
+
+        /**
+         * Obtains a pooled instance.
+         * @hide
+         */
+        @NonNull
+        public static ExtraRenderingInfo obtain() {
+            final ExtraRenderingInfo info = sPool.acquire();
+            if (info == null) {
+                return new ExtraRenderingInfo(null);
+            }
+            return info;
+        }
+
+        /** Obtains a pooled instance that is a clone of another one. */
+        private static ExtraRenderingInfo obtain(ExtraRenderingInfo other) {
+            ExtraRenderingInfo extraRenderingInfo = ExtraRenderingInfo.obtain();
+            extraRenderingInfo.mLayoutSize = other.mLayoutSize;
+            extraRenderingInfo.mTextSizeInPx = other.mTextSizeInPx;
+            extraRenderingInfo.mTextSizeUnit = other.mTextSizeUnit;
+            return extraRenderingInfo;
+        }
+
+        /**
+         * Creates a new rendering info of a view, and this new instance is initialized from
+         * the given <code>other</code>.
+         *
+         * @param other The instance to clone.
+         */
+        private ExtraRenderingInfo(@Nullable ExtraRenderingInfo other) {
+            if (other != null) {
+                mLayoutSize = other.mLayoutSize;
+                mTextSizeInPx = other.mTextSizeInPx;
+                mTextSizeUnit = other.mTextSizeUnit;
+            }
+        }
+
+        /**
+         * Gets the size object containing the height and the width of
+         * {@link android.view.ViewGroup.LayoutParams}  if the node is a {@link ViewGroup} or
+         * a {@link TextView}, or null otherwise. Useful for some accessibility services to
+         * understand whether the text is scalable and fits the view or not.
+         *
+         * @return a {@link Size} stores layout height and layout width of the view, or null
+         * otherwise. And the size value may be in pixels,
+         * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT},
+         * or {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}
+         */
+        public @Nullable Size getLayoutSize() {
+            return mLayoutSize;
+        }
+
+        /**
+         * Sets layout width and layout height of the view.
+         *
+         * @param width The layout width.
+         * @param height The layout height.
+         * @hide
+         */
+        public void setLayoutSize(int width, int height) {
+            mLayoutSize = new Size(width, height);
+        }
+
+        /**
+         * Gets the text size if the node is a {@link TextView}, or -1 otherwise. Useful for some
+         * accessibility services to understand whether the text is scalable and fits the view or
+         * not.
+         *
+         * @return the text size of a {@code TextView}, or -1 otherwise.
+         */
+        public float getTextSizeInPx() {
+            return mTextSizeInPx;
+        }
+
+        /**
+         * Sets text size of the view.
+         *
+         * @param textSizeInPx The text size in pixels.
+         * @hide
+         */
+        public void setTextSizeInPx(float textSizeInPx) {
+            mTextSizeInPx = textSizeInPx;
+        }
+
+        /**
+         * Gets the text size unit if the node is a {@link TextView}, or -1 otherwise.
+         * Text size returned from {@link #getTextSizeInPx} in raw pixels may scale by factors and
+         * convert from other units. Useful for some accessibility services to understand whether
+         * the text is scalable and fits the view or not.
+         *
+         * @return the text size unit which type is {@link TypedValue#TYPE_DIMENSION} of a
+         *         {@code TextView}, or -1 otherwise.
+         *
+         * @see TypedValue#TYPE_DIMENSION
+         */
+        public int getTextSizeUnit() {
+            return mTextSizeUnit;
+        }
+
+        /**
+         * Sets text size unit of the view.
+         *
+         * @param textSizeUnit The text size unit.
+         * @hide
+         */
+        public void setTextSizeUnit(int textSizeUnit) {
+            mTextSizeUnit = textSizeUnit;
+        }
+
+        /**
+         * Recycles this instance.
+         *
+         * <p>In most situations object pooling is not beneficial, and recycling is not necessary.
+         */
+        void recycle() {
+            clear();
+            sPool.release(this);
+        }
+
+        private void clear() {
+            mLayoutSize = null;
+            mTextSizeInPx = UNDEFINED_VALUE;
+            mTextSizeUnit = UNDEFINED_VALUE;
+        }
+    }
+
+    /**
+     * @see android.os.Parcelable.Creator
+     */
+    public static final @android.annotation.NonNull Parcelable.Creator<AccessibilityNodeInfo> CREATOR =
+            new Parcelable.Creator<AccessibilityNodeInfo>() {
+        @Override
+        public AccessibilityNodeInfo createFromParcel(Parcel parcel) {
+            AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+            info.initFromParcel(parcel);
+            return info;
+        }
+
+        @Override
+        public AccessibilityNodeInfo[] newArray(int size) {
+            return new AccessibilityNodeInfo[size];
+        }
+    };
+}
diff --git a/android/view/accessibility/AccessibilityNodeProvider.java b/android/view/accessibility/AccessibilityNodeProvider.java
new file mode 100644
index 0000000..f4c7b96
--- /dev/null
+++ b/android/view/accessibility/AccessibilityNodeProvider.java
@@ -0,0 +1,274 @@
+/*
+ * 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.view.accessibility;
+
+import android.accessibilityservice.AccessibilityService;
+import android.os.Bundle;
+import android.view.View;
+
+import java.util.List;
+
+/**
+ * This class is the contract a client should implement to enable support of a
+ * virtual view hierarchy rooted at a given view for accessibility purposes. A virtual
+ * view hierarchy is a tree of imaginary Views that is reported as a part of the view
+ * hierarchy when an {@link AccessibilityService} explores the window content.
+ * Since the virtual View tree does not exist this class is responsible for
+ * managing the {@link AccessibilityNodeInfo}s describing that tree to accessibility
+ * services.
+ * </p>
+ * <p>
+ * The main use case of these APIs is to enable a custom view that draws complex content,
+ * for example a monthly calendar grid, to be presented as a tree of logical nodes,
+ * for example month days each containing events, thus conveying its logical structure.
+ * <p>
+ * <p>
+ * A typical use case is to override {@link View#getAccessibilityNodeProvider()} of the
+ * View that is a root of a virtual View hierarchy to return an instance of this class.
+ * In such a case this instance is responsible for managing {@link AccessibilityNodeInfo}s
+ * describing the virtual sub-tree rooted at the View including the one representing the
+ * View itself. Similarly the returned instance is responsible for performing accessibility
+ * actions on any virtual view or the root view itself. For example:
+ * </p>
+ * <div>
+ * <div class="ds-selector-tabs"><section><h3 id="kotlin">Kotlin</h3>
+ * <pre class="prettyprint lang-kotlin">
+ * // "view" is the View instance on which this class performs accessibility functions.
+ * class MyCalendarViewAccessibilityDelegate(
+ *       private var view: MyCalendarView) : AccessibilityDelegate() {
+ *     override fun getAccessibilityNodeProvider(host: View): AccessibilityNodeProvider {
+ *         return object : AccessibilityNodeProvider() {
+ *             override fun createAccessibilityNodeInfo(virtualViewId: Int):
+ *                     AccessibilityNodeInfo? {
+ *                 when (virtualViewId) {
+ *                     <var>host-view-id</var> -&gt; {
+ *                         val node = AccessibilityNodeInfo.obtain(view)
+ *                         node.addChild(view, <var>child-view-id</var>)
+ *                         // Set other attributes like screenReaderFocusable
+ *                         // and contentDescription.
+ *                         return node
+ *                     }
+ *                     <var>child-view-id</var> -&gt; {
+ *                         val node = AccessibilityNodeInfo
+ *                                 .obtain(view, virtualViewId)
+ *                         node.setParent(view)
+ *                         node.addAction(ACTION_SCROLL_UP)
+ *                         node.addAction(ACTION_SCROLL_DOWN)
+ *                         // Set other attributes like focusable and visibleToUser.
+ *                         node.setBoundsInScreen(
+ *                                 Rect(<var>coords-of-edges-relative-to-screen</var>))
+ *                         return node
+ *                     }
+ *                     else -&gt; return null
+ *                 }
+ *             }
+ *
+ *             override fun performAction(
+ *                 virtualViewId: Int,
+ *                 action: Int,
+ *                 arguments: Bundle
+ *             ): Boolean {
+ *                 if (virtualViewId == <var>host-view-id</var>) {
+ *                     return view.performAccessibilityAction(action, arguments)
+ *                 }
+ *                 when (action) {
+ *                     ACTION_SCROLL_UP.id -&gt; {
+ *                         // Implement logic in a separate method.
+ *                         navigateToPreviousMonth()
+ *
+ *                         return true
+ *                     }
+ *                     ACTION_SCROLL_DOWN.id -&gt;
+ *                         // Implement logic in a separate method.
+ *                         navigateToNextMonth()
+ *
+ *                         return true
+ *                     else -&gt; return false
+ *                 }
+ *             }
+ *         }
+ *     }
+ * }
+ * </pre>
+ * </section><section><h3 id="java">Java</h3>
+ * <pre class="prettyprint lang-java">
+ * final class MyCalendarViewAccessibilityDelegate extends AccessibilityDelegate {
+ *     // The View instance on which this class performs accessibility functions.
+ *     private final MyCalendarView view;
+ *
+ *     MyCalendarViewAccessibilityDelegate(MyCalendarView view) {
+ *         this.view = view;
+ *     }
+ *
+ *     &#64;Override
+ *     public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) {
+ *         return new AccessibilityNodeProvider() {
+ *             &#64;Override
+ *             &#64;Nullable
+ *             public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
+ *                 if (virtualViewId == <var>host-view-id</var>) {
+ *                     AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain(view);
+ *                     node.addChild(view, <var>child-view-id</var>);
+ *                     // Set other attributes like screenReaderFocusable and contentDescription.
+ *                     return node;
+ *                 } else if (virtualViewId == <var>child-view-id</var>) {
+ *                     AccessibilityNodeInfo node =
+ *                         AccessibilityNodeInfo.obtain(view, virtualViewId);
+ *                     node.setParent(view);
+ *                     node.addAction(ACTION_SCROLL_UP);
+ *                     node.addAction(ACTION_SCROLL_DOWN);
+ *                     // Set other attributes like focusable and visibleToUser.
+ *                     node.setBoundsInScreen(
+ *                         new Rect(<var>coordinates-of-edges-relative-to-screen</var>));
+ *                     return node;
+ *                 } else {
+ *                     return null;
+ *                 }
+ *             }
+ *
+ *             &#64;Override
+ *             public boolean performAction(int virtualViewId, int action, Bundle arguments) {
+ *                 if (virtualViewId == <var>host-view-id</var>) {
+ *                     return view.performAccessibilityAction(action, arguments);
+ *                 }
+ *
+ *                 if (action == ACTION_SCROLL_UP.getId()) {
+ *                     // Implement logic in a separate method.
+ *                     navigateToPreviousMonth();
+ *
+ *                     return true;
+ *                 } else if (action == ACTION_SCROLL_DOWN.getId()) {
+ *                     // Implement logic in a separate method.
+ *                     navigateToNextMonth();
+ *
+ *                     return true;
+ *                 } else {
+ *                     return false;
+ *                 }
+ *             }
+ *         };
+ *     }
+ * }
+ * </pre></section></div></div>
+ */
+public abstract class AccessibilityNodeProvider {
+
+    /**
+     * The virtual id for the hosting View.
+     */
+    public static final int HOST_VIEW_ID = -1;
+
+    /**
+     * Returns an {@link AccessibilityNodeInfo} representing a virtual view,
+     * such as a descendant of the host View, with the given <code>virtualViewId</code>
+     * or the host View itself if <code>virtualViewId</code> equals to {@link #HOST_VIEW_ID}.
+     * <p>
+     * A virtual descendant is an imaginary View that is reported as a part of the view
+     * hierarchy for accessibility purposes. This enables custom views that draw complex
+     * content to report them selves as a tree of virtual views, thus conveying their
+     * logical structure.
+     * </p>
+     * <p>
+     * The implementer is responsible for obtaining an accessibility node info from the
+     * pool of reusable instances and setting the desired properties of the node info
+     * before returning it.
+     * </p>
+     *
+     * @param virtualViewId A client defined virtual view id.
+     * @return A populated {@link AccessibilityNodeInfo} for a virtual descendant or the
+     *     host View.
+     *
+     * @see View#createAccessibilityNodeInfo()
+     * @see AccessibilityNodeInfo
+     */
+    public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
+        return null;
+    }
+
+    /**
+     * Adds extra data to an {@link AccessibilityNodeInfo} based on an explicit request for the
+     * additional data.
+     * <p>
+     * This method only needs to be implemented if a virtual view offers to provide additional
+     * data.
+     * </p>
+     *
+     * @param virtualViewId The virtual view id used to create the node
+     * @param info The info to which to add the extra data
+     * @param extraDataKey A key specifying the type of extra data to add to the info. The
+     *                     extra data should be added to the {@link Bundle} returned by
+     *                     the info's {@link AccessibilityNodeInfo#getExtras} method.
+     * @param arguments A {@link Bundle} holding any arguments relevant for this request.
+     *
+     * @see AccessibilityNodeInfo#setAvailableExtraData(List)
+     */
+    public void addExtraDataToAccessibilityNodeInfo(
+            int virtualViewId, AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
+    }
+
+    /**
+     * Performs an accessibility action on a virtual view, such as a descendant of the
+     * host View, with the given <code>virtualViewId</code> or the host View itself
+     * if <code>virtualViewId</code> equals to {@link #HOST_VIEW_ID}.
+     *
+     * @param virtualViewId A client defined virtual view id.
+     * @param action The action to perform.
+     * @param arguments Optional action arguments.
+     * @return True if the action was performed.
+     *
+     * @see View#performAccessibilityAction(int, Bundle)
+     * @see #createAccessibilityNodeInfo(int)
+     * @see AccessibilityNodeInfo
+     */
+    public boolean performAction(int virtualViewId, int action, Bundle arguments) {
+        return false;
+    }
+
+    /**
+     * Finds {@link AccessibilityNodeInfo}s by text. The match is case insensitive
+     * containment. The search is relative to the virtual view, i.e. a descendant of the
+     * host View, with the given <code>virtualViewId</code> or the host View itself
+     * <code>virtualViewId</code> equals to {@link #HOST_VIEW_ID}.
+     *
+     * @param virtualViewId A client defined virtual view id which defined
+     *     the root of the tree in which to perform the search.
+     * @param text The searched text.
+     * @return A list of node info.
+     *
+     * @see #createAccessibilityNodeInfo(int)
+     * @see AccessibilityNodeInfo
+     */
+    public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text,
+            int virtualViewId) {
+        return null;
+    }
+
+    /**
+     * Find the virtual view, such as a descendant of the host View, that has the
+     * specified focus type.
+     *
+     * @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 null;
+    }
+}
diff --git a/android/view/accessibility/AccessibilityRecord.java b/android/view/accessibility/AccessibilityRecord.java
new file mode 100644
index 0000000..c3a4d32
--- /dev/null
+++ b/android/view/accessibility/AccessibilityRecord.java
@@ -0,0 +1,989 @@
+/*
+ * 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.view.accessibility;
+
+import static com.android.internal.util.CollectionUtils.isEmpty;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Parcelable;
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a record in an {@link AccessibilityEvent} and contains information
+ * about state change of its source {@link android.view.View}. When a view fires
+ * an accessibility event it requests from its parent to dispatch the
+ * constructed event. The parent may optionally append a record for itself
+ * for providing more context to
+ * {@link android.accessibilityservice.AccessibilityService}s. Hence,
+ * accessibility services can facilitate additional accessibility records
+ * to enhance feedback.
+ * </p>
+ * <p>
+ * Once the accessibility event containing a record is dispatched the record is
+ * made immutable and calling a state mutation method generates an error.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Not all properties are applicable to all accessibility
+ * event types. For detailed information please refer to {@link AccessibilityEvent}.
+ * </p>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about creating and processing AccessibilityRecords, read the
+ * <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a>
+ * developer guide.</p>
+ * </div>
+ *
+ * @see AccessibilityEvent
+ * @see AccessibilityManager
+ * @see android.accessibilityservice.AccessibilityService
+ * @see AccessibilityNodeInfo
+ */
+public class AccessibilityRecord {
+    /** @hide */
+    protected static final boolean DEBUG_CONCISE_TOSTRING = false;
+
+    private static final int UNDEFINED = -1;
+
+    private static final int PROPERTY_CHECKED = 0x00000001;
+    private static final int PROPERTY_ENABLED = 0x00000002;
+    private static final int PROPERTY_PASSWORD = 0x00000004;
+    private static final int PROPERTY_FULL_SCREEN = 0x00000080;
+    private static final int PROPERTY_SCROLLABLE = 0x00000100;
+    private static final int PROPERTY_IMPORTANT_FOR_ACCESSIBILITY = 0x00000200;
+
+    private static final int GET_SOURCE_PREFETCH_FLAGS =
+        AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS
+        | AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS
+        | AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS;
+
+    // Housekeeping
+    private static final int MAX_POOL_SIZE = 10;
+    private static final Object sPoolLock = new Object();
+    private static AccessibilityRecord sPool;
+    private static int sPoolSize;
+    private AccessibilityRecord mNext;
+    private boolean mIsInPool;
+
+    @UnsupportedAppUsage
+    boolean mSealed;
+    int mBooleanProperties = 0;
+    int mCurrentItemIndex = UNDEFINED;
+    int mItemCount = UNDEFINED;
+    int mFromIndex = UNDEFINED;
+    int mToIndex = UNDEFINED;
+    int mScrollX = 0;
+    int mScrollY = 0;
+
+    int mScrollDeltaX = UNDEFINED;
+    int mScrollDeltaY = UNDEFINED;
+    int mMaxScrollX = 0;
+    int mMaxScrollY = 0;
+
+    int mAddedCount= UNDEFINED;
+    int mRemovedCount = UNDEFINED;
+    @UnsupportedAppUsage
+    long mSourceNodeId = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
+    int mSourceWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+
+    CharSequence mClassName;
+    CharSequence mContentDescription;
+    CharSequence mBeforeText;
+    Parcelable mParcelableData;
+
+    final List<CharSequence> mText = new ArrayList<CharSequence>();
+
+    int mConnectionId = UNDEFINED;
+
+    /**
+     * Creates a new {@link AccessibilityRecord}.
+     */
+    public AccessibilityRecord() {
+    }
+
+    /**
+     * Copy constructor. Creates a new {@link AccessibilityRecord}, and this instance is initialized
+     * with data from the given <code>record</code>.
+     *
+     * @param record The other record.
+     */
+    public AccessibilityRecord(@NonNull AccessibilityRecord record) {
+        init(record);
+    }
+
+    /**
+     * Sets the event source.
+     *
+     * @param source The source.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setSource(View source) {
+        setSource(source, AccessibilityNodeProvider.HOST_VIEW_ID);
+    }
+
+    /**
+     * Sets the source to be a virtual descendant of the given <code>root</code>.
+     * If <code>virtualDescendantId</code> equals to {@link View#NO_ID} the root
+     * is set as the source.
+     * <p>
+     * A virtual descendant is an imaginary View that is reported as a part of the view
+     * hierarchy for accessibility purposes. This enables custom views that draw complex
+     * content to report them selves as a tree of virtual views, thus conveying their
+     * logical structure.
+     * </p>
+     *
+     * @param root The root of the virtual subtree.
+     * @param virtualDescendantId The id of the virtual descendant.
+     */
+    public void setSource(@Nullable View root, int virtualDescendantId) {
+        enforceNotSealed();
+        boolean important = true;
+        int rootViewId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
+        mSourceWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+        if (root != null) {
+            important = root.isImportantForAccessibility();
+            rootViewId = root.getAccessibilityViewId();
+            mSourceWindowId = root.getAccessibilityWindowId();
+        }
+        setBooleanProperty(PROPERTY_IMPORTANT_FOR_ACCESSIBILITY, important);
+        mSourceNodeId = AccessibilityNodeInfo.makeNodeId(rootViewId, virtualDescendantId);
+    }
+
+    /**
+     * Set the source node ID directly
+     *
+     * @param sourceNodeId The source node Id
+     * @hide
+     */
+    public void setSourceNodeId(long sourceNodeId) {
+        mSourceNodeId = sourceNodeId;
+    }
+
+    /**
+     * Gets the {@link AccessibilityNodeInfo} of the event source.
+     * <p>
+     *   <strong>Note:</strong> It is a client responsibility to recycle the received info
+     *   by calling {@link AccessibilityNodeInfo#recycle() AccessibilityNodeInfo#recycle()}
+     *   to avoid creating of multiple instances.
+     * </p>
+     * @return The info of the source.
+     */
+    public AccessibilityNodeInfo getSource() {
+        enforceSealed();
+        if ((mConnectionId == UNDEFINED)
+                || (mSourceWindowId == AccessibilityWindowInfo.UNDEFINED_WINDOW_ID)
+                || (AccessibilityNodeInfo.getAccessibilityViewId(mSourceNodeId)
+                        == AccessibilityNodeInfo.UNDEFINED_ITEM_ID)) {
+            return null;
+        }
+        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mSourceWindowId,
+                mSourceNodeId, false, GET_SOURCE_PREFETCH_FLAGS, null);
+    }
+
+    /**
+     * Sets the window id.
+     *
+     * @param windowId The window id.
+     *
+     * @hide
+     */
+    public void setWindowId(int windowId) {
+        mSourceWindowId = windowId;
+    }
+
+    /**
+     * Gets the id of the window from which the event comes from.
+     *
+     * @return The window id.
+     */
+    public int getWindowId() {
+        return mSourceWindowId;
+    }
+
+    /**
+     * Gets if the source is checked.
+     *
+     * @return True if the view is checked, false otherwise.
+     */
+    public boolean isChecked() {
+        return getBooleanProperty(PROPERTY_CHECKED);
+    }
+
+    /**
+     * Sets if the source is checked.
+     *
+     * @param isChecked True if the view is checked, false otherwise.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setChecked(boolean isChecked) {
+        enforceNotSealed();
+        setBooleanProperty(PROPERTY_CHECKED, isChecked);
+    }
+
+    /**
+     * Gets if the source is enabled.
+     *
+     * @return True if the view is enabled, false otherwise.
+     */
+    public boolean isEnabled() {
+        return getBooleanProperty(PROPERTY_ENABLED);
+    }
+
+    /**
+     * Sets if the source is enabled.
+     *
+     * @param isEnabled True if the view is enabled, false otherwise.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setEnabled(boolean isEnabled) {
+        enforceNotSealed();
+        setBooleanProperty(PROPERTY_ENABLED, isEnabled);
+    }
+
+    /**
+     * Gets if the source is a password field.
+     *
+     * @return True if the view is a password field, false otherwise.
+     */
+    public boolean isPassword() {
+        return getBooleanProperty(PROPERTY_PASSWORD);
+    }
+
+    /**
+     * Sets if the source is a password field.
+     *
+     * @param isPassword True if the view is a password field, false otherwise.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setPassword(boolean isPassword) {
+        enforceNotSealed();
+        setBooleanProperty(PROPERTY_PASSWORD, isPassword);
+    }
+
+    /**
+     * Gets if the source is taking the entire screen.
+     *
+     * @return True if the source is full screen, false otherwise.
+     */
+    public boolean isFullScreen() {
+        return getBooleanProperty(PROPERTY_FULL_SCREEN);
+    }
+
+    /**
+     * Sets if the source is taking the entire screen.
+     *
+     * @param isFullScreen True if the source is full screen, false otherwise.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setFullScreen(boolean isFullScreen) {
+        enforceNotSealed();
+        setBooleanProperty(PROPERTY_FULL_SCREEN, isFullScreen);
+    }
+
+    /**
+     * Gets if the source is scrollable.
+     *
+     * @return True if the source is scrollable, false otherwise.
+     */
+    public boolean isScrollable() {
+        return getBooleanProperty(PROPERTY_SCROLLABLE);
+    }
+
+    /**
+     * Sets if the source is scrollable.
+     *
+     * @param scrollable True if the source is scrollable, false otherwise.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setScrollable(boolean scrollable) {
+        enforceNotSealed();
+        setBooleanProperty(PROPERTY_SCROLLABLE, scrollable);
+    }
+
+    /**
+     * Gets if the source is important for accessibility.
+     *
+     * <strong>Note:</strong> Used only internally to determine whether
+     * to deliver the event to a given accessibility service since some
+     * services may want to regard all views for accessibility while others
+     * may want to regard only the important views for accessibility.
+     *
+     * @return True if the source is important for accessibility,
+     *        false otherwise.
+     *
+     * @hide
+     */
+    public boolean isImportantForAccessibility() {
+        return getBooleanProperty(PROPERTY_IMPORTANT_FOR_ACCESSIBILITY);
+    }
+
+    /**
+     * Sets if the source is important for accessibility.
+     *
+     * @param importantForAccessibility True if the source is important for accessibility,
+     *                                  false otherwise.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     * @hide
+     */
+    public void setImportantForAccessibility(boolean importantForAccessibility) {
+        enforceNotSealed();
+        setBooleanProperty(PROPERTY_IMPORTANT_FOR_ACCESSIBILITY, importantForAccessibility);
+    }
+
+    /**
+     * Gets the number of items that can be visited.
+     *
+     * @return The number of items.
+     */
+    public int getItemCount() {
+        return mItemCount;
+    }
+
+    /**
+     * Sets the number of items that can be visited.
+     *
+     * @param itemCount The number of items.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setItemCount(int itemCount) {
+        enforceNotSealed();
+        mItemCount = itemCount;
+    }
+
+    /**
+     * Gets the index of the source in the list of items the can be visited.
+     *
+     * @return The current item index.
+     */
+    public int getCurrentItemIndex() {
+        return mCurrentItemIndex;
+    }
+
+    /**
+     * Sets the index of the source in the list of items that can be visited.
+     *
+     * @param currentItemIndex The current item index.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setCurrentItemIndex(int currentItemIndex) {
+        enforceNotSealed();
+        mCurrentItemIndex = currentItemIndex;
+    }
+
+    /**
+     * Gets the index of the first character of the changed sequence,
+     * or the beginning of a text selection or the index of the first
+     * visible item when scrolling.
+     *
+     * @return The index of the first character or selection
+     *        start or the first visible item.
+     */
+    public int getFromIndex() {
+        return mFromIndex;
+    }
+
+    /**
+     * Sets the index of the first character of the changed sequence
+     * or the beginning of a text selection or the index of the first
+     * visible item when scrolling.
+     *
+     * @param fromIndex The index of the first character or selection
+     *        start or the first visible item.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setFromIndex(int fromIndex) {
+        enforceNotSealed();
+        mFromIndex = fromIndex;
+    }
+
+    /**
+     * Gets the index of text selection end or the index of the last
+     * visible item when scrolling.
+     *
+     * @return The index of selection end or last item index.
+     */
+    public int getToIndex() {
+        return mToIndex;
+    }
+
+    /**
+     * Sets the index of text selection end or the index of the last
+     * visible item when scrolling.
+     *
+     * @param toIndex The index of selection end or last item index.
+     */
+    public void setToIndex(int toIndex) {
+        enforceNotSealed();
+        mToIndex = toIndex;
+    }
+
+    /**
+     * Gets the scroll offset of the source left edge in pixels.
+     *
+     * @return The scroll.
+     */
+    public int getScrollX() {
+        return mScrollX;
+    }
+
+    /**
+     * Sets the scroll offset of the source left edge in pixels.
+     *
+     * @param scrollX The scroll.
+     */
+    public void setScrollX(int scrollX) {
+        enforceNotSealed();
+        mScrollX = scrollX;
+    }
+
+    /**
+     * Gets the scroll offset of the source top edge in pixels.
+     *
+     * @return The scroll.
+     */
+    public int getScrollY() {
+        return mScrollY;
+    }
+
+    /**
+     * Sets the scroll offset of the source top edge in pixels.
+     *
+     * @param scrollY The scroll.
+     */
+    public void setScrollY(int scrollY) {
+        enforceNotSealed();
+        mScrollY = scrollY;
+    }
+
+    /**
+     * Gets the difference in pixels between the horizontal position before the scroll and the
+     * current horizontal position
+     *
+     * @return the scroll delta x
+     */
+    public int getScrollDeltaX() {
+        return mScrollDeltaX;
+    }
+
+    /**
+     * Sets the difference in pixels between the horizontal position before the scroll and the
+     * current horizontal position
+     *
+     * @param scrollDeltaX the scroll delta x
+     */
+    public void setScrollDeltaX(int scrollDeltaX) {
+        enforceNotSealed();
+        mScrollDeltaX = scrollDeltaX;
+    }
+
+    /**
+     * Gets the difference in pixels between the vertical position before the scroll and the
+     * current vertical position
+     *
+     * @return the scroll delta y
+     */
+    public int getScrollDeltaY() {
+        return mScrollDeltaY;
+    }
+
+    /**
+     * Sets the difference in pixels between the vertical position before the scroll and the
+     * current vertical position
+     *
+     * @param scrollDeltaY the scroll delta y
+     */
+    public void setScrollDeltaY(int scrollDeltaY) {
+        enforceNotSealed();
+        mScrollDeltaY = scrollDeltaY;
+    }
+
+    /**
+     * Gets the max scroll offset of the source left edge in pixels.
+     *
+     * @return The max scroll.
+     */
+    public int getMaxScrollX() {
+        return mMaxScrollX;
+    }
+
+    /**
+     * Sets the max scroll offset of the source left edge in pixels.
+     *
+     * @param maxScrollX The max scroll.
+     */
+    public void setMaxScrollX(int maxScrollX) {
+        enforceNotSealed();
+        mMaxScrollX = maxScrollX;
+    }
+
+    /**
+     * Gets the max scroll offset of the source top edge in pixels.
+     *
+     * @return The max scroll.
+     */
+    public int getMaxScrollY() {
+        return mMaxScrollY;
+    }
+
+    /**
+     * Sets the max scroll offset of the source top edge in pixels.
+     *
+     * @param maxScrollY The max scroll.
+     */
+    public void setMaxScrollY(int maxScrollY) {
+        enforceNotSealed();
+        mMaxScrollY = maxScrollY;
+    }
+
+    /**
+     * Gets the number of added characters.
+     *
+     * @return The number of added characters.
+     */
+    public int getAddedCount() {
+        return mAddedCount;
+    }
+
+    /**
+     * Sets the number of added characters.
+     *
+     * @param addedCount The number of added characters.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setAddedCount(int addedCount) {
+        enforceNotSealed();
+        mAddedCount = addedCount;
+    }
+
+    /**
+     * Gets the number of removed characters.
+     *
+     * @return The number of removed characters.
+     */
+    public int getRemovedCount() {
+        return mRemovedCount;
+    }
+
+    /**
+     * Sets the number of removed characters.
+     *
+     * @param removedCount The number of removed characters.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setRemovedCount(int removedCount) {
+        enforceNotSealed();
+        mRemovedCount = removedCount;
+    }
+
+    /**
+     * Gets the class name of the source.
+     *
+     * @return The class name.
+     */
+    public CharSequence getClassName() {
+        return mClassName;
+    }
+
+    /**
+     * Sets the class name of the source.
+     *
+     * @param className The lass name.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setClassName(CharSequence className) {
+        enforceNotSealed();
+        mClassName = className;
+    }
+
+    /**
+     * Gets the text of the event. The index in the list represents the priority
+     * of the text. Specifically, the lower the index the higher the priority.
+     *
+     * @return The text.
+     */
+    public List<CharSequence> getText() {
+        return mText;
+    }
+
+    /**
+     * Gets the text before a change.
+     *
+     * @return The text before the change.
+     */
+    public CharSequence getBeforeText() {
+        return mBeforeText;
+    }
+
+    /**
+     * Sets the text before a change.
+     *
+     * @param beforeText The text before the change.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setBeforeText(CharSequence beforeText) {
+        enforceNotSealed();
+        mBeforeText = (beforeText == null) ? null
+                : beforeText.subSequence(0, beforeText.length());
+    }
+
+    /**
+     * Gets the description of the source.
+     *
+     * @return The description.
+     */
+    public CharSequence getContentDescription() {
+        return mContentDescription;
+    }
+
+    /**
+     * Sets the description of the source.
+     *
+     * @param contentDescription The description.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setContentDescription(CharSequence contentDescription) {
+        enforceNotSealed();
+        mContentDescription = (contentDescription == null) ? null
+                : contentDescription.subSequence(0, contentDescription.length());
+    }
+
+    /**
+     * Gets the {@link Parcelable} data.
+     *
+     * @return The parcelable data.
+     */
+    public Parcelable getParcelableData() {
+        return mParcelableData;
+    }
+
+    /**
+     * Sets the {@link Parcelable} data of the event.
+     *
+     * @param parcelableData The parcelable data.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setParcelableData(Parcelable parcelableData) {
+        enforceNotSealed();
+        mParcelableData = parcelableData;
+    }
+
+    /**
+     * Gets the id of the source node.
+     *
+     * @return The id.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public long getSourceNodeId() {
+        return mSourceNodeId;
+    }
+
+    /**
+     * Sets the unique id of the IAccessibilityServiceConnection over which
+     * this instance can send requests to the system.
+     *
+     * @param connectionId The connection id.
+     *
+     * @hide
+     */
+    public void setConnectionId(int connectionId) {
+        enforceNotSealed();
+        mConnectionId = connectionId;
+    }
+
+    /**
+     * Sets if this instance is sealed.
+     *
+     * @param sealed Whether is sealed.
+     *
+     * @hide
+     */
+    public void setSealed(boolean sealed) {
+        mSealed = sealed;
+    }
+
+    /**
+     * Gets if this instance is sealed.
+     *
+     * @return Whether is sealed.
+     */
+    boolean isSealed() {
+        return mSealed;
+    }
+
+    /**
+     * Enforces that this instance is sealed.
+     *
+     * @throws IllegalStateException If this instance is not sealed.
+     */
+    void enforceSealed() {
+        if (!isSealed()) {
+            throw new IllegalStateException("Cannot perform this "
+                    + "action on a not sealed instance.");
+        }
+    }
+
+    /**
+     * Enforces that this instance is not sealed.
+     *
+     * @throws IllegalStateException If this instance is sealed.
+     */
+    void enforceNotSealed() {
+        if (isSealed()) {
+            throw new IllegalStateException("Cannot perform this "
+                    + "action on a sealed instance.");
+        }
+    }
+
+    /**
+     * Gets the value of a boolean property.
+     *
+     * @param property The property.
+     * @return The value.
+     */
+    private boolean getBooleanProperty(int property) {
+        return (mBooleanProperties & property) == property;
+    }
+
+    /**
+     * Sets a boolean property.
+     *
+     * @param property The property.
+     * @param value The value.
+     */
+    private void setBooleanProperty(int property, boolean value) {
+        if (value) {
+            mBooleanProperties |= property;
+        } else {
+            mBooleanProperties &= ~property;
+        }
+    }
+
+    /**
+     * Returns a cached instance if such is available or a new one is
+     * instantiated. The instance is initialized with data from the
+     * given record.
+     *
+     * <p>In most situations object pooling is not beneficial. Create a new instance using the
+     * constructor {@link #AccessibilityRecord(AccessibilityRecord)} instead.
+     *
+     * @return An instance.
+     */
+    public static AccessibilityRecord obtain(AccessibilityRecord record) {
+       AccessibilityRecord clone = AccessibilityRecord.obtain();
+       clone.init(record);
+       return clone;
+    }
+
+    /**
+     * Returns a cached instance if such is available or a new one is
+     * instantiated.
+     *
+     * <p>In most situations object pooling is not beneficial. Create a new instance using the
+     * constructor {@link #AccessibilityRecord()} instead.
+     *
+     * @return An instance.
+     */
+    public static AccessibilityRecord obtain() {
+        synchronized (sPoolLock) {
+            if (sPool != null) {
+                AccessibilityRecord record = sPool;
+                sPool = sPool.mNext;
+                sPoolSize--;
+                record.mNext = null;
+                record.mIsInPool = false;
+                return record;
+            }
+            return new AccessibilityRecord();
+        }
+    }
+
+    /**
+     * Return an instance back to be reused.
+     * <p>
+     * <strong>Note:</strong> You must not touch the object after calling this function.
+     *
+     * <p>In most situations object pooling is not beneficial, and recycling is not necessary.
+     *
+     * @throws IllegalStateException If the record is already recycled.
+     */
+    public void recycle() {
+        if (mIsInPool) {
+            throw new IllegalStateException("Record already recycled!");
+        }
+        clear();
+        synchronized (sPoolLock) {
+            if (sPoolSize <= MAX_POOL_SIZE) {
+                mNext = sPool;
+                sPool = this;
+                mIsInPool = true;
+                sPoolSize++;
+            }
+        }
+    }
+
+    /**
+     * Initialize this record from another one.
+     *
+     * @param record The to initialize from.
+     */
+    void init(AccessibilityRecord record) {
+        mSealed = record.mSealed;
+        mBooleanProperties = record.mBooleanProperties;
+        mCurrentItemIndex = record.mCurrentItemIndex;
+        mItemCount = record.mItemCount;
+        mFromIndex = record.mFromIndex;
+        mToIndex = record.mToIndex;
+        mScrollX = record.mScrollX;
+        mScrollY = record.mScrollY;
+        mMaxScrollX = record.mMaxScrollX;
+        mMaxScrollY = record.mMaxScrollY;
+        mScrollDeltaX = record.mScrollDeltaX;
+        mScrollDeltaY = record.mScrollDeltaY;
+        mAddedCount = record.mAddedCount;
+        mRemovedCount = record.mRemovedCount;
+        mClassName = record.mClassName;
+        mContentDescription = record.mContentDescription;
+        mBeforeText = record.mBeforeText;
+        mParcelableData = record.mParcelableData;
+        mText.addAll(record.mText);
+        mSourceWindowId = record.mSourceWindowId;
+        mSourceNodeId = record.mSourceNodeId;
+        mConnectionId = record.mConnectionId;
+    }
+
+    /**
+     * Clears the state of this instance.
+     */
+    void clear() {
+        mSealed = false;
+        mBooleanProperties = 0;
+        mCurrentItemIndex = UNDEFINED;
+        mItemCount = UNDEFINED;
+        mFromIndex = UNDEFINED;
+        mToIndex = UNDEFINED;
+        mScrollX = 0;
+        mScrollY = 0;
+        mMaxScrollX = 0;
+        mMaxScrollY = 0;
+        mScrollDeltaX = UNDEFINED;
+        mScrollDeltaY = UNDEFINED;
+        mAddedCount = UNDEFINED;
+        mRemovedCount = UNDEFINED;
+        mClassName = null;
+        mContentDescription = null;
+        mBeforeText = null;
+        mParcelableData = null;
+        mText.clear();
+        mSourceNodeId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
+        mSourceWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+        mConnectionId = UNDEFINED;
+    }
+
+    @Override
+    public String toString() {
+        return appendTo(new StringBuilder()).toString();
+    }
+
+    StringBuilder appendTo(StringBuilder builder) {
+        builder.append(" [ ClassName: ").append(mClassName);
+        if (!DEBUG_CONCISE_TOSTRING || !isEmpty(mText)) {
+            appendPropName(builder, "Text").append(mText);
+        }
+        append(builder, "ContentDescription", mContentDescription);
+        append(builder, "ItemCount", mItemCount);
+        append(builder, "CurrentItemIndex", mCurrentItemIndex);
+
+        appendUnless(true, PROPERTY_ENABLED, builder);
+        appendUnless(false, PROPERTY_PASSWORD, builder);
+        appendUnless(false, PROPERTY_CHECKED, builder);
+        appendUnless(false, PROPERTY_FULL_SCREEN, builder);
+        appendUnless(false, PROPERTY_SCROLLABLE, builder);
+
+        append(builder, "BeforeText", mBeforeText);
+        append(builder, "FromIndex", mFromIndex);
+        append(builder, "ToIndex", mToIndex);
+        append(builder, "ScrollX", mScrollX);
+        append(builder, "ScrollY", mScrollY);
+        append(builder, "MaxScrollX", mMaxScrollX);
+        append(builder, "MaxScrollY", mMaxScrollY);
+        append(builder, "ScrollDeltaX", mScrollDeltaX);
+        append(builder, "ScrollDeltaY", mScrollDeltaY);
+        append(builder, "AddedCount", mAddedCount);
+        append(builder, "RemovedCount", mRemovedCount);
+        append(builder, "ParcelableData", mParcelableData);
+        builder.append(" ]");
+        return builder;
+    }
+
+    private void appendUnless(boolean defValue, int prop, StringBuilder builder) {
+        boolean value = getBooleanProperty(prop);
+        if (DEBUG_CONCISE_TOSTRING && value == defValue) return;
+        appendPropName(builder, singleBooleanPropertyToString(prop))
+                .append(value);
+    }
+
+    private static String singleBooleanPropertyToString(int prop) {
+        switch (prop) {
+            case PROPERTY_CHECKED: return "Checked";
+            case PROPERTY_ENABLED: return "Enabled";
+            case PROPERTY_PASSWORD: return "Password";
+            case PROPERTY_FULL_SCREEN: return "FullScreen";
+            case PROPERTY_SCROLLABLE: return "Scrollable";
+            case PROPERTY_IMPORTANT_FOR_ACCESSIBILITY:
+                return "ImportantForAccessibility";
+            default: return Integer.toHexString(prop);
+        }
+    }
+
+    private void append(StringBuilder builder, String propName, int propValue) {
+        if (DEBUG_CONCISE_TOSTRING && propValue == UNDEFINED) return;
+        appendPropName(builder, propName).append(propValue);
+    }
+
+    private void append(StringBuilder builder, String propName, Object propValue) {
+        if (DEBUG_CONCISE_TOSTRING && propValue == null) return;
+        appendPropName(builder, propName).append(propValue);
+    }
+
+    private StringBuilder appendPropName(StringBuilder builder, String propName) {
+        return builder.append("; ").append(propName).append(": ");
+    }
+}
diff --git a/android/view/accessibility/AccessibilityRequestPreparer.java b/android/view/accessibility/AccessibilityRequestPreparer.java
new file mode 100644
index 0000000..8108d37
--- /dev/null
+++ b/android/view/accessibility/AccessibilityRequestPreparer.java
@@ -0,0 +1,127 @@
+/*
+ * 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.view.accessibility;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Message;
+import android.view.View;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+
+/**
+ * Object responsible to ensuring that a {@link View} is prepared to meet a synchronous request for
+ * accessibility data.
+ * <p>
+ * Because accessibility requests arrive to {@link View}s synchronously on the UI thread, a View
+ * that requires information from other processes can struggle to meet those requests. Registering
+ * an instance of this class with {@link AccessibilityManager} allows a View to be notified when
+ * a request is about to be made, and to asynchronously inform the accessibility system when it is
+ * ready to meet the request.
+ * <p>
+ * <strong>Note:</strong> This class should only be needed in exceptional situations where a
+ * {@link View} cannot otherwise synchronously meet the request for accessibility data.
+ */
+public abstract class AccessibilityRequestPreparer {
+    public static final int REQUEST_TYPE_EXTRA_DATA = 0x00000001;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "REQUEST_TYPE_" }, value = {
+            REQUEST_TYPE_EXTRA_DATA
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RequestTypes {}
+
+    private final WeakReference<View> mViewRef;
+    private final int mAccessibilityViewId;
+    private final int mRequestTypes;
+
+    /**
+     * @param view The view whose requests need preparation. It must be attached to a
+     * window. This object will retain a weak reference to this view, and will unregister itself
+     * from AccessibilityManager if the view is detached from a window. It will not re-register
+     * itself.
+     * @param requestTypes The types of requests that require preparation. Different types may
+     * be ORed together.
+     *
+     * @throws IllegalStateException if the view is not attached to a window.
+     */
+    public AccessibilityRequestPreparer(View view, @RequestTypes int requestTypes) {
+        if (!view.isAttachedToWindow()) {
+            throw new IllegalStateException("View must be attached to a window");
+        }
+        mViewRef = new WeakReference<>(view);
+        mAccessibilityViewId = view.getAccessibilityViewId();
+        mRequestTypes = requestTypes;
+        view.addOnAttachStateChangeListener(new ViewAttachStateListener());
+    }
+
+    /**
+     * Callback to allow preparation for filling extra data. Only called back if
+     * REQUEST_TYPE_EXTRA_DATA is requested.
+     *
+     * @param virtualViewId The ID of a virtual child node, if the {@link View} for this preparer
+     * supports virtual descendents, or {@link AccessibilityNodeProvider#HOST_VIEW_ID}
+     * if the request is for the view itself.
+     * @param extraDataKey The extra data key for the request
+     * @param args The arguments for the request
+     * @param preparationFinishedMessage A message that must be sent to its target when preparations
+     * are complete.
+     *
+     * @see View#addExtraDataToAccessibilityNodeInfo(AccessibilityNodeInfo, String, Bundle)
+     * @see View.AccessibilityDelegate#addExtraDataToAccessibilityNodeInfo(
+     * View, AccessibilityNodeInfo, String, Bundle)
+     * @see AccessibilityNodeProvider#addExtraDataToAccessibilityNodeInfo(
+     * int, AccessibilityNodeInfo, String, Bundle)
+     */
+    public abstract void onPrepareExtraData(int virtualViewId, String extraDataKey,
+            Bundle args, Message preparationFinishedMessage);
+
+    /**
+     * Get the view this object was created with.
+     *
+     * @return The view this object was created with, or {@code null} if the weak reference held
+     * to the view is no longer valid.
+     */
+    public @Nullable View getView() {
+        return mViewRef.get();
+    }
+
+    private class ViewAttachStateListener implements View.OnAttachStateChangeListener {
+        @Override
+        public void onViewAttachedToWindow(View v) {
+        }
+
+        @Override
+        public void onViewDetachedFromWindow(View v) {
+            Context context = v.getContext();
+            if (context != null) {
+                context.getSystemService(AccessibilityManager.class)
+                        .removeAccessibilityRequestPreparer(AccessibilityRequestPreparer.this);
+            }
+            v.removeOnAttachStateChangeListener(this);
+        }
+    }
+
+    int getAccessibilityViewId() {
+        return mAccessibilityViewId;
+    }
+}
diff --git a/android/view/accessibility/AccessibilityWindowInfo.java b/android/view/accessibility/AccessibilityWindowInfo.java
new file mode 100644
index 0000000..edcb59a
--- /dev/null
+++ b/android/view/accessibility/AccessibilityWindowInfo.java
@@ -0,0 +1,893 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.LongArray;
+import android.util.Pools.SynchronizedPool;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.accessibility.AccessibilityEvent.WindowsChangeTypes;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * This class represents a state snapshot of a window for accessibility
+ * purposes. The screen content contains one or more windows where some
+ * windows can be descendants of other windows, which is the windows are
+ * hierarchically ordered. Note that there is no root window. Hence, the
+ * screen content can be seen as a collection of window trees.
+ */
+public final class AccessibilityWindowInfo implements Parcelable {
+
+    private static final boolean DEBUG = false;
+
+    /**
+     * Window type: This is an application window. Such a window shows UI for
+     * interacting with an application.
+     */
+    public static final int TYPE_APPLICATION = 1;
+
+    /**
+     * Window type: This is an input method window. Such a window shows UI for
+     * inputting text such as keyboard, suggestions, etc.
+     */
+    public static final int TYPE_INPUT_METHOD = 2;
+
+    /**
+     * Window type: This is a system window. Such a window shows UI for
+     * interacting with the system.
+     */
+    public static final int TYPE_SYSTEM = 3;
+
+    /**
+     * Window type: Windows that are overlaid <em>only</em> by an {@link
+     * android.accessibilityservice.AccessibilityService} for interception of
+     * user interactions without changing the windows an accessibility service
+     * can introspect. In particular, an accessibility service can introspect
+     * only windows that a sighted user can interact with which they can touch
+     * these windows or can type into these windows. For example, if there
+     * is a full screen accessibility overlay that is touchable, the windows
+     * below it will be introspectable by an accessibility service regardless
+     * they are covered by a touchable window.
+     */
+    public static final int TYPE_ACCESSIBILITY_OVERLAY = 4;
+
+    /**
+     * Window type: A system window used to divide the screen in split-screen mode.
+     * This type of window is present only in split-screen mode.
+     */
+    public static final int TYPE_SPLIT_SCREEN_DIVIDER = 5;
+
+    /* Special values for window IDs */
+    /** @hide */
+    public static final int ACTIVE_WINDOW_ID = Integer.MAX_VALUE;
+    /** @hide */
+    public static final int UNDEFINED_CONNECTION_ID = -1;
+    /** @hide */
+    public static final int UNDEFINED_WINDOW_ID = -1;
+    /** @hide */
+    public static final int ANY_WINDOW_ID = -2;
+    /** @hide */
+    public static final int PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID = -3;
+
+    private static final int BOOLEAN_PROPERTY_ACTIVE = 1 << 0;
+    private static final int BOOLEAN_PROPERTY_FOCUSED = 1 << 1;
+    private static final int BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED = 1 << 2;
+    private static final int BOOLEAN_PROPERTY_PICTURE_IN_PICTURE = 1 << 3;
+
+    // Housekeeping.
+    private static final int MAX_POOL_SIZE = 10;
+    private static final SynchronizedPool<AccessibilityWindowInfo> sPool =
+            new SynchronizedPool<AccessibilityWindowInfo>(MAX_POOL_SIZE);
+    // TODO(b/129300068): Remove sNumInstancesInUse.
+    private static AtomicInteger sNumInstancesInUse;
+
+    // Data.
+    private int mDisplayId = Display.INVALID_DISPLAY;
+    private int mType = UNDEFINED_WINDOW_ID;
+    private int mLayer = UNDEFINED_WINDOW_ID;
+    private int mBooleanProperties;
+    private int mId = UNDEFINED_WINDOW_ID;
+    private int mParentId = UNDEFINED_WINDOW_ID;
+    private Region mRegionInScreen = new Region();
+    private LongArray mChildIds;
+    private CharSequence mTitle;
+    private long mAnchorId = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
+
+    private int mConnectionId = UNDEFINED_CONNECTION_ID;
+
+    /**
+     * Creates a new {@link AccessibilityWindowInfo}.
+     */
+    public AccessibilityWindowInfo() {
+    }
+
+    /**
+     * Copy constructor. Creates a new {@link AccessibilityWindowInfo}, and this new instance is
+     * initialized from given <code>info</code>.
+     *
+     * @param info The other info.
+     */
+    public AccessibilityWindowInfo(@NonNull AccessibilityWindowInfo info) {
+        init(info);
+    }
+
+    /**
+     * Gets the title of the window.
+     *
+     * @return The title of the window, or {@code null} if none is available.
+     */
+    @Nullable
+    public CharSequence getTitle() {
+        return mTitle;
+    }
+
+    /**
+     * Sets the title of the window.
+     *
+     * @param title The title.
+     *
+     * @hide
+     */
+    public void setTitle(CharSequence title) {
+        mTitle = title;
+    }
+
+    /**
+     * Gets the type of the window.
+     *
+     * @return The type.
+     *
+     * @see #TYPE_APPLICATION
+     * @see #TYPE_INPUT_METHOD
+     * @see #TYPE_SYSTEM
+     * @see #TYPE_ACCESSIBILITY_OVERLAY
+     */
+    public int getType() {
+        return mType;
+    }
+
+    /**
+     * Sets the type of the window.
+     *
+     * @param type The type
+     *
+     * @hide
+     */
+    public void setType(int type) {
+        mType = type;
+    }
+
+    /**
+     * Gets the layer which determines the Z-order of the window. Windows
+     * with greater layer appear on top of windows with lesser layer.
+     *
+     * @return The window layer.
+     */
+    public int getLayer() {
+        return mLayer;
+    }
+
+    /**
+     * Sets the layer which determines the Z-order of the window. Windows
+     * with greater layer appear on top of windows with lesser layer.
+     *
+     * @param layer The window layer.
+     *
+     * @hide
+     */
+    public void setLayer(int layer) {
+        mLayer = layer;
+    }
+
+    /**
+     * Gets the root node in the window's hierarchy.
+     *
+     * @return The root node.
+     */
+    public AccessibilityNodeInfo getRoot() {
+        if (mConnectionId == UNDEFINED_WINDOW_ID) {
+            return null;
+        }
+        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
+                mId, AccessibilityNodeInfo.ROOT_NODE_ID,
+                true, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS, null);
+    }
+
+    /**
+     * Sets the anchor node's ID.
+     *
+     * @param anchorId The anchor's accessibility id in its window.
+     *
+     * @hide
+     */
+    public void setAnchorId(long anchorId) {
+        mAnchorId = anchorId;
+    }
+
+    /**
+     * Gets the node that anchors this window to another.
+     *
+     * @return The anchor node, or {@code null} if none exists.
+     */
+    public AccessibilityNodeInfo getAnchor() {
+        if ((mConnectionId == UNDEFINED_WINDOW_ID)
+                || (mAnchorId == AccessibilityNodeInfo.UNDEFINED_NODE_ID)
+                || (mParentId == UNDEFINED_WINDOW_ID)) {
+            return null;
+        }
+
+        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
+                mParentId, mAnchorId, true, 0, null);
+    }
+
+    /** @hide */
+    public void setPictureInPicture(boolean pictureInPicture) {
+        setBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE, pictureInPicture);
+    }
+
+    /**
+     * Check if the window is in picture-in-picture mode.
+     *
+     * @return {@code true} if the window is in picture-in-picture mode, {@code false} otherwise.
+     */
+    public boolean isInPictureInPictureMode() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE);
+    }
+
+    /**
+     * Gets the parent window.
+     *
+     * @return The parent window, or {@code null} if none exists.
+     */
+    public AccessibilityWindowInfo getParent() {
+        if (mConnectionId == UNDEFINED_WINDOW_ID || mParentId == UNDEFINED_WINDOW_ID) {
+            return null;
+        }
+        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        return client.getWindow(mConnectionId, mParentId);
+    }
+
+    /**
+     * Sets the parent window id.
+     *
+     * @param parentId The parent id.
+     *
+     * @hide
+     */
+    public void setParentId(int parentId) {
+        mParentId = parentId;
+    }
+
+    /**
+     * Gets the unique window id.
+     *
+     * @return windowId The window id.
+     */
+    public int getId() {
+        return mId;
+    }
+
+    /**
+     * Sets the unique window id.
+     *
+     * @param id The window id.
+     *
+     * @hide
+     */
+    public void setId(int id) {
+        mId = id;
+    }
+
+    /**
+     * Sets the unique id of the IAccessibilityServiceConnection over which
+     * this instance can send requests to the system.
+     *
+     * @param connectionId The connection id.
+     *
+     * @hide
+     */
+    public void setConnectionId(int connectionId) {
+        mConnectionId = connectionId;
+    }
+
+    /**
+     * Gets the touchable region of this window in the screen.
+     *
+     * @param outRegion The out window region.
+     */
+    public void getRegionInScreen(@NonNull Region outRegion) {
+        outRegion.set(mRegionInScreen);
+    }
+
+    /**
+     * Sets the touchable region of this window in the screen.
+     *
+     * @param region The window region.
+     *
+     * @hide
+     */
+    public void setRegionInScreen(Region region) {
+        mRegionInScreen.set(region);
+    }
+
+    /**
+     * Gets the bounds of this window in the screen. This is equivalent to get the bounds of the
+     * Region from {@link #getRegionInScreen(Region)}.
+     *
+     * @param outBounds The out window bounds.
+     */
+    public void getBoundsInScreen(Rect outBounds) {
+        outBounds.set(mRegionInScreen.getBounds());
+    }
+
+    /**
+     * Gets if this window is active. An active window is the one
+     * the user is currently touching or the window has input focus
+     * and the user is not touching any window.
+     *
+     * @return Whether this is the active window.
+     */
+    public boolean isActive() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_ACTIVE);
+    }
+
+    /**
+     * Sets if this window is active, which is this is the window
+     * the user is currently touching or the window has input focus
+     * and the user is not touching any window.
+     *
+     * @param active Whether this is the active window.
+     *
+     * @hide
+     */
+    public void setActive(boolean active) {
+        setBooleanProperty(BOOLEAN_PROPERTY_ACTIVE, active);
+    }
+
+    /**
+     * Gets if this window has input focus.
+     *
+     * @return Whether has input focus.
+     */
+    public boolean isFocused() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_FOCUSED);
+    }
+
+    /**
+     * Sets if this window has input focus.
+     *
+     * @param focused Whether has input focus.
+     *
+     * @hide
+     */
+    public void setFocused(boolean focused) {
+        setBooleanProperty(BOOLEAN_PROPERTY_FOCUSED, focused);
+    }
+
+    /**
+     * Gets if this window has accessibility focus.
+     *
+     * @return Whether has accessibility focus.
+     */
+    public boolean isAccessibilityFocused() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED);
+    }
+
+    /**
+     * Sets if this window has accessibility focus.
+     *
+     * @param focused Whether has accessibility focus.
+     *
+     * @hide
+     */
+    public void setAccessibilityFocused(boolean focused) {
+        setBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED, focused);
+    }
+
+    /**
+     * Gets the number of child windows.
+     *
+     * @return The child count.
+     */
+    public int getChildCount() {
+        return (mChildIds != null) ? mChildIds.size() : 0;
+    }
+
+    /**
+     * Gets the child window at a given index.
+     *
+     * @param index The index.
+     * @return The child.
+     */
+    public AccessibilityWindowInfo getChild(int index) {
+        if (mChildIds == null) {
+            throw new IndexOutOfBoundsException();
+        }
+        if (mConnectionId == UNDEFINED_WINDOW_ID) {
+            return null;
+        }
+        final int childId = (int) mChildIds.get(index);
+        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        return client.getWindow(mConnectionId, childId);
+    }
+
+    /**
+     * Adds a child window.
+     *
+     * @param childId The child window id.
+     *
+     * @hide
+     */
+    public void addChild(int childId) {
+        if (mChildIds == null) {
+            mChildIds = new LongArray();
+        }
+        mChildIds.add(childId);
+    }
+
+    /**
+     * Sets the display Id.
+     *
+     * @param displayId The display id.
+     *
+     * @hide
+     */
+    public void setDisplayId(int displayId) {
+        mDisplayId = displayId;
+    }
+
+    /**
+     * Returns the ID of the display this window is on, for use with
+     * {@link android.hardware.display.DisplayManager#getDisplay(int)}.
+     *
+     * @return The logical display id.
+     */
+    public int getDisplayId() {
+        return mDisplayId;
+    }
+
+    /**
+     * Returns a cached instance if such is available or a new one is
+     * created.
+     *
+     * <p>In most situations object pooling is not beneficial. Create a new instance using the
+     * constructor {@link #AccessibilityWindowInfo()} instead.
+     *
+     * @return An instance.
+     */
+    public static AccessibilityWindowInfo obtain() {
+        AccessibilityWindowInfo info = sPool.acquire();
+        if (info == null) {
+            info = new AccessibilityWindowInfo();
+        }
+        if (sNumInstancesInUse != null) {
+            sNumInstancesInUse.incrementAndGet();
+        }
+        return info;
+    }
+
+    /**
+     * Returns a cached instance if such is available or a new one is
+     * created. The returned instance is initialized from the given
+     * <code>info</code>.
+     *
+     * <p>In most situations object pooling is not beneficial. Create a new instance using the
+     * constructor {@link #AccessibilityWindowInfo(AccessibilityWindowInfo)} instead.
+     *
+     * @param info The other info.
+     * @return An instance.
+     */
+    public static AccessibilityWindowInfo obtain(AccessibilityWindowInfo info) {
+        AccessibilityWindowInfo infoClone = obtain();
+        infoClone.init(info);
+        return infoClone;
+    }
+
+    /**
+     * Specify a counter that will be incremented on obtain() and decremented on recycle()
+     *
+     * @hide
+     */
+    @TestApi
+    public static void setNumInstancesInUseCounter(AtomicInteger counter) {
+        if (sNumInstancesInUse != null) {
+            sNumInstancesInUse = counter;
+        }
+    }
+
+    /**
+     * Return an instance back to be reused.
+     * <p>
+     * <strong>Note:</strong> You must not touch the object after calling this function.
+     * </p>
+     *
+     * <p>In most situations object pooling is not beneficial, and recycling is not necessary.
+     *
+     * @throws IllegalStateException If the info is already recycled.
+     */
+    public void recycle() {
+        clear();
+        sPool.release(this);
+        if (sNumInstancesInUse != null) {
+            sNumInstancesInUse.decrementAndGet();
+        }
+    }
+
+    /**
+     * Refreshes this window with the latest state of the window it represents.
+     * <p>
+     * <strong>Note:</strong> If this method returns false this info is obsolete
+     * since it represents a window that is no longer exist.
+     * </p>
+     *
+     * @hide
+     */
+    public boolean refresh() {
+        if (mConnectionId == UNDEFINED_CONNECTION_ID || mId == UNDEFINED_WINDOW_ID) {
+            return false;
+        }
+        final AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        final AccessibilityWindowInfo refreshedInfo = client.getWindow(mConnectionId,
+                mId, /* bypassCache */true);
+        if (refreshedInfo == null) {
+            return false;
+        }
+        init(refreshedInfo);
+        refreshedInfo.recycle();
+        return true;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeInt(mDisplayId);
+        parcel.writeInt(mType);
+        parcel.writeInt(mLayer);
+        parcel.writeInt(mBooleanProperties);
+        parcel.writeInt(mId);
+        parcel.writeInt(mParentId);
+        mRegionInScreen.writeToParcel(parcel, flags);
+        parcel.writeCharSequence(mTitle);
+        parcel.writeLong(mAnchorId);
+
+        final LongArray childIds = mChildIds;
+        if (childIds == null) {
+            parcel.writeInt(0);
+        } else {
+            final int childCount = childIds.size();
+            parcel.writeInt(childCount);
+            for (int i = 0; i < childCount; i++) {
+                parcel.writeInt((int) childIds.get(i));
+            }
+        }
+
+        parcel.writeInt(mConnectionId);
+    }
+
+    /**
+     * Initializes this instance from another one.
+     *
+     * @param other The other instance.
+     */
+    private void init(AccessibilityWindowInfo other) {
+        mDisplayId = other.mDisplayId;
+        mType = other.mType;
+        mLayer = other.mLayer;
+        mBooleanProperties = other.mBooleanProperties;
+        mId = other.mId;
+        mParentId = other.mParentId;
+        mRegionInScreen.set(other.mRegionInScreen);
+        mTitle = other.mTitle;
+        mAnchorId = other.mAnchorId;
+
+        if (mChildIds != null) mChildIds.clear();
+        if (other.mChildIds != null && other.mChildIds.size() > 0) {
+            if (mChildIds == null) {
+                mChildIds = other.mChildIds.clone();
+            } else {
+                mChildIds.addAll(other.mChildIds);
+            }
+        }
+
+        mConnectionId = other.mConnectionId;
+    }
+
+    private void initFromParcel(Parcel parcel) {
+        mDisplayId = parcel.readInt();
+        mType = parcel.readInt();
+        mLayer = parcel.readInt();
+        mBooleanProperties = parcel.readInt();
+        mId = parcel.readInt();
+        mParentId = parcel.readInt();
+        mRegionInScreen = Region.CREATOR.createFromParcel(parcel);
+        mTitle = parcel.readCharSequence();
+        mAnchorId = parcel.readLong();
+
+        final int childCount = parcel.readInt();
+        if (childCount > 0) {
+            if (mChildIds == null) {
+                mChildIds = new LongArray(childCount);
+            }
+            for (int i = 0; i < childCount; i++) {
+                final int childId = parcel.readInt();
+                mChildIds.add(childId);
+            }
+        }
+
+        mConnectionId = parcel.readInt();
+    }
+
+    @Override
+    public int hashCode() {
+        return mId;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        AccessibilityWindowInfo other = (AccessibilityWindowInfo) obj;
+        return (mId == other.mId);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("AccessibilityWindowInfo[");
+        builder.append("title=").append(mTitle);
+        builder.append(", displayId=").append(mDisplayId);
+        builder.append(", id=").append(mId);
+        builder.append(", type=").append(typeToString(mType));
+        builder.append(", layer=").append(mLayer);
+        builder.append(", region=").append(mRegionInScreen);
+        builder.append(", bounds=").append(mRegionInScreen.getBounds());
+        builder.append(", focused=").append(isFocused());
+        builder.append(", active=").append(isActive());
+        builder.append(", pictureInPicture=").append(isInPictureInPictureMode());
+        if (DEBUG) {
+            builder.append(", parent=").append(mParentId);
+            builder.append(", children=[");
+            if (mChildIds != null) {
+                final int childCount = mChildIds.size();
+                for (int i = 0; i < childCount; i++) {
+                    builder.append(mChildIds.get(i));
+                    if (i < childCount - 1) {
+                        builder.append(',');
+                    }
+                }
+            } else {
+                builder.append("null");
+            }
+            builder.append(']');
+        } else {
+            builder.append(", hasParent=").append(mParentId != UNDEFINED_WINDOW_ID);
+            builder.append(", isAnchored=")
+                    .append(mAnchorId != AccessibilityNodeInfo.UNDEFINED_NODE_ID);
+            builder.append(", hasChildren=").append(mChildIds != null
+                    && mChildIds.size() > 0);
+        }
+        builder.append(']');
+        return builder.toString();
+    }
+
+    /**
+     * Clears the internal state.
+     */
+    private void clear() {
+        mDisplayId = Display.INVALID_DISPLAY;
+        mType = UNDEFINED_WINDOW_ID;
+        mLayer = UNDEFINED_WINDOW_ID;
+        mBooleanProperties = 0;
+        mId = UNDEFINED_WINDOW_ID;
+        mParentId = UNDEFINED_WINDOW_ID;
+        mRegionInScreen.setEmpty();
+        mChildIds = null;
+        mConnectionId = UNDEFINED_WINDOW_ID;
+        mAnchorId = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
+        mTitle = null;
+    }
+
+    /**
+     * Gets the value of a boolean property.
+     *
+     * @param property The property.
+     * @return The value.
+     */
+    private boolean getBooleanProperty(int property) {
+        return (mBooleanProperties & property) != 0;
+    }
+
+    /**
+     * Sets a boolean property.
+     *
+     * @param property The property.
+     * @param value The value.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    private void setBooleanProperty(int property, boolean value) {
+        if (value) {
+            mBooleanProperties |= property;
+        } else {
+            mBooleanProperties &= ~property;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public static String typeToString(int type) {
+        switch (type) {
+            case TYPE_APPLICATION: {
+                return "TYPE_APPLICATION";
+            }
+            case TYPE_INPUT_METHOD: {
+                return "TYPE_INPUT_METHOD";
+            }
+            case TYPE_SYSTEM: {
+                return "TYPE_SYSTEM";
+            }
+            case TYPE_ACCESSIBILITY_OVERLAY: {
+                return "TYPE_ACCESSIBILITY_OVERLAY";
+            }
+            case TYPE_SPLIT_SCREEN_DIVIDER: {
+                return "TYPE_SPLIT_SCREEN_DIVIDER";
+            }
+            default:
+                return "<UNKNOWN:" + type + ">";
+        }
+    }
+
+    /**
+     * Reports how this window differs from a possibly different state of the same window. The
+     * argument must have the same id and type as neither of those properties may change.
+     *
+     * @param other The new state.
+     * @return A set of flags showing how the window has changes, or 0 if the two states are the
+     * same.
+     *
+     * @hide
+     */
+    @WindowsChangeTypes
+    public int differenceFrom(AccessibilityWindowInfo other) {
+        if (other.mId != mId) {
+            throw new IllegalArgumentException("Not same window.");
+        }
+        if (other.mType != mType) {
+            throw new IllegalArgumentException("Not same type.");
+        }
+        int changes = 0;
+        if (!TextUtils.equals(mTitle, other.mTitle)) {
+            changes |= AccessibilityEvent.WINDOWS_CHANGE_TITLE;
+        }
+        if (!mRegionInScreen.equals(other.mRegionInScreen)) {
+            changes |= AccessibilityEvent.WINDOWS_CHANGE_BOUNDS;
+        }
+        if (mLayer != other.mLayer) {
+            changes |= AccessibilityEvent.WINDOWS_CHANGE_LAYER;
+        }
+        if (getBooleanProperty(BOOLEAN_PROPERTY_ACTIVE)
+                != other.getBooleanProperty(BOOLEAN_PROPERTY_ACTIVE)) {
+            changes |= AccessibilityEvent.WINDOWS_CHANGE_ACTIVE;
+        }
+        if (getBooleanProperty(BOOLEAN_PROPERTY_FOCUSED)
+                != other.getBooleanProperty(BOOLEAN_PROPERTY_FOCUSED)) {
+            changes |= AccessibilityEvent.WINDOWS_CHANGE_FOCUSED;
+        }
+        if (getBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED)
+                != other.getBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED)) {
+            changes |= AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED;
+        }
+        if (getBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE)
+                != other.getBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE)) {
+            changes |= AccessibilityEvent.WINDOWS_CHANGE_PIP;
+        }
+        if (mParentId != other.mParentId) {
+            changes |= AccessibilityEvent.WINDOWS_CHANGE_PARENT;
+        }
+        if (!Objects.equals(mChildIds, other.mChildIds)) {
+            changes |= AccessibilityEvent.WINDOWS_CHANGE_CHILDREN;
+        }
+        //TODO(b/1338122): Add DISPLAY_CHANGED type for multi-display
+        return changes;
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<AccessibilityWindowInfo> CREATOR =
+            new Creator<AccessibilityWindowInfo>() {
+        @Override
+        public AccessibilityWindowInfo createFromParcel(Parcel parcel) {
+            AccessibilityWindowInfo info = obtain();
+            info.initFromParcel(parcel);
+            return info;
+        }
+
+        @Override
+        public AccessibilityWindowInfo[] newArray(int size) {
+            return new AccessibilityWindowInfo[size];
+        }
+    };
+
+    /**
+     * Transfers a sparsearray with lists having {@link AccessibilityWindowInfo}s across an IPC.
+     * The key of this sparsearray is display Id.
+     *
+     * @hide
+     */
+    public static final class WindowListSparseArray
+            extends SparseArray<List<AccessibilityWindowInfo>> implements Parcelable {
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            final int count = size();
+            dest.writeInt(count);
+            for (int i = 0; i < count; i++) {
+                dest.writeParcelableList(valueAt(i), 0);
+                dest.writeInt(keyAt(i));
+            }
+        }
+
+        public static final Parcelable.Creator<WindowListSparseArray> CREATOR =
+                new Parcelable.Creator<WindowListSparseArray>() {
+            public WindowListSparseArray createFromParcel(
+                    Parcel source) {
+                final WindowListSparseArray array = new WindowListSparseArray();
+                final ClassLoader loader = array.getClass().getClassLoader();
+                final int count = source.readInt();
+                for (int i = 0; i < count; i++) {
+                    List<AccessibilityWindowInfo> windows = new ArrayList<>();
+                    source.readParcelableList(windows, loader);
+                    array.put(source.readInt(), windows);
+                }
+                return array;
+            }
+
+            public WindowListSparseArray[] newArray(int size) {
+                return new WindowListSparseArray[size];
+            }
+        };
+    }
+}
diff --git a/android/view/accessibility/CaptioningManager.java b/android/view/accessibility/CaptioningManager.java
new file mode 100644
index 0000000..3d68692
--- /dev/null
+++ b/android/view/accessibility/CaptioningManager.java
@@ -0,0 +1,557 @@
+/*
+ * 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.view.accessibility;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemService;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.Settings.Secure;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+/**
+ * Contains methods for accessing and monitoring preferred video captioning state and visual
+ * properties.
+ */
+@SystemService(Context.CAPTIONING_SERVICE)
+public class CaptioningManager {
+    /** Default captioning enabled value. */
+    private static final int DEFAULT_ENABLED = 0;
+
+    /** Default style preset as an index into {@link CaptionStyle#PRESETS}. */
+    private static final int DEFAULT_PRESET = 0;
+
+    /** Default scaling value for caption fonts. */
+    private static final float DEFAULT_FONT_SCALE = 1;
+
+    private final ArrayList<CaptioningChangeListener> mListeners = new ArrayList<>();
+    private final ContentResolver mContentResolver;
+    private final ContentObserver mContentObserver;
+
+    /**
+     * Creates a new captioning manager for the specified context.
+     *
+     * @hide
+     */
+    public CaptioningManager(Context context) {
+        mContentResolver = context.getContentResolver();
+
+        final Handler handler = new Handler(context.getMainLooper());
+        mContentObserver = new MyContentObserver(handler);
+    }
+
+    /**
+     * @return the user's preferred captioning enabled state
+     */
+    public final boolean isEnabled() {
+        return Secure.getInt(
+                mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_ENABLED, DEFAULT_ENABLED) == 1;
+    }
+
+    /**
+     * @return the raw locale string for the user's preferred captioning
+     *         language
+     * @hide
+     */
+    @Nullable
+    public final String getRawLocale() {
+        return Secure.getString(mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_LOCALE);
+    }
+
+    /**
+     * @return the locale for the user's preferred captioning language, or null
+     *         if not specified
+     */
+    @Nullable
+    public final Locale getLocale() {
+        final String rawLocale = getRawLocale();
+        if (!TextUtils.isEmpty(rawLocale)) {
+            final String[] splitLocale = rawLocale.split("_");
+            switch (splitLocale.length) {
+                case 3:
+                    return new Locale(splitLocale[0], splitLocale[1], splitLocale[2]);
+                case 2:
+                    return new Locale(splitLocale[0], splitLocale[1]);
+                case 1:
+                    return new Locale(splitLocale[0]);
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * @return the user's preferred font scaling factor for video captions, or 1 if not
+     *         specified
+     */
+    public final float getFontScale() {
+        return Secure.getFloat(
+                mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE, DEFAULT_FONT_SCALE);
+    }
+
+    /**
+     * @return the raw preset number, or the first preset if not specified
+     * @hide
+     */
+    public int getRawUserStyle() {
+        return Secure.getInt(
+                mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_PRESET, DEFAULT_PRESET);
+    }
+
+    /**
+     * @return the user's preferred visual properties for captions as a
+     *         {@link CaptionStyle}, or the default style if not specified
+     */
+    @NonNull
+    public CaptionStyle getUserStyle() {
+        final int preset = getRawUserStyle();
+        if (preset == CaptionStyle.PRESET_CUSTOM) {
+            return CaptionStyle.getCustomStyle(mContentResolver);
+        }
+
+        return CaptionStyle.PRESETS[preset];
+    }
+
+    /**
+     * Adds a listener for changes in the user's preferred captioning enabled
+     * state and visual properties.
+     *
+     * @param listener the listener to add
+     */
+    public void addCaptioningChangeListener(@NonNull CaptioningChangeListener listener) {
+        synchronized (mListeners) {
+            if (mListeners.isEmpty()) {
+                registerObserver(Secure.ACCESSIBILITY_CAPTIONING_ENABLED);
+                registerObserver(Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR);
+                registerObserver(Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR);
+                registerObserver(Secure.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR);
+                registerObserver(Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE);
+                registerObserver(Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR);
+                registerObserver(Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE);
+                registerObserver(Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE);
+                registerObserver(Secure.ACCESSIBILITY_CAPTIONING_LOCALE);
+                registerObserver(Secure.ACCESSIBILITY_CAPTIONING_PRESET);
+            }
+
+            mListeners.add(listener);
+        }
+    }
+
+    private void registerObserver(String key) {
+        mContentResolver.registerContentObserver(Secure.getUriFor(key), false, mContentObserver);
+    }
+
+    /**
+     * Removes a listener previously added using
+     * {@link #addCaptioningChangeListener}.
+     *
+     * @param listener the listener to remove
+     */
+    public void removeCaptioningChangeListener(@NonNull CaptioningChangeListener listener) {
+        synchronized (mListeners) {
+            mListeners.remove(listener);
+
+            if (mListeners.isEmpty()) {
+                mContentResolver.unregisterContentObserver(mContentObserver);
+            }
+        }
+    }
+
+    private void notifyEnabledChanged() {
+        final boolean enabled = isEnabled();
+        synchronized (mListeners) {
+            for (CaptioningChangeListener listener : mListeners) {
+                listener.onEnabledChanged(enabled);
+            }
+        }
+    }
+
+    private void notifyUserStyleChanged() {
+        final CaptionStyle userStyle = getUserStyle();
+        synchronized (mListeners) {
+            for (CaptioningChangeListener listener : mListeners) {
+                listener.onUserStyleChanged(userStyle);
+            }
+        }
+    }
+
+    private void notifyLocaleChanged() {
+        final Locale locale = getLocale();
+        synchronized (mListeners) {
+            for (CaptioningChangeListener listener : mListeners) {
+                listener.onLocaleChanged(locale);
+            }
+        }
+    }
+
+    private void notifyFontScaleChanged() {
+        final float fontScale = getFontScale();
+        synchronized (mListeners) {
+            for (CaptioningChangeListener listener : mListeners) {
+                listener.onFontScaleChanged(fontScale);
+            }
+        }
+    }
+
+    private class MyContentObserver extends ContentObserver {
+        private final Handler mHandler;
+
+        public MyContentObserver(Handler handler) {
+            super(handler);
+
+            mHandler = handler;
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            final String uriPath = uri.getPath();
+            final String name = uriPath.substring(uriPath.lastIndexOf('/') + 1);
+            if (Secure.ACCESSIBILITY_CAPTIONING_ENABLED.equals(name)) {
+                notifyEnabledChanged();
+            } else if (Secure.ACCESSIBILITY_CAPTIONING_LOCALE.equals(name)) {
+                notifyLocaleChanged();
+            } else if (Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE.equals(name)) {
+                notifyFontScaleChanged();
+            } else {
+                // We only need a single callback when multiple style properties
+                // change in rapid succession.
+                mHandler.removeCallbacks(mStyleChangedRunnable);
+                mHandler.post(mStyleChangedRunnable);
+            }
+        }
+    };
+
+    /**
+     * Runnable posted when user style properties change. This is used to
+     * prevent unnecessary change notifications when multiple properties change
+     * in rapid succession.
+     */
+    private final Runnable mStyleChangedRunnable = new Runnable() {
+        @Override
+        public void run() {
+            notifyUserStyleChanged();
+        }
+    };
+
+    /**
+     * Specifies visual properties for video captions, including foreground and
+     * background colors, edge properties, and typeface.
+     */
+    public static final class CaptionStyle {
+        /**
+         * Packed value for a color of 'none' and a cached opacity of 100%.
+         *
+         * @hide
+         */
+        private static final int COLOR_NONE_OPAQUE = 0x000000FF;
+
+        /**
+         * Packed value for a color of 'default' and opacity of 100%.
+         *
+         * @hide
+         */
+        public static final int COLOR_UNSPECIFIED = 0x00FFFFFF;
+
+        private static final CaptionStyle WHITE_ON_BLACK;
+        private static final CaptionStyle BLACK_ON_WHITE;
+        private static final CaptionStyle YELLOW_ON_BLACK;
+        private static final CaptionStyle YELLOW_ON_BLUE;
+        private static final CaptionStyle DEFAULT_CUSTOM;
+        private static final CaptionStyle UNSPECIFIED;
+
+        /** The default caption style used to fill in unspecified values. @hide */
+        public static final CaptionStyle DEFAULT;
+
+        /** @hide */
+        @UnsupportedAppUsage
+        public static final CaptionStyle[] PRESETS;
+
+        /** @hide */
+        public static final int PRESET_CUSTOM = -1;
+
+        /** Unspecified edge type value. */
+        public static final int EDGE_TYPE_UNSPECIFIED = -1;
+
+        /** Edge type value specifying no character edges. */
+        public static final int EDGE_TYPE_NONE = 0;
+
+        /** Edge type value specifying uniformly outlined character edges. */
+        public static final int EDGE_TYPE_OUTLINE = 1;
+
+        /** Edge type value specifying drop-shadowed character edges. */
+        public static final int EDGE_TYPE_DROP_SHADOW = 2;
+
+        /** Edge type value specifying raised bevel character edges. */
+        public static final int EDGE_TYPE_RAISED = 3;
+
+        /** Edge type value specifying depressed bevel character edges. */
+        public static final int EDGE_TYPE_DEPRESSED = 4;
+
+        /** The preferred foreground color for video captions. */
+        public final int foregroundColor;
+
+        /** The preferred background color for video captions. */
+        public final int backgroundColor;
+
+        /**
+         * The preferred edge type for video captions, one of:
+         * <ul>
+         * <li>{@link #EDGE_TYPE_UNSPECIFIED}
+         * <li>{@link #EDGE_TYPE_NONE}
+         * <li>{@link #EDGE_TYPE_OUTLINE}
+         * <li>{@link #EDGE_TYPE_DROP_SHADOW}
+         * <li>{@link #EDGE_TYPE_RAISED}
+         * <li>{@link #EDGE_TYPE_DEPRESSED}
+         * </ul>
+         */
+        public final int edgeType;
+
+        /**
+         * The preferred edge color for video captions, if using an edge type
+         * other than {@link #EDGE_TYPE_NONE}.
+         */
+        public final int edgeColor;
+
+        /** The preferred window color for video captions. */
+        public final int windowColor;
+
+        /**
+         * @hide
+         */
+        public final String mRawTypeface;
+
+        private final boolean mHasForegroundColor;
+        private final boolean mHasBackgroundColor;
+        private final boolean mHasEdgeType;
+        private final boolean mHasEdgeColor;
+        private final boolean mHasWindowColor;
+
+        /** Lazily-created typeface based on the raw typeface string. */
+        private Typeface mParsedTypeface;
+
+        private CaptionStyle(int foregroundColor, int backgroundColor, int edgeType, int edgeColor,
+                int windowColor, String rawTypeface) {
+            mHasForegroundColor = hasColor(foregroundColor);
+            mHasBackgroundColor = hasColor(backgroundColor);
+            mHasEdgeType = edgeType != EDGE_TYPE_UNSPECIFIED;
+            mHasEdgeColor = hasColor(edgeColor);
+            mHasWindowColor = hasColor(windowColor);
+
+            // Always use valid colors, even when no override is specified, to
+            // ensure backwards compatibility with apps targeting KitKat MR2.
+            this.foregroundColor = mHasForegroundColor ? foregroundColor : Color.WHITE;
+            this.backgroundColor = mHasBackgroundColor ? backgroundColor : Color.BLACK;
+            this.edgeType = mHasEdgeType ? edgeType : EDGE_TYPE_NONE;
+            this.edgeColor = mHasEdgeColor ? edgeColor : Color.BLACK;
+            this.windowColor = mHasWindowColor ? windowColor : COLOR_NONE_OPAQUE;
+
+            mRawTypeface = rawTypeface;
+        }
+
+        /**
+         * Returns whether a packed color indicates a non-default value.
+         *
+         * @param packedColor the packed color value
+         * @return {@code true} if a non-default value is specified
+         * @hide
+         */
+        public static boolean hasColor(int packedColor) {
+            // Matches the color packing code from Settings. "Default" packed
+            // colors are indicated by zero alpha and non-zero red/blue. The
+            // cached alpha value used by Settings is stored in green.
+            return (packedColor >>> 24) != 0 || (packedColor & 0xFFFF00) == 0;
+        }
+
+        /**
+         * Applies a caption style, overriding any properties that are specified
+         * in the overlay caption.
+         *
+         * @param overlay The style to apply
+         * @return A caption style with the overlay style applied
+         * @hide
+         */
+        @NonNull
+        public CaptionStyle applyStyle(@NonNull CaptionStyle overlay) {
+            final int newForegroundColor = overlay.hasForegroundColor() ?
+                    overlay.foregroundColor : foregroundColor;
+            final int newBackgroundColor = overlay.hasBackgroundColor() ?
+                    overlay.backgroundColor : backgroundColor;
+            final int newEdgeType = overlay.hasEdgeType() ?
+                    overlay.edgeType : edgeType;
+            final int newEdgeColor = overlay.hasEdgeColor() ?
+                    overlay.edgeColor : edgeColor;
+            final int newWindowColor = overlay.hasWindowColor() ?
+                    overlay.windowColor : windowColor;
+            final String newRawTypeface = overlay.mRawTypeface != null ?
+                    overlay.mRawTypeface : mRawTypeface;
+            return new CaptionStyle(newForegroundColor, newBackgroundColor, newEdgeType,
+                    newEdgeColor, newWindowColor, newRawTypeface);
+        }
+
+        /**
+         * @return {@code true} if the user has specified a background color
+         *         that should override the application default, {@code false}
+         *         otherwise
+         */
+        public boolean hasBackgroundColor() {
+            return mHasBackgroundColor;
+        }
+
+        /**
+         * @return {@code true} if the user has specified a foreground color
+         *         that should override the application default, {@code false}
+         *         otherwise
+         */
+        public boolean hasForegroundColor() {
+            return mHasForegroundColor;
+        }
+
+        /**
+         * @return {@code true} if the user has specified an edge type that
+         *         should override the application default, {@code false}
+         *         otherwise
+         */
+        public boolean hasEdgeType() {
+            return mHasEdgeType;
+        }
+
+        /**
+         * @return {@code true} if the user has specified an edge color that
+         *         should override the application default, {@code false}
+         *         otherwise
+         */
+        public boolean hasEdgeColor() {
+            return mHasEdgeColor;
+        }
+
+        /**
+         * @return {@code true} if the user has specified a window color that
+         *         should override the application default, {@code false}
+         *         otherwise
+         */
+        public boolean hasWindowColor() {
+            return mHasWindowColor;
+        }
+
+        /**
+         * @return the preferred {@link Typeface} for video captions, or null if
+         *         not specified
+         */
+        @Nullable
+        public Typeface getTypeface() {
+            if (mParsedTypeface == null && !TextUtils.isEmpty(mRawTypeface)) {
+                mParsedTypeface = Typeface.create(mRawTypeface, Typeface.NORMAL);
+            }
+            return mParsedTypeface;
+        }
+
+        /**
+         * @hide
+         */
+        @NonNull
+        public static CaptionStyle getCustomStyle(ContentResolver cr) {
+            final CaptionStyle defStyle = CaptionStyle.DEFAULT_CUSTOM;
+            final int foregroundColor = Secure.getInt(
+                    cr, Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR, defStyle.foregroundColor);
+            final int backgroundColor = Secure.getInt(
+                    cr, Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR, defStyle.backgroundColor);
+            final int edgeType = Secure.getInt(
+                    cr, Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE, defStyle.edgeType);
+            final int edgeColor = Secure.getInt(
+                    cr, Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR, defStyle.edgeColor);
+            final int windowColor = Secure.getInt(
+                    cr, Secure.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR, defStyle.windowColor);
+
+            String rawTypeface = Secure.getString(cr, Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE);
+            if (rawTypeface == null) {
+                rawTypeface = defStyle.mRawTypeface;
+            }
+
+            return new CaptionStyle(foregroundColor, backgroundColor, edgeType, edgeColor,
+                    windowColor, rawTypeface);
+        }
+
+        static {
+            WHITE_ON_BLACK = new CaptionStyle(Color.WHITE, Color.BLACK, EDGE_TYPE_NONE,
+                    Color.BLACK, COLOR_NONE_OPAQUE, null);
+            BLACK_ON_WHITE = new CaptionStyle(Color.BLACK, Color.WHITE, EDGE_TYPE_NONE,
+                    Color.BLACK, COLOR_NONE_OPAQUE, null);
+            YELLOW_ON_BLACK = new CaptionStyle(Color.YELLOW, Color.BLACK, EDGE_TYPE_NONE,
+                    Color.BLACK, COLOR_NONE_OPAQUE, null);
+            YELLOW_ON_BLUE = new CaptionStyle(Color.YELLOW, Color.BLUE, EDGE_TYPE_NONE,
+                    Color.BLACK, COLOR_NONE_OPAQUE, null);
+            UNSPECIFIED = new CaptionStyle(COLOR_UNSPECIFIED, COLOR_UNSPECIFIED,
+                    EDGE_TYPE_UNSPECIFIED, COLOR_UNSPECIFIED, COLOR_UNSPECIFIED, null);
+
+            // The ordering of these cannot change since we store the index
+            // directly in preferences.
+            PRESETS = new CaptionStyle[] {
+                    WHITE_ON_BLACK, BLACK_ON_WHITE, YELLOW_ON_BLACK, YELLOW_ON_BLUE, UNSPECIFIED
+            };
+
+            DEFAULT_CUSTOM = WHITE_ON_BLACK;
+            DEFAULT = WHITE_ON_BLACK;
+        }
+    }
+
+    /**
+     * Listener for changes in captioning properties, including enabled state
+     * and user style preferences.
+     */
+    public static abstract class CaptioningChangeListener {
+        /**
+         * Called when the captioning enabled state changes.
+         *
+         * @param enabled the user's new preferred captioning enabled state
+         */
+        public void onEnabledChanged(boolean enabled) {}
+
+        /**
+         * Called when the captioning user style changes.
+         *
+         * @param userStyle the user's new preferred style
+         * @see CaptioningManager#getUserStyle()
+         */
+        public void onUserStyleChanged(@NonNull CaptionStyle userStyle) {}
+
+        /**
+         * Called when the captioning locale changes.
+         *
+         * @param locale the preferred captioning locale, or {@code null} if not specified
+         * @see CaptioningManager#getLocale()
+         */
+        public void onLocaleChanged(@Nullable Locale locale) {}
+
+        /**
+         * Called when the captioning font scaling factor changes.
+         *
+         * @param fontScale the preferred font scaling factor
+         * @see CaptioningManager#getFontScale()
+         */
+        public void onFontScaleChanged(float fontScale) {}
+    }
+}
diff --git a/android/view/accessibility/MagnificationAnimationCallback.java b/android/view/accessibility/MagnificationAnimationCallback.java
new file mode 100644
index 0000000..bc9fb0a
--- /dev/null
+++ b/android/view/accessibility/MagnificationAnimationCallback.java
@@ -0,0 +1,31 @@
+/*
+ * 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.view.accessibility;
+
+/**
+ * A callback for magnification animation result.
+ * @hide
+ */
+public interface MagnificationAnimationCallback {
+    /**
+     * Called when the animation is finished or interrupted during animating.
+     *
+     * @param success {@code true} if animating successfully with given spec or the spec did not
+     *                change. Otherwise {@code false}
+     */
+    void onResult(boolean success);
+}
\ No newline at end of file
diff --git a/android/view/accessibility/WeakSparseArray.java b/android/view/accessibility/WeakSparseArray.java
new file mode 100644
index 0000000..04a4cc7
--- /dev/null
+++ b/android/view/accessibility/WeakSparseArray.java
@@ -0,0 +1,63 @@
+/*
+ * 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.view.accessibility;
+
+import android.util.SparseArray;
+
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+
+
+final class WeakSparseArray<E> {
+
+    private final ReferenceQueue<E> mRefQueue = new ReferenceQueue<>();
+    private final SparseArray<WeakReferenceWithId<E>> mSparseArray = new SparseArray<>();
+
+    public void append(int key, E value) {
+        removeUnreachableValues();
+        mSparseArray.append(key, new WeakReferenceWithId(value, mRefQueue, key));
+    }
+
+    public void remove(int key) {
+        removeUnreachableValues();
+        mSparseArray.remove(key);
+    }
+
+    public E get(int key) {
+        removeUnreachableValues();
+        WeakReferenceWithId<E> ref = mSparseArray.get(key);
+        return ref != null ? ref.get() : null;
+    }
+
+    private void removeUnreachableValues() {
+        for (Reference ref = mRefQueue.poll(); ref != null; ref = mRefQueue.poll()) {
+            mSparseArray.remove(((WeakReferenceWithId) ref).mId);
+        }
+    }
+
+    private static class WeakReferenceWithId<E> extends WeakReference<E> {
+
+        final int mId;
+
+        WeakReferenceWithId(E referent, ReferenceQueue<? super E> q, int id) {
+            super(referent, q);
+            mId = id;
+        }
+    }
+}
+
diff --git a/android/view/animation/AccelerateDecelerateInterpolator.java b/android/view/animation/AccelerateDecelerateInterpolator.java
new file mode 100644
index 0000000..a2bbc5c
--- /dev/null
+++ b/android/view/animation/AccelerateDecelerateInterpolator.java
@@ -0,0 +1,48 @@
+/*
+ * 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.view.animation;
+
+import android.content.Context;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
+import android.util.AttributeSet;
+
+/**
+ * An interpolator where the rate of change starts and ends slowly but
+ * accelerates through the middle.
+ */
+@HasNativeInterpolator
+public class AccelerateDecelerateInterpolator extends BaseInterpolator
+        implements NativeInterpolator {
+    public AccelerateDecelerateInterpolator() {
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) {
+    }
+
+    public float getInterpolation(float input) {
+        return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
+    }
+
+    /** @hide */
+    @Override
+    public long createNativeInterpolator() {
+        return NativeInterpolatorFactory.createAccelerateDecelerateInterpolator();
+    }
+}
diff --git a/android/view/animation/AccelerateInterpolator.java b/android/view/animation/AccelerateInterpolator.java
new file mode 100644
index 0000000..9d4cd32
--- /dev/null
+++ b/android/view/animation/AccelerateInterpolator.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.animation;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
+import android.util.AttributeSet;
+
+import com.android.internal.R;
+
+/**
+ * An interpolator where the rate of change starts out slowly and
+ * and then accelerates.
+ *
+ */
+@HasNativeInterpolator
+public class AccelerateInterpolator extends BaseInterpolator implements NativeInterpolator {
+    private final float mFactor;
+    private final double mDoubleFactor;
+
+    public AccelerateInterpolator() {
+        mFactor = 1.0f;
+        mDoubleFactor = 2.0;
+    }
+
+    /**
+     * Constructor
+     *
+     * @param factor Degree to which the animation should be eased. Seting
+     *        factor to 1.0f produces a y=x^2 parabola. Increasing factor above
+     *        1.0f  exaggerates the ease-in effect (i.e., it starts even
+     *        slower and ends evens faster)
+     */
+    public AccelerateInterpolator(float factor) {
+        mFactor = factor;
+        mDoubleFactor = 2 * mFactor;
+    }
+
+    public AccelerateInterpolator(Context context, AttributeSet attrs) {
+        this(context.getResources(), context.getTheme(), attrs);
+    }
+
+    /** @hide */
+    public AccelerateInterpolator(Resources res, Theme theme, AttributeSet attrs) {
+        TypedArray a;
+        if (theme != null) {
+            a = theme.obtainStyledAttributes(attrs, R.styleable.AccelerateInterpolator, 0, 0);
+        } else {
+            a = res.obtainAttributes(attrs, R.styleable.AccelerateInterpolator);
+        }
+
+        mFactor = a.getFloat(R.styleable.AccelerateInterpolator_factor, 1.0f);
+        mDoubleFactor = 2 * mFactor;
+        setChangingConfiguration(a.getChangingConfigurations());
+        a.recycle();
+    }
+
+    public float getInterpolation(float input) {
+        if (mFactor == 1.0f) {
+            return input * input;
+        } else {
+            return (float)Math.pow(input, mDoubleFactor);
+        }
+    }
+
+    /** @hide */
+    @Override
+    public long createNativeInterpolator() {
+        return NativeInterpolatorFactory.createAccelerateInterpolator(mFactor);
+    }
+}
diff --git a/android/view/animation/AlphaAnimation.java b/android/view/animation/AlphaAnimation.java
new file mode 100644
index 0000000..c4d9afc
--- /dev/null
+++ b/android/view/animation/AlphaAnimation.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.animation;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+
+/**
+ * An animation that controls the alpha level of an object.
+ * Useful for fading things in and out. This animation ends up
+ * changing the alpha property of a {@link Transformation}
+ *
+ */
+public class AlphaAnimation extends Animation {
+    private float mFromAlpha;
+    private float mToAlpha;
+
+    /**
+     * Constructor used when an AlphaAnimation is loaded from a resource. 
+     * 
+     * @param context Application context to use
+     * @param attrs Attribute set from which to read values
+     */
+    public AlphaAnimation(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        
+        TypedArray a =
+            context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AlphaAnimation);
+        
+        mFromAlpha = a.getFloat(com.android.internal.R.styleable.AlphaAnimation_fromAlpha, 1.0f);
+        mToAlpha = a.getFloat(com.android.internal.R.styleable.AlphaAnimation_toAlpha, 1.0f);
+        
+        a.recycle();
+    }
+    
+    /**
+     * Constructor to use when building an AlphaAnimation from code
+     * 
+     * @param fromAlpha Starting alpha value for the animation, where 1.0 means
+     *        fully opaque and 0.0 means fully transparent.
+     * @param toAlpha Ending alpha value for the animation.
+     */
+    public AlphaAnimation(float fromAlpha, float toAlpha) {
+        mFromAlpha = fromAlpha;
+        mToAlpha = toAlpha;
+    }
+    
+    /**
+     * Changes the alpha property of the supplied {@link Transformation}
+     */
+    @Override
+    protected void applyTransformation(float interpolatedTime, Transformation t) {
+        final float alpha = mFromAlpha;
+        t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime));
+    }
+
+    @Override
+    public boolean willChangeTransformationMatrix() {
+        return false;
+    }
+
+    @Override
+    public boolean willChangeBounds() {
+        return false;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public boolean hasAlpha() {
+        return true;
+    }
+}
diff --git a/android/view/animation/Animation.java b/android/view/animation/Animation.java
new file mode 100644
index 0000000..b1d618e
--- /dev/null
+++ b/android/view/animation/Animation.java
@@ -0,0 +1,1256 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.animation;
+
+import android.annotation.AnimRes;
+import android.annotation.ColorInt;
+import android.annotation.InterpolatorRes;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.RectF;
+import android.os.Build;
+import android.os.Handler;
+import android.os.SystemProperties;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+
+import dalvik.system.CloseGuard;
+
+/**
+ * Abstraction for an Animation that can be applied to Views, Surfaces, or
+ * other objects. See the {@link android.view.animation animation package
+ * description file}.
+ */
+public abstract class Animation implements Cloneable {
+    /**
+     * Repeat the animation indefinitely.
+     */
+    public static final int INFINITE = -1;
+
+    /**
+     * When the animation reaches the end and the repeat count is INFINTE_REPEAT
+     * or a positive value, the animation restarts from the beginning.
+     */
+    public static final int RESTART = 1;
+
+    /**
+     * When the animation reaches the end and the repeat count is INFINTE_REPEAT
+     * or a positive value, the animation plays backward (and then forward again).
+     */
+    public static final int REVERSE = 2;
+
+    /**
+     * Can be used as the start time to indicate the start time should be the current
+     * time when {@link #getTransformation(long, Transformation)} is invoked for the
+     * first animation frame. This can is useful for short animations.
+     */
+    public static final int START_ON_FIRST_FRAME = -1;
+
+    /**
+     * The specified dimension is an absolute number of pixels.
+     */
+    public static final int ABSOLUTE = 0;
+
+    /**
+     * The specified dimension holds a float and should be multiplied by the
+     * height or width of the object being animated.
+     */
+    public static final int RELATIVE_TO_SELF = 1;
+
+    /**
+     * The specified dimension holds a float and should be multiplied by the
+     * height or width of the parent of the object being animated.
+     */
+    public static final int RELATIVE_TO_PARENT = 2;
+
+    /**
+     * Requests that the content being animated be kept in its current Z
+     * order.
+     */
+    public static final int ZORDER_NORMAL = 0;
+
+    /**
+     * Requests that the content being animated be forced on top of all other
+     * content for the duration of the animation.
+     */
+    public static final int ZORDER_TOP = 1;
+
+    /**
+     * Requests that the content being animated be forced under all other
+     * content for the duration of the animation.
+     */
+    public static final int ZORDER_BOTTOM = -1;
+
+    // Use a preload holder to isolate static initialization into inner class, which allows
+    // Animation and its subclasses to be compile-time initialized.
+    private static class NoImagePreloadHolder {
+        public static final boolean USE_CLOSEGUARD
+                = SystemProperties.getBoolean("log.closeguard.Animation", false);
+    }
+
+    /**
+     * Set by {@link #getTransformation(long, Transformation)} when the animation ends.
+     */
+    boolean mEnded = false;
+
+    /**
+     * Set by {@link #getTransformation(long, Transformation)} when the animation starts.
+     */
+    boolean mStarted = false;
+
+    /**
+     * Set by {@link #getTransformation(long, Transformation)} when the animation repeats
+     * in REVERSE mode.
+     */
+    boolean mCycleFlip = false;
+
+    /**
+     * This value must be set to true by {@link #initialize(int, int, int, int)}. It
+     * indicates the animation was successfully initialized and can be played.
+     */
+    boolean mInitialized = false;
+
+    /**
+     * Indicates whether the animation transformation should be applied before the
+     * animation starts. The value of this variable is only relevant if mFillEnabled is true;
+     * otherwise it is assumed to be true.
+     */
+    boolean mFillBefore = true;
+
+    /**
+     * Indicates whether the animation transformation should be applied after the
+     * animation ends.
+     */
+    boolean mFillAfter = false;
+
+    /**
+     * Indicates whether fillBefore should be taken into account.
+     */
+    boolean mFillEnabled = false;
+
+    /**
+     * The time in milliseconds at which the animation must start;
+     */
+    long mStartTime = -1;
+
+    /**
+     * The delay in milliseconds after which the animation must start. When the
+     * start offset is > 0, the start time of the animation is startTime + startOffset.
+     */
+    long mStartOffset;
+
+    /**
+     * The duration of one animation cycle in milliseconds.
+     */
+    long mDuration;
+
+    /**
+     * The number of times the animation must repeat. By default, an animation repeats
+     * indefinitely.
+     */
+    int mRepeatCount = 0;
+
+    /**
+     * Indicates how many times the animation was repeated.
+     */
+    int mRepeated = 0;
+
+    /**
+     * The behavior of the animation when it repeats. The repeat mode is either
+     * {@link #RESTART} or {@link #REVERSE}.
+     *
+     */
+    int mRepeatMode = RESTART;
+
+    /**
+     * The interpolator used by the animation to smooth the movement.
+     */
+    Interpolator mInterpolator;
+
+    /**
+     * An animation listener to be notified when the animation starts, ends or repeats.
+     */
+    // If you need to chain the AnimationListener, wrap the existing Animation into an AnimationSet
+    // and add your new listener to that set
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 117519981)
+    private AnimationListener mListener;
+
+    /**
+     * Desired Z order mode during animation.
+     */
+    private int mZAdjustment;
+
+    /**
+     * Desired background color behind animation.
+     */
+    private int mBackgroundColor;
+
+    /**
+     * scalefactor to apply to pivot points, etc. during animation. Subclasses retrieve the
+     * value via getScaleFactor().
+     */
+    private float mScaleFactor = 1f;
+
+    private boolean mShowWallpaper;
+    private boolean mHasRoundedCorners;
+
+    private boolean mMore = true;
+    private boolean mOneMoreTime = true;
+
+    @UnsupportedAppUsage
+    RectF mPreviousRegion = new RectF();
+    @UnsupportedAppUsage
+    RectF mRegion = new RectF();
+    @UnsupportedAppUsage
+    Transformation mTransformation = new Transformation();
+    @UnsupportedAppUsage
+    Transformation mPreviousTransformation = new Transformation();
+
+    private final CloseGuard guard = CloseGuard.get();
+
+    private Handler mListenerHandler;
+    private Runnable mOnStart;
+    private Runnable mOnRepeat;
+    private Runnable mOnEnd;
+
+    /**
+     * Creates a new animation with a duration of 0ms, the default interpolator, with
+     * fillBefore set to true and fillAfter set to false
+     */
+    public Animation() {
+        ensureInterpolator();
+    }
+
+    /**
+     * Creates a new animation whose parameters come from the specified context and
+     * attributes set.
+     *
+     * @param context the application environment
+     * @param attrs the set of attributes holding the animation parameters
+     */
+    public Animation(Context context, AttributeSet attrs) {
+        TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Animation);
+
+        setDuration((long) a.getInt(com.android.internal.R.styleable.Animation_duration, 0));
+        setStartOffset((long) a.getInt(com.android.internal.R.styleable.Animation_startOffset, 0));
+
+        setFillEnabled(a.getBoolean(com.android.internal.R.styleable.Animation_fillEnabled, mFillEnabled));
+        setFillBefore(a.getBoolean(com.android.internal.R.styleable.Animation_fillBefore, mFillBefore));
+        setFillAfter(a.getBoolean(com.android.internal.R.styleable.Animation_fillAfter, mFillAfter));
+
+        setRepeatCount(a.getInt(com.android.internal.R.styleable.Animation_repeatCount, mRepeatCount));
+        setRepeatMode(a.getInt(com.android.internal.R.styleable.Animation_repeatMode, RESTART));
+
+        setZAdjustment(a.getInt(com.android.internal.R.styleable.Animation_zAdjustment, ZORDER_NORMAL));
+
+        setBackgroundColor(a.getInt(com.android.internal.R.styleable.Animation_background, 0));
+
+        setDetachWallpaper(
+                a.getBoolean(com.android.internal.R.styleable.Animation_detachWallpaper, false));
+        setShowWallpaper(
+                a.getBoolean(com.android.internal.R.styleable.Animation_showWallpaper, false));
+        setHasRoundedCorners(
+                a.getBoolean(com.android.internal.R.styleable.Animation_hasRoundedCorners, false));
+
+        final int resID = a.getResourceId(com.android.internal.R.styleable.Animation_interpolator, 0);
+
+        a.recycle();
+
+        if (resID > 0) {
+            setInterpolator(context, resID);
+        }
+
+        ensureInterpolator();
+    }
+
+    @Override
+    protected Animation clone() throws CloneNotSupportedException {
+        final Animation animation = (Animation) super.clone();
+        animation.mPreviousRegion = new RectF();
+        animation.mRegion = new RectF();
+        animation.mTransformation = new Transformation();
+        animation.mPreviousTransformation = new Transformation();
+        return animation;
+    }
+
+    /**
+     * Reset the initialization state of this animation.
+     *
+     * @see #initialize(int, int, int, int)
+     */
+    public void reset() {
+        mPreviousRegion.setEmpty();
+        mPreviousTransformation.clear();
+        mInitialized = false;
+        mCycleFlip = false;
+        mRepeated = 0;
+        mMore = true;
+        mOneMoreTime = true;
+        mListenerHandler = null;
+    }
+
+    /**
+     * Cancel the animation. Cancelling an animation invokes the animation
+     * listener, if set, to notify the end of the animation.
+     *
+     * If you cancel an animation manually, you must call {@link #reset()}
+     * before starting the animation again.
+     *
+     * @see #reset()
+     * @see #start()
+     * @see #startNow()
+     */
+    public void cancel() {
+        if (mStarted && !mEnded) {
+            fireAnimationEnd();
+            mEnded = true;
+            guard.close();
+        }
+        // Make sure we move the animation to the end
+        mStartTime = Long.MIN_VALUE;
+        mMore = mOneMoreTime = false;
+    }
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void detach() {
+        if (mStarted && !mEnded) {
+            mEnded = true;
+            guard.close();
+            fireAnimationEnd();
+        }
+    }
+
+    /**
+     * Whether or not the animation has been initialized.
+     *
+     * @return Has this animation been initialized.
+     * @see #initialize(int, int, int, int)
+     */
+    public boolean isInitialized() {
+        return mInitialized;
+    }
+
+    /**
+     * Initialize this animation with the dimensions of the object being
+     * animated as well as the objects parents. (This is to support animation
+     * sizes being specified relative to these dimensions.)
+     *
+     * <p>Objects that interpret Animations should call this method when
+     * the sizes of the object being animated and its parent are known, and
+     * before calling {@link #getTransformation}.
+     *
+     *
+     * @param width Width of the object being animated
+     * @param height Height of the object being animated
+     * @param parentWidth Width of the animated object's parent
+     * @param parentHeight Height of the animated object's parent
+     */
+    public void initialize(int width, int height, int parentWidth, int parentHeight) {
+        reset();
+        mInitialized = true;
+    }
+
+    /**
+     * Sets the handler used to invoke listeners.
+     *
+     * @hide
+     */
+    public void setListenerHandler(Handler handler) {
+        if (mListenerHandler == null) {
+            mOnStart = new Runnable() {
+                public void run() {
+                    dispatchAnimationStart();
+                }
+            };
+            mOnRepeat = new Runnable() {
+                public void run() {
+                    dispatchAnimationRepeat();
+                }
+            };
+            mOnEnd = new Runnable() {
+                public void run() {
+                    dispatchAnimationEnd();
+                }
+            };
+        }
+        mListenerHandler = handler;
+    }
+
+    /**
+     * Sets the acceleration curve for this animation. The interpolator is loaded as
+     * a resource from the specified context.
+     *
+     * @param context The application environment
+     * @param resID The resource identifier of the interpolator to load
+     * @attr ref android.R.styleable#Animation_interpolator
+     */
+    public void setInterpolator(Context context, @AnimRes @InterpolatorRes int resID) {
+        setInterpolator(AnimationUtils.loadInterpolator(context, resID));
+    }
+
+    /**
+     * Sets the acceleration curve for this animation. Defaults to a linear
+     * interpolation.
+     *
+     * @param i The interpolator which defines the acceleration curve
+     * @attr ref android.R.styleable#Animation_interpolator
+     */
+    public void setInterpolator(Interpolator i) {
+        mInterpolator = i;
+    }
+
+    /**
+     * When this animation should start relative to the start time. This is most
+     * useful when composing complex animations using an {@link AnimationSet }
+     * where some of the animations components start at different times.
+     *
+     * @param startOffset When this Animation should start, in milliseconds from
+     *                    the start time of the root AnimationSet.
+     * @attr ref android.R.styleable#Animation_startOffset
+     */
+    public void setStartOffset(long startOffset) {
+        mStartOffset = startOffset;
+    }
+
+    /**
+     * How long this animation should last. The duration cannot be negative.
+     *
+     * @param durationMillis Duration in milliseconds
+     *
+     * @throws java.lang.IllegalArgumentException if the duration is < 0
+     *
+     * @attr ref android.R.styleable#Animation_duration
+     */
+    public void setDuration(long durationMillis) {
+        if (durationMillis < 0) {
+            throw new IllegalArgumentException("Animation duration cannot be negative");
+        }
+        mDuration = durationMillis;
+    }
+
+    /**
+     * Ensure that the duration that this animation will run is not longer
+     * than <var>durationMillis</var>.  In addition to adjusting the duration
+     * itself, this ensures that the repeat count also will not make it run
+     * longer than the given time.
+     *
+     * @param durationMillis The maximum duration the animation is allowed
+     * to run.
+     */
+    public void restrictDuration(long durationMillis) {
+        // If we start after the duration, then we just won't run.
+        if (mStartOffset > durationMillis) {
+            mStartOffset = durationMillis;
+            mDuration = 0;
+            mRepeatCount = 0;
+            return;
+        }
+
+        long dur = mDuration + mStartOffset;
+        if (dur > durationMillis) {
+            mDuration = durationMillis-mStartOffset;
+            dur = durationMillis;
+        }
+        // If the duration is 0 or less, then we won't run.
+        if (mDuration <= 0) {
+            mDuration = 0;
+            mRepeatCount = 0;
+            return;
+        }
+        // Reduce the number of repeats to keep below the maximum duration.
+        // The comparison between mRepeatCount and duration is to catch
+        // overflows after multiplying them.
+        if (mRepeatCount < 0 || mRepeatCount > durationMillis
+                || (dur*mRepeatCount) > durationMillis) {
+            // Figure out how many times to do the animation.  Subtract 1 since
+            // repeat count is the number of times to repeat so 0 runs once.
+            mRepeatCount = (int)(durationMillis/dur) - 1;
+            if (mRepeatCount < 0) {
+                mRepeatCount = 0;
+            }
+        }
+    }
+
+    /**
+     * How much to scale the duration by.
+     *
+     * @param scale The amount to scale the duration.
+     */
+    public void scaleCurrentDuration(float scale) {
+        mDuration = (long) (mDuration * scale);
+        mStartOffset = (long) (mStartOffset * scale);
+    }
+
+    /**
+     * When this animation should start. When the start time is set to
+     * {@link #START_ON_FIRST_FRAME}, the animation will start the first time
+     * {@link #getTransformation(long, Transformation)} is invoked. The time passed
+     * to this method should be obtained by calling
+     * {@link AnimationUtils#currentAnimationTimeMillis()} instead of
+     * {@link System#currentTimeMillis()}.
+     *
+     * @param startTimeMillis the start time in milliseconds
+     */
+    public void setStartTime(long startTimeMillis) {
+        mStartTime = startTimeMillis;
+        mStarted = mEnded = false;
+        mCycleFlip = false;
+        mRepeated = 0;
+        mMore = true;
+    }
+
+    /**
+     * Convenience method to start the animation the first time
+     * {@link #getTransformation(long, Transformation)} is invoked.
+     */
+    public void start() {
+        setStartTime(-1);
+    }
+
+    /**
+     * Convenience method to start the animation at the current time in
+     * milliseconds.
+     */
+    public void startNow() {
+        setStartTime(AnimationUtils.currentAnimationTimeMillis());
+    }
+
+    /**
+     * Defines what this animation should do when it reaches the end. This
+     * setting is applied only when the repeat count is either greater than
+     * 0 or {@link #INFINITE}. Defaults to {@link #RESTART}.
+     *
+     * @param repeatMode {@link #RESTART} or {@link #REVERSE}
+     * @attr ref android.R.styleable#Animation_repeatMode
+     */
+    public void setRepeatMode(int repeatMode) {
+        mRepeatMode = repeatMode;
+    }
+
+    /**
+     * Sets how many times the animation should be repeated. If the repeat
+     * count is 0, the animation is never repeated. If the repeat count is
+     * greater than 0 or {@link #INFINITE}, the repeat mode will be taken
+     * into account. The repeat count is 0 by default.
+     *
+     * @param repeatCount the number of times the animation should be repeated
+     * @attr ref android.R.styleable#Animation_repeatCount
+     */
+    public void setRepeatCount(int repeatCount) {
+        if (repeatCount < 0) {
+            repeatCount = INFINITE;
+        }
+        mRepeatCount = repeatCount;
+    }
+
+    /**
+     * If fillEnabled is true, this animation will apply the value of fillBefore.
+     *
+     * @return true if the animation will take fillBefore into account
+     * @attr ref android.R.styleable#Animation_fillEnabled
+     */
+    public boolean isFillEnabled() {
+        return mFillEnabled;
+    }
+
+    /**
+     * If fillEnabled is true, the animation will apply the value of fillBefore.
+     * Otherwise, fillBefore is ignored and the animation
+     * transformation is always applied until the animation ends.
+     *
+     * @param fillEnabled true if the animation should take the value of fillBefore into account
+     * @attr ref android.R.styleable#Animation_fillEnabled
+     *
+     * @see #setFillBefore(boolean)
+     * @see #setFillAfter(boolean)
+     */
+    public void setFillEnabled(boolean fillEnabled) {
+        mFillEnabled = fillEnabled;
+    }
+
+    /**
+     * If fillBefore is true, this animation will apply its transformation
+     * before the start time of the animation. Defaults to true if
+     * {@link #setFillEnabled(boolean)} is not set to true.
+     * Note that this applies when using an {@link
+     * android.view.animation.AnimationSet AnimationSet} to chain
+     * animations. The transformation is not applied before the AnimationSet
+     * itself starts.
+     *
+     * @param fillBefore true if the animation should apply its transformation before it starts
+     * @attr ref android.R.styleable#Animation_fillBefore
+     *
+     * @see #setFillEnabled(boolean)
+     */
+    public void setFillBefore(boolean fillBefore) {
+        mFillBefore = fillBefore;
+    }
+
+    /**
+     * If fillAfter is true, the transformation that this animation performed
+     * will persist when it is finished. Defaults to false if not set.
+     * Note that this applies to individual animations and when using an {@link
+     * android.view.animation.AnimationSet AnimationSet} to chain
+     * animations.
+     *
+     * @param fillAfter true if the animation should apply its transformation after it ends
+     * @attr ref android.R.styleable#Animation_fillAfter
+     *
+     * @see #setFillEnabled(boolean)
+     */
+    public void setFillAfter(boolean fillAfter) {
+        mFillAfter = fillAfter;
+    }
+
+    /**
+     * Set the Z ordering mode to use while running the animation.
+     *
+     * @param zAdjustment The desired mode, one of {@link #ZORDER_NORMAL},
+     * {@link #ZORDER_TOP}, or {@link #ZORDER_BOTTOM}.
+     * @attr ref android.R.styleable#Animation_zAdjustment
+     */
+    public void setZAdjustment(int zAdjustment) {
+        mZAdjustment = zAdjustment;
+    }
+
+    /**
+     * Set background behind animation.
+     *
+     * @param bg The background color.  If 0, no background.  Currently must
+     * be black, with any desired alpha level.
+     *
+     * @deprecated None of window animations are running with background color.
+     */
+    @Deprecated
+    public void setBackgroundColor(@ColorInt int bg) {
+        // The background color is not needed any more, do nothing.
+    }
+
+    /**
+     * The scale factor is set by the call to <code>getTransformation</code>. Overrides of
+     * {@link #getTransformation(long, Transformation, float)} will get this value
+     * directly. Overrides of {@link #applyTransformation(float, Transformation)} can
+     * call this method to get the value.
+     *
+     * @return float The scale factor that should be applied to pre-scaled values in
+     * an Animation such as the pivot points in {@link ScaleAnimation} and {@link RotateAnimation}.
+     */
+    protected float getScaleFactor() {
+        return mScaleFactor;
+    }
+
+    /**
+     * If detachWallpaper is true, and this is a window animation of a window
+     * that has a wallpaper background, then the window will be detached from
+     * the wallpaper while it runs.  That is, the animation will only be applied
+     * to the window, and the wallpaper behind it will remain static.
+     *
+     * @param detachWallpaper true if the wallpaper should be detached from the animation
+     * @attr ref android.R.styleable#Animation_detachWallpaper
+     *
+     * @deprecated All window animations are running with detached wallpaper.
+     */
+    @Deprecated
+    public void setDetachWallpaper(boolean detachWallpaper) {
+    }
+
+    /**
+     * If this animation is run as a window animation, this will make the wallpaper visible behind
+     * the animation.
+     *
+     * @param showWallpaper Whether the wallpaper should be shown during the animation.
+     * @attr ref android.R.styleable#Animation_detachWallpaper
+     * @hide
+     */
+    public void setShowWallpaper(boolean showWallpaper) {
+        mShowWallpaper = showWallpaper;
+    }
+
+    /**
+     * If this is a window animation, the window will have rounded corners matching the display
+     * corner radius.
+     *
+     * @param hasRoundedCorners Whether the window should have rounded corners or not.
+     * @attr ref android.R.styleable#Animation_hasRoundedCorners
+     * @see com.android.internal.policy.ScreenDecorationsUtils#getWindowCornerRadius(Resources)
+     * @hide
+     */
+    public void setHasRoundedCorners(boolean hasRoundedCorners) {
+        mHasRoundedCorners = hasRoundedCorners;
+    }
+
+    /**
+     * Gets the acceleration curve type for this animation.
+     *
+     * @return the {@link Interpolator} associated to this animation
+     * @attr ref android.R.styleable#Animation_interpolator
+     */
+    public Interpolator getInterpolator() {
+        return mInterpolator;
+    }
+
+    /**
+     * When this animation should start. If the animation has not startet yet,
+     * this method might return {@link #START_ON_FIRST_FRAME}.
+     *
+     * @return the time in milliseconds when the animation should start or
+     *         {@link #START_ON_FIRST_FRAME}
+     */
+    public long getStartTime() {
+        return mStartTime;
+    }
+
+    /**
+     * How long this animation should last
+     *
+     * @return the duration in milliseconds of the animation
+     * @attr ref android.R.styleable#Animation_duration
+     */
+    public long getDuration() {
+        return mDuration;
+    }
+
+    /**
+     * When this animation should start, relative to StartTime
+     *
+     * @return the start offset in milliseconds
+     * @attr ref android.R.styleable#Animation_startOffset
+     */
+    public long getStartOffset() {
+        return mStartOffset;
+    }
+
+    /**
+     * Defines what this animation should do when it reaches the end.
+     *
+     * @return either one of {@link #REVERSE} or {@link #RESTART}
+     * @attr ref android.R.styleable#Animation_repeatMode
+     */
+    public int getRepeatMode() {
+        return mRepeatMode;
+    }
+
+    /**
+     * Defines how many times the animation should repeat. The default value
+     * is 0.
+     *
+     * @return the number of times the animation should repeat, or {@link #INFINITE}
+     * @attr ref android.R.styleable#Animation_repeatCount
+     */
+    public int getRepeatCount() {
+        return mRepeatCount;
+    }
+
+    /**
+     * If fillBefore is true, this animation will apply its transformation
+     * before the start time of the animation. If fillBefore is false and
+     * {@link #isFillEnabled() fillEnabled} is true, the transformation will not be applied until
+     * the start time of the animation.
+     *
+     * @return true if the animation applies its transformation before it starts
+     * @attr ref android.R.styleable#Animation_fillBefore
+     */
+    public boolean getFillBefore() {
+        return mFillBefore;
+    }
+
+    /**
+     * If fillAfter is true, this animation will apply its transformation
+     * after the end time of the animation.
+     *
+     * @return true if the animation applies its transformation after it ends
+     * @attr ref android.R.styleable#Animation_fillAfter
+     */
+    public boolean getFillAfter() {
+        return mFillAfter;
+    }
+
+    /**
+     * Returns the Z ordering mode to use while running the animation as
+     * previously set by {@link #setZAdjustment}.
+     *
+     * @return Returns one of {@link #ZORDER_NORMAL},
+     * {@link #ZORDER_TOP}, or {@link #ZORDER_BOTTOM}.
+     * @attr ref android.R.styleable#Animation_zAdjustment
+     */
+    public int getZAdjustment() {
+        return mZAdjustment;
+    }
+
+    /**
+     * Returns the background color behind the animation.
+     *
+     * @deprecated None of window animations are running with background color.
+     */
+    @Deprecated
+    @ColorInt
+    public int getBackgroundColor() {
+        return 0;
+    }
+
+    /**
+     * Return value of {@link #setDetachWallpaper(boolean)}.
+     * @attr ref android.R.styleable#Animation_detachWallpaper
+     *
+     * @deprecated All window animations are running with detached wallpaper.
+     */
+    @Deprecated
+    public boolean getDetachWallpaper() {
+        return true;
+    }
+
+    /**
+     * @return If run as a window animation, returns whether the wallpaper will be shown behind
+     *         during the animation.
+     * @attr ref android.R.styleable#Animation_showWallpaper
+     * @hide
+     */
+    public boolean getShowWallpaper() {
+        return mShowWallpaper;
+    }
+
+    /**
+     * @return if a window animation should have rounded corners or not.
+     *
+     * @attr ref android.R.styleable#Animation_hasRoundedCorners
+     * @hide
+     */
+    public boolean hasRoundedCorners() {
+        return mHasRoundedCorners;
+    }
+
+    /**
+     * <p>Indicates whether or not this animation will affect the transformation
+     * matrix. For instance, a fade animation will not affect the matrix whereas
+     * a scale animation will.</p>
+     *
+     * @return true if this animation will change the transformation matrix
+     */
+    public boolean willChangeTransformationMatrix() {
+        // assume we will change the matrix
+        return true;
+    }
+
+    /**
+     * <p>Indicates whether or not this animation will affect the bounds of the
+     * animated view. For instance, a fade animation will not affect the bounds
+     * whereas a 200% scale animation will.</p>
+     *
+     * @return true if this animation will change the view's bounds
+     */
+    public boolean willChangeBounds() {
+        // assume we will change the bounds
+        return true;
+    }
+
+    private boolean hasAnimationListener() {
+        return mListener != null;
+    }
+
+    /**
+     * <p>Binds an animation listener to this animation. The animation listener
+     * is notified of animation events such as the end of the animation or the
+     * repetition of the animation.</p>
+     *
+     * @param listener the animation listener to be notified
+     */
+    public void setAnimationListener(AnimationListener listener) {
+        mListener = listener;
+    }
+
+    /**
+     * Gurantees that this animation has an interpolator. Will use
+     * a AccelerateDecelerateInterpolator is nothing else was specified.
+     */
+    protected void ensureInterpolator() {
+        if (mInterpolator == null) {
+            mInterpolator = new AccelerateDecelerateInterpolator();
+        }
+    }
+
+    /**
+     * Compute a hint at how long the entire animation may last, in milliseconds.
+     * Animations can be written to cause themselves to run for a different
+     * duration than what is computed here, but generally this should be
+     * accurate.
+     */
+    public long computeDurationHint() {
+        return (getStartOffset() + getDuration()) * (getRepeatCount() + 1);
+    }
+
+    /**
+     * Gets the transformation to apply at a specified point in time. Implementations of this
+     * method should always replace the specified Transformation or document they are doing
+     * otherwise.
+     *
+     * @param currentTime Where we are in the animation. This is wall clock time.
+     * @param outTransformation A transformation object that is provided by the
+     *        caller and will be filled in by the animation.
+     * @return True if the animation is still running
+     */
+    public boolean getTransformation(long currentTime, Transformation outTransformation) {
+        if (mStartTime == -1) {
+            mStartTime = currentTime;
+        }
+
+        final long startOffset = getStartOffset();
+        final long duration = mDuration;
+        float normalizedTime;
+        if (duration != 0) {
+            normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
+                    (float) duration;
+        } else {
+            // time is a step-change with a zero duration
+            normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
+        }
+
+        final boolean expired = normalizedTime >= 1.0f || isCanceled();
+        mMore = !expired;
+
+        if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
+
+        if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
+            if (!mStarted) {
+                fireAnimationStart();
+                mStarted = true;
+                if (NoImagePreloadHolder.USE_CLOSEGUARD) {
+                    guard.open("cancel or detach or getTransformation");
+                }
+            }
+
+            if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
+
+            if (mCycleFlip) {
+                normalizedTime = 1.0f - normalizedTime;
+            }
+
+            final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
+            applyTransformation(interpolatedTime, outTransformation);
+        }
+
+        if (expired) {
+            if (mRepeatCount == mRepeated || isCanceled()) {
+                if (!mEnded) {
+                    mEnded = true;
+                    guard.close();
+                    fireAnimationEnd();
+                }
+            } else {
+                if (mRepeatCount > 0) {
+                    mRepeated++;
+                }
+
+                if (mRepeatMode == REVERSE) {
+                    mCycleFlip = !mCycleFlip;
+                }
+
+                mStartTime = -1;
+                mMore = true;
+
+                fireAnimationRepeat();
+            }
+        }
+
+        if (!mMore && mOneMoreTime) {
+            mOneMoreTime = false;
+            return true;
+        }
+
+        return mMore;
+    }
+
+    private boolean isCanceled() {
+        return mStartTime == Long.MIN_VALUE;
+    }
+
+    private void fireAnimationStart() {
+        if (hasAnimationListener()) {
+            if (mListenerHandler == null) dispatchAnimationStart();
+            else mListenerHandler.postAtFrontOfQueue(mOnStart);
+        }
+    }
+
+    private void fireAnimationRepeat() {
+        if (hasAnimationListener()) {
+            if (mListenerHandler == null) dispatchAnimationRepeat();
+            else mListenerHandler.postAtFrontOfQueue(mOnRepeat);
+        }
+    }
+
+    private void fireAnimationEnd() {
+        if (hasAnimationListener()) {
+            if (mListenerHandler == null) dispatchAnimationEnd();
+            else mListenerHandler.postAtFrontOfQueue(mOnEnd);
+        }
+    }
+
+    void dispatchAnimationStart() {
+        if (mListener != null) {
+            mListener.onAnimationStart(this);
+        }
+    }
+
+    void dispatchAnimationRepeat() {
+        if (mListener != null) {
+            mListener.onAnimationRepeat(this);
+        }
+    }
+
+    void dispatchAnimationEnd() {
+        if (mListener != null) {
+            mListener.onAnimationEnd(this);
+        }
+    }
+
+    /**
+     * Gets the transformation to apply at a specified point in time. Implementations of this
+     * method should always replace the specified Transformation or document they are doing
+     * otherwise.
+     *
+     * @param currentTime Where we are in the animation. This is wall clock time.
+     * @param outTransformation A transformation object that is provided by the
+     *        caller and will be filled in by the animation.
+     * @param scale Scaling factor to apply to any inputs to the transform operation, such
+     *        pivot points being rotated or scaled around.
+     * @return True if the animation is still running
+     */
+    public boolean getTransformation(long currentTime, Transformation outTransformation,
+            float scale) {
+        mScaleFactor = scale;
+        return getTransformation(currentTime, outTransformation);
+    }
+
+    /**
+     * <p>Indicates whether this animation has started or not.</p>
+     *
+     * @return true if the animation has started, false otherwise
+     */
+    public boolean hasStarted() {
+        return mStarted;
+    }
+
+    /**
+     * <p>Indicates whether this animation has ended or not.</p>
+     *
+     * @return true if the animation has ended, false otherwise
+     */
+    public boolean hasEnded() {
+        return mEnded;
+    }
+
+    /**
+     * Helper for getTransformation. Subclasses should implement this to apply
+     * their transforms given an interpolation value.  Implementations of this
+     * method should always replace the specified Transformation or document
+     * they are doing otherwise.
+     *
+     * @param interpolatedTime The value of the normalized time (0.0 to 1.0)
+     *        after it has been run through the interpolation function.
+     * @param t The Transformation object to fill in with the current
+     *        transforms.
+     */
+    protected void applyTransformation(float interpolatedTime, Transformation t) {
+    }
+
+    /**
+     * Convert the information in the description of a size to an actual
+     * dimension
+     *
+     * @param type One of Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+     *             Animation.RELATIVE_TO_PARENT.
+     * @param value The dimension associated with the type parameter
+     * @param size The size of the object being animated
+     * @param parentSize The size of the parent of the object being animated
+     * @return The dimension to use for the animation
+     */
+    protected float resolveSize(int type, float value, int size, int parentSize) {
+        switch (type) {
+            case ABSOLUTE:
+                return value;
+            case RELATIVE_TO_SELF:
+                return size * value;
+            case RELATIVE_TO_PARENT:
+                return parentSize * value;
+            default:
+                return value;
+        }
+    }
+
+    /**
+     * @param left
+     * @param top
+     * @param right
+     * @param bottom
+     * @param invalidate
+     * @param transformation
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void getInvalidateRegion(int left, int top, int right, int bottom,
+            RectF invalidate, Transformation transformation) {
+
+        final RectF tempRegion = mRegion;
+        final RectF previousRegion = mPreviousRegion;
+
+        invalidate.set(left, top, right, bottom);
+        transformation.getMatrix().mapRect(invalidate);
+        // Enlarge the invalidate region to account for rounding errors
+        invalidate.inset(-1.0f, -1.0f);
+        tempRegion.set(invalidate);
+        invalidate.union(previousRegion);
+
+        previousRegion.set(tempRegion);
+
+        final Transformation tempTransformation = mTransformation;
+        final Transformation previousTransformation = mPreviousTransformation;
+
+        tempTransformation.set(transformation);
+        transformation.set(previousTransformation);
+        previousTransformation.set(tempTransformation);
+    }
+
+    /**
+     * @param left
+     * @param top
+     * @param right
+     * @param bottom
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void initializeInvalidateRegion(int left, int top, int right, int bottom) {
+        final RectF region = mPreviousRegion;
+        region.set(left, top, right, bottom);
+        // Enlarge the invalidate region to account for rounding errors
+        region.inset(-1.0f, -1.0f);
+        if (mFillBefore) {
+            final Transformation previousTransformation = mPreviousTransformation;
+            applyTransformation(mInterpolator.getInterpolation(0.0f), previousTransformation);
+        }
+    }
+
+    protected void finalize() throws Throwable {
+        try {
+            if (guard != null) {
+                guard.warnIfOpen();
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Return true if this animation changes the view's alpha property.
+     *
+     * @hide
+     */
+    public boolean hasAlpha() {
+        return false;
+    }
+
+    /**
+     * Utility class to parse a string description of a size.
+     */
+    protected static class Description {
+        /**
+         * One of Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+         * Animation.RELATIVE_TO_PARENT.
+         */
+        public int type;
+
+        /**
+         * The absolute or relative dimension for this Description.
+         */
+        public float value;
+
+        /**
+         * Size descriptions can appear inthree forms:
+         * <ol>
+         * <li>An absolute size. This is represented by a number.</li>
+         * <li>A size relative to the size of the object being animated. This
+         * is represented by a number followed by "%".</li> *
+         * <li>A size relative to the size of the parent of object being
+         * animated. This is represented by a number followed by "%p".</li>
+         * </ol>
+         * @param value The typed value to parse
+         * @return The parsed version of the description
+         */
+        static Description parseValue(TypedValue value) {
+            Description d = new Description();
+            if (value == null) {
+                d.type = ABSOLUTE;
+                d.value = 0;
+            } else {
+                if (value.type == TypedValue.TYPE_FRACTION) {
+                    d.type = (value.data & TypedValue.COMPLEX_UNIT_MASK) ==
+                            TypedValue.COMPLEX_UNIT_FRACTION_PARENT ?
+                                    RELATIVE_TO_PARENT : RELATIVE_TO_SELF;
+                    d.value = TypedValue.complexToFloat(value.data);
+                    return d;
+                } else if (value.type == TypedValue.TYPE_FLOAT) {
+                    d.type = ABSOLUTE;
+                    d.value = value.getFloat();
+                    return d;
+                } else if (value.type >= TypedValue.TYPE_FIRST_INT &&
+                        value.type <= TypedValue.TYPE_LAST_INT) {
+                    d.type = ABSOLUTE;
+                    d.value = value.data;
+                    return d;
+                }
+            }
+
+            d.type = ABSOLUTE;
+            d.value = 0.0f;
+
+            return d;
+        }
+    }
+
+    /**
+     * <p>An animation listener receives notifications from an animation.
+     * Notifications indicate animation related events, such as the end or the
+     * repetition of the animation.</p>
+     */
+    public static interface AnimationListener {
+        /**
+         * <p>Notifies the start of the animation.</p>
+         *
+         * @param animation The started animation.
+         */
+        void onAnimationStart(Animation animation);
+
+        /**
+         * <p>Notifies the end of the animation. This callback is not invoked
+         * for animations with repeat count set to INFINITE.</p>
+         *
+         * @param animation The animation which reached its end.
+         */
+        void onAnimationEnd(Animation animation);
+
+        /**
+         * <p>Notifies the repetition of the animation.</p>
+         *
+         * @param animation The animation which was repeated.
+         */
+        void onAnimationRepeat(Animation animation);
+    }
+}
diff --git a/android/view/animation/AnimationSet.java b/android/view/animation/AnimationSet.java
new file mode 100644
index 0000000..03c6ca6
--- /dev/null
+++ b/android/view/animation/AnimationSet.java
@@ -0,0 +1,520 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.animation;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.RectF;
+import android.os.Build;
+import android.util.AttributeSet;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a group of Animations that should be played together.
+ * The transformation of each individual animation are composed
+ * together into a single transform.
+ * If AnimationSet sets any properties that its children also set
+ * (for example, duration or fillBefore), the values of AnimationSet
+ * override the child values.
+ *
+ * <p>The way that AnimationSet inherits behavior from Animation is important to
+ * understand. Some of the Animation attributes applied to AnimationSet affect the
+ * AnimationSet itself, some are pushed down to the children, and some are ignored,
+ * as follows:
+ * <ul>
+ *     <li>duration, repeatMode, fillBefore, fillAfter: These properties, when set
+ *     on an AnimationSet object, will be pushed down to all child animations.</li>
+ *     <li>repeatCount, fillEnabled: These properties are ignored for AnimationSet.</li>
+ *     <li>startOffset, shareInterpolator: These properties apply to the AnimationSet itself.</li>
+ * </ul>
+ * Starting with {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH},
+ * the behavior of these properties is the same in XML resources and at runtime (prior to that
+ * release, the values set in XML were ignored for AnimationSet). That is, calling
+ * <code>setDuration(500)</code> on an AnimationSet has the same effect as declaring
+ * <code>android:duration="500"</code> in an XML resource for an AnimationSet object.</p>
+ */
+public class AnimationSet extends Animation {
+    private static final int PROPERTY_FILL_AFTER_MASK         = 0x1;
+    private static final int PROPERTY_FILL_BEFORE_MASK        = 0x2;
+    private static final int PROPERTY_REPEAT_MODE_MASK        = 0x4;
+    private static final int PROPERTY_START_OFFSET_MASK       = 0x8;
+    private static final int PROPERTY_SHARE_INTERPOLATOR_MASK = 0x10;
+    private static final int PROPERTY_DURATION_MASK           = 0x20;
+    private static final int PROPERTY_MORPH_MATRIX_MASK       = 0x40;
+    private static final int PROPERTY_CHANGE_BOUNDS_MASK      = 0x80;
+
+    private int mFlags = 0;
+    private boolean mDirty;
+    private boolean mHasAlpha;
+
+    private ArrayList<Animation> mAnimations = new ArrayList<Animation>();
+
+    private Transformation mTempTransformation = new Transformation();
+
+    private long mLastEnd;
+
+    private long[] mStoredOffsets;
+
+    /**
+     * Constructor used when an AnimationSet is loaded from a resource.
+     *
+     * @param context Application context to use
+     * @param attrs Attribute set from which to read values
+     */
+    public AnimationSet(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray a =
+            context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AnimationSet);
+
+        setFlag(PROPERTY_SHARE_INTERPOLATOR_MASK,
+                a.getBoolean(com.android.internal.R.styleable.AnimationSet_shareInterpolator, true));
+        init();
+
+        if (context.getApplicationInfo().targetSdkVersion >=
+                Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+            if (a.hasValue(com.android.internal.R.styleable.AnimationSet_duration)) {
+                mFlags |= PROPERTY_DURATION_MASK;
+            }
+            if (a.hasValue(com.android.internal.R.styleable.AnimationSet_fillBefore)) {
+                mFlags |= PROPERTY_FILL_BEFORE_MASK;
+            }
+            if (a.hasValue(com.android.internal.R.styleable.AnimationSet_fillAfter)) {
+                mFlags |= PROPERTY_FILL_AFTER_MASK;
+            }
+            if (a.hasValue(com.android.internal.R.styleable.AnimationSet_repeatMode)) {
+                mFlags |= PROPERTY_REPEAT_MODE_MASK;
+            }
+            if (a.hasValue(com.android.internal.R.styleable.AnimationSet_startOffset)) {
+                mFlags |= PROPERTY_START_OFFSET_MASK;
+            }
+        }
+
+        a.recycle();
+    }
+
+
+    /**
+     * Constructor to use when building an AnimationSet from code
+     *
+     * @param shareInterpolator Pass true if all of the animations in this set
+     *        should use the interpolator associated with this AnimationSet.
+     *        Pass false if each animation should use its own interpolator.
+     */
+    public AnimationSet(boolean shareInterpolator) {
+        setFlag(PROPERTY_SHARE_INTERPOLATOR_MASK, shareInterpolator);
+        init();
+    }
+
+    @Override
+    protected AnimationSet clone() throws CloneNotSupportedException {
+        final AnimationSet animation = (AnimationSet) super.clone();
+        animation.mTempTransformation = new Transformation();
+        animation.mAnimations = new ArrayList<Animation>();
+
+        final int count = mAnimations.size();
+        final ArrayList<Animation> animations = mAnimations;
+
+        for (int i = 0; i < count; i++) {
+            animation.mAnimations.add(animations.get(i).clone());
+        }
+
+        return animation;
+    }
+
+    private void setFlag(int mask, boolean value) {
+        if (value) {
+            mFlags |= mask;
+        } else {
+            mFlags &= ~mask;
+        }
+    }
+
+    private void init() {
+        mStartTime = 0;
+    }
+
+    @Override
+    public void setFillAfter(boolean fillAfter) {
+        mFlags |= PROPERTY_FILL_AFTER_MASK;
+        super.setFillAfter(fillAfter);
+    }
+
+    @Override
+    public void setFillBefore(boolean fillBefore) {
+        mFlags |= PROPERTY_FILL_BEFORE_MASK;
+        super.setFillBefore(fillBefore);
+    }
+
+    @Override
+    public void setRepeatMode(int repeatMode) {
+        mFlags |= PROPERTY_REPEAT_MODE_MASK;
+        super.setRepeatMode(repeatMode);
+    }
+
+    @Override
+    public void setStartOffset(long startOffset) {
+        mFlags |= PROPERTY_START_OFFSET_MASK;
+        super.setStartOffset(startOffset);
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public boolean hasAlpha() {
+        if (mDirty) {
+            mDirty = mHasAlpha = false;
+
+            final int count = mAnimations.size();
+            final ArrayList<Animation> animations = mAnimations;
+
+            for (int i = 0; i < count; i++) {
+                if (animations.get(i).hasAlpha()) {
+                    mHasAlpha = true;
+                    break;
+                }
+            }
+        }
+
+        return mHasAlpha;
+    }
+
+    /**
+     * <p>Sets the duration of every child animation.</p>
+     *
+     * @param durationMillis the duration of the animation, in milliseconds, for
+     *        every child in this set
+     */
+    @Override
+    public void setDuration(long durationMillis) {
+        mFlags |= PROPERTY_DURATION_MASK;
+        super.setDuration(durationMillis);
+        mLastEnd = mStartOffset + mDuration;
+    }
+
+    /**
+     * Add a child animation to this animation set.
+     * The transforms of the child animations are applied in the order
+     * that they were added
+     * @param a Animation to add.
+     */
+    public void addAnimation(Animation a) {
+        mAnimations.add(a);
+
+        boolean noMatrix = (mFlags & PROPERTY_MORPH_MATRIX_MASK) == 0;
+        if (noMatrix && a.willChangeTransformationMatrix()) {
+            mFlags |= PROPERTY_MORPH_MATRIX_MASK;
+        }
+
+        boolean changeBounds = (mFlags & PROPERTY_CHANGE_BOUNDS_MASK) == 0;
+
+
+        if (changeBounds && a.willChangeBounds()) {
+            mFlags |= PROPERTY_CHANGE_BOUNDS_MASK;
+        }
+
+        if ((mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK) {
+            mLastEnd = mStartOffset + mDuration;
+        } else {
+            if (mAnimations.size() == 1) {
+                mDuration = a.getStartOffset() + a.getDuration();
+                mLastEnd = mStartOffset + mDuration;
+            } else {
+                mLastEnd = Math.max(mLastEnd, mStartOffset + a.getStartOffset() + a.getDuration());
+                mDuration = mLastEnd - mStartOffset;
+            }
+        }
+
+        mDirty = true;
+    }
+
+    /**
+     * Sets the start time of this animation and all child animations
+     *
+     * @see android.view.animation.Animation#setStartTime(long)
+     */
+    @Override
+    public void setStartTime(long startTimeMillis) {
+        super.setStartTime(startTimeMillis);
+
+        final int count = mAnimations.size();
+        final ArrayList<Animation> animations = mAnimations;
+
+        for (int i = 0; i < count; i++) {
+            Animation a = animations.get(i);
+            a.setStartTime(startTimeMillis);
+        }
+    }
+
+    @Override
+    public long getStartTime() {
+        long startTime = Long.MAX_VALUE;
+
+        final int count = mAnimations.size();
+        final ArrayList<Animation> animations = mAnimations;
+
+        for (int i = 0; i < count; i++) {
+            Animation a = animations.get(i);
+            startTime = Math.min(startTime, a.getStartTime());
+        }
+
+        return startTime;
+    }
+
+    @Override
+    public void restrictDuration(long durationMillis) {
+        super.restrictDuration(durationMillis);
+
+        final ArrayList<Animation> animations = mAnimations;
+        int count = animations.size();
+
+        for (int i = 0; i < count; i++) {
+            animations.get(i).restrictDuration(durationMillis);
+        }
+    }
+
+    /**
+     * The duration of an AnimationSet is defined to be the
+     * duration of the longest child animation.
+     *
+     * @see android.view.animation.Animation#getDuration()
+     */
+    @Override
+    public long getDuration() {
+        final ArrayList<Animation> animations = mAnimations;
+        final int count = animations.size();
+        long duration = 0;
+
+        boolean durationSet = (mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK;
+        if (durationSet) {
+            duration = mDuration;
+        } else {
+            for (int i = 0; i < count; i++) {
+                duration = Math.max(duration, animations.get(i).getDuration());
+            }
+        }
+
+        return duration;
+    }
+
+    /**
+     * The duration hint of an animation set is the maximum of the duration
+     * hints of all of its component animations.
+     *
+     * @see android.view.animation.Animation#computeDurationHint
+     */
+    public long computeDurationHint() {
+        long duration = 0;
+        final int count = mAnimations.size();
+        final ArrayList<Animation> animations = mAnimations;
+        for (int i = count - 1; i >= 0; --i) {
+            final long d = animations.get(i).computeDurationHint();
+            if (d > duration) duration = d;
+        }
+        return duration;
+    }
+
+    /**
+     * @hide
+     */
+    public void initializeInvalidateRegion(int left, int top, int right, int bottom) {
+        final RectF region = mPreviousRegion;
+        region.set(left, top, right, bottom);
+        region.inset(-1.0f, -1.0f);
+
+        if (mFillBefore) {
+            final int count = mAnimations.size();
+            final ArrayList<Animation> animations = mAnimations;
+            final Transformation temp = mTempTransformation;
+
+            final Transformation previousTransformation = mPreviousTransformation;
+
+            for (int i = count - 1; i >= 0; --i) {
+                final Animation a = animations.get(i);
+                if (!a.isFillEnabled() || a.getFillBefore() || a.getStartOffset() == 0) {
+                    temp.clear();
+                    final Interpolator interpolator = a.mInterpolator;
+                    a.applyTransformation(interpolator != null ? interpolator.getInterpolation(0.0f)
+                            : 0.0f, temp);
+                    previousTransformation.compose(temp);
+                }
+            }
+        }
+    }
+
+    /**
+     * The transformation of an animation set is the concatenation of all of its
+     * component animations.
+     *
+     * @see android.view.animation.Animation#getTransformation
+     */
+    @Override
+    public boolean getTransformation(long currentTime, Transformation t) {
+        final int count = mAnimations.size();
+        final ArrayList<Animation> animations = mAnimations;
+        final Transformation temp = mTempTransformation;
+
+        boolean more = false;
+        boolean started = false;
+        boolean ended = true;
+
+        t.clear();
+
+        for (int i = count - 1; i >= 0; --i) {
+            final Animation a = animations.get(i);
+
+            temp.clear();
+            more = a.getTransformation(currentTime, temp, getScaleFactor()) || more;
+            t.compose(temp);
+
+            started = started || a.hasStarted();
+            ended = a.hasEnded() && ended;
+        }
+
+        if (started && !mStarted) {
+            dispatchAnimationStart();
+            mStarted = true;
+        }
+
+        if (ended != mEnded) {
+            dispatchAnimationEnd();
+            mEnded = ended;
+        }
+
+        return more;
+    }
+
+    /**
+     * @see android.view.animation.Animation#scaleCurrentDuration(float)
+     */
+    @Override
+    public void scaleCurrentDuration(float scale) {
+        final ArrayList<Animation> animations = mAnimations;
+        int count = animations.size();
+        for (int i = 0; i < count; i++) {
+            animations.get(i).scaleCurrentDuration(scale);
+        }
+    }
+
+    /**
+     * @see android.view.animation.Animation#initialize(int, int, int, int)
+     */
+    @Override
+    public void initialize(int width, int height, int parentWidth, int parentHeight) {
+        super.initialize(width, height, parentWidth, parentHeight);
+
+        boolean durationSet = (mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK;
+        boolean fillAfterSet = (mFlags & PROPERTY_FILL_AFTER_MASK) == PROPERTY_FILL_AFTER_MASK;
+        boolean fillBeforeSet = (mFlags & PROPERTY_FILL_BEFORE_MASK) == PROPERTY_FILL_BEFORE_MASK;
+        boolean repeatModeSet = (mFlags & PROPERTY_REPEAT_MODE_MASK) == PROPERTY_REPEAT_MODE_MASK;
+        boolean shareInterpolator = (mFlags & PROPERTY_SHARE_INTERPOLATOR_MASK)
+                == PROPERTY_SHARE_INTERPOLATOR_MASK;
+        boolean startOffsetSet = (mFlags & PROPERTY_START_OFFSET_MASK)
+                == PROPERTY_START_OFFSET_MASK;
+
+        if (shareInterpolator) {
+            ensureInterpolator();
+        }
+
+        final ArrayList<Animation> children = mAnimations;
+        final int count = children.size();
+
+        final long duration = mDuration;
+        final boolean fillAfter = mFillAfter;
+        final boolean fillBefore = mFillBefore;
+        final int repeatMode = mRepeatMode;
+        final Interpolator interpolator = mInterpolator;
+        final long startOffset = mStartOffset;
+
+
+        long[] storedOffsets = mStoredOffsets;
+        if (startOffsetSet) {
+            if (storedOffsets == null || storedOffsets.length != count) {
+                storedOffsets = mStoredOffsets = new long[count];
+            }
+        } else if (storedOffsets != null) {
+            storedOffsets = mStoredOffsets = null;
+        }
+
+        for (int i = 0; i < count; i++) {
+            Animation a = children.get(i);
+            if (durationSet) {
+                a.setDuration(duration);
+            }
+            if (fillAfterSet) {
+                a.setFillAfter(fillAfter);
+            }
+            if (fillBeforeSet) {
+                a.setFillBefore(fillBefore);
+            }
+            if (repeatModeSet) {
+                a.setRepeatMode(repeatMode);
+            }
+            if (shareInterpolator) {
+                a.setInterpolator(interpolator);
+            }
+            if (startOffsetSet) {
+                long offset = a.getStartOffset();
+                a.setStartOffset(offset + startOffset);
+                storedOffsets[i] = offset;
+            }
+            a.initialize(width, height, parentWidth, parentHeight);
+        }
+    }
+
+    @Override
+    public void reset() {
+        super.reset();
+        restoreChildrenStartOffset();
+    }
+
+    /**
+     * @hide
+     */
+    void restoreChildrenStartOffset() {
+        final long[] offsets = mStoredOffsets;
+        if (offsets == null) return;
+
+        final ArrayList<Animation> children = mAnimations;
+        final int count = children.size();
+
+        for (int i = 0; i < count; i++) {
+            children.get(i).setStartOffset(offsets[i]);
+        }
+    }
+
+    /**
+     * @return All the child animations in this AnimationSet. Note that
+     * this may include other AnimationSets, which are not expanded.
+     */
+    public List<Animation> getAnimations() {
+        return mAnimations;
+    }
+
+    @Override
+    public boolean willChangeTransformationMatrix() {
+        return (mFlags & PROPERTY_MORPH_MATRIX_MASK) == PROPERTY_MORPH_MATRIX_MASK;
+    }
+
+    @Override
+    public boolean willChangeBounds() {
+        return (mFlags & PROPERTY_CHANGE_BOUNDS_MASK) == PROPERTY_CHANGE_BOUNDS_MASK;
+    }
+}
diff --git a/android/view/animation/AnimationUtils.java b/android/view/animation/AnimationUtils.java
new file mode 100644
index 0000000..7ce0f45
--- /dev/null
+++ b/android/view/animation/AnimationUtils.java
@@ -0,0 +1,436 @@
+/*
+ * 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.view.animation;
+
+import android.annotation.AnimRes;
+import android.annotation.InterpolatorRes;
+import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.content.res.Resources.Theme;
+import android.content.res.XmlResourceParser;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * Defines common utilities for working with animations.
+ *
+ */
+public class AnimationUtils {
+
+    /**
+     * These flags are used when parsing AnimatorSet objects
+     */
+    private static final int TOGETHER = 0;
+    private static final int SEQUENTIALLY = 1;
+
+    private static class AnimationState {
+        boolean animationClockLocked;
+        long currentVsyncTimeMillis;
+        long lastReportedTimeMillis;
+    };
+
+    private static ThreadLocal<AnimationState> sAnimationState
+            = new ThreadLocal<AnimationState>() {
+        @Override
+        protected AnimationState initialValue() {
+            return new AnimationState();
+        }
+    };
+
+    /**
+     * Locks AnimationUtils{@link #currentAnimationTimeMillis()} to a fixed value for the current
+     * thread. This is used by {@link android.view.Choreographer} to ensure that all accesses
+     * during a vsync update are synchronized to the timestamp of the vsync.
+     *
+     * It is also exposed to tests to allow for rapid, flake-free headless testing.
+     *
+     * Must be followed by a call to {@link #unlockAnimationClock()} to allow time to
+     * progress. Failing to do this will result in stuck animations, scrolls, and flings.
+     *
+     * Note that time is not allowed to "rewind" and must perpetually flow forward. So the
+     * lock may fail if the time is in the past from a previously returned value, however
+     * time will be frozen for the duration of the lock. The clock is a thread-local, so
+     * ensure that {@link #lockAnimationClock(long)}, {@link #unlockAnimationClock()}, and
+     * {@link #currentAnimationTimeMillis()} are all called on the same thread.
+     *
+     * This is also not reference counted in any way. Any call to {@link #unlockAnimationClock()}
+     * will unlock the clock for everyone on the same thread. It is therefore recommended
+     * for tests to use their own thread to ensure that there is no collision with any existing
+     * {@link android.view.Choreographer} instance.
+     *
+     * @hide
+     * */
+    @TestApi
+    public static void lockAnimationClock(long vsyncMillis) {
+        AnimationState state = sAnimationState.get();
+        state.animationClockLocked = true;
+        state.currentVsyncTimeMillis = vsyncMillis;
+    }
+
+    /**
+     * Frees the time lock set in place by {@link #lockAnimationClock(long)}. Must be called
+     * to allow the animation clock to self-update.
+     *
+     * @hide
+     */
+    @TestApi
+    public static void unlockAnimationClock() {
+        sAnimationState.get().animationClockLocked = false;
+    }
+
+    /**
+     * Returns the current animation time in milliseconds. This time should be used when invoking
+     * {@link Animation#setStartTime(long)}. Refer to {@link android.os.SystemClock} for more
+     * information about the different available clocks. The clock used by this method is
+     * <em>not</em> the "wall" clock (it is not {@link System#currentTimeMillis}).
+     *
+     * @return the current animation time in milliseconds
+     *
+     * @see android.os.SystemClock
+     */
+    public static long currentAnimationTimeMillis() {
+        AnimationState state = sAnimationState.get();
+        if (state.animationClockLocked) {
+            // It's important that time never rewinds
+            return Math.max(state.currentVsyncTimeMillis,
+                    state.lastReportedTimeMillis);
+        }
+        state.lastReportedTimeMillis = SystemClock.uptimeMillis();
+        return state.lastReportedTimeMillis;
+    }
+
+    /**
+     * Loads an {@link Animation} object from a resource
+     *
+     * @param context Application context used to access resources
+     * @param id The resource id of the animation to load
+     * @return The animation object referenced by the specified id
+     * @throws NotFoundException when the animation cannot be loaded
+     */
+    public static Animation loadAnimation(Context context, @AnimRes int id)
+            throws NotFoundException {
+
+        XmlResourceParser parser = null;
+        try {
+            parser = context.getResources().getAnimation(id);
+            return createAnimationFromXml(context, parser);
+        } catch (XmlPullParserException ex) {
+            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
+                    Integer.toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
+        } catch (IOException ex) {
+            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
+                    Integer.toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
+        } finally {
+            if (parser != null) parser.close();
+        }
+    }
+
+    private static Animation createAnimationFromXml(Context c, XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+
+        return createAnimationFromXml(c, parser, null, Xml.asAttributeSet(parser));
+    }
+
+    @UnsupportedAppUsage
+    private static Animation createAnimationFromXml(Context c, XmlPullParser parser,
+            AnimationSet parent, AttributeSet attrs) throws XmlPullParserException, IOException {
+
+        Animation anim = null;
+
+        // Make sure we are on a start tag.
+        int type;
+        int depth = parser.getDepth();
+
+        while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+               && type != XmlPullParser.END_DOCUMENT) {
+
+            if (type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            String  name = parser.getName();
+
+            if (name.equals("set")) {
+                anim = new AnimationSet(c, attrs);
+                createAnimationFromXml(c, parser, (AnimationSet)anim, attrs);
+            } else if (name.equals("alpha")) {
+                anim = new AlphaAnimation(c, attrs);
+            } else if (name.equals("scale")) {
+                anim = new ScaleAnimation(c, attrs);
+            }  else if (name.equals("rotate")) {
+                anim = new RotateAnimation(c, attrs);
+            }  else if (name.equals("translate")) {
+                anim = new TranslateAnimation(c, attrs);
+            } else if (name.equals("cliprect")) {
+                anim = new ClipRectAnimation(c, attrs);
+            } else {
+                throw new RuntimeException("Unknown animation name: " + parser.getName());
+            }
+
+            if (parent != null) {
+                parent.addAnimation(anim);
+            }
+        }
+
+        return anim;
+
+    }
+
+    /**
+     * Loads a {@link LayoutAnimationController} object from a resource
+     *
+     * @param context Application context used to access resources
+     * @param id The resource id of the animation to load
+     * @return The animation controller object referenced by the specified id
+     * @throws NotFoundException when the layout animation controller cannot be loaded
+     */
+    public static LayoutAnimationController loadLayoutAnimation(Context context, @AnimRes int id)
+            throws NotFoundException {
+
+        XmlResourceParser parser = null;
+        try {
+            parser = context.getResources().getAnimation(id);
+            return createLayoutAnimationFromXml(context, parser);
+        } catch (XmlPullParserException ex) {
+            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
+                    Integer.toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
+        } catch (IOException ex) {
+            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
+                    Integer.toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
+        } finally {
+            if (parser != null) parser.close();
+        }
+    }
+
+    private static LayoutAnimationController createLayoutAnimationFromXml(Context c,
+            XmlPullParser parser) throws XmlPullParserException, IOException {
+
+        return createLayoutAnimationFromXml(c, parser, Xml.asAttributeSet(parser));
+    }
+
+    private static LayoutAnimationController createLayoutAnimationFromXml(Context c,
+            XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException {
+
+        LayoutAnimationController controller = null;
+
+        int type;
+        int depth = parser.getDepth();
+
+        while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+                && type != XmlPullParser.END_DOCUMENT) {
+
+            if (type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            String name = parser.getName();
+
+            if ("layoutAnimation".equals(name)) {
+                controller = new LayoutAnimationController(c, attrs);
+            } else if ("gridLayoutAnimation".equals(name)) {
+                controller = new GridLayoutAnimationController(c, attrs);
+            } else {
+                throw new RuntimeException("Unknown layout animation name: " + name);
+            }
+        }
+
+        return controller;
+    }
+
+    /**
+     * Make an animation for objects becoming visible. Uses a slide and fade
+     * effect.
+     *
+     * @param c Context for loading resources
+     * @param fromLeft is the object to be animated coming from the left
+     * @return The new animation
+     */
+    public static Animation makeInAnimation(Context c, boolean fromLeft) {
+        Animation a;
+        if (fromLeft) {
+            a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_in_left);
+        } else {
+            a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_in_right);
+        }
+
+        a.setInterpolator(new DecelerateInterpolator());
+        a.setStartTime(currentAnimationTimeMillis());
+        return a;
+    }
+
+    /**
+     * Make an animation for objects becoming invisible. Uses a slide and fade
+     * effect.
+     *
+     * @param c Context for loading resources
+     * @param toRight is the object to be animated exiting to the right
+     * @return The new animation
+     */
+    public static Animation makeOutAnimation(Context c, boolean toRight) {
+        Animation a;
+        if (toRight) {
+            a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_out_right);
+        } else {
+            a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_out_left);
+        }
+
+        a.setInterpolator(new AccelerateInterpolator());
+        a.setStartTime(currentAnimationTimeMillis());
+        return a;
+    }
+
+
+    /**
+     * Make an animation for objects becoming visible. Uses a slide up and fade
+     * effect.
+     *
+     * @param c Context for loading resources
+     * @return The new animation
+     */
+    public static Animation makeInChildBottomAnimation(Context c) {
+        Animation a;
+        a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_in_child_bottom);
+        a.setInterpolator(new AccelerateInterpolator());
+        a.setStartTime(currentAnimationTimeMillis());
+        return a;
+    }
+
+    /**
+     * Loads an {@link Interpolator} object from a resource
+     *
+     * @param context Application context used to access resources
+     * @param id The resource id of the animation to load
+     * @return The interpolator object referenced by the specified id
+     * @throws NotFoundException
+     */
+    public static Interpolator loadInterpolator(Context context, @AnimRes @InterpolatorRes int id)
+            throws NotFoundException {
+        XmlResourceParser parser = null;
+        try {
+            parser = context.getResources().getAnimation(id);
+            return createInterpolatorFromXml(context.getResources(), context.getTheme(), parser);
+        } catch (XmlPullParserException ex) {
+            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
+                    Integer.toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
+        } catch (IOException ex) {
+            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
+                    Integer.toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
+        } finally {
+            if (parser != null) parser.close();
+        }
+
+    }
+
+    /**
+     * Loads an {@link Interpolator} object from a resource
+     *
+     * @param res The resources
+     * @param id The resource id of the animation to load
+     * @return The interpolator object referenced by the specified id
+     * @throws NotFoundException
+     * @hide
+     */
+    public static Interpolator loadInterpolator(Resources res, Theme theme, int id) throws NotFoundException {
+        XmlResourceParser parser = null;
+        try {
+            parser = res.getAnimation(id);
+            return createInterpolatorFromXml(res, theme, parser);
+        } catch (XmlPullParserException ex) {
+            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
+                    Integer.toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
+        } catch (IOException ex) {
+            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
+                    Integer.toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
+        } finally {
+            if (parser != null)
+                parser.close();
+        }
+
+    }
+
+    private static Interpolator createInterpolatorFromXml(Resources res, Theme theme, XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+
+        BaseInterpolator interpolator = null;
+
+        // Make sure we are on a start tag.
+        int type;
+        int depth = parser.getDepth();
+
+        while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+                && type != XmlPullParser.END_DOCUMENT) {
+
+            if (type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            AttributeSet attrs = Xml.asAttributeSet(parser);
+
+            String name = parser.getName();
+
+            if (name.equals("linearInterpolator")) {
+                interpolator = new LinearInterpolator();
+            } else if (name.equals("accelerateInterpolator")) {
+                interpolator = new AccelerateInterpolator(res, theme, attrs);
+            } else if (name.equals("decelerateInterpolator")) {
+                interpolator = new DecelerateInterpolator(res, theme, attrs);
+            } else if (name.equals("accelerateDecelerateInterpolator")) {
+                interpolator = new AccelerateDecelerateInterpolator();
+            } else if (name.equals("cycleInterpolator")) {
+                interpolator = new CycleInterpolator(res, theme, attrs);
+            } else if (name.equals("anticipateInterpolator")) {
+                interpolator = new AnticipateInterpolator(res, theme, attrs);
+            } else if (name.equals("overshootInterpolator")) {
+                interpolator = new OvershootInterpolator(res, theme, attrs);
+            } else if (name.equals("anticipateOvershootInterpolator")) {
+                interpolator = new AnticipateOvershootInterpolator(res, theme, attrs);
+            } else if (name.equals("bounceInterpolator")) {
+                interpolator = new BounceInterpolator();
+            } else if (name.equals("pathInterpolator")) {
+                interpolator = new PathInterpolator(res, theme, attrs);
+            } else {
+                throw new RuntimeException("Unknown interpolator name: " + parser.getName());
+            }
+        }
+        return interpolator;
+    }
+}
diff --git a/android/view/animation/AnticipateInterpolator.java b/android/view/animation/AnticipateInterpolator.java
new file mode 100644
index 0000000..d146394
--- /dev/null
+++ b/android/view/animation/AnticipateInterpolator.java
@@ -0,0 +1,78 @@
+/*
+ * 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.view.animation;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
+import android.util.AttributeSet;
+
+import com.android.internal.R;
+
+/**
+ * An interpolator where the change starts backward then flings forward.
+ */
+@HasNativeInterpolator
+public class AnticipateInterpolator extends BaseInterpolator implements NativeInterpolator {
+    private final float mTension;
+
+    public AnticipateInterpolator() {
+        mTension = 2.0f;
+    }
+
+    /**
+     * @param tension Amount of anticipation. When tension equals 0.0f, there is
+     *                no anticipation and the interpolator becomes a simple
+     *                acceleration interpolator.
+     */
+    public AnticipateInterpolator(float tension) {
+        mTension = tension;
+    }
+
+    public AnticipateInterpolator(Context context, AttributeSet attrs) {
+        this(context.getResources(), context.getTheme(), attrs);
+    }
+
+    /** @hide */
+    public AnticipateInterpolator(Resources res, Theme theme, AttributeSet attrs) {
+        TypedArray a;
+        if (theme != null) {
+            a = theme.obtainStyledAttributes(attrs, R.styleable.AnticipateInterpolator, 0, 0);
+        } else {
+            a = res.obtainAttributes(attrs, R.styleable.AnticipateInterpolator);
+        }
+
+        mTension = a.getFloat(R.styleable.AnticipateInterpolator_tension, 2.0f);
+        setChangingConfiguration(a.getChangingConfigurations());
+        a.recycle();
+    }
+
+    public float getInterpolation(float t) {
+        // a(t) = t * t * ((tension + 1) * t - tension)
+        return t * t * ((mTension + 1) * t - mTension);
+    }
+
+    /** @hide */
+    @Override
+    public long createNativeInterpolator() {
+        return NativeInterpolatorFactory.createAnticipateInterpolator(mTension);
+    }
+}
diff --git a/android/view/animation/AnticipateOvershootInterpolator.java b/android/view/animation/AnticipateOvershootInterpolator.java
new file mode 100644
index 0000000..4d6a390
--- /dev/null
+++ b/android/view/animation/AnticipateOvershootInterpolator.java
@@ -0,0 +1,108 @@
+/*
+ * 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.view.animation;
+
+import static com.android.internal.R.styleable.AnticipateOvershootInterpolator;
+import static com.android.internal.R.styleable.AnticipateOvershootInterpolator_extraTension;
+import static com.android.internal.R.styleable.AnticipateOvershootInterpolator_tension;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
+import android.util.AttributeSet;
+
+
+/**
+ * An interpolator where the change starts backward then flings forward and overshoots
+ * the target value and finally goes back to the final value.
+ */
+@HasNativeInterpolator
+public class AnticipateOvershootInterpolator extends BaseInterpolator
+        implements NativeInterpolator {
+    private final float mTension;
+
+    public AnticipateOvershootInterpolator() {
+        mTension = 2.0f * 1.5f;
+    }
+
+    /**
+     * @param tension Amount of anticipation/overshoot. When tension equals 0.0f,
+     *                there is no anticipation/overshoot and the interpolator becomes
+     *                a simple acceleration/deceleration interpolator.
+     */
+    public AnticipateOvershootInterpolator(float tension) {
+        mTension = tension * 1.5f;
+    }
+
+    /**
+     * @param tension Amount of anticipation/overshoot. When tension equals 0.0f,
+     *                there is no anticipation/overshoot and the interpolator becomes
+     *                a simple acceleration/deceleration interpolator.
+     * @param extraTension Amount by which to multiply the tension. For instance,
+     *                     to get the same overshoot as an OvershootInterpolator with
+     *                     a tension of 2.0f, you would use an extraTension of 1.5f.
+     */
+    public AnticipateOvershootInterpolator(float tension, float extraTension) {
+        mTension = tension * extraTension;
+    }
+
+    public AnticipateOvershootInterpolator(Context context, AttributeSet attrs) {
+        this(context.getResources(), context.getTheme(), attrs);
+    }
+
+    /** @hide */
+    public AnticipateOvershootInterpolator(Resources res, Theme theme, AttributeSet attrs) {
+        TypedArray a;
+        if (theme != null) {
+            a = theme.obtainStyledAttributes(attrs, AnticipateOvershootInterpolator, 0, 0);
+        } else {
+            a = res.obtainAttributes(attrs, AnticipateOvershootInterpolator);
+        }
+
+        mTension = a.getFloat(AnticipateOvershootInterpolator_tension, 2.0f) *
+                a.getFloat(AnticipateOvershootInterpolator_extraTension, 1.5f);
+        setChangingConfiguration(a.getChangingConfigurations());
+        a.recycle();
+    }
+
+    private static float a(float t, float s) {
+        return t * t * ((s + 1) * t - s);
+    }
+
+    private static float o(float t, float s) {
+        return t * t * ((s + 1) * t + s);
+    }
+
+    public float getInterpolation(float t) {
+        // a(t, s) = t * t * ((s + 1) * t - s)
+        // o(t, s) = t * t * ((s + 1) * t + s)
+        // f(t) = 0.5 * a(t * 2, tension * extraTension), when t < 0.5
+        // f(t) = 0.5 * (o(t * 2 - 2, tension * extraTension) + 2), when t <= 1.0
+        if (t < 0.5f) return 0.5f * a(t * 2.0f, mTension);
+        else return 0.5f * (o(t * 2.0f - 2.0f, mTension) + 2.0f);
+    }
+
+    /** @hide */
+    @Override
+    public long createNativeInterpolator() {
+        return NativeInterpolatorFactory.createAnticipateOvershootInterpolator(mTension);
+    }
+}
diff --git a/android/view/animation/BaseInterpolator.java b/android/view/animation/BaseInterpolator.java
new file mode 100644
index 0000000..a78fa1e
--- /dev/null
+++ b/android/view/animation/BaseInterpolator.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.animation;
+
+import android.content.pm.ActivityInfo.Config;
+
+/**
+ * An abstract class which is extended by default interpolators.
+ */
+abstract public class BaseInterpolator implements Interpolator {
+    private @Config int mChangingConfiguration;
+    /**
+     * @hide
+     */
+    public @Config int getChangingConfiguration() {
+        return mChangingConfiguration;
+    }
+
+    /**
+     * @hide
+     */
+    void setChangingConfiguration(@Config int changingConfiguration) {
+        mChangingConfiguration = changingConfiguration;
+    }
+}
diff --git a/android/view/animation/BounceInterpolator.java b/android/view/animation/BounceInterpolator.java
new file mode 100644
index 0000000..d3f6a3f
--- /dev/null
+++ b/android/view/animation/BounceInterpolator.java
@@ -0,0 +1,60 @@
+/*
+ * 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.view.animation;
+
+import android.content.Context;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
+import android.util.AttributeSet;
+
+/**
+ * An interpolator where the change bounces at the end.
+ */
+@HasNativeInterpolator
+public class BounceInterpolator extends BaseInterpolator implements NativeInterpolator {
+    public BounceInterpolator() {
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    public BounceInterpolator(Context context, AttributeSet attrs) {
+    }
+
+    private static float bounce(float t) {
+        return t * t * 8.0f;
+    }
+
+    public float getInterpolation(float t) {
+        // _b(t) = t * t * 8
+        // bs(t) = _b(t) for t < 0.3535
+        // bs(t) = _b(t - 0.54719) + 0.7 for t < 0.7408
+        // bs(t) = _b(t - 0.8526) + 0.9 for t < 0.9644
+        // bs(t) = _b(t - 1.0435) + 0.95 for t <= 1.0
+        // b(t) = bs(t * 1.1226)
+        t *= 1.1226f;
+        if (t < 0.3535f) return bounce(t);
+        else if (t < 0.7408f) return bounce(t - 0.54719f) + 0.7f;
+        else if (t < 0.9644f) return bounce(t - 0.8526f) + 0.9f;
+        else return bounce(t - 1.0435f) + 0.95f;
+    }
+
+    /** @hide */
+    @Override
+    public long createNativeInterpolator() {
+        return NativeInterpolatorFactory.createBounceInterpolator();
+    }
+}
\ No newline at end of file
diff --git a/android/view/animation/ClipRectAnimation.java b/android/view/animation/ClipRectAnimation.java
new file mode 100644
index 0000000..21509d3
--- /dev/null
+++ b/android/view/animation/ClipRectAnimation.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.animation;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+
+/**
+ * An animation that controls the clip of an object. See the
+ * {@link android.view.animation full package} description for details and
+ * sample code.
+ *
+ * @hide
+ */
+public class ClipRectAnimation extends Animation {
+    protected final Rect mFromRect = new Rect();
+    protected final Rect mToRect = new Rect();
+
+    private int mFromLeftType = ABSOLUTE;
+    private int mFromTopType = ABSOLUTE;
+    private int mFromRightType = ABSOLUTE;
+    private int mFromBottomType = ABSOLUTE;
+
+    private int mToLeftType = ABSOLUTE;
+    private int mToTopType = ABSOLUTE;
+    private int mToRightType = ABSOLUTE;
+    private int mToBottomType = ABSOLUTE;
+
+    private float mFromLeftValue;
+    private float mFromTopValue;
+    private float mFromRightValue;
+    private float mFromBottomValue;
+
+    private float mToLeftValue;
+    private float mToTopValue;
+    private float mToRightValue;
+    private float mToBottomValue;
+
+    /**
+     * Constructor used when a ClipRectAnimation is loaded from a resource.
+     *
+     * @param context Application context to use
+     * @param attrs Attribute set from which to read values
+     */
+    public ClipRectAnimation(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.ClipRectAnimation);
+
+        Description d = Description.parseValue(a.peekValue(
+                com.android.internal.R.styleable.ClipRectAnimation_fromLeft));
+        mFromLeftType = d.type;
+        mFromLeftValue = d.value;
+
+        d = Description.parseValue(a.peekValue(
+                com.android.internal.R.styleable.ClipRectAnimation_fromTop));
+        mFromTopType = d.type;
+        mFromTopValue = d.value;
+
+        d = Description.parseValue(a.peekValue(
+                com.android.internal.R.styleable.ClipRectAnimation_fromRight));
+        mFromRightType = d.type;
+        mFromRightValue = d.value;
+
+        d = Description.parseValue(a.peekValue(
+                com.android.internal.R.styleable.ClipRectAnimation_fromBottom));
+        mFromBottomType = d.type;
+        mFromBottomValue = d.value;
+
+
+        d = Description.parseValue(a.peekValue(
+                com.android.internal.R.styleable.ClipRectAnimation_toLeft));
+        mToLeftType = d.type;
+        mToLeftValue = d.value;
+
+        d = Description.parseValue(a.peekValue(
+                com.android.internal.R.styleable.ClipRectAnimation_toTop));
+        mToTopType = d.type;
+        mToTopValue = d.value;
+
+        d = Description.parseValue(a.peekValue(
+                com.android.internal.R.styleable.ClipRectAnimation_toRight));
+        mToRightType = d.type;
+        mToRightValue = d.value;
+
+        d = Description.parseValue(a.peekValue(
+                com.android.internal.R.styleable.ClipRectAnimation_toBottom));
+        mToBottomType = d.type;
+        mToBottomValue = d.value;
+
+        a.recycle();
+    }
+
+    /**
+     * Constructor to use when building a ClipRectAnimation from code
+     *
+     * @param fromClip the clip rect to animate from
+     * @param toClip the clip rect to animate to
+     */
+    public ClipRectAnimation(Rect fromClip, Rect toClip) {
+        if (fromClip == null || toClip == null) {
+            throw new RuntimeException("Expected non-null animation clip rects");
+        }
+        mFromLeftValue = fromClip.left;
+        mFromTopValue = fromClip.top;
+        mFromRightValue= fromClip.right;
+        mFromBottomValue = fromClip.bottom;
+
+        mToLeftValue = toClip.left;
+        mToTopValue = toClip.top;
+        mToRightValue= toClip.right;
+        mToBottomValue = toClip.bottom;
+    }
+
+    /**
+     * Constructor to use when building a ClipRectAnimation from code
+     */
+    public ClipRectAnimation(int fromL, int fromT, int fromR, int fromB,
+            int toL, int toT, int toR, int toB) {
+        this(new Rect(fromL, fromT, fromR, fromB), new Rect(toL, toT, toR, toB));
+    }
+
+    @Override
+    protected void applyTransformation(float it, Transformation tr) {
+        int l = mFromRect.left + (int) ((mToRect.left - mFromRect.left) * it);
+        int t = mFromRect.top + (int) ((mToRect.top - mFromRect.top) * it);
+        int r = mFromRect.right + (int) ((mToRect.right - mFromRect.right) * it);
+        int b = mFromRect.bottom + (int) ((mToRect.bottom - mFromRect.bottom) * it);
+        tr.setClipRect(l, t, r, b);
+    }
+
+    @Override
+    public boolean willChangeTransformationMatrix() {
+        return false;
+    }
+
+    @Override
+    public void initialize(int width, int height, int parentWidth, int parentHeight) {
+        super.initialize(width, height, parentWidth, parentHeight);
+        mFromRect.set((int) resolveSize(mFromLeftType, mFromLeftValue, width, parentWidth),
+                (int) resolveSize(mFromTopType, mFromTopValue, height, parentHeight),
+                (int) resolveSize(mFromRightType, mFromRightValue, width, parentWidth),
+                (int) resolveSize(mFromBottomType, mFromBottomValue, height, parentHeight));
+        mToRect.set((int) resolveSize(mToLeftType, mToLeftValue, width, parentWidth),
+                (int) resolveSize(mToTopType, mToTopValue, height, parentHeight),
+                (int) resolveSize(mToRightType, mToRightValue, width, parentWidth),
+                (int) resolveSize(mToBottomType, mToBottomValue, height, parentHeight));
+    }
+}
diff --git a/android/view/animation/CycleInterpolator.java b/android/view/animation/CycleInterpolator.java
new file mode 100644
index 0000000..6b1a80a
--- /dev/null
+++ b/android/view/animation/CycleInterpolator.java
@@ -0,0 +1,70 @@
+/*
+ * 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.view.animation;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
+import android.util.AttributeSet;
+
+import com.android.internal.R;
+
+/**
+ * Repeats the animation for a specified number of cycles. The
+ * rate of change follows a sinusoidal pattern.
+ *
+ */
+@HasNativeInterpolator
+public class CycleInterpolator extends BaseInterpolator implements NativeInterpolator {
+    public CycleInterpolator(float cycles) {
+        mCycles = cycles;
+    }
+
+    public CycleInterpolator(Context context, AttributeSet attrs) {
+        this(context.getResources(), context.getTheme(), attrs);
+    }
+
+    /** @hide */
+    public CycleInterpolator(Resources resources, Theme theme, AttributeSet attrs) {
+        TypedArray a;
+        if (theme != null) {
+            a = theme.obtainStyledAttributes(attrs, R.styleable.CycleInterpolator, 0, 0);
+        } else {
+            a = resources.obtainAttributes(attrs, R.styleable.CycleInterpolator);
+        }
+
+        mCycles = a.getFloat(R.styleable.CycleInterpolator_cycles, 1.0f);
+        setChangingConfiguration(a.getChangingConfigurations());
+        a.recycle();
+    }
+
+    public float getInterpolation(float input) {
+        return (float)(Math.sin(2 * mCycles * Math.PI * input));
+    }
+
+    private float mCycles;
+
+    /** @hide */
+    @Override
+    public long createNativeInterpolator() {
+        return NativeInterpolatorFactory.createCycleInterpolator(mCycles);
+    }
+}
diff --git a/android/view/animation/DecelerateInterpolator.java b/android/view/animation/DecelerateInterpolator.java
new file mode 100644
index 0000000..2d2f770
--- /dev/null
+++ b/android/view/animation/DecelerateInterpolator.java
@@ -0,0 +1,86 @@
+/*
+ * 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.view.animation;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
+import android.util.AttributeSet;
+
+import com.android.internal.R;
+
+/**
+ * An interpolator where the rate of change starts out quickly and
+ * and then decelerates.
+ *
+ */
+@HasNativeInterpolator
+public class DecelerateInterpolator extends BaseInterpolator implements NativeInterpolator {
+    public DecelerateInterpolator() {
+    }
+
+    /**
+     * Constructor
+     *
+     * @param factor Degree to which the animation should be eased. Setting factor to 1.0f produces
+     *        an upside-down y=x^2 parabola. Increasing factor above 1.0f exaggerates the
+     *        ease-out effect (i.e., it starts even faster and ends evens slower).
+     */
+    public DecelerateInterpolator(float factor) {
+        mFactor = factor;
+    }
+
+    public DecelerateInterpolator(Context context, AttributeSet attrs) {
+        this(context.getResources(), context.getTheme(), attrs);
+    }
+
+    /** @hide */
+    public DecelerateInterpolator(Resources res, Theme theme, AttributeSet attrs) {
+        TypedArray a;
+        if (theme != null) {
+            a = theme.obtainStyledAttributes(attrs, R.styleable.DecelerateInterpolator, 0, 0);
+        } else {
+            a = res.obtainAttributes(attrs, R.styleable.DecelerateInterpolator);
+        }
+
+        mFactor = a.getFloat(R.styleable.DecelerateInterpolator_factor, 1.0f);
+        setChangingConfiguration(a.getChangingConfigurations());
+        a.recycle();
+    }
+
+    public float getInterpolation(float input) {
+        float result;
+        if (mFactor == 1.0f) {
+            result = (float)(1.0f - (1.0f - input) * (1.0f - input));
+        } else {
+            result = (float)(1.0f - Math.pow((1.0f - input), 2 * mFactor));
+        }
+        return result;
+    }
+
+    private float mFactor = 1.0f;
+
+    /** @hide */
+    @Override
+    public long createNativeInterpolator() {
+        return NativeInterpolatorFactory.createDecelerateInterpolator(mFactor);
+    }
+}
diff --git a/android/view/animation/GridLayoutAnimationController.java b/android/view/animation/GridLayoutAnimationController.java
new file mode 100644
index 0000000..0f189ae
--- /dev/null
+++ b/android/view/animation/GridLayoutAnimationController.java
@@ -0,0 +1,424 @@
+/*
+ * 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.view.animation;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.Random;
+
+/**
+ * A layout animation controller is used to animated a grid layout's children.
+ *
+ * While {@link LayoutAnimationController} relies only on the index of the child
+ * in the view group to compute the animation delay, this class uses both the
+ * X and Y coordinates of the child within a grid.
+ *
+ * In addition, the animation direction can be controlled. The default direction
+ * is <code>DIRECTION_LEFT_TO_RIGHT | DIRECTION_TOP_TO_BOTTOM</code>. You can
+ * also set the animation priority to columns or rows. The default priority is
+ * none.
+ *
+ * Information used to compute the animation delay of each child are stored
+ * in an instance of
+ * {@link android.view.animation.GridLayoutAnimationController.AnimationParameters},
+ * itself stored in the {@link android.view.ViewGroup.LayoutParams} of the view.
+ *
+ * @see LayoutAnimationController
+ * @see android.widget.GridView
+ *
+ * @attr ref android.R.styleable#GridLayoutAnimation_columnDelay
+ * @attr ref android.R.styleable#GridLayoutAnimation_rowDelay
+ * @attr ref android.R.styleable#GridLayoutAnimation_direction
+ * @attr ref android.R.styleable#GridLayoutAnimation_directionPriority
+ */
+public class GridLayoutAnimationController extends LayoutAnimationController {
+    /**
+     * Animates the children starting from the left of the grid to the right.
+     */
+    public static final int DIRECTION_LEFT_TO_RIGHT = 0x0;
+
+    /**
+     * Animates the children starting from the right of the grid to the left.
+     */
+    public static final int DIRECTION_RIGHT_TO_LEFT = 0x1;
+
+    /**
+     * Animates the children starting from the top of the grid to the bottom.
+     */
+    public static final int DIRECTION_TOP_TO_BOTTOM = 0x0;
+
+    /**
+     * Animates the children starting from the bottom of the grid to the top.
+     */
+    public static final int DIRECTION_BOTTOM_TO_TOP = 0x2;
+
+    /**
+     * Bitmask used to retrieve the horizontal component of the direction.
+     */
+    public static final int DIRECTION_HORIZONTAL_MASK = 0x1;
+
+    /**
+     * Bitmask used to retrieve the vertical component of the direction.
+     */
+    public static final int DIRECTION_VERTICAL_MASK   = 0x2;
+
+    /**
+     * Rows and columns are animated at the same time.
+     */
+    public static final int PRIORITY_NONE   = 0;
+
+    /**
+     * Columns are animated first.
+     */
+    public static final int PRIORITY_COLUMN = 1;
+
+    /**
+     * Rows are animated first.
+     */
+    public static final int PRIORITY_ROW    = 2;
+
+    private float mColumnDelay;
+    private float mRowDelay;
+
+    private int mDirection;
+    private int mDirectionPriority;
+
+    /**
+     * Creates a new grid layout animation controller from external resources.
+     *
+     * @param context the Context the view  group is running in, through which
+     *        it can access the resources
+     * @param attrs the attributes of the XML tag that is inflating the
+     *        layout animation controller
+     */
+    public GridLayoutAnimationController(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.GridLayoutAnimation);
+
+        Animation.Description d = Animation.Description.parseValue(
+                a.peekValue(com.android.internal.R.styleable.GridLayoutAnimation_columnDelay));
+        mColumnDelay = d.value;
+        d = Animation.Description.parseValue(
+                a.peekValue(com.android.internal.R.styleable.GridLayoutAnimation_rowDelay));
+        mRowDelay = d.value;
+        //noinspection PointlessBitwiseExpression
+        mDirection = a.getInt(com.android.internal.R.styleable.GridLayoutAnimation_direction,
+                DIRECTION_LEFT_TO_RIGHT | DIRECTION_TOP_TO_BOTTOM);
+        mDirectionPriority = a.getInt(com.android.internal.R.styleable.GridLayoutAnimation_directionPriority,
+                PRIORITY_NONE);
+
+        a.recycle();
+    }
+
+    /**
+     * Creates a new layout animation controller with a delay of 50%
+     * for both rows and columns and the specified animation.
+     *
+     * @param animation the animation to use on each child of the view group
+     */
+    public GridLayoutAnimationController(Animation animation) {
+        this(animation, 0.5f, 0.5f);
+    }
+
+    /**
+     * Creates a new layout animation controller with the specified delays
+     * and the specified animation.
+     *
+     * @param animation the animation to use on each child of the view group
+     * @param columnDelay the delay by which each column animation must be offset
+     * @param rowDelay the delay by which each row animation must be offset
+     */
+    public GridLayoutAnimationController(Animation animation, float columnDelay, float rowDelay) {
+        super(animation);
+        mColumnDelay = columnDelay;
+        mRowDelay = rowDelay;
+    }
+
+    /**
+     * Returns the delay by which the children's animation are offset from one
+     * column to the other. The delay is expressed as a fraction of the
+     * animation duration.
+     *
+     * @return a fraction of the animation duration
+     *
+     * @see #setColumnDelay(float)
+     * @see #getRowDelay()
+     * @see #setRowDelay(float)
+     */
+    public float getColumnDelay() {
+        return mColumnDelay;
+    }
+
+    /**
+     * Sets the delay, as a fraction of the animation duration, by which the
+     * children's animations are offset from one column to the other.
+     *
+     * @param columnDelay a fraction of the animation duration
+     *
+     * @see #getColumnDelay()
+     * @see #getRowDelay()
+     * @see #setRowDelay(float)
+     */
+    public void setColumnDelay(float columnDelay) {
+        mColumnDelay = columnDelay;
+    }
+
+    /**
+     * Returns the delay by which the children's animation are offset from one
+     * row to the other. The delay is expressed as a fraction of the
+     * animation duration.
+     *
+     * @return a fraction of the animation duration
+     *
+     * @see #setRowDelay(float)
+     * @see #getColumnDelay()
+     * @see #setColumnDelay(float)
+     */
+    public float getRowDelay() {
+        return mRowDelay;
+    }
+
+    /**
+     * Sets the delay, as a fraction of the animation duration, by which the
+     * children's animations are offset from one row to the other.
+     *
+     * @param rowDelay a fraction of the animation duration
+     *
+     * @see #getRowDelay()
+     * @see #getColumnDelay()
+     * @see #setColumnDelay(float)
+     */
+    public void setRowDelay(float rowDelay) {
+        mRowDelay = rowDelay;
+    }
+
+    /**
+     * Returns the direction of the animation. {@link #DIRECTION_HORIZONTAL_MASK}
+     * and {@link #DIRECTION_VERTICAL_MASK} can be used to retrieve the
+     * horizontal and vertical components of the direction.
+     *
+     * @return the direction of the animation
+     *
+     * @see #setDirection(int)
+     * @see #DIRECTION_BOTTOM_TO_TOP
+     * @see #DIRECTION_TOP_TO_BOTTOM
+     * @see #DIRECTION_LEFT_TO_RIGHT
+     * @see #DIRECTION_RIGHT_TO_LEFT
+     * @see #DIRECTION_HORIZONTAL_MASK
+     * @see #DIRECTION_VERTICAL_MASK
+     */
+    public int getDirection() {
+        return mDirection;
+    }
+
+    /**
+     * Sets the direction of the animation. The direction is expressed as an
+     * integer containing a horizontal and vertical component. For instance,
+     * <code>DIRECTION_BOTTOM_TO_TOP | DIRECTION_RIGHT_TO_LEFT</code>.
+     *
+     * @param direction the direction of the animation
+     *
+     * @see #getDirection()
+     * @see #DIRECTION_BOTTOM_TO_TOP
+     * @see #DIRECTION_TOP_TO_BOTTOM
+     * @see #DIRECTION_LEFT_TO_RIGHT
+     * @see #DIRECTION_RIGHT_TO_LEFT
+     * @see #DIRECTION_HORIZONTAL_MASK
+     * @see #DIRECTION_VERTICAL_MASK
+     */
+    public void setDirection(int direction) {
+        mDirection = direction;
+    }
+
+    /**
+     * Returns the direction priority for the animation. The priority can
+     * be either {@link #PRIORITY_NONE}, {@link #PRIORITY_COLUMN} or
+     * {@link #PRIORITY_ROW}.
+     *
+     * @return the priority of the animation direction
+     *
+     * @see #setDirectionPriority(int)
+     * @see #PRIORITY_COLUMN
+     * @see #PRIORITY_NONE
+     * @see #PRIORITY_ROW
+     */
+    public int getDirectionPriority() {
+        return mDirectionPriority;
+    }
+
+    /**
+     * Specifies the direction priority of the animation. For instance,
+     * {@link #PRIORITY_COLUMN} will give priority to columns: the animation
+     * will first play on the column, then on the rows.Z
+     *
+     * @param directionPriority the direction priority of the animation
+     *
+     * @see #getDirectionPriority()
+     * @see #PRIORITY_COLUMN
+     * @see #PRIORITY_NONE
+     * @see #PRIORITY_ROW
+     */
+    public void setDirectionPriority(int directionPriority) {
+        mDirectionPriority = directionPriority;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean willOverlap() {
+        return mColumnDelay < 1.0f || mRowDelay < 1.0f;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected long getDelayForView(View view) {
+        ViewGroup.LayoutParams lp = view.getLayoutParams();
+        AnimationParameters params = (AnimationParameters) lp.layoutAnimationParameters;
+
+        if (params == null) {
+            return 0;
+        }
+
+        final int column = getTransformedColumnIndex(params);
+        final int row = getTransformedRowIndex(params);
+
+        final int rowsCount = params.rowsCount;
+        final int columnsCount = params.columnsCount;
+
+        final long duration = mAnimation.getDuration();
+        final float columnDelay = mColumnDelay * duration;
+        final float rowDelay = mRowDelay * duration;
+
+        float totalDelay;
+        long viewDelay;
+
+        if (mInterpolator == null) {
+            mInterpolator = new LinearInterpolator();
+        }
+
+        switch (mDirectionPriority) {
+            case PRIORITY_COLUMN:
+                viewDelay = (long) (row * rowDelay + column * rowsCount * rowDelay);
+                totalDelay = rowsCount * rowDelay + columnsCount * rowsCount * rowDelay;
+                break;
+            case PRIORITY_ROW:
+                viewDelay = (long) (column * columnDelay + row * columnsCount * columnDelay);
+                totalDelay = columnsCount * columnDelay + rowsCount * columnsCount * columnDelay;
+                break;
+            case PRIORITY_NONE:
+            default:
+                viewDelay = (long) (column * columnDelay + row * rowDelay);
+                totalDelay = columnsCount * columnDelay + rowsCount * rowDelay;
+                break;
+        }
+
+        float normalizedDelay = viewDelay / totalDelay;
+        normalizedDelay = mInterpolator.getInterpolation(normalizedDelay);
+
+        return (long) (normalizedDelay * totalDelay);
+    }
+
+    private int getTransformedColumnIndex(AnimationParameters params) {
+        int index;
+        switch (getOrder()) {
+            case ORDER_REVERSE:
+                index = params.columnsCount - 1 - params.column;
+                break;
+            case ORDER_RANDOM:
+                if (mRandomizer == null) {
+                    mRandomizer = new Random();
+                }
+                index = (int) (params.columnsCount * mRandomizer.nextFloat());
+                break;
+            case ORDER_NORMAL:
+            default:
+                index = params.column;
+                break;
+        }
+
+        int direction = mDirection & DIRECTION_HORIZONTAL_MASK;
+        if (direction == DIRECTION_RIGHT_TO_LEFT) {
+            index = params.columnsCount - 1 - index;
+        }
+
+        return index;
+    }
+
+    private int getTransformedRowIndex(AnimationParameters params) {
+        int index;
+        switch (getOrder()) {
+            case ORDER_REVERSE:
+                index = params.rowsCount - 1 - params.row;
+                break;
+            case ORDER_RANDOM:
+                if (mRandomizer == null) {
+                    mRandomizer = new Random();
+                }
+                index = (int) (params.rowsCount * mRandomizer.nextFloat());
+                break;
+            case ORDER_NORMAL:
+            default:
+                index = params.row;
+                break;
+        }
+
+        int direction = mDirection & DIRECTION_VERTICAL_MASK;
+        if (direction == DIRECTION_BOTTOM_TO_TOP) {
+            index = params.rowsCount - 1 - index;
+        }
+
+        return index;
+    }
+
+    /**
+     * The set of parameters that has to be attached to each view contained in
+     * the view group animated by the grid layout animation controller. These
+     * parameters are used to compute the start time of each individual view's
+     * animation.
+     */
+    public static class AnimationParameters extends
+            LayoutAnimationController.AnimationParameters {
+        /**
+         * The view group's column to which the view belongs.
+         */
+        public int column;
+
+        /**
+         * The view group's row to which the view belongs.
+         */
+        public int row;
+
+        /**
+         * The number of columns in the view's enclosing grid layout.
+         */
+        public int columnsCount;
+
+        /**
+         * The number of rows in the view's enclosing grid layout.
+         */
+        public int rowsCount;
+    }
+}
diff --git a/android/view/animation/Interpolator.java b/android/view/animation/Interpolator.java
new file mode 100644
index 0000000..5d0fe7e
--- /dev/null
+++ b/android/view/animation/Interpolator.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.animation;
+
+import android.animation.TimeInterpolator;
+
+/**
+ * An interpolator defines the rate of change of an animation. This allows
+ * the basic animation effects (alpha, scale, translate, rotate) to be 
+ * accelerated, decelerated, repeated, etc.
+ */
+public interface Interpolator extends TimeInterpolator {
+    // A new interface, TimeInterpolator, was introduced for the new android.animation
+    // package. This older Interpolator interface extends TimeInterpolator so that users of
+    // the new Animator-based animations can use either the old Interpolator implementations or
+    // new classes that implement TimeInterpolator directly.
+}
diff --git a/android/view/animation/LayoutAnimationController.java b/android/view/animation/LayoutAnimationController.java
new file mode 100644
index 0000000..e2b7519
--- /dev/null
+++ b/android/view/animation/LayoutAnimationController.java
@@ -0,0 +1,437 @@
+/*
+ * 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.view.animation;
+
+import android.annotation.AnimRes;
+import android.annotation.InterpolatorRes;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.Random;
+
+/**
+ * A layout animation controller is used to animate the children of a layout or a view
+ * group. Each child uses the same animation but for every one of
+ * them, the animation starts at a different time. A layout animation controller
+ * is used by {@link android.view.ViewGroup} to compute the delay by which each
+ * child's animation start must be offset. The delay is computed by using
+ * characteristics of each child, like its index in the view group.
+ *
+ * This standard implementation computes the delay by multiplying a fixed
+ * amount of miliseconds by the index of the child in its parent view group.
+ * Subclasses are supposed to override
+ * {@link #getDelayForView(android.view.View)} to implement a different way
+ * of computing the delay. For instance, a
+ * {@link android.view.animation.GridLayoutAnimationController} will compute the
+ * delay based on the column and row indices of the child in its parent view
+ * group.
+ *
+ * Information used to compute the animation delay of each child are stored
+ * in an instance of
+ * {@link android.view.animation.LayoutAnimationController.AnimationParameters},
+ * itself stored in the {@link android.view.ViewGroup.LayoutParams} of the view.
+ *
+ * @attr ref android.R.styleable#LayoutAnimation_delay
+ * @attr ref android.R.styleable#LayoutAnimation_animationOrder
+ * @attr ref android.R.styleable#LayoutAnimation_interpolator
+ * @attr ref android.R.styleable#LayoutAnimation_animation
+ */
+public class LayoutAnimationController {
+    /**
+     * Distributes the animation delays in the order in which view were added
+     * to their view group.
+     */
+    public static final int ORDER_NORMAL  = 0;
+
+    /**
+     * Distributes the animation delays in the reverse order in which view were
+     * added to their view group.
+     */
+    public static final int ORDER_REVERSE = 1;
+
+    /**
+     * Randomly distributes the animation delays.
+     */
+    public static final int ORDER_RANDOM  = 2;
+
+    /**
+     * The animation applied on each child of the view group on which this
+     * layout animation controller is set.
+     */
+    protected Animation mAnimation;
+
+    /**
+     * The randomizer used when the order is set to random. Subclasses should
+     * use this object to avoid creating their own.
+     */
+    protected Random mRandomizer;
+
+    /**
+     * The interpolator used to interpolate the delays.
+     */
+    protected Interpolator mInterpolator;
+
+    private float mDelay;
+    private int mOrder;
+
+    private long mDuration;
+    private long mMaxDelay;    
+
+    /**
+     * Creates a new layout animation controller from external resources.
+     *
+     * @param context the Context the view  group is running in, through which
+     *        it can access the resources
+     * @param attrs the attributes of the XML tag that is inflating the
+     *        layout animation controller
+     */
+    public LayoutAnimationController(Context context, AttributeSet attrs) {
+        TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LayoutAnimation);
+
+        Animation.Description d = Animation.Description.parseValue(
+                a.peekValue(com.android.internal.R.styleable.LayoutAnimation_delay));
+        mDelay = d.value;
+
+        mOrder = a.getInt(com.android.internal.R.styleable.LayoutAnimation_animationOrder, ORDER_NORMAL);
+
+        int resource = a.getResourceId(com.android.internal.R.styleable.LayoutAnimation_animation, 0);
+        if (resource > 0) {
+            setAnimation(context, resource);
+        }
+
+        resource = a.getResourceId(com.android.internal.R.styleable.LayoutAnimation_interpolator, 0);
+        if (resource > 0) {
+            setInterpolator(context, resource);
+        }
+
+        a.recycle();
+    }
+
+    /**
+     * Creates a new layout animation controller with a delay of 50%
+     * and the specified animation.
+     *
+     * @param animation the animation to use on each child of the view group
+     */
+    public LayoutAnimationController(Animation animation) {
+        this(animation, 0.5f);
+    }
+
+    /**
+     * Creates a new layout animation controller with the specified delay
+     * and the specified animation.
+     *
+     * @param animation the animation to use on each child of the view group
+     * @param delay the delay by which each child's animation must be offset
+     */
+    public LayoutAnimationController(Animation animation, float delay) {
+        mDelay = delay;
+        setAnimation(animation);
+    }
+
+    /**
+     * Returns the order used to compute the delay of each child's animation.
+     *
+     * @return one of {@link #ORDER_NORMAL}, {@link #ORDER_REVERSE} or
+     *         {@link #ORDER_RANDOM}
+     *
+     * @attr ref android.R.styleable#LayoutAnimation_animationOrder
+     */
+    public int getOrder() {
+        return mOrder;
+    }
+
+    /**
+     * Sets the order used to compute the delay of each child's animation.
+     *
+     * @param order one of {@link #ORDER_NORMAL}, {@link #ORDER_REVERSE} or
+     *        {@link #ORDER_RANDOM}
+     *
+     * @attr ref android.R.styleable#LayoutAnimation_animationOrder
+     */
+    public void setOrder(int order) {
+        mOrder = order;
+    }
+
+    /**
+     * Sets the animation to be run on each child of the view group on which
+     * this layout animation controller is .
+     *
+     * @param context the context from which the animation must be inflated
+     * @param resourceID the resource identifier of the animation
+     *
+     * @see #setAnimation(Animation)
+     * @see #getAnimation() 
+     *
+     * @attr ref android.R.styleable#LayoutAnimation_animation
+     */
+    public void setAnimation(Context context, @AnimRes int resourceID) {
+        setAnimation(AnimationUtils.loadAnimation(context, resourceID));
+    }
+
+    /**
+     * Sets the animation to be run on each child of the view group on which
+     * this layout animation controller is .
+     *
+     * @param animation the animation to run on each child of the view group
+
+     * @see #setAnimation(android.content.Context, int)
+     * @see #getAnimation()
+     *
+     * @attr ref android.R.styleable#LayoutAnimation_animation
+     */
+    public void setAnimation(Animation animation) {
+        mAnimation = animation;
+        mAnimation.setFillBefore(true);
+    }
+
+    /**
+     * Returns the animation applied to each child of the view group on which
+     * this controller is set.
+     *
+     * @return an {@link android.view.animation.Animation} instance
+     *
+     * @see #setAnimation(android.content.Context, int)
+     * @see #setAnimation(Animation)
+     */
+    public Animation getAnimation() {
+        return mAnimation;
+    }
+
+    /**
+     * Sets the interpolator used to interpolate the delays between the
+     * children.
+     *
+     * @param context the context from which the interpolator must be inflated
+     * @param resourceID the resource identifier of the interpolator
+     *
+     * @see #getInterpolator()
+     * @see #setInterpolator(Interpolator)
+     *
+     * @attr ref android.R.styleable#LayoutAnimation_interpolator
+     */
+    public void setInterpolator(Context context, @InterpolatorRes int resourceID) {
+        setInterpolator(AnimationUtils.loadInterpolator(context, resourceID));
+    }
+
+    /**
+     * Sets the interpolator used to interpolate the delays between the
+     * children.
+     *
+     * @param interpolator the interpolator
+     *
+     * @see #getInterpolator()
+     * @see #setInterpolator(Interpolator)
+     *
+     * @attr ref android.R.styleable#LayoutAnimation_interpolator
+     */
+    public void setInterpolator(Interpolator interpolator) {
+        mInterpolator = interpolator;
+    }
+
+    /**
+     * Returns the interpolator used to interpolate the delays between the
+     * children.
+     *
+     * @return an {@link android.view.animation.Interpolator}
+     */
+    public Interpolator getInterpolator() {
+        return mInterpolator;
+    }
+
+    /**
+     * Returns the delay by which the children's animation are offset. The
+     * delay is expressed as a fraction of the animation duration.
+     *
+     * @return a fraction of the animation duration
+     *
+     * @see #setDelay(float)
+     */
+    public float getDelay() {
+        return mDelay;
+    }
+
+    /**
+     * Sets the delay, as a fraction of the animation duration, by which the
+     * children's animations are offset. The general formula is:
+     *
+     * <pre>
+     * child animation delay = child index * delay * animation duration
+     * </pre>
+     *
+     * @param delay a fraction of the animation duration
+     *
+     * @see #getDelay()
+     */
+    public void setDelay(float delay) {
+        mDelay = delay;
+    }
+
+    /**
+     * Indicates whether two children's animations will overlap. Animations
+     * overlap when the delay is lower than 100% (or 1.0).
+     *
+     * @return true if animations will overlap, false otherwise
+     */
+    public boolean willOverlap() {
+        return mDelay < 1.0f;
+    }
+
+    /**
+     * Starts the animation.
+     */
+    public void start() {
+        mDuration = mAnimation.getDuration();
+        mMaxDelay = Long.MIN_VALUE;
+        mAnimation.setStartTime(-1);
+    }
+
+    /**
+     * Returns the animation to be applied to the specified view. The returned
+     * animation is delayed by an offset computed according to the information
+     * provided by
+     * {@link android.view.animation.LayoutAnimationController.AnimationParameters}.
+     * This method is called by view groups to obtain the animation to set on
+     * a specific child.
+     *
+     * @param view the view to animate
+     * @return an animation delayed by the number of milliseconds returned by
+     *         {@link #getDelayForView(android.view.View)}
+     *
+     * @see #getDelay()
+     * @see #setDelay(float)
+     * @see #getDelayForView(android.view.View)
+     */
+    public final Animation getAnimationForView(View view) {
+        final long delay = getDelayForView(view) + mAnimation.getStartOffset();
+        mMaxDelay = Math.max(mMaxDelay, delay);
+
+        try {
+            final Animation animation = mAnimation.clone();
+            animation.setStartOffset(delay);
+            return animation;
+        } catch (CloneNotSupportedException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Indicates whether the layout animation is over or not. A layout animation
+     * is considered done when the animation with the longest delay is done.
+     *
+     * @return true if all of the children's animations are over, false otherwise
+     */
+    public boolean isDone() {
+        return AnimationUtils.currentAnimationTimeMillis() >
+                mAnimation.getStartTime() + mMaxDelay + mDuration;
+    }
+
+    /**
+     * Returns the amount of milliseconds by which the specified view's
+     * animation must be delayed or offset. Subclasses should override this
+     * method to return a suitable value.
+     *
+     * This implementation returns <code>child animation delay</code>
+     * milliseconds where:
+     *
+     * <pre>
+     * child animation delay = child index * delay
+     * </pre>
+     *
+     * The index is retrieved from the
+     * {@link android.view.animation.LayoutAnimationController.AnimationParameters}
+     * found in the view's {@link android.view.ViewGroup.LayoutParams}.
+     *
+     * @param view the view for which to obtain the animation's delay
+     * @return a delay in milliseconds
+     *
+     * @see #getAnimationForView(android.view.View)
+     * @see #getDelay()
+     * @see #getTransformedIndex(android.view.animation.LayoutAnimationController.AnimationParameters)
+     * @see android.view.ViewGroup.LayoutParams
+     */
+    protected long getDelayForView(View view) {
+        ViewGroup.LayoutParams lp = view.getLayoutParams();
+        AnimationParameters params = lp.layoutAnimationParameters;
+
+        if (params == null) {
+            return 0;
+        }
+
+        final float delay = mDelay * mAnimation.getDuration();
+        final long viewDelay = (long) (getTransformedIndex(params) * delay);
+        final float totalDelay = delay * params.count;
+
+        if (mInterpolator == null) {
+            mInterpolator = new LinearInterpolator();
+        }
+
+        float normalizedDelay = viewDelay / totalDelay;
+        normalizedDelay = mInterpolator.getInterpolation(normalizedDelay);
+
+        return (long) (normalizedDelay * totalDelay);
+    }
+
+    /**
+     * Transforms the index stored in
+     * {@link android.view.animation.LayoutAnimationController.AnimationParameters}
+     * by the order returned by {@link #getOrder()}. Subclasses should override
+     * this method to provide additional support for other types of ordering.
+     * This method should be invoked by
+     * {@link #getDelayForView(android.view.View)} prior to any computation. 
+     *
+     * @param params the animation parameters containing the index
+     * @return a transformed index
+     */
+    protected int getTransformedIndex(AnimationParameters params) {
+        switch (getOrder()) {
+            case ORDER_REVERSE:
+                return params.count - 1 - params.index;
+            case ORDER_RANDOM:
+                if (mRandomizer == null) {
+                    mRandomizer = new Random();
+                }
+                return (int) (params.count * mRandomizer.nextFloat());
+            case ORDER_NORMAL:
+            default:
+                return params.index;
+        }
+    }
+
+    /**
+     * The set of parameters that has to be attached to each view contained in
+     * the view group animated by the layout animation controller. These
+     * parameters are used to compute the start time of each individual view's
+     * animation.
+     */
+    public static class AnimationParameters {
+        /**
+         * The number of children in the view group containing the view to which
+         * these parameters are attached.
+         */
+        public int count;
+
+        /**
+         * The index of the view to which these parameters are attached in its
+         * containing view group.
+         */
+        public int index;
+    }
+}
diff --git a/android/view/animation/LinearInterpolator.java b/android/view/animation/LinearInterpolator.java
new file mode 100644
index 0000000..f6a820c
--- /dev/null
+++ b/android/view/animation/LinearInterpolator.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.animation;
+
+import android.content.Context;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
+import android.util.AttributeSet;
+
+/**
+ * An interpolator where the rate of change is constant
+ */
+@HasNativeInterpolator
+public class LinearInterpolator extends BaseInterpolator implements NativeInterpolator {
+
+    public LinearInterpolator() {
+    }
+
+    public LinearInterpolator(Context context, AttributeSet attrs) {
+    }
+
+    public float getInterpolation(float input) {
+        return input;
+    }
+
+    /** @hide */
+    @Override
+    public long createNativeInterpolator() {
+        return NativeInterpolatorFactory.createLinearInterpolator();
+    }
+}
diff --git a/android/view/animation/OvershootInterpolator.java b/android/view/animation/OvershootInterpolator.java
new file mode 100644
index 0000000..e6445d7
--- /dev/null
+++ b/android/view/animation/OvershootInterpolator.java
@@ -0,0 +1,81 @@
+/*
+ * 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.view.animation;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
+import android.util.AttributeSet;
+
+import com.android.internal.R;
+
+/**
+ * An interpolator where the change flings forward and overshoots the last value
+ * then comes back.
+ */
+@HasNativeInterpolator
+public class OvershootInterpolator extends BaseInterpolator implements NativeInterpolator {
+    private final float mTension;
+
+    public OvershootInterpolator() {
+        mTension = 2.0f;
+    }
+
+    /**
+     * @param tension Amount of overshoot. When tension equals 0.0f, there is
+     *                no overshoot and the interpolator becomes a simple
+     *                deceleration interpolator.
+     */
+    public OvershootInterpolator(float tension) {
+        mTension = tension;
+    }
+
+    public OvershootInterpolator(Context context, AttributeSet attrs) {
+        this(context.getResources(), context.getTheme(), attrs);
+    }
+
+    /** @hide */
+    public OvershootInterpolator(Resources res, Theme theme, AttributeSet attrs) {
+        TypedArray a;
+        if (theme != null) {
+            a = theme.obtainStyledAttributes(attrs, R.styleable.OvershootInterpolator, 0, 0);
+        } else {
+            a = res.obtainAttributes(attrs, R.styleable.OvershootInterpolator);
+        }
+
+        mTension = a.getFloat(R.styleable.OvershootInterpolator_tension, 2.0f);
+        setChangingConfiguration(a.getChangingConfigurations());
+        a.recycle();
+    }
+
+    public float getInterpolation(float t) {
+        // _o(t) = t * t * ((tension + 1) * t + tension)
+        // o(t) = _o(t - 1) + 1
+        t -= 1.0f;
+        return t * t * ((mTension + 1) * t + mTension) + 1.0f;
+    }
+
+    /** @hide */
+    @Override
+    public long createNativeInterpolator() {
+        return NativeInterpolatorFactory.createOvershootInterpolator(mTension);
+    }
+}
diff --git a/android/view/animation/PathInterpolator.java b/android/view/animation/PathInterpolator.java
new file mode 100644
index 0000000..99d6b9c
--- /dev/null
+++ b/android/view/animation/PathInterpolator.java
@@ -0,0 +1,243 @@
+/*
+ * 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.view.animation;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.Path;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
+import android.util.AttributeSet;
+import android.util.PathParser;
+import android.view.InflateException;
+
+import com.android.internal.R;
+
+/**
+ * An interpolator that can traverse a Path that extends from <code>Point</code>
+ * <code>(0, 0)</code> to <code>(1, 1)</code>. The x coordinate along the <code>Path</code>
+ * is the input value and the output is the y coordinate of the line at that point.
+ * This means that the Path must conform to a function <code>y = f(x)</code>.
+ *
+ * <p>The <code>Path</code> must not have gaps in the x direction and must not
+ * loop back on itself such that there can be two points sharing the same x coordinate.
+ * It is alright to have a disjoint line in the vertical direction:</p>
+ * <p><blockquote><pre>
+ *     Path path = new Path();
+ *     path.lineTo(0.25f, 0.25f);
+ *     path.moveTo(0.25f, 0.5f);
+ *     path.lineTo(1f, 1f);
+ * </pre></blockquote></p>
+ */
+@HasNativeInterpolator
+public class PathInterpolator extends BaseInterpolator implements NativeInterpolator {
+
+    // This governs how accurate the approximation of the Path is.
+    private static final float PRECISION = 0.002f;
+
+    private float[] mX; // x coordinates in the line
+
+    private float[] mY; // y coordinates in the line
+
+    /**
+     * Create an interpolator for an arbitrary <code>Path</code>. The <code>Path</code>
+     * must begin at <code>(0, 0)</code> and end at <code>(1, 1)</code>.
+     *
+     * @param path The <code>Path</code> to use to make the line representing the interpolator.
+     */
+    public PathInterpolator(Path path) {
+        initPath(path);
+    }
+
+    /**
+     * Create an interpolator for a quadratic Bezier curve. The end points
+     * <code>(0, 0)</code> and <code>(1, 1)</code> are assumed.
+     *
+     * @param controlX The x coordinate of the quadratic Bezier control point.
+     * @param controlY The y coordinate of the quadratic Bezier control point.
+     */
+    public PathInterpolator(float controlX, float controlY) {
+        initQuad(controlX, controlY);
+    }
+
+    /**
+     * Create an interpolator for a cubic Bezier curve.  The end points
+     * <code>(0, 0)</code> and <code>(1, 1)</code> are assumed.
+     *
+     * @param controlX1 The x coordinate of the first control point of the cubic Bezier.
+     * @param controlY1 The y coordinate of the first control point of the cubic Bezier.
+     * @param controlX2 The x coordinate of the second control point of the cubic Bezier.
+     * @param controlY2 The y coordinate of the second control point of the cubic Bezier.
+     */
+    public PathInterpolator(float controlX1, float controlY1, float controlX2, float controlY2) {
+        initCubic(controlX1, controlY1, controlX2, controlY2);
+    }
+
+    public PathInterpolator(Context context, AttributeSet attrs) {
+        this(context.getResources(), context.getTheme(), attrs);
+    }
+
+    /** @hide */
+    public PathInterpolator(Resources res, Theme theme, AttributeSet attrs) {
+        TypedArray a;
+        if (theme != null) {
+            a = theme.obtainStyledAttributes(attrs, R.styleable.PathInterpolator, 0, 0);
+        } else {
+            a = res.obtainAttributes(attrs, R.styleable.PathInterpolator);
+        }
+        parseInterpolatorFromTypeArray(a);
+        setChangingConfiguration(a.getChangingConfigurations());
+        a.recycle();
+    }
+
+    private void parseInterpolatorFromTypeArray(TypedArray a) {
+        // If there is pathData defined in the xml file, then the controls points
+        // will be all coming from pathData.
+        if (a.hasValue(R.styleable.PathInterpolator_pathData)) {
+            String pathData = a.getString(R.styleable.PathInterpolator_pathData);
+            Path path = PathParser.createPathFromPathData(pathData);
+            if (path == null) {
+                throw new InflateException("The path is null, which is created"
+                        + " from " + pathData);
+            }
+            initPath(path);
+        } else {
+            if (!a.hasValue(R.styleable.PathInterpolator_controlX1)) {
+                throw new InflateException("pathInterpolator requires the controlX1 attribute");
+            } else if (!a.hasValue(R.styleable.PathInterpolator_controlY1)) {
+                throw new InflateException("pathInterpolator requires the controlY1 attribute");
+            }
+            float x1 = a.getFloat(R.styleable.PathInterpolator_controlX1, 0);
+            float y1 = a.getFloat(R.styleable.PathInterpolator_controlY1, 0);
+
+            boolean hasX2 = a.hasValue(R.styleable.PathInterpolator_controlX2);
+            boolean hasY2 = a.hasValue(R.styleable.PathInterpolator_controlY2);
+
+            if (hasX2 != hasY2) {
+                throw new InflateException(
+                        "pathInterpolator requires both controlX2 and controlY2 for cubic Beziers.");
+            }
+
+            if (!hasX2) {
+                initQuad(x1, y1);
+            } else {
+                float x2 = a.getFloat(R.styleable.PathInterpolator_controlX2, 0);
+                float y2 = a.getFloat(R.styleable.PathInterpolator_controlY2, 0);
+                initCubic(x1, y1, x2, y2);
+            }
+        }
+    }
+
+    private void initQuad(float controlX, float controlY) {
+        Path path = new Path();
+        path.moveTo(0, 0);
+        path.quadTo(controlX, controlY, 1f, 1f);
+        initPath(path);
+    }
+
+    private void initCubic(float x1, float y1, float x2, float y2) {
+        Path path = new Path();
+        path.moveTo(0, 0);
+        path.cubicTo(x1, y1, x2, y2, 1f, 1f);
+        initPath(path);
+    }
+
+    private void initPath(Path path) {
+        float[] pointComponents = path.approximate(PRECISION);
+
+        int numPoints = pointComponents.length / 3;
+        if (pointComponents[1] != 0 || pointComponents[2] != 0
+                || pointComponents[pointComponents.length - 2] != 1
+                || pointComponents[pointComponents.length - 1] != 1) {
+            throw new IllegalArgumentException("The Path must start at (0,0) and end at (1,1)");
+        }
+
+        mX = new float[numPoints];
+        mY = new float[numPoints];
+        float prevX = 0;
+        float prevFraction = 0;
+        int componentIndex = 0;
+        for (int i = 0; i < numPoints; i++) {
+            float fraction = pointComponents[componentIndex++];
+            float x = pointComponents[componentIndex++];
+            float y = pointComponents[componentIndex++];
+            if (fraction == prevFraction && x != prevX) {
+                throw new IllegalArgumentException(
+                        "The Path cannot have discontinuity in the X axis.");
+            }
+            if (x < prevX) {
+                throw new IllegalArgumentException("The Path cannot loop back on itself.");
+            }
+            mX[i] = x;
+            mY[i] = y;
+            prevX = x;
+            prevFraction = fraction;
+        }
+    }
+
+    /**
+     * Using the line in the Path in this interpolator that can be described as
+     * <code>y = f(x)</code>, finds the y coordinate of the line given <code>t</code>
+     * as the x coordinate. Values less than 0 will always return 0 and values greater
+     * than 1 will always return 1.
+     *
+     * @param t Treated as the x coordinate along the line.
+     * @return The y coordinate of the Path along the line where x = <code>t</code>.
+     * @see Interpolator#getInterpolation(float)
+     */
+    @Override
+    public float getInterpolation(float t) {
+        if (t <= 0) {
+            return 0;
+        } else if (t >= 1) {
+            return 1;
+        }
+        // Do a binary search for the correct x to interpolate between.
+        int startIndex = 0;
+        int endIndex = mX.length - 1;
+
+        while (endIndex - startIndex > 1) {
+            int midIndex = (startIndex + endIndex) / 2;
+            if (t < mX[midIndex]) {
+                endIndex = midIndex;
+            } else {
+                startIndex = midIndex;
+            }
+        }
+
+        float xRange = mX[endIndex] - mX[startIndex];
+        if (xRange == 0) {
+            return mY[startIndex];
+        }
+
+        float tInRange = t - mX[startIndex];
+        float fraction = tInRange / xRange;
+
+        float startY = mY[startIndex];
+        float endY = mY[endIndex];
+        return startY + (fraction * (endY - startY));
+    }
+
+    /** @hide **/
+    @Override
+    public long createNativeInterpolator() {
+        return NativeInterpolatorFactory.createPathInterpolator(mX, mY);
+    }
+
+}
diff --git a/android/view/animation/RotateAnimation.java b/android/view/animation/RotateAnimation.java
new file mode 100644
index 0000000..3c325d9
--- /dev/null
+++ b/android/view/animation/RotateAnimation.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.animation;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+
+/**
+ * An animation that controls the rotation of an object. This rotation takes
+ * place in the X-Y plane. You can specify the point to use for the center of
+ * the rotation, where (0,0) is the top left point. If not specified, (0,0) is
+ * the default rotation point.
+ * 
+ */
+public class RotateAnimation extends Animation {
+    private float mFromDegrees;
+    private float mToDegrees;
+
+    private int mPivotXType = ABSOLUTE;
+    private int mPivotYType = ABSOLUTE;
+    private float mPivotXValue = 0.0f;
+    private float mPivotYValue = 0.0f;
+
+    private float mPivotX;
+    private float mPivotY;
+
+    /**
+     * Constructor used when a RotateAnimation is loaded from a resource.
+     * 
+     * @param context Application context to use
+     * @param attrs Attribute set from which to read values
+     */
+    public RotateAnimation(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.RotateAnimation);
+
+        mFromDegrees = a.getFloat(
+                com.android.internal.R.styleable.RotateAnimation_fromDegrees, 0.0f);
+        mToDegrees = a.getFloat(com.android.internal.R.styleable.RotateAnimation_toDegrees, 0.0f);
+
+        Description d = Description.parseValue(a.peekValue(
+            com.android.internal.R.styleable.RotateAnimation_pivotX));
+        mPivotXType = d.type;
+        mPivotXValue = d.value;
+
+        d = Description.parseValue(a.peekValue(
+            com.android.internal.R.styleable.RotateAnimation_pivotY));
+        mPivotYType = d.type;
+        mPivotYValue = d.value;
+
+        a.recycle();
+
+        initializePivotPoint();
+    }
+
+    /**
+     * Constructor to use when building a RotateAnimation from code.
+     * Default pivotX/pivotY point is (0,0).
+     * 
+     * @param fromDegrees Rotation offset to apply at the start of the
+     *        animation.
+     * 
+     * @param toDegrees Rotation offset to apply at the end of the animation.
+     */
+    public RotateAnimation(float fromDegrees, float toDegrees) {
+        mFromDegrees = fromDegrees;
+        mToDegrees = toDegrees;
+        mPivotX = 0.0f;
+        mPivotY = 0.0f;
+    }
+
+    /**
+     * Constructor to use when building a RotateAnimation from code
+     * 
+     * @param fromDegrees Rotation offset to apply at the start of the
+     *        animation.
+     * 
+     * @param toDegrees Rotation offset to apply at the end of the animation.
+     * 
+     * @param pivotX The X coordinate of the point about which the object is
+     *        being rotated, specified as an absolute number where 0 is the left
+     *        edge.
+     * @param pivotY The Y coordinate of the point about which the object is
+     *        being rotated, specified as an absolute number where 0 is the top
+     *        edge.
+     */
+    public RotateAnimation(float fromDegrees, float toDegrees, float pivotX, float pivotY) {
+        mFromDegrees = fromDegrees;
+        mToDegrees = toDegrees;
+
+        mPivotXType = ABSOLUTE;
+        mPivotYType = ABSOLUTE;
+        mPivotXValue = pivotX;
+        mPivotYValue = pivotY;
+        initializePivotPoint();
+    }
+
+    /**
+     * Constructor to use when building a RotateAnimation from code
+     * 
+     * @param fromDegrees Rotation offset to apply at the start of the
+     *        animation.
+     * 
+     * @param toDegrees Rotation offset to apply at the end of the animation.
+     * 
+     * @param pivotXType Specifies how pivotXValue should be interpreted. One of
+     *        Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+     *        Animation.RELATIVE_TO_PARENT.
+     * @param pivotXValue The X coordinate of the point about which the object
+     *        is being rotated, specified as an absolute number where 0 is the
+     *        left edge. This value can either be an absolute number if
+     *        pivotXType is ABSOLUTE, or a percentage (where 1.0 is 100%)
+     *        otherwise.
+     * @param pivotYType Specifies how pivotYValue should be interpreted. One of
+     *        Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+     *        Animation.RELATIVE_TO_PARENT.
+     * @param pivotYValue The Y coordinate of the point about which the object
+     *        is being rotated, specified as an absolute number where 0 is the
+     *        top edge. This value can either be an absolute number if
+     *        pivotYType is ABSOLUTE, or a percentage (where 1.0 is 100%)
+     *        otherwise.
+     */
+    public RotateAnimation(float fromDegrees, float toDegrees, int pivotXType, float pivotXValue,
+            int pivotYType, float pivotYValue) {
+        mFromDegrees = fromDegrees;
+        mToDegrees = toDegrees;
+
+        mPivotXValue = pivotXValue;
+        mPivotXType = pivotXType;
+        mPivotYValue = pivotYValue;
+        mPivotYType = pivotYType;
+        initializePivotPoint();
+    }
+
+    /**
+     * Called at the end of constructor methods to initialize, if possible, values for
+     * the pivot point. This is only possible for ABSOLUTE pivot values.
+     */
+    private void initializePivotPoint() {
+        if (mPivotXType == ABSOLUTE) {
+            mPivotX = mPivotXValue;
+        }
+        if (mPivotYType == ABSOLUTE) {
+            mPivotY = mPivotYValue;
+        }
+    }
+
+    @Override
+    protected void applyTransformation(float interpolatedTime, Transformation t) {
+        float degrees = mFromDegrees + ((mToDegrees - mFromDegrees) * interpolatedTime);
+        float scale = getScaleFactor();
+        
+        if (mPivotX == 0.0f && mPivotY == 0.0f) {
+            t.getMatrix().setRotate(degrees);
+        } else {
+            t.getMatrix().setRotate(degrees, mPivotX * scale, mPivotY * scale);
+        }
+    }
+
+    @Override
+    public void initialize(int width, int height, int parentWidth, int parentHeight) {
+        super.initialize(width, height, parentWidth, parentHeight);
+        mPivotX = resolveSize(mPivotXType, mPivotXValue, width, parentWidth);
+        mPivotY = resolveSize(mPivotYType, mPivotYValue, height, parentHeight);
+    }
+}
diff --git a/android/view/animation/ScaleAnimation.java b/android/view/animation/ScaleAnimation.java
new file mode 100644
index 0000000..e9a8436
--- /dev/null
+++ b/android/view/animation/ScaleAnimation.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.animation;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+
+/**
+ * An animation that controls the scale of an object. You can specify the point
+ * to use for the center of scaling.
+ * 
+ */
+public class ScaleAnimation extends Animation {
+    private final Resources mResources;
+
+    private float mFromX;
+    private float mToX;
+    private float mFromY;
+    private float mToY;
+
+    private int mFromXType = TypedValue.TYPE_NULL;
+    private int mToXType = TypedValue.TYPE_NULL;
+    private int mFromYType = TypedValue.TYPE_NULL;
+    private int mToYType = TypedValue.TYPE_NULL;
+
+    private int mFromXData = 0;
+    private int mToXData = 0;
+    private int mFromYData = 0;
+    private int mToYData = 0;
+
+    private int mPivotXType = ABSOLUTE;
+    private int mPivotYType = ABSOLUTE;
+    private float mPivotXValue = 0.0f;
+    private float mPivotYValue = 0.0f;
+
+    private float mPivotX;
+    private float mPivotY;
+
+    /**
+     * Constructor used when a ScaleAnimation is loaded from a resource.
+     * 
+     * @param context Application context to use
+     * @param attrs Attribute set from which to read values
+     */
+    public ScaleAnimation(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        mResources = context.getResources();
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.ScaleAnimation);
+
+        TypedValue tv = a.peekValue(
+                com.android.internal.R.styleable.ScaleAnimation_fromXScale);
+        mFromX = 0.0f;
+        if (tv != null) {
+            if (tv.type == TypedValue.TYPE_FLOAT) {
+                // This is a scaling factor.
+                mFromX = tv.getFloat();
+            } else {
+                mFromXType = tv.type;
+                mFromXData = tv.data;
+            }
+        }
+        tv = a.peekValue(
+                com.android.internal.R.styleable.ScaleAnimation_toXScale);
+        mToX = 0.0f;
+        if (tv != null) {
+            if (tv.type == TypedValue.TYPE_FLOAT) {
+                // This is a scaling factor.
+                mToX = tv.getFloat();
+            } else {
+                mToXType = tv.type;
+                mToXData = tv.data;
+            }
+        }
+
+        tv = a.peekValue(
+                com.android.internal.R.styleable.ScaleAnimation_fromYScale);
+        mFromY = 0.0f;
+        if (tv != null) {
+            if (tv.type == TypedValue.TYPE_FLOAT) {
+                // This is a scaling factor.
+                mFromY = tv.getFloat();
+            } else {
+                mFromYType = tv.type;
+                mFromYData = tv.data;
+            }
+        }
+        tv = a.peekValue(
+                com.android.internal.R.styleable.ScaleAnimation_toYScale);
+        mToY = 0.0f;
+        if (tv != null) {
+            if (tv.type == TypedValue.TYPE_FLOAT) {
+                // This is a scaling factor.
+                mToY = tv.getFloat();
+            } else {
+                mToYType = tv.type;
+                mToYData = tv.data;
+            }
+        }
+
+        Description d = Description.parseValue(a.peekValue(
+                com.android.internal.R.styleable.ScaleAnimation_pivotX));
+        mPivotXType = d.type;
+        mPivotXValue = d.value;
+
+        d = Description.parseValue(a.peekValue(
+            com.android.internal.R.styleable.ScaleAnimation_pivotY));
+        mPivotYType = d.type;
+        mPivotYValue = d.value;
+
+        a.recycle();
+
+        initializePivotPoint();
+    }
+
+    /**
+     * Constructor to use when building a ScaleAnimation from code
+     * 
+     * @param fromX Horizontal scaling factor to apply at the start of the
+     *        animation
+     * @param toX Horizontal scaling factor to apply at the end of the animation
+     * @param fromY Vertical scaling factor to apply at the start of the
+     *        animation
+     * @param toY Vertical scaling factor to apply at the end of the animation
+     */
+    public ScaleAnimation(float fromX, float toX, float fromY, float toY) {
+        mResources = null;
+        mFromX = fromX;
+        mToX = toX;
+        mFromY = fromY;
+        mToY = toY;
+        mPivotX = 0;
+        mPivotY = 0;
+    }
+
+    /**
+     * Constructor to use when building a ScaleAnimation from code
+     * 
+     * @param fromX Horizontal scaling factor to apply at the start of the
+     *        animation
+     * @param toX Horizontal scaling factor to apply at the end of the animation
+     * @param fromY Vertical scaling factor to apply at the start of the
+     *        animation
+     * @param toY Vertical scaling factor to apply at the end of the animation
+     * @param pivotX The X coordinate of the point about which the object is
+     *        being scaled, specified as an absolute number where 0 is the left
+     *        edge. (This point remains fixed while the object changes size.)
+     * @param pivotY The Y coordinate of the point about which the object is
+     *        being scaled, specified as an absolute number where 0 is the top
+     *        edge. (This point remains fixed while the object changes size.)
+     */
+    public ScaleAnimation(float fromX, float toX, float fromY, float toY,
+            float pivotX, float pivotY) {
+        mResources = null;
+        mFromX = fromX;
+        mToX = toX;
+        mFromY = fromY;
+        mToY = toY;
+
+        mPivotXType = ABSOLUTE;
+        mPivotYType = ABSOLUTE;
+        mPivotXValue = pivotX;
+        mPivotYValue = pivotY;
+        initializePivotPoint();
+    }
+
+    /**
+     * Constructor to use when building a ScaleAnimation from code
+     * 
+     * @param fromX Horizontal scaling factor to apply at the start of the
+     *        animation
+     * @param toX Horizontal scaling factor to apply at the end of the animation
+     * @param fromY Vertical scaling factor to apply at the start of the
+     *        animation
+     * @param toY Vertical scaling factor to apply at the end of the animation
+     * @param pivotXType Specifies how pivotXValue should be interpreted. One of
+     *        Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+     *        Animation.RELATIVE_TO_PARENT.
+     * @param pivotXValue The X coordinate of the point about which the object
+     *        is being scaled, specified as an absolute number where 0 is the
+     *        left edge. (This point remains fixed while the object changes
+     *        size.) This value can either be an absolute number if pivotXType
+     *        is ABSOLUTE, or a percentage (where 1.0 is 100%) otherwise.
+     * @param pivotYType Specifies how pivotYValue should be interpreted. One of
+     *        Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+     *        Animation.RELATIVE_TO_PARENT.
+     * @param pivotYValue The Y coordinate of the point about which the object
+     *        is being scaled, specified as an absolute number where 0 is the
+     *        top edge. (This point remains fixed while the object changes
+     *        size.) This value can either be an absolute number if pivotYType
+     *        is ABSOLUTE, or a percentage (where 1.0 is 100%) otherwise.
+     */
+    public ScaleAnimation(float fromX, float toX, float fromY, float toY,
+            int pivotXType, float pivotXValue, int pivotYType, float pivotYValue) {
+        mResources = null;
+        mFromX = fromX;
+        mToX = toX;
+        mFromY = fromY;
+        mToY = toY;
+
+        mPivotXValue = pivotXValue;
+        mPivotXType = pivotXType;
+        mPivotYValue = pivotYValue;
+        mPivotYType = pivotYType;
+        initializePivotPoint();
+    }
+
+    /**
+     * Called at the end of constructor methods to initialize, if possible, values for
+     * the pivot point. This is only possible for ABSOLUTE pivot values.
+     */
+    private void initializePivotPoint() {
+        if (mPivotXType == ABSOLUTE) {
+            mPivotX = mPivotXValue;
+        }
+        if (mPivotYType == ABSOLUTE) {
+            mPivotY = mPivotYValue;
+        }
+    }
+
+    @Override
+    protected void applyTransformation(float interpolatedTime, Transformation t) {
+        float sx = 1.0f;
+        float sy = 1.0f;
+        float scale = getScaleFactor();
+
+        if (mFromX != 1.0f || mToX != 1.0f) {
+            sx = mFromX + ((mToX - mFromX) * interpolatedTime);
+        }
+        if (mFromY != 1.0f || mToY != 1.0f) {
+            sy = mFromY + ((mToY - mFromY) * interpolatedTime);
+        }
+
+        if (mPivotX == 0 && mPivotY == 0) {
+            t.getMatrix().setScale(sx, sy);
+        } else {
+            t.getMatrix().setScale(sx, sy, scale * mPivotX, scale * mPivotY);
+        }
+    }
+
+    float resolveScale(float scale, int type, int data, int size, int psize) {
+        float targetSize;
+        if (type == TypedValue.TYPE_FRACTION) {
+            targetSize = TypedValue.complexToFraction(data, size, psize);
+        } else if (type == TypedValue.TYPE_DIMENSION) {
+            targetSize = TypedValue.complexToDimension(data, mResources.getDisplayMetrics());
+        } else {
+            return scale;
+        }
+
+        if (size == 0) {
+            return 1;
+        }
+
+        return targetSize/(float)size;
+    }
+
+    @Override
+    public void initialize(int width, int height, int parentWidth, int parentHeight) {
+        super.initialize(width, height, parentWidth, parentHeight);
+
+        mFromX = resolveScale(mFromX, mFromXType, mFromXData, width, parentWidth);
+        mToX = resolveScale(mToX, mToXType, mToXData, width, parentWidth);
+        mFromY = resolveScale(mFromY, mFromYType, mFromYData, height, parentHeight);
+        mToY = resolveScale(mToY, mToYType, mToYData, height, parentHeight);
+
+        mPivotX = resolveSize(mPivotXType, mPivotXValue, width, parentWidth);
+        mPivotY = resolveSize(mPivotYType, mPivotYValue, height, parentHeight);
+    }
+}
diff --git a/android/view/animation/Transformation.java b/android/view/animation/Transformation.java
new file mode 100644
index 0000000..b35a66e
--- /dev/null
+++ b/android/view/animation/Transformation.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.animation;
+
+import android.annotation.FloatRange;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+
+import java.io.PrintWriter;
+
+/**
+ * Defines the transformation to be applied at
+ * one point in time of an Animation.
+ *
+ */
+public class Transformation {
+    /**
+     * Indicates a transformation that has no effect (alpha = 1 and identity matrix.)
+     */
+    public static final int TYPE_IDENTITY = 0x0;
+    /**
+     * Indicates a transformation that applies an alpha only (uses an identity matrix.)
+     */
+    public static final int TYPE_ALPHA = 0x1;
+    /**
+     * Indicates a transformation that applies a matrix only (alpha = 1.)
+     */
+    public static final int TYPE_MATRIX = 0x2;
+    /**
+     * Indicates a transformation that applies an alpha and a matrix.
+     */
+    public static final int TYPE_BOTH = TYPE_ALPHA | TYPE_MATRIX;
+
+    protected Matrix mMatrix;
+    protected float mAlpha;
+    protected int mTransformationType;
+
+    private boolean mHasClipRect;
+    private Rect mClipRect = new Rect();
+
+    /**
+     * Creates a new transformation with alpha = 1 and the identity matrix.
+     */
+    public Transformation() {
+        clear();
+    }
+
+    /**
+     * Reset the transformation to a state that leaves the object
+     * being animated in an unmodified state. The transformation type is
+     * {@link #TYPE_BOTH} by default.
+     */
+    public void clear() {
+        if (mMatrix == null) {
+            mMatrix = new Matrix();
+        } else {
+            mMatrix.reset();
+        }
+        mClipRect.setEmpty();
+        mHasClipRect = false;
+        mAlpha = 1.0f;
+        mTransformationType = TYPE_BOTH;
+    }
+
+    /**
+     * Indicates the nature of this transformation.
+     *
+     * @return {@link #TYPE_ALPHA}, {@link #TYPE_MATRIX},
+     *         {@link #TYPE_BOTH} or {@link #TYPE_IDENTITY}.
+     */
+    public int getTransformationType() {
+        return mTransformationType;
+    }
+
+    /**
+     * Sets the transformation type.
+     *
+     * @param transformationType One of {@link #TYPE_ALPHA},
+     *        {@link #TYPE_MATRIX}, {@link #TYPE_BOTH} or
+     *        {@link #TYPE_IDENTITY}.
+     */
+    public void setTransformationType(int transformationType) {
+        mTransformationType = transformationType;
+    }
+
+    /**
+     * Clones the specified transformation.
+     *
+     * @param t The transformation to clone.
+     */
+    public void set(Transformation t) {
+        mAlpha = t.getAlpha();
+        mMatrix.set(t.getMatrix());
+        if (t.mHasClipRect) {
+            setClipRect(t.getClipRect());
+        } else {
+            mHasClipRect = false;
+            mClipRect.setEmpty();
+        }
+        mTransformationType = t.getTransformationType();
+    }
+
+    /**
+     * Apply this Transformation to an existing Transformation, e.g. apply
+     * a scale effect to something that has already been rotated.
+     * @param t
+     */
+    public void compose(Transformation t) {
+        mAlpha *= t.getAlpha();
+        mMatrix.preConcat(t.getMatrix());
+        if (t.mHasClipRect) {
+            Rect bounds = t.getClipRect();
+            if (mHasClipRect) {
+                setClipRect(mClipRect.left + bounds.left, mClipRect.top + bounds.top,
+                        mClipRect.right + bounds.right, mClipRect.bottom + bounds.bottom);
+            } else {
+                setClipRect(bounds);
+            }
+        }
+    }
+    
+    /**
+     * Like {@link #compose(Transformation)} but does this.postConcat(t) of
+     * the transformation matrix.
+     * @hide
+     */
+    public void postCompose(Transformation t) {
+        mAlpha *= t.getAlpha();
+        mMatrix.postConcat(t.getMatrix());
+        if (t.mHasClipRect) {
+            Rect bounds = t.getClipRect();
+            if (mHasClipRect) {
+                setClipRect(mClipRect.left + bounds.left, mClipRect.top + bounds.top,
+                        mClipRect.right + bounds.right, mClipRect.bottom + bounds.bottom);
+            } else {
+                setClipRect(bounds);
+            }
+        }
+    }
+
+    /**
+     * @return The 3x3 Matrix representing the trnasformation to apply to the
+     * coordinates of the object being animated
+     */
+    public Matrix getMatrix() {
+        return mMatrix;
+    }
+    
+    /**
+     * Sets the degree of transparency
+     * @param alpha 1.0 means fully opaqe and 0.0 means fully transparent
+     */
+    public void setAlpha(@FloatRange(from=0.0, to=1.0) float alpha) {
+        mAlpha = alpha;
+    }
+
+    /**
+     * Sets the current Transform's clip rect
+     * @hide
+     */
+    public void setClipRect(Rect r) {
+        setClipRect(r.left, r.top, r.right, r.bottom);
+    }
+
+    /**
+     * Sets the current Transform's clip rect
+     * @hide
+     */
+    public void setClipRect(int l, int t, int r, int b) {
+        mClipRect.set(l, t, r, b);
+        mHasClipRect = true;
+    }
+
+    /**
+     * Returns the current Transform's clip rect
+     * @hide
+     */
+    public Rect getClipRect() {
+        return mClipRect;
+    }
+
+    /**
+     * Returns whether the current Transform's clip rect is set
+     * @hide
+     */
+    public boolean hasClipRect() {
+        return mHasClipRect;
+    }
+
+    /**
+     * @return The degree of transparency
+     */
+    public float getAlpha() {
+        return mAlpha;
+    }
+    
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(64);
+        sb.append("Transformation");
+        toShortString(sb);
+        return sb.toString();
+    }
+    
+    /**
+     * Return a string representation of the transformation in a compact form.
+     */
+    public String toShortString() {
+        StringBuilder sb = new StringBuilder(64);
+        toShortString(sb);
+        return sb.toString();
+    }
+    
+    /**
+     * @hide
+     */
+    public void toShortString(StringBuilder sb) {
+        sb.append("{alpha="); sb.append(mAlpha);
+        sb.append(" matrix="); sb.append(mMatrix.toShortString());
+        sb.append('}');
+    }
+    
+    /**
+     * Print short string, to optimize dumping.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void printShortString(PrintWriter pw) {
+        pw.print("{alpha="); pw.print(mAlpha);
+        pw.print(" matrix=");
+        mMatrix.dump(pw);
+        pw.print('}');
+    }
+}
diff --git a/android/view/animation/TranslateAnimation.java b/android/view/animation/TranslateAnimation.java
new file mode 100644
index 0000000..ec55a02
--- /dev/null
+++ b/android/view/animation/TranslateAnimation.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.animation;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+
+/**
+ * An animation that controls the position of an object. See the
+ * {@link android.view.animation full package} description for details and
+ * sample code.
+ *
+ */
+public class TranslateAnimation extends Animation {
+    private int mFromXType = ABSOLUTE;
+    private int mToXType = ABSOLUTE;
+
+    private int mFromYType = ABSOLUTE;
+    private int mToYType = ABSOLUTE;
+
+    /** @hide */
+    @UnsupportedAppUsage
+    protected float mFromXValue = 0.0f;
+    /** @hide */
+    @UnsupportedAppUsage
+    protected float mToXValue = 0.0f;
+
+    /** @hide */
+    @UnsupportedAppUsage
+    protected float mFromYValue = 0.0f;
+    /** @hide */
+    @UnsupportedAppUsage
+    protected float mToYValue = 0.0f;
+
+    /** @hide */
+    protected float mFromXDelta;
+    /** @hide */
+    protected float mToXDelta;
+    /** @hide */
+    protected float mFromYDelta;
+    /** @hide */
+    protected float mToYDelta;
+
+    /**
+     * Constructor used when a TranslateAnimation is loaded from a resource.
+     *
+     * @param context Application context to use
+     * @param attrs Attribute set from which to read values
+     */
+    public TranslateAnimation(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.TranslateAnimation);
+
+        Description d = Description.parseValue(a.peekValue(
+            com.android.internal.R.styleable.TranslateAnimation_fromXDelta));
+        mFromXType = d.type;
+        mFromXValue = d.value;
+
+        d = Description.parseValue(a.peekValue(
+                com.android.internal.R.styleable.TranslateAnimation_toXDelta));
+        mToXType = d.type;
+        mToXValue = d.value;
+
+        d = Description.parseValue(a.peekValue(
+            com.android.internal.R.styleable.TranslateAnimation_fromYDelta));
+        mFromYType = d.type;
+        mFromYValue = d.value;
+
+        d = Description.parseValue(a.peekValue(
+            com.android.internal.R.styleable.TranslateAnimation_toYDelta));
+        mToYType = d.type;
+        mToYValue = d.value;
+
+        a.recycle();
+    }
+
+    /**
+     * Constructor to use when building a TranslateAnimation from code
+     *
+     * @param fromXDelta Change in X coordinate to apply at the start of the
+     *        animation
+     * @param toXDelta Change in X coordinate to apply at the end of the
+     *        animation
+     * @param fromYDelta Change in Y coordinate to apply at the start of the
+     *        animation
+     * @param toYDelta Change in Y coordinate to apply at the end of the
+     *        animation
+     */
+    public TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta) {
+        mFromXValue = fromXDelta;
+        mToXValue = toXDelta;
+        mFromYValue = fromYDelta;
+        mToYValue = toYDelta;
+
+        mFromXType = ABSOLUTE;
+        mToXType = ABSOLUTE;
+        mFromYType = ABSOLUTE;
+        mToYType = ABSOLUTE;
+    }
+
+    /**
+     * Constructor to use when building a TranslateAnimation from code
+     * 
+     * @param fromXType Specifies how fromXValue should be interpreted. One of
+     *        Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+     *        Animation.RELATIVE_TO_PARENT.
+     * @param fromXValue Change in X coordinate to apply at the start of the
+     *        animation. This value can either be an absolute number if fromXType
+     *        is ABSOLUTE, or a percentage (where 1.0 is 100%) otherwise.
+     * @param toXType Specifies how toXValue should be interpreted. One of
+     *        Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+     *        Animation.RELATIVE_TO_PARENT.
+     * @param toXValue Change in X coordinate to apply at the end of the
+     *        animation. This value can either be an absolute number if toXType
+     *        is ABSOLUTE, or a percentage (where 1.0 is 100%) otherwise.
+     * @param fromYType Specifies how fromYValue should be interpreted. One of
+     *        Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+     *        Animation.RELATIVE_TO_PARENT.
+     * @param fromYValue Change in Y coordinate to apply at the start of the
+     *        animation. This value can either be an absolute number if fromYType
+     *        is ABSOLUTE, or a percentage (where 1.0 is 100%) otherwise.
+     * @param toYType Specifies how toYValue should be interpreted. One of
+     *        Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+     *        Animation.RELATIVE_TO_PARENT.
+     * @param toYValue Change in Y coordinate to apply at the end of the
+     *        animation. This value can either be an absolute number if toYType
+     *        is ABSOLUTE, or a percentage (where 1.0 is 100%) otherwise.
+     */
+    public TranslateAnimation(int fromXType, float fromXValue, int toXType, float toXValue,
+            int fromYType, float fromYValue, int toYType, float toYValue) {
+
+        mFromXValue = fromXValue;
+        mToXValue = toXValue;
+        mFromYValue = fromYValue;
+        mToYValue = toYValue;
+
+        mFromXType = fromXType;
+        mToXType = toXType;
+        mFromYType = fromYType;
+        mToYType = toYType;
+    }
+
+
+    @Override
+    protected void applyTransformation(float interpolatedTime, Transformation t) {
+        float dx = mFromXDelta;
+        float dy = mFromYDelta;
+        if (mFromXDelta != mToXDelta) {
+            dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);
+        }
+        if (mFromYDelta != mToYDelta) {
+            dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);
+        }
+        t.getMatrix().setTranslate(dx, dy);
+    }
+
+    @Override
+    public void initialize(int width, int height, int parentWidth, int parentHeight) {
+        super.initialize(width, height, parentWidth, parentHeight);
+        mFromXDelta = resolveSize(mFromXType, mFromXValue, width, parentWidth);
+        mToXDelta = resolveSize(mToXType, mToXValue, width, parentWidth);
+        mFromYDelta = resolveSize(mFromYType, mFromYValue, height, parentHeight);
+        mToYDelta = resolveSize(mToYType, mToYValue, height, parentHeight);
+    }
+}
diff --git a/android/view/animation/TranslateXAnimation.java b/android/view/animation/TranslateXAnimation.java
new file mode 100644
index 0000000..d75323f
--- /dev/null
+++ b/android/view/animation/TranslateXAnimation.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.animation;
+
+import android.graphics.Matrix;
+
+/**
+ * Special case of TranslateAnimation that translates only horizontally, picking up the
+ * vertical values from whatever is set on the Transformation already. When used in
+ * conjunction with a TranslateYAnimation, allows independent animation of x and y
+ * position.
+ * @hide
+ */
+public class TranslateXAnimation extends TranslateAnimation {
+    float[] mTmpValues = new float[9];
+
+    /**
+     * Constructor. Passes in 0 for the y parameters of TranslateAnimation
+     */
+    public TranslateXAnimation(float fromXDelta, float toXDelta) {
+        super(fromXDelta, toXDelta, 0, 0);
+    }
+
+    /**
+     * Constructor. Passes in 0 for the y parameters of TranslateAnimation
+     */
+    public TranslateXAnimation(int fromXType, float fromXValue, int toXType, float toXValue) {
+        super(fromXType, fromXValue, toXType, toXValue, ABSOLUTE, 0, ABSOLUTE, 0);
+    }
+
+    /**
+     * Calculates and sets x translation values on given transformation.
+     */
+    @Override
+    protected void applyTransformation(float interpolatedTime, Transformation t) {
+        Matrix m = t.getMatrix();
+        m.getValues(mTmpValues);
+        float dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);
+        t.getMatrix().setTranslate(dx, mTmpValues[Matrix.MTRANS_Y]);
+    }
+}
diff --git a/android/view/animation/TranslateYAnimation.java b/android/view/animation/TranslateYAnimation.java
new file mode 100644
index 0000000..1a1dfbf
--- /dev/null
+++ b/android/view/animation/TranslateYAnimation.java
@@ -0,0 +1,57 @@
+/*
+ * 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.view.animation;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.Matrix;
+
+/**
+ * Special case of TranslateAnimation that translates only vertically, picking up the
+ * horizontal values from whatever is set on the Transformation already. When used in
+ * conjunction with a TranslateXAnimation, allows independent animation of x and y
+ * position.
+ * @hide
+ */
+public class TranslateYAnimation extends TranslateAnimation {
+    float[] mTmpValues = new float[9];
+
+    /**
+     * Constructor. Passes in 0 for the x parameters of TranslateAnimation
+     */
+    public TranslateYAnimation(float fromYDelta, float toYDelta) {
+        super(0, 0, fromYDelta, toYDelta);
+    }
+
+    /**
+     * Constructor. Passes in 0 for the x parameters of TranslateAnimation
+     */
+    @UnsupportedAppUsage
+    public TranslateYAnimation(int fromYType, float fromYValue, int toYType, float toYValue) {
+        super(ABSOLUTE, 0, ABSOLUTE, 0, fromYType, fromYValue, toYType, toYValue);
+    }
+
+    /**
+     * Calculates and sets y translation values on given transformation.
+     */
+    @Override
+    protected void applyTransformation(float interpolatedTime, Transformation t) {
+        Matrix m = t.getMatrix();
+        m.getValues(mTmpValues);
+        float dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);
+        t.getMatrix().setTranslate(mTmpValues[Matrix.MTRANS_X], dy);
+    }
+}
diff --git a/android/view/autofill/AbstractAutofillPerfTestCase.java b/android/view/autofill/AbstractAutofillPerfTestCase.java
new file mode 100644
index 0000000..54e1860
--- /dev/null
+++ b/android/view/autofill/AbstractAutofillPerfTestCase.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.autofill;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import static org.junit.Assert.assertTrue;
+
+import android.os.Looper;
+import android.perftests.utils.PerfStatusReporter;
+import android.perftests.utils.PerfTestActivity;
+import android.perftests.utils.SettingsStateKeeperRule;
+import android.provider.Settings;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+
+/**
+ * Base class for all autofill tests.
+ */
+public abstract class AbstractAutofillPerfTestCase {
+
+    private static final String TAG = "AbstractAutofillPerfTestCase";
+
+    @ClassRule
+    public static final SettingsStateKeeperRule mServiceSettingsKeeper =
+            new SettingsStateKeeperRule(InstrumentationRegistry.getTargetContext(),
+                    Settings.Secure.AUTOFILL_SERVICE);
+
+    protected final AutofillTestWatcher mTestWatcher = MyAutofillService.getTestWatcher();
+    protected ActivityTestRule<PerfTestActivity> mActivityRule =
+            new ActivityTestRule<>(PerfTestActivity.class);
+    protected PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Rule
+    public final RuleChain mAllRules = RuleChain
+            .outerRule(mTestWatcher)
+            .around(mPerfStatusReporter)
+            .around(mActivityRule);
+
+    private final int mLayoutId;
+
+    protected AbstractAutofillPerfTestCase(int layoutId) {
+        mLayoutId = layoutId;
+    }
+
+    @BeforeClass
+    public static void disableDefaultAugmentedService() {
+        Log.v(TAG, "@BeforeClass: disableDefaultAugmentedService()");
+        setDefaultAugmentedAutofillServiceEnabled(false);
+    }
+
+    @AfterClass
+    public static void enableDefaultAugmentedService() {
+        Log.v(TAG, "@AfterClass: enableDefaultAugmentedService()");
+        setDefaultAugmentedAutofillServiceEnabled(true);
+    }
+
+    /**
+     * Enables / disables the default augmented autofill service.
+     */
+    private static void setDefaultAugmentedAutofillServiceEnabled(boolean enabled) {
+        Log.d(TAG, "setDefaultAugmentedAutofillServiceEnabled(): " + enabled);
+        runShellCommand("cmd autofill set default-augmented-service-enabled 0 %s",
+                Boolean.toString(enabled));
+    }
+
+    /**
+     * Prepares the activity so that by the time the test is run it has reference to its fields.
+     */
+    @Before
+    public void prepareActivity() throws Throwable {
+        mActivityRule.runOnUiThread(() -> {
+            assertTrue("We should be running on the main thread",
+                    Looper.getMainLooper().getThread() == Thread.currentThread());
+            assertTrue("We should be running on the main thread",
+                    Looper.myLooper() == Looper.getMainLooper());
+            PerfTestActivity activity = mActivityRule.getActivity();
+            activity.setContentView(mLayoutId);
+            onCreate(activity);
+        });
+    }
+
+    /**
+     * Initializes the {@link PerfTestActivity} after it was launched.
+     */
+    protected abstract void onCreate(PerfTestActivity activity);
+}
diff --git a/android/view/autofill/AutofillId.java b/android/view/autofill/AutofillId.java
new file mode 100644
index 0000000..ae145de
--- /dev/null
+++ b/android/view/autofill/AutofillId.java
@@ -0,0 +1,267 @@
+/*
+ * 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.view.autofill;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.View;
+
+/**
+ * A unique identifier for an autofill node inside an {@link android.app.Activity}.
+ */
+public final class AutofillId implements Parcelable {
+
+    /** @hide */
+    public static final int NO_SESSION = 0;
+
+    private static final int FLAG_IS_VIRTUAL_INT = 0x1;
+    private static final int FLAG_IS_VIRTUAL_LONG = 0x2;
+    private static final int FLAG_HAS_SESSION = 0x4;
+
+    private final int mViewId;
+    private int mFlags;
+    private final int mVirtualIntId;
+    private final long mVirtualLongId;
+    private int mSessionId;
+
+    /** @hide */
+    @TestApi
+    public AutofillId(int id) {
+        this(/* flags= */ 0, id, View.NO_ID, NO_SESSION);
+    }
+
+    /** @hide */
+    @TestApi
+    public AutofillId(@NonNull AutofillId hostId, int virtualChildId) {
+        this(FLAG_IS_VIRTUAL_INT, hostId.mViewId, virtualChildId, NO_SESSION);
+    }
+
+    /** @hide */
+    @TestApi
+    public AutofillId(int hostId, int virtualChildId) {
+        this(FLAG_IS_VIRTUAL_INT, hostId, virtualChildId, NO_SESSION);
+    }
+
+    /** @hide */
+    @TestApi
+    public AutofillId(@NonNull AutofillId hostId, long virtualChildId, int sessionId) {
+        this(FLAG_IS_VIRTUAL_LONG | FLAG_HAS_SESSION, hostId.mViewId, virtualChildId, sessionId);
+    }
+
+    private AutofillId(int flags, int parentId, long virtualChildId, int sessionId) {
+        mFlags = flags;
+        mViewId = parentId;
+        mVirtualIntId = ((flags & FLAG_IS_VIRTUAL_INT) != 0) ? (int) virtualChildId : View.NO_ID;
+        mVirtualLongId = ((flags & FLAG_IS_VIRTUAL_LONG) != 0) ? virtualChildId : View.NO_ID;
+        mSessionId = sessionId;
+    }
+
+    /** @hide */
+    @NonNull
+    @TestApi
+    public static AutofillId withoutSession(@NonNull AutofillId id) {
+        final int flags = id.mFlags & ~FLAG_HAS_SESSION;
+        final long virtualChildId =
+                ((id.mFlags & FLAG_IS_VIRTUAL_LONG) != 0) ? id.mVirtualLongId
+                        : id.mVirtualIntId;
+        return new AutofillId(flags, id.mViewId, virtualChildId, NO_SESSION);
+    }
+
+    /** @hide */
+    public int getViewId() {
+        return mViewId;
+    }
+
+    /**
+     * Gets the virtual child id.
+     *
+     * <p>Should only be used on subsystems where such id is represented by an {@code int}
+     * (Assist and Autofill).
+     *
+     * @hide
+     */
+    public int getVirtualChildIntId() {
+        return mVirtualIntId;
+    }
+
+    /**
+     * Gets the virtual child id.
+     *
+     * <p>Should only be used on subsystems where such id is represented by a {@code long}
+     * (ContentCapture).
+     *
+     * @hide
+     */
+    public long getVirtualChildLongId() {
+        return mVirtualLongId;
+    }
+
+    /**
+     * Checks whether this node represents a virtual child, whose id is represented by an
+     * {@code int}.
+     *
+     * <p>Should only be used on subsystems where such id is represented by an {@code int}
+     * (Assist and Autofill).
+     *
+     * @hide
+     */
+    public boolean isVirtualInt() {
+        return (mFlags & FLAG_IS_VIRTUAL_INT) != 0;
+    }
+
+    /**
+     * Checks whether this node represents a virtual child, whose id is represented by an
+     * {@code long}.
+     *
+     * <p>Should only be used on subsystems where such id is represented by a {@code long}
+     * (ContentCapture).
+     *
+     * @hide
+     */
+    public boolean isVirtualLong() {
+        return (mFlags & FLAG_IS_VIRTUAL_LONG) != 0;
+    }
+
+    /**
+     * Checks whether this node represents a non-virtual child.
+     *
+     * @hide
+     */
+    @TestApi
+    public boolean isNonVirtual() {
+        return !isVirtualInt() && !isVirtualLong();
+    }
+
+    /** @hide */
+    public boolean hasSession() {
+        return (mFlags & FLAG_HAS_SESSION) != 0;
+    }
+
+    /** @hide */
+    public int getSessionId() {
+        return mSessionId;
+    }
+
+    /** @hide */
+    public void setSessionId(int sessionId) {
+        mFlags |= FLAG_HAS_SESSION;
+        mSessionId = sessionId;
+    }
+
+    /** @hide */
+    public void resetSessionId() {
+        mFlags &= ~FLAG_HAS_SESSION;
+        mSessionId = NO_SESSION;
+    }
+
+    /////////////////////////////////
+    //  Object "contract" methods. //
+    /////////////////////////////////
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + mViewId;
+        result = prime * result + mVirtualIntId;
+        result = prime * result + (int) (mVirtualLongId ^ (mVirtualLongId >>> 32));
+        result = prime * result + mSessionId;
+        return result;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (this == obj) return true;
+        if (obj == null) return false;
+        if (getClass() != obj.getClass()) return false;
+        final AutofillId other = (AutofillId) obj;
+        if (mViewId != other.mViewId) return false;
+        if (mVirtualIntId != other.mVirtualIntId) return false;
+        if (mVirtualLongId != other.mVirtualLongId) return false;
+        if (mSessionId != other.mSessionId) return false;
+        return true;
+    }
+
+    /** @hide */
+    @TestApi
+    public boolean equalsIgnoreSession(@Nullable AutofillId other) {
+        if (this == other) return true;
+        if (other == null) return false;
+        if (mViewId != other.mViewId) return false;
+        if (mVirtualIntId != other.mVirtualIntId) return false;
+        if (mVirtualLongId != other.mVirtualLongId) return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder builder = new StringBuilder().append(mViewId);
+        if (isVirtualInt()) {
+            builder.append(':').append(mVirtualIntId);
+        } else if (isVirtualLong()) {
+            builder.append(':').append(mVirtualLongId);
+        }
+
+        if (hasSession()) {
+            builder.append('@').append(mSessionId);
+        }
+        return builder.toString();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeInt(mViewId);
+        parcel.writeInt(mFlags);
+        if (hasSession()) {
+            parcel.writeInt(mSessionId);
+        }
+        if (isVirtualInt()) {
+            parcel.writeInt(mVirtualIntId);
+        } else if (isVirtualLong()) {
+            parcel.writeLong(mVirtualLongId);
+        }
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<AutofillId> CREATOR =
+            new Parcelable.Creator<AutofillId>() {
+        @Override
+        public AutofillId createFromParcel(Parcel source) {
+            final int viewId = source.readInt();
+            final int flags = source.readInt();
+            final int sessionId = (flags & FLAG_HAS_SESSION) != 0 ? source.readInt() : NO_SESSION;
+            if ((flags & FLAG_IS_VIRTUAL_INT) != 0) {
+                return new AutofillId(flags, viewId, source.readInt(), sessionId);
+            }
+            if ((flags & FLAG_IS_VIRTUAL_LONG) != 0) {
+                return new AutofillId(flags, viewId, source.readLong(), sessionId);
+            }
+            return new AutofillId(flags, viewId, View.NO_ID, sessionId);
+        }
+
+        @Override
+        public AutofillId[] newArray(int size) {
+            return new AutofillId[size];
+        }
+    };
+}
diff --git a/android/view/autofill/AutofillManager.java b/android/view/autofill/AutofillManager.java
new file mode 100644
index 0000000..d065147
--- /dev/null
+++ b/android/view/autofill/AutofillManager.java
@@ -0,0 +1,3755 @@
+/*
+ * 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.view.autofill;
+
+import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
+import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
+import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED;
+import static android.view.ContentInfo.SOURCE_AUTOFILL;
+import static android.view.autofill.Helper.sDebug;
+import static android.view.autofill.Helper.sVerbose;
+import static android.view.autofill.Helper.toList;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresFeature;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.app.assist.AssistStructure.ViewNode;
+import android.app.assist.AssistStructure.ViewNodeBuilder;
+import android.app.assist.AssistStructure.ViewNodeParcelable;
+import android.content.AutofillOptions;
+import android.content.ClipData;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.Rect;
+import android.metrics.LogMaker;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.service.autofill.AutofillService;
+import android.service.autofill.FillEventHistory;
+import android.service.autofill.UserData;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.DebugUtils;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.Choreographer;
+import android.view.ContentInfo;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeProvider;
+import android.view.accessibility.AccessibilityWindowInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.os.IResultReceiver;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.SyncResultReceiver;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import sun.misc.Cleaner;
+
+//TODO: use java.lang.ref.Cleaner once Android supports Java 9
+
+/**
+ * <p>The {@link AutofillManager} class provides ways for apps and custom views to
+ * integrate with the Autofill Framework lifecycle.
+ *
+ * <p>To learn about using Autofill in your app, read
+ * the <a href="/guide/topics/text/autofill">Autofill Framework</a> guides.
+ *
+ * <h3 id="autofill-lifecycle">Autofill lifecycle</h3>
+ *
+ * <p>The autofill lifecycle starts with the creation of an autofill context associated with an
+ * activity context. The autofill context is created when one of the following methods is called for
+ * the first time in an activity context, and the current user has an enabled autofill service:
+ *
+ * <ul>
+ *   <li>{@link #notifyViewEntered(View)}
+ *   <li>{@link #notifyViewEntered(View, int, Rect)}
+ *   <li>{@link #requestAutofill(View)}
+ * </ul>
+ *
+ * <p>Typically, the context is automatically created when the first view of the activity is
+ * focused because {@code View.onFocusChanged()} indirectly calls
+ * {@link #notifyViewEntered(View)}. App developers can call {@link #requestAutofill(View)} to
+ * explicitly create it (for example, a custom view developer could offer a contextual menu action
+ * in a text-field view to let users manually request autofill).
+ *
+ * <p>After the context is created, the Android System creates a {@link android.view.ViewStructure}
+ * that represents the view hierarchy by calling
+ * {@link View#dispatchProvideAutofillStructure(android.view.ViewStructure, int)} in the root views
+ * of all application windows. By default, {@code dispatchProvideAutofillStructure()} results in
+ * subsequent calls to {@link View#onProvideAutofillStructure(android.view.ViewStructure, int)} and
+ * {@link View#onProvideAutofillVirtualStructure(android.view.ViewStructure, int)} for each view in
+ * the hierarchy.
+ *
+ * <p>The resulting {@link android.view.ViewStructure} is then passed to the autofill service, which
+ * parses it looking for views that can be autofilled. If the service finds such views, it returns
+ * a data structure to the Android System containing the following optional info:
+ *
+ * <ul>
+ *   <li>Datasets used to autofill subsets of views in the activity.
+ *   <li>Id of views that the service can save their values for future autofilling.
+ * </ul>
+ *
+ * <p>When the service returns datasets, the Android System displays an autofill dataset picker
+ * UI associated with the view, when the view is focused on and is part of a dataset.
+ * The application can be notified when the UI is shown by registering an
+ * {@link AutofillCallback} through {@link #registerCallback(AutofillCallback)}. When the user
+ * selects a dataset from the UI, all views present in the dataset are autofilled, through
+ * calls to {@link View#autofill(AutofillValue)} or {@link View#autofill(SparseArray)}.
+ *
+ * <p>When the service returns ids of savable views, the Android System keeps track of changes
+ * made to these views, so they can be used to determine if the autofill save UI is shown later.
+ *
+ * <p>The context is then finished when one of the following occurs:
+ *
+ * <ul>
+ *   <li>{@link #commit()} is called or all savable views are gone.
+ *   <li>{@link #cancel()} is called.
+ * </ul>
+ *
+ * <p>Finally, after the autofill context is commited (i.e., not cancelled), the Android System
+ * shows an autofill save UI if the value of savable views have changed. If the user selects the
+ * option to Save, the current value of the views is then sent to the autofill service.
+ *
+ * <h3 id="additional-notes">Additional notes</h3>
+ *
+ * <p>It is safe to call <code>AutofillManager</code> methods from any thread.
+ */
+@SystemService(Context.AUTOFILL_MANAGER_SERVICE)
+@RequiresFeature(PackageManager.FEATURE_AUTOFILL)
+public final class AutofillManager {
+
+    private static final String TAG = "AutofillManager";
+
+    /**
+     * Intent extra: The assist structure which captures the filled screen.
+     *
+     * <p>
+     * Type: {@link android.app.assist.AssistStructure}
+     */
+    public static final String EXTRA_ASSIST_STRUCTURE =
+            "android.view.autofill.extra.ASSIST_STRUCTURE";
+
+    /**
+     * Intent extra: The result of an authentication operation. It is
+     * either a fully populated {@link android.service.autofill.FillResponse}
+     * or a fully populated {@link android.service.autofill.Dataset} if
+     * a response or a dataset is being authenticated respectively.
+     *
+     * <p>
+     * Type: {@link android.service.autofill.FillResponse} or a
+     * {@link android.service.autofill.Dataset}
+     */
+    public static final String EXTRA_AUTHENTICATION_RESULT =
+            "android.view.autofill.extra.AUTHENTICATION_RESULT";
+
+    /**
+     * Intent extra: The optional boolean extra field provided by the
+     * {@link android.service.autofill.AutofillService} accompanying the {@link
+     * android.service.autofill.Dataset} result of an authentication operation.
+     *
+     * <p> Before {@link android.os.Build.VERSION_CODES#R}, if the authentication result is a
+     * {@link android.service.autofill.Dataset}, it'll be used to autofill the fields, and also
+     * replace the existing dataset in the cached {@link android.service.autofill.FillResponse}.
+     * That means if the user clears the field values, the autofill suggestion will show up again
+     * with the new authenticated Dataset.
+     *
+     * <p> In {@link android.os.Build.VERSION_CODES#R}, we added an exception to this behavior
+     * that if the Dataset being authenticated is a pinned dataset (see
+     * {@link android.service.autofill.InlinePresentation#isPinned()}), the old Dataset will not be
+     * replaced.
+     *
+     * <p> In {@link android.os.Build.VERSION_CODES#S}, we added this boolean extra field to
+     * allow the {@link android.service.autofill.AutofillService} to explicitly specify whether
+     * the returned authenticated Dataset is ephemeral. An ephemeral Dataset will be used to
+     * autofill once and then thrown away. Therefore, when the boolean extra is set to true, the
+     * returned Dataset will not replace the old dataset from the existing
+     * {@link android.service.autofill.FillResponse}. When it's set to false, it will. When it's not
+     * set, the old dataset will be replaced, unless it is a pinned inline suggestion, which is
+     * consistent with the behavior in {@link android.os.Build.VERSION_CODES#R}.
+     */
+    public static final String EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET =
+            "android.view.autofill.extra.AUTHENTICATION_RESULT_EPHEMERAL_DATASET";
+
+    /**
+     * Intent extra: The optional extras provided by the
+     * {@link android.service.autofill.AutofillService}.
+     *
+     * <p>For example, when the service responds to a {@link
+     * android.service.autofill.FillCallback#onSuccess(android.service.autofill.FillResponse)} with
+     * a {@code FillResponse} that requires authentication, the Intent that launches the
+     * service authentication will contain the Bundle set by
+     * {@link android.service.autofill.FillResponse.Builder#setClientState(Bundle)} on this extra.
+     *
+     * <p>On Android {@link android.os.Build.VERSION_CODES#P} and higher, the autofill service
+     * can also add this bundle to the {@link Intent} set as the
+     * {@link android.app.Activity#setResult(int, Intent) result} for an authentication request,
+     * so the bundle can be recovered later on
+     * {@link android.service.autofill.SaveRequest#getClientState()}.
+     *
+     * <p>
+     * Type: {@link android.os.Bundle}
+     */
+    public static final String EXTRA_CLIENT_STATE =
+            "android.view.autofill.extra.CLIENT_STATE";
+
+    /**
+     * Intent extra: the {@link android.view.inputmethod.InlineSuggestionsRequest} in the
+     * autofill request.
+     *
+     * <p>This is filled in the authentication intent so the
+     * {@link android.service.autofill.AutofillService} can use it to create the inline
+     * suggestion {@link android.service.autofill.Dataset} in the response, if the original autofill
+     * request contains the {@link android.view.inputmethod.InlineSuggestionsRequest}.
+     */
+    public static final String EXTRA_INLINE_SUGGESTIONS_REQUEST =
+            "android.view.autofill.extra.INLINE_SUGGESTIONS_REQUEST";
+
+    /** @hide */
+    public static final String EXTRA_RESTORE_SESSION_TOKEN =
+            "android.view.autofill.extra.RESTORE_SESSION_TOKEN";
+
+    /** @hide */
+    public static final String EXTRA_RESTORE_CROSS_ACTIVITY =
+            "android.view.autofill.extra.RESTORE_CROSS_ACTIVITY";
+
+    /**
+     * Internal extra used to pass a binder to the {@link IAugmentedAutofillManagerClient}.
+     *
+     * @hide
+     */
+    public static final String EXTRA_AUGMENTED_AUTOFILL_CLIENT =
+            "android.view.autofill.extra.AUGMENTED_AUTOFILL_CLIENT";
+
+    private static final String SESSION_ID_TAG = "android:sessionId";
+    private static final String STATE_TAG = "android:state";
+    private static final String LAST_AUTOFILLED_DATA_TAG = "android:lastAutoFilledData";
+
+    /** @hide */ public static final int ACTION_START_SESSION = 1;
+    /** @hide */ public static final int ACTION_VIEW_ENTERED =  2;
+    /** @hide */ public static final int ACTION_VIEW_EXITED = 3;
+    /** @hide */ public static final int ACTION_VALUE_CHANGED = 4;
+    /** @hide */ public static final int ACTION_RESPONSE_EXPIRED = 5;
+
+    /** @hide */ public static final int NO_LOGGING = 0;
+    /** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED = 0x1;
+    /** @hide */ public static final int FLAG_ADD_CLIENT_DEBUG = 0x2;
+    /** @hide */ public static final int FLAG_ADD_CLIENT_VERBOSE = 0x4;
+    /** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY = 0x8;
+
+    // NOTE: flag below is used by the session start receiver only, hence it can have values above
+    /** @hide */ public static final int RECEIVER_FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY = 0x1;
+
+    /** @hide */
+    public static final int DEFAULT_LOGGING_LEVEL = Build.IS_DEBUGGABLE
+            ? AutofillManager.FLAG_ADD_CLIENT_DEBUG
+            : AutofillManager.NO_LOGGING;
+
+    /** @hide */
+    public static final int DEFAULT_MAX_PARTITIONS_SIZE = 10;
+
+    /** Which bits in an authentication id are used for the dataset id */
+    private static final int AUTHENTICATION_ID_DATASET_ID_MASK = 0xFFFF;
+    /** How many bits in an authentication id are used for the dataset id */
+    private static final int AUTHENTICATION_ID_DATASET_ID_SHIFT = 16;
+    /** @hide The index for an undefined data set */
+    public static final int AUTHENTICATION_ID_DATASET_ID_UNDEFINED = 0xFFFF;
+
+    /**
+     * Used on {@link #onPendingSaveUi(int, IBinder)} to cancel the pending UI.
+     *
+     * @hide
+     */
+    public static final int PENDING_UI_OPERATION_CANCEL = 1;
+
+    /**
+     * Used on {@link #onPendingSaveUi(int, IBinder)} to restore the pending UI.
+     *
+     * @hide
+     */
+    public static final int PENDING_UI_OPERATION_RESTORE = 2;
+
+    /**
+     * Initial state of the autofill context, set when there is no session (i.e., when
+     * {@link #mSessionId} is {@link #NO_SESSION}).
+     *
+     * <p>In this state, app callbacks (such as {@link #notifyViewEntered(View)}) are notified to
+     * the server.
+     *
+     * @hide
+     */
+    public static final int STATE_UNKNOWN = 0;
+
+    /**
+     * State where the autofill context hasn't been {@link #commit() finished} nor
+     * {@link #cancel() canceled} yet.
+     *
+     * @hide
+     */
+    public static final int STATE_ACTIVE = 1;
+
+    /**
+     * State where the autofill context was finished by the server because the autofill
+     * service could not autofill the activity.
+     *
+     * <p>In this state, most apps callback (such as {@link #notifyViewEntered(View)}) are ignored,
+     * exception {@link #requestAutofill(View)} (and {@link #requestAutofill(View, int, Rect)}).
+     *
+     * @hide
+     */
+    public static final int STATE_FINISHED = 2;
+
+    /**
+     * State where the autofill context has been {@link #commit() finished} but the server still has
+     * a session because the Save UI hasn't been dismissed yet.
+     *
+     * @hide
+     */
+    public static final int STATE_SHOWING_SAVE_UI = 3;
+
+    /**
+     * State where the autofill is disabled because the service cannot autofill the activity at all.
+     *
+     * <p>In this state, every call is ignored, even {@link #requestAutofill(View)}
+     * (and {@link #requestAutofill(View, int, Rect)}).
+     *
+     * @hide
+     */
+    public static final int STATE_DISABLED_BY_SERVICE = 4;
+
+    /**
+     * Same as {@link #STATE_UNKNOWN}, but used on
+     * {@link AutofillManagerClient#setSessionFinished(int, List)} when the session was finished
+     * because the URL bar changed on client mode
+     *
+     * @hide
+     */
+    public static final int STATE_UNKNOWN_COMPAT_MODE = 5;
+
+    /**
+     * Same as {@link #STATE_UNKNOWN}, but used on
+     * {@link AutofillManagerClient#setSessionFinished(int, List)} when the session was finished
+     * because the service failed to fullfil a request.
+     *
+     * @hide
+     */
+    public static final int STATE_UNKNOWN_FAILED = 6;
+
+    /**
+     * Timeout in ms for calls to the field classification service.
+     * @hide
+     */
+    public static final int FC_SERVICE_TIMEOUT = 5000;
+
+    /**
+     * Timeout for calls to system_server.
+     */
+    private static final int SYNC_CALLS_TIMEOUT_MS = 5000;
+
+    /**
+     * @hide
+     */
+    @TestApi
+    public static final int MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
+
+    /**
+     * Disables Augmented Autofill.
+     *
+     * @hide
+     */
+    @TestApi
+    public static final int FLAG_SMART_SUGGESTION_OFF = 0x0;
+
+    /**
+     * Displays the Augment Autofill window using the same mechanism (such as a popup-window
+     * attached to the focused view) as the standard autofill.
+     *
+     * @hide
+     */
+    @TestApi
+    public static final int FLAG_SMART_SUGGESTION_SYSTEM = 0x1;
+
+    /** @hide */
+    @IntDef(flag = false, value = { FLAG_SMART_SUGGESTION_OFF, FLAG_SMART_SUGGESTION_SYSTEM })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SmartSuggestionMode {}
+
+    /**
+     * {@code DeviceConfig} property used to set which Smart Suggestion modes for Augmented Autofill
+     * are available.
+     *
+     * @hide
+     */
+    @TestApi
+    public static final String DEVICE_CONFIG_AUTOFILL_SMART_SUGGESTION_SUPPORTED_MODES =
+            "smart_suggestion_supported_modes";
+
+    /**
+     * Sets how long (in ms) the augmented autofill service is bound while idle.
+     *
+     * <p>Use {@code 0} to keep it permanently bound.
+     *
+     * @hide
+     */
+    public static final String DEVICE_CONFIG_AUGMENTED_SERVICE_IDLE_UNBIND_TIMEOUT =
+            "augmented_service_idle_unbind_timeout";
+
+    /**
+     * Sets how long (in ms) the augmented autofill service request is killed if not replied.
+     *
+     * @hide
+     */
+    public static final String DEVICE_CONFIG_AUGMENTED_SERVICE_REQUEST_TIMEOUT =
+            "augmented_service_request_timeout";
+
+    /** @hide */
+    public static final int RESULT_OK = 0;
+    /** @hide */
+    public static final int RESULT_CODE_NOT_SERVICE = -1;
+
+    /**
+     * Makes an authentication id from a request id and a dataset id.
+     *
+     * @param requestId The request id.
+     * @param datasetId The dataset id.
+     * @return The authentication id.
+     * @hide
+     */
+    public static int makeAuthenticationId(int requestId, int datasetId) {
+        return (requestId << AUTHENTICATION_ID_DATASET_ID_SHIFT)
+                | (datasetId & AUTHENTICATION_ID_DATASET_ID_MASK);
+    }
+
+    /**
+     * Gets the request id from an authentication id.
+     *
+     * @param authRequestId The authentication id.
+     * @return The request id.
+     * @hide
+     */
+    public static int getRequestIdFromAuthenticationId(int authRequestId) {
+        return (authRequestId >> AUTHENTICATION_ID_DATASET_ID_SHIFT);
+    }
+
+    /**
+     * Gets the dataset id from an authentication id.
+     *
+     * @param authRequestId The authentication id.
+     * @return The dataset id.
+     * @hide
+     */
+    public static int getDatasetIdFromAuthenticationId(int authRequestId) {
+        return (authRequestId & AUTHENTICATION_ID_DATASET_ID_MASK);
+    }
+
+    private final MetricsLogger mMetricsLogger = new MetricsLogger();
+
+    /**
+     * There is currently no session running.
+     * {@hide}
+     */
+    public static final int NO_SESSION = Integer.MAX_VALUE;
+
+    private final IAutoFillManager mService;
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private IAutoFillManagerClient mServiceClient;
+
+    @GuardedBy("mLock")
+    private Cleaner mServiceClientCleaner;
+
+    @GuardedBy("mLock")
+    private IAugmentedAutofillManagerClient mAugmentedAutofillServiceClient;
+
+    @GuardedBy("mLock")
+    private AutofillCallback mCallback;
+
+    private final Context mContext;
+
+    @GuardedBy("mLock")
+    private int mSessionId = NO_SESSION;
+
+    @GuardedBy("mLock")
+    private int mState = STATE_UNKNOWN;
+
+    @GuardedBy("mLock")
+    private boolean mEnabled;
+
+    /** If a view changes to this mapping the autofill operation was successful */
+    @GuardedBy("mLock")
+    @Nullable private ParcelableMap mLastAutofilledData;
+
+    /** If view tracking is enabled, contains the tracking state */
+    @GuardedBy("mLock")
+    @Nullable private TrackedViews mTrackedViews;
+
+    /** Views that are only tracked because they are fillable and could be anchoring the UI. */
+    @GuardedBy("mLock")
+    @Nullable private ArraySet<AutofillId> mFillableIds;
+
+    /** id of last requested autofill ui */
+    @Nullable private AutofillId mIdShownFillUi;
+
+    /**
+     * Views that were already "entered" - if they're entered again when the session is not active,
+     * they're ignored
+     * */
+    @GuardedBy("mLock")
+    @Nullable private ArraySet<AutofillId> mEnteredIds;
+
+    /**
+     * Views that were otherwised not important for autofill but triggered a session because the
+     * context is allowlisted for augmented autofill.
+     */
+    @GuardedBy("mLock")
+    @Nullable private Set<AutofillId> mEnteredForAugmentedAutofillIds;
+
+    /** If set, session is commited when the field is clicked. */
+    @GuardedBy("mLock")
+    @Nullable private AutofillId mSaveTriggerId;
+
+    /** set to true when onInvisibleForAutofill is called, used by onAuthenticationResult */
+    @GuardedBy("mLock")
+    private boolean mOnInvisibleCalled;
+
+    /** If set, session is commited when the activity is finished; otherwise session is canceled. */
+    @GuardedBy("mLock")
+    private boolean mSaveOnFinish;
+
+    /** If compatibility mode is enabled - this is a bridge to interact with a11y */
+    @GuardedBy("mLock")
+    private CompatibilityBridge mCompatibilityBridge;
+
+    @Nullable
+    private final AutofillOptions mOptions;
+
+    /** When set, session is only used for augmented autofill requests. */
+    @GuardedBy("mLock")
+    private boolean mForAugmentedAutofillOnly;
+
+    /**
+     * When set, standard autofill is disabled, but sessions can still be created for augmented
+     * autofill only.
+     */
+    @GuardedBy("mLock")
+    private boolean mEnabledForAugmentedAutofillOnly;
+
+    /** @hide */
+    public interface AutofillClient {
+        /**
+         * Asks the client to start an authentication flow.
+         *
+         * @param authenticationId A unique id of the authentication operation.
+         * @param intent The authentication intent.
+         * @param fillInIntent The authentication fill-in intent.
+         */
+        void autofillClientAuthenticate(int authenticationId, IntentSender intent,
+                Intent fillInIntent, boolean authenticateInline);
+
+        /**
+         * Tells the client this manager has state to be reset.
+         */
+        void autofillClientResetableStateAvailable();
+
+        /**
+         * Request showing the autofill UI.
+         *
+         * @param anchor The real view the UI needs to anchor to.
+         * @param width The width of the fill UI content.
+         * @param height The height of the fill UI content.
+         * @param virtualBounds The bounds of the virtual decendant of the anchor.
+         * @param presenter The presenter that controls the fill UI window.
+         * @return Whether the UI was shown.
+         */
+        boolean autofillClientRequestShowFillUi(@NonNull View anchor, int width, int height,
+                @Nullable Rect virtualBounds, IAutofillWindowPresenter presenter);
+
+        /**
+         * Dispatch unhandled keyevent from Autofill window
+         * @param anchor The real view the UI needs to anchor to.
+         * @param keyEvent Unhandled KeyEvent from autofill window.
+         */
+        void autofillClientDispatchUnhandledKey(@NonNull View anchor, @NonNull KeyEvent keyEvent);
+
+        /**
+         * Request hiding the autofill UI.
+         *
+         * @return Whether the UI was hidden.
+         */
+        boolean autofillClientRequestHideFillUi();
+
+        /**
+         * Gets whether the fill UI is currenlty being shown.
+         *
+         * @return Whether the fill UI is currently being shown
+         */
+        boolean autofillClientIsFillUiShowing();
+
+        /**
+         * Checks if views are currently attached and visible.
+         *
+         * @return And array with {@code true} iff the view is attached or visible
+         */
+        @NonNull boolean[] autofillClientGetViewVisibility(@NonNull AutofillId[] autofillIds);
+
+        /**
+         * Checks is the client is currently visible as understood by autofill.
+         *
+         * @return {@code true} if the client is currently visible
+         */
+        boolean autofillClientIsVisibleForAutofill();
+
+        /**
+         * Client might disable enter/exit event e.g. when activity is paused.
+         */
+        boolean isDisablingEnterExitEventForAutofill();
+
+        /**
+         * Finds views by traversing the hierarchies of the client.
+         *
+         * @param autofillIds The autofill ids of the views to find
+         *
+         * @return And array containing the views (empty if no views found).
+         */
+        @NonNull View[] autofillClientFindViewsByAutofillIdTraversal(
+                @NonNull AutofillId[] autofillIds);
+
+        /**
+         * Finds a view by traversing the hierarchies of the client.
+         *
+         * @param autofillId The autofill id of the views to find
+         *
+         * @return The view, or {@code null} if not found
+         */
+        @Nullable View autofillClientFindViewByAutofillIdTraversal(@NonNull AutofillId autofillId);
+
+        /**
+         * Finds a view by a11y id in a given client window.
+         *
+         * @param viewId The accessibility id of the views to find
+         * @param windowId The accessibility window id where to search
+         *
+         * @return The view, or {@code null} if not found
+         */
+        @Nullable View autofillClientFindViewByAccessibilityIdTraversal(int viewId, int windowId);
+
+        /**
+         * Runs the specified action on the UI thread.
+         */
+        void autofillClientRunOnUiThread(Runnable action);
+
+        /**
+         * Gets the complete component name of this client.
+         */
+        ComponentName autofillClientGetComponentName();
+
+        /**
+         * Gets the activity token
+         */
+        @Nullable IBinder autofillClientGetActivityToken();
+
+        /**
+          * @return Whether compatibility mode is enabled.
+          */
+        boolean autofillClientIsCompatibilityModeEnabled();
+
+        /**
+         * Gets the next unique autofill ID.
+         *
+         * <p>Typically used to manage views whose content is recycled - see
+         * {@link View#setAutofillId(AutofillId)} for more info.
+         *
+         * @return An ID that is unique in the activity.
+         */
+        @Nullable AutofillId autofillClientGetNextAutofillId();
+    }
+
+    /**
+     * @hide
+     */
+    public AutofillManager(Context context, IAutoFillManager service) {
+        mContext = Preconditions.checkNotNull(context, "context cannot be null");
+        mService = service;
+        mOptions = context.getAutofillOptions();
+
+        if (mOptions != null) {
+            sDebug = (mOptions.loggingLevel & FLAG_ADD_CLIENT_DEBUG) != 0;
+            sVerbose = (mOptions.loggingLevel & FLAG_ADD_CLIENT_VERBOSE) != 0;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public void enableCompatibilityMode() {
+        synchronized (mLock) {
+            // The accessibility manager is a singleton so we may need to plug
+            // different bridge based on which activity is currently focused
+            // in the current process. Since compat would be rarely used, just
+            // create and register a new instance every time.
+            if (sDebug) {
+                Slog.d(TAG, "creating CompatibilityBridge for " + mContext);
+            }
+            mCompatibilityBridge = new CompatibilityBridge();
+        }
+    }
+
+    /**
+     * Restore state after activity lifecycle
+     *
+     * @param savedInstanceState The state to be restored
+     *
+     * {@hide}
+     */
+    public void onCreate(Bundle savedInstanceState) {
+        if (!hasAutofillFeature()) {
+            return;
+        }
+        synchronized (mLock) {
+            mLastAutofilledData = savedInstanceState.getParcelable(LAST_AUTOFILLED_DATA_TAG);
+
+            if (isActiveLocked()) {
+                Log.w(TAG, "New session was started before onCreate()");
+                return;
+            }
+
+            mSessionId = savedInstanceState.getInt(SESSION_ID_TAG, NO_SESSION);
+            mState = savedInstanceState.getInt(STATE_TAG, STATE_UNKNOWN);
+
+            if (mSessionId != NO_SESSION) {
+                final boolean clientAdded = tryAddServiceClientIfNeededLocked();
+
+                final AutofillClient client = getClient();
+                if (client != null) {
+                    final SyncResultReceiver receiver = new SyncResultReceiver(
+                            SYNC_CALLS_TIMEOUT_MS);
+                    try {
+                        boolean sessionWasRestored = false;
+                        if (clientAdded) {
+                            mService.restoreSession(mSessionId,
+                                    client.autofillClientGetActivityToken(),
+                                    mServiceClient.asBinder(), receiver);
+                            sessionWasRestored = receiver.getIntResult() == 1;
+                        } else {
+                            Log.w(TAG, "No service client for session " + mSessionId);
+                        }
+
+                        if (!sessionWasRestored) {
+                            Log.w(TAG, "Session " + mSessionId + " could not be restored");
+                            mSessionId = NO_SESSION;
+                            mState = STATE_UNKNOWN;
+                        } else {
+                            if (sDebug) {
+                                Log.d(TAG, "session " + mSessionId + " was restored");
+                            }
+
+                            client.autofillClientResetableStateAvailable();
+                        }
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Could not figure out if there was an autofill session", e);
+                    } catch (SyncResultReceiver.TimeoutException e) {
+                        Log.e(TAG, "Fail to get session restore status: " + e);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Called once the client becomes visible.
+     *
+     * @see AutofillClient#autofillClientIsVisibleForAutofill()
+     *
+     * {@hide}
+     */
+    public void onVisibleForAutofill() {
+        // This gets called when the client just got visible at which point the visibility
+        // of the tracked views may not have been computed (due to a pending layout, etc).
+        // While generally we have no way to know when the UI has settled. We will evaluate
+        // the tracked views state at the end of next frame to guarantee that everything
+        // that may need to be laid out is laid out.
+        Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, () -> {
+            synchronized (mLock) {
+                if (mEnabled && isActiveLocked() && mTrackedViews != null) {
+                    mTrackedViews.onVisibleForAutofillChangedLocked();
+                }
+            }
+        }, null);
+    }
+
+    /**
+     * Called once the client becomes invisible.
+     *
+     * @see AutofillClient#autofillClientIsVisibleForAutofill()
+     *
+     * @param isExpiredResponse The response has expired or not
+     *
+     * {@hide}
+     */
+    public void onInvisibleForAutofill(boolean isExpiredResponse) {
+        synchronized (mLock) {
+            mOnInvisibleCalled = true;
+
+            if (isExpiredResponse) {
+                // Notify service the response has expired.
+                updateSessionLocked(/* id= */ null, /* bounds= */ null, /* value= */ null,
+                        ACTION_RESPONSE_EXPIRED, /* flags= */ 0);
+            }
+        }
+    }
+
+    /**
+     * Save state before activity lifecycle
+     *
+     * @param outState Place to store the state
+     *
+     * {@hide}
+     */
+    public void onSaveInstanceState(Bundle outState) {
+        if (!hasAutofillFeature()) {
+            return;
+        }
+        synchronized (mLock) {
+            if (mSessionId != NO_SESSION) {
+                outState.putInt(SESSION_ID_TAG, mSessionId);
+            }
+            if (mState != STATE_UNKNOWN) {
+                outState.putInt(STATE_TAG, mState);
+            }
+            if (mLastAutofilledData != null) {
+                outState.putParcelable(LAST_AUTOFILLED_DATA_TAG, mLastAutofilledData);
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @GuardedBy("mLock")
+    public boolean isCompatibilityModeEnabledLocked() {
+        return mCompatibilityBridge != null;
+    }
+
+    /**
+     * Checks whether autofill is enabled for the current user.
+     *
+     * <p>Typically used to determine whether the option to explicitly request autofill should
+     * be offered - see {@link #requestAutofill(View)}.
+     *
+     * @return whether autofill is enabled for the current user.
+     */
+    public boolean isEnabled() {
+        if (!hasAutofillFeature()) {
+            return false;
+        }
+        synchronized (mLock) {
+            if (isDisabledByServiceLocked()) {
+                return false;
+            }
+            final boolean clientAdded = tryAddServiceClientIfNeededLocked();
+            return clientAdded ? mEnabled : false;
+        }
+    }
+
+    /**
+     * Should always be called from {@link AutofillService#getFillEventHistory()}.
+     *
+     * @hide
+     */
+    @Nullable public FillEventHistory getFillEventHistory() {
+        try {
+            final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
+            mService.getFillEventHistory(receiver);
+            return receiver.getParcelableResult();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (SyncResultReceiver.TimeoutException e) {
+            Log.e(TAG, "Fail to get fill event history: " + e);
+            return null;
+        }
+    }
+
+    /**
+     * Explicitly requests a new autofill context.
+     *
+     * <p>Normally, the autofill context is automatically started if necessary when
+     * {@link #notifyViewEntered(View)} is called, but this method should be used in the
+     * cases where it must be explicitly started. For example, when the view offers an AUTOFILL
+     * option on its contextual overflow menu, and the user selects it.
+     *
+     * @param view view requesting the new autofill context.
+     */
+    public void requestAutofill(@NonNull View view) {
+        int flags = FLAG_MANUAL_REQUEST;
+        if (!view.isFocused()) {
+            flags |= FLAG_VIEW_NOT_FOCUSED;
+        }
+        notifyViewEntered(view, flags);
+    }
+
+    /**
+     * Explicitly cancels the current session and requests a new autofill context.
+     *
+     * <p>Normally, the autofill context is automatically started if necessary when
+     * {@link #notifyViewEntered(View)} is called, but this method should be used in
+     * cases where it must be explicitly started or restarted. Currently, this method should only
+     * be called by
+     * {@link android.service.autofill.augmented.AugmentedAutofillService#requestAutofill(
+     * ComponentName, AutofillId)} to cancel the current session and trigger the autofill flow in
+     * a new session, giving the autofill service or the augmented autofill service a chance to
+     * send updated suggestions.
+     *
+     * @param view view requesting the new autofill context.
+     */
+    void requestAutofillFromNewSession(@NonNull View view) {
+        cancel();
+        notifyViewEntered(view);
+    }
+
+    /**
+     * Explicitly requests a new autofill context for virtual views.
+     *
+     * <p>Normally, the autofill context is automatically started if necessary when
+     * {@link #notifyViewEntered(View, int, Rect)} is called, but this method should be used in the
+     * cases where it must be explicitly started. For example, when the virtual view offers an
+     * AUTOFILL option on its contextual overflow menu, and the user selects it.
+     *
+     * <p>The virtual view boundaries must be absolute screen coordinates. For example, if the
+     * parent view uses {@code bounds} to draw the virtual view inside its Canvas,
+     * the absolute bounds could be calculated by:
+     *
+     * <pre class="prettyprint">
+     *   int offset[] = new int[2];
+     *   getLocationOnScreen(offset);
+     *   Rect absBounds = new Rect(bounds.left + offset[0],
+     *       bounds.top + offset[1],
+     *       bounds.right + offset[0], bounds.bottom + offset[1]);
+     * </pre>
+     *
+     * @param view the virtual view parent.
+     * @param virtualId id identifying the virtual child inside the parent view.
+     * @param absBounds absolute boundaries of the virtual view in the screen.
+     */
+    public void requestAutofill(@NonNull View view, int virtualId, @NonNull Rect absBounds) {
+        int flags = FLAG_MANUAL_REQUEST;
+        if (!view.isFocused()) {
+            flags |= FLAG_VIEW_NOT_FOCUSED;
+        }
+        notifyViewEntered(view, virtualId, absBounds, flags);
+    }
+
+    /**
+     * Called when a {@link View} that supports autofill is entered.
+     *
+     * @param view {@link View} that was entered.
+     */
+    public void notifyViewEntered(@NonNull View view) {
+        notifyViewEntered(view, 0);
+    }
+
+    @GuardedBy("mLock")
+    private boolean shouldIgnoreViewEnteredLocked(@NonNull AutofillId id, int flags) {
+        if (isDisabledByServiceLocked()) {
+            if (sVerbose) {
+                Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + id
+                        + ") on state " + getStateAsStringLocked() + " because disabled by svc");
+            }
+            return true;
+        }
+        if (isFinishedLocked()) {
+            // Session already finished: ignore if automatic request and view already entered
+            if ((flags & FLAG_MANUAL_REQUEST) == 0 && mEnteredIds != null
+                    && mEnteredIds.contains(id)) {
+                if (sVerbose) {
+                    Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + id
+                            + ") on state " + getStateAsStringLocked()
+                            + " because view was already entered: " + mEnteredIds);
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean isClientVisibleForAutofillLocked() {
+        final AutofillClient client = getClient();
+        return client != null && client.autofillClientIsVisibleForAutofill();
+    }
+
+    private boolean isClientDisablingEnterExitEvent() {
+        final AutofillClient client = getClient();
+        return client != null && client.isDisablingEnterExitEventForAutofill();
+    }
+
+    private void notifyViewEntered(@NonNull View view, int flags) {
+        if (!hasAutofillFeature()) {
+            return;
+        }
+        AutofillCallback callback;
+        synchronized (mLock) {
+            callback = notifyViewEnteredLocked(view, flags);
+        }
+
+        if (callback != null) {
+            mCallback.onAutofillEvent(view, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
+        }
+    }
+
+    /** Returns AutofillCallback if need fire EVENT_INPUT_UNAVAILABLE */
+    @GuardedBy("mLock")
+    private AutofillCallback notifyViewEnteredLocked(@NonNull View view, int flags) {
+        final AutofillId id = view.getAutofillId();
+        if (shouldIgnoreViewEnteredLocked(id, flags)) return null;
+
+        AutofillCallback callback = null;
+
+        final boolean clientAdded = tryAddServiceClientIfNeededLocked();
+
+        if (!clientAdded) {
+            if (sVerbose) Log.v(TAG, "ignoring notifyViewEntered(" + id + "): no service client");
+            return callback;
+        }
+
+        if (!mEnabled && !mEnabledForAugmentedAutofillOnly) {
+            if (sVerbose) Log.v(TAG, "ignoring notifyViewEntered(" + id + "): disabled");
+
+            if (mCallback != null) {
+                callback = mCallback;
+            }
+        } else {
+            // don't notify entered when Activity is already in background
+            if (!isClientDisablingEnterExitEvent()) {
+                final AutofillValue value = view.getAutofillValue();
+
+                if (view instanceof TextView && ((TextView) view).isAnyPasswordInputType()) {
+                    flags |= FLAG_PASSWORD_INPUT_TYPE;
+                }
+
+                if (!isActiveLocked()) {
+                    // Starts new session.
+                    startSessionLocked(id, null, value, flags);
+                } else {
+                    // Update focus on existing session.
+                    if (mForAugmentedAutofillOnly && (flags & FLAG_MANUAL_REQUEST) != 0) {
+                        if (sDebug) {
+                            Log.d(TAG, "notifyViewEntered(" + id + "): resetting "
+                                    + "mForAugmentedAutofillOnly on manual request");
+                        }
+                        mForAugmentedAutofillOnly = false;
+                    }
+                    updateSessionLocked(id, null, value, ACTION_VIEW_ENTERED, flags);
+                }
+                addEnteredIdLocked(id);
+            }
+        }
+        return callback;
+    }
+
+    /**
+     * Called when a {@link View} that supports autofill is exited.
+     *
+     * @param view {@link View} that was exited.
+     */
+    public void notifyViewExited(@NonNull View view) {
+        if (!hasAutofillFeature()) {
+            return;
+        }
+        synchronized (mLock) {
+            notifyViewExitedLocked(view);
+        }
+    }
+
+    @GuardedBy("mLock")
+    void notifyViewExitedLocked(@NonNull View view) {
+        final boolean clientAdded = tryAddServiceClientIfNeededLocked();
+
+        if (clientAdded && (mEnabled || mEnabledForAugmentedAutofillOnly)
+                && isActiveLocked()) {
+            // dont notify exited when Activity is already in background
+            if (!isClientDisablingEnterExitEvent()) {
+                final AutofillId id = view.getAutofillId();
+
+                // Update focus on existing session.
+                updateSessionLocked(id, null, null, ACTION_VIEW_EXITED, 0);
+            }
+        }
+    }
+
+    /**
+     * Called when a {@link View view's} visibility changed.
+     *
+     * @param view {@link View} that was exited.
+     * @param isVisible visible if the view is visible in the view hierarchy.
+     */
+    public void notifyViewVisibilityChanged(@NonNull View view, boolean isVisible) {
+        notifyViewVisibilityChangedInternal(view, 0, isVisible, false);
+    }
+
+    /**
+     * Called when a virtual view's visibility changed.
+     *
+     * @param view {@link View} that was exited.
+     * @param virtualId id identifying the virtual child inside the parent view.
+     * @param isVisible visible if the view is visible in the view hierarchy.
+     */
+    public void notifyViewVisibilityChanged(@NonNull View view, int virtualId, boolean isVisible) {
+        notifyViewVisibilityChangedInternal(view, virtualId, isVisible, true);
+    }
+
+    /**
+     * Called when a view/virtual view's visibility changed.
+     *
+     * @param view {@link View} that was exited.
+     * @param virtualId id identifying the virtual child inside the parent view.
+     * @param isVisible visible if the view is visible in the view hierarchy.
+     * @param virtual Whether the view is virtual.
+     */
+    private void notifyViewVisibilityChangedInternal(@NonNull View view, int virtualId,
+            boolean isVisible, boolean virtual) {
+        synchronized (mLock) {
+            if (mForAugmentedAutofillOnly) {
+                if (sVerbose) {
+                    Log.v(TAG,  "notifyViewVisibilityChanged(): ignoring on augmented only mode");
+                }
+                return;
+            }
+            if (mEnabled && isActiveLocked()) {
+                final AutofillId id = virtual ? getAutofillId(view, virtualId)
+                        : view.getAutofillId();
+                if (sVerbose) Log.v(TAG, "visibility changed for " + id + ": " + isVisible);
+                if (!isVisible && mFillableIds != null) {
+                    if (mFillableIds.contains(id)) {
+                        if (sDebug) Log.d(TAG, "Hidding UI when view " + id + " became invisible");
+                        requestHideFillUi(id, view);
+                    }
+                }
+                if (mTrackedViews != null) {
+                    mTrackedViews.notifyViewVisibilityChangedLocked(id, isVisible);
+                } else if (sVerbose) {
+                    Log.v(TAG, "Ignoring visibility change on " + id + ": no tracked views");
+                }
+            } else if (!virtual && isVisible) {
+                startAutofillIfNeededLocked(view);
+            }
+        }
+    }
+
+    /**
+     * Called when a virtual view that supports autofill is entered.
+     *
+     * <p>The virtual view boundaries must be absolute screen coordinates. For example, if the
+     * parent, non-virtual view uses {@code bounds} to draw the virtual view inside its Canvas,
+     * the absolute bounds could be calculated by:
+     *
+     * <pre class="prettyprint">
+     *   int offset[] = new int[2];
+     *   getLocationOnScreen(offset);
+     *   Rect absBounds = new Rect(bounds.left + offset[0],
+     *       bounds.top + offset[1],
+     *       bounds.right + offset[0], bounds.bottom + offset[1]);
+     * </pre>
+     *
+     * @param view the virtual view parent.
+     * @param virtualId id identifying the virtual child inside the parent view.
+     * @param absBounds absolute boundaries of the virtual view in the screen.
+     */
+    public void notifyViewEntered(@NonNull View view, int virtualId, @NonNull Rect absBounds) {
+        notifyViewEntered(view, virtualId, absBounds, 0);
+    }
+
+    private void notifyViewEntered(View view, int virtualId, Rect bounds, int flags) {
+        if (!hasAutofillFeature()) {
+            return;
+        }
+        AutofillCallback callback;
+        synchronized (mLock) {
+            callback = notifyViewEnteredLocked(view, virtualId, bounds, flags);
+        }
+
+        if (callback != null) {
+            callback.onAutofillEvent(view, virtualId,
+                    AutofillCallback.EVENT_INPUT_UNAVAILABLE);
+        }
+    }
+
+    /** Returns AutofillCallback if need fire EVENT_INPUT_UNAVAILABLE */
+    @GuardedBy("mLock")
+    private AutofillCallback notifyViewEnteredLocked(View view, int virtualId, Rect bounds,
+                                                     int flags) {
+        final AutofillId id = getAutofillId(view, virtualId);
+        AutofillCallback callback = null;
+        if (shouldIgnoreViewEnteredLocked(id, flags)) return callback;
+
+        final boolean clientAdded = tryAddServiceClientIfNeededLocked();
+
+        if (!clientAdded) {
+            if (sVerbose) Log.v(TAG, "ignoring notifyViewEntered(" + id + "): no service client");
+            return callback;
+        }
+
+        if (!mEnabled && !mEnabledForAugmentedAutofillOnly) {
+            if (sVerbose) {
+                Log.v(TAG, "ignoring notifyViewEntered(" + id + "): disabled");
+            }
+            if (mCallback != null) {
+                callback = mCallback;
+            }
+        } else {
+            // don't notify entered when Activity is already in background
+            if (!isClientDisablingEnterExitEvent()) {
+                if (view instanceof TextView && ((TextView) view).isAnyPasswordInputType()) {
+                    flags |= FLAG_PASSWORD_INPUT_TYPE;
+                }
+
+                if (!isActiveLocked()) {
+                    // Starts new session.
+                    startSessionLocked(id, bounds, null, flags);
+                } else {
+                    // Update focus on existing session.
+                    if (mForAugmentedAutofillOnly && (flags & FLAG_MANUAL_REQUEST) != 0) {
+                        if (sDebug) {
+                            Log.d(TAG, "notifyViewEntered(" + id + "): resetting "
+                                    + "mForAugmentedAutofillOnly on manual request");
+                        }
+                        mForAugmentedAutofillOnly = false;
+                    }
+                    updateSessionLocked(id, bounds, null, ACTION_VIEW_ENTERED, flags);
+                }
+                addEnteredIdLocked(id);
+            }
+        }
+        return callback;
+    }
+
+    @GuardedBy("mLock")
+    private void addEnteredIdLocked(@NonNull AutofillId id) {
+        if (mEnteredIds == null) {
+            mEnteredIds = new ArraySet<>(1);
+        }
+        id.resetSessionId();
+        mEnteredIds.add(id);
+    }
+
+    /**
+     * Called when a virtual view that supports autofill is exited.
+     *
+     * @param view the virtual view parent.
+     * @param virtualId id identifying the virtual child inside the parent view.
+     */
+    public void notifyViewExited(@NonNull View view, int virtualId) {
+        if (sVerbose) Log.v(TAG, "notifyViewExited(" + view.getAutofillId() + ", " + virtualId);
+        if (!hasAutofillFeature()) {
+            return;
+        }
+        synchronized (mLock) {
+            notifyViewExitedLocked(view, virtualId);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void notifyViewExitedLocked(@NonNull View view, int virtualId) {
+        final boolean clientAdded = tryAddServiceClientIfNeededLocked();
+
+        if (clientAdded && (mEnabled || mEnabledForAugmentedAutofillOnly)
+                && isActiveLocked()) {
+            // don't notify exited when Activity is already in background
+            if (!isClientDisablingEnterExitEvent()) {
+                final AutofillId id = getAutofillId(view, virtualId);
+
+                // Update focus on existing session.
+                updateSessionLocked(id, null, null, ACTION_VIEW_EXITED, 0);
+            }
+        }
+    }
+
+    /**
+     * Called to indicate the value of an autofillable {@link View} changed.
+     *
+     * @param view view whose value changed.
+     */
+    public void notifyValueChanged(View view) {
+        if (!hasAutofillFeature()) {
+            return;
+        }
+        AutofillId id = null;
+        boolean valueWasRead = false;
+        AutofillValue value = null;
+
+        synchronized (mLock) {
+            // If the session is gone some fields might still be highlighted, hence we have to
+            // remove the isAutofilled property even if no sessions are active.
+            if (mLastAutofilledData == null) {
+                view.setAutofilled(false, false);
+            } else {
+                id = view.getAutofillId();
+                if (mLastAutofilledData.containsKey(id)) {
+                    value = view.getAutofillValue();
+                    valueWasRead = true;
+                    final boolean hideHighlight = mLastAutofilledData.keySet().size() == 1;
+
+                    if (Objects.equals(mLastAutofilledData.get(id), value)) {
+                        view.setAutofilled(true, hideHighlight);
+                    } else {
+                        view.setAutofilled(false, false);
+                        mLastAutofilledData.remove(id);
+                    }
+                } else {
+                    view.setAutofilled(false, false);
+                }
+            }
+
+            if (!mEnabled || !isActiveLocked()) {
+                if (!startAutofillIfNeededLocked(view)) {
+                    if (sVerbose) {
+                        Log.v(TAG, "notifyValueChanged(" + view.getAutofillId()
+                                + "): ignoring on state " + getStateAsStringLocked());
+                    }
+                }
+                return;
+            }
+
+            if (id == null) {
+                id = view.getAutofillId();
+            }
+
+            if (!valueWasRead) {
+                value = view.getAutofillValue();
+            }
+
+            updateSessionLocked(id, null, value, ACTION_VALUE_CHANGED, 0);
+        }
+    }
+
+    /**
+     * Called to indicate the value of an autofillable virtual view has changed.
+     *
+     * @param view the virtual view parent.
+     * @param virtualId id identifying the virtual child inside the parent view.
+     * @param value new value of the child.
+     */
+    public void notifyValueChanged(View view, int virtualId, AutofillValue value) {
+        if (!hasAutofillFeature()) {
+            return;
+        }
+        synchronized (mLock) {
+            if (!mEnabled || !isActiveLocked()) {
+                if (sVerbose) {
+                    Log.v(TAG, "notifyValueChanged(" + view.getAutofillId() + ":" + virtualId
+                            + "): ignoring on state " + getStateAsStringLocked());
+                }
+                return;
+            }
+
+            final AutofillId id = getAutofillId(view, virtualId);
+            updateSessionLocked(id, null, value, ACTION_VALUE_CHANGED, 0);
+        }
+    }
+
+    /**
+     * Called to indicate a {@link View} is clicked.
+     *
+     * @param view view that has been clicked.
+     */
+    public void notifyViewClicked(@NonNull View view) {
+        notifyViewClicked(view.getAutofillId());
+    }
+
+    /**
+     * Called to indicate a virtual view has been clicked.
+     *
+     * @param view the virtual view parent.
+     * @param virtualId id identifying the virtual child inside the parent view.
+     */
+    public void notifyViewClicked(@NonNull View view, int virtualId) {
+        notifyViewClicked(getAutofillId(view, virtualId));
+    }
+
+    private void notifyViewClicked(AutofillId id) {
+        if (!hasAutofillFeature()) {
+            return;
+        }
+        if (sVerbose) Log.v(TAG, "notifyViewClicked(): id=" + id + ", trigger=" + mSaveTriggerId);
+
+        synchronized (mLock) {
+            if (!mEnabled || !isActiveLocked()) {
+                return;
+            }
+            if (mSaveTriggerId != null && mSaveTriggerId.equals(id)) {
+                if (sDebug) Log.d(TAG, "triggering commit by click of " + id);
+                commitLocked();
+                mMetricsLogger.write(newLog(MetricsEvent.AUTOFILL_SAVE_EXPLICITLY_TRIGGERED));
+            }
+        }
+    }
+
+    /**
+     * Called by {@link android.app.Activity} to commit or cancel the session on finish.
+     *
+     * @hide
+     */
+    public void onActivityFinishing() {
+        if (!hasAutofillFeature()) {
+            return;
+        }
+        synchronized (mLock) {
+            if (mSaveOnFinish) {
+                if (sDebug) Log.d(TAG, "onActivityFinishing(): calling commitLocked()");
+                commitLocked();
+            } else {
+                if (sDebug) Log.d(TAG, "onActivityFinishing(): calling cancelLocked()");
+                cancelLocked();
+            }
+        }
+    }
+
+    /**
+     * Called to indicate the current autofill context should be commited.
+     *
+     * <p>This method is typically called by {@link View Views} that manage virtual views; for
+     * example, when the view is rendering an {@code HTML} page with a form and virtual views
+     * that represent the HTML elements, it should call this method after the form is submitted and
+     * another page is rendered.
+     *
+     * <p><b>Note:</b> This method does not need to be called on regular application lifecycle
+     * methods such as {@link android.app.Activity#finish()}.
+     */
+    public void commit() {
+        if (!hasAutofillFeature()) {
+            return;
+        }
+        if (sVerbose) Log.v(TAG, "commit() called by app");
+        synchronized (mLock) {
+            commitLocked();
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void commitLocked() {
+        if (!mEnabled && !isActiveLocked()) {
+            return;
+        }
+        finishSessionLocked();
+    }
+
+    /**
+     * Called to indicate the current autofill context should be cancelled.
+     *
+     * <p>This method is typically called by {@link View Views} that manage virtual views; for
+     * example, when the view is rendering an {@code HTML} page with a form and virtual views
+     * that represent the HTML elements, it should call this method if the user does not post the
+     * form but moves to another form in this page.
+     *
+     * <p><b>Note:</b> This method does not need to be called on regular application lifecycle
+     * methods such as {@link android.app.Activity#finish()}.
+     */
+    public void cancel() {
+        if (sVerbose) Log.v(TAG, "cancel() called by app or augmented autofill service");
+        if (!hasAutofillFeature()) {
+            return;
+        }
+        synchronized (mLock) {
+            cancelLocked();
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void cancelLocked() {
+        if (!mEnabled && !isActiveLocked()) {
+            return;
+        }
+        cancelSessionLocked();
+    }
+
+    /** @hide */
+    public void disableOwnedAutofillServices() {
+        disableAutofillServices();
+    }
+
+    /**
+     * If the app calling this API has enabled autofill services they
+     * will be disabled.
+     */
+    public void disableAutofillServices() {
+        if (!hasAutofillFeature()) {
+            return;
+        }
+        try {
+            mService.disableOwnedAutofillServices(mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns {@code true} if the calling application provides a {@link AutofillService} that is
+     * enabled for the current user, or {@code false} otherwise.
+     */
+    public boolean hasEnabledAutofillServices() {
+        if (mService == null) return false;
+
+        final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
+        try {
+            mService.isServiceEnabled(mContext.getUserId(), mContext.getPackageName(),
+                    receiver);
+            return receiver.getIntResult() == 1;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (SyncResultReceiver.TimeoutException e) {
+            throw new RuntimeException("Fail to get enabled autofill services status.");
+        }
+    }
+
+    /**
+     * Returns the component name of the {@link AutofillService} that is enabled for the current
+     * user.
+     */
+    @Nullable
+    public ComponentName getAutofillServiceComponentName() {
+        if (mService == null) return null;
+
+        final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
+        try {
+            mService.getAutofillServiceComponentName(receiver);
+            return receiver.getParcelableResult();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (SyncResultReceiver.TimeoutException e) {
+            throw new RuntimeException("Fail to get autofill services component name.");
+        }
+    }
+
+    /**
+     * Gets the id of the {@link UserData} used for
+     * <a href="AutofillService.html#FieldClassification">field classification</a>.
+     *
+     * <p>This method is useful when the service must check the status of the {@link UserData} in
+     * the device without fetching the whole object.
+     *
+     * <p><b>Note:</b> This method should only be called by an app providing an autofill service,
+     * and it's ignored if the caller currently doesn't have an enabled autofill service for
+     * the user.
+     *
+     * @return id of the {@link UserData} previously set by {@link #setUserData(UserData)}
+     * or {@code null} if it was reset or if the caller currently does not have an enabled autofill
+     * service for the user.
+     */
+    @Nullable public String getUserDataId() {
+        try {
+            final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
+            mService.getUserDataId(receiver);
+            return receiver.getStringResult();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (SyncResultReceiver.TimeoutException e) {
+            throw new RuntimeException("Fail to get user data id for field classification.");
+        }
+    }
+
+    /**
+     * Gets the user data used for
+     * <a href="AutofillService.html#FieldClassification">field classification</a>.
+     *
+     * <p><b>Note:</b> This method should only be called by an app providing an autofill service,
+     * and it's ignored if the caller currently doesn't have an enabled autofill service for
+     * the user.
+     *
+     * @return value previously set by {@link #setUserData(UserData)} or {@code null} if it was
+     * reset or if the caller currently does not have an enabled autofill service for the user.
+     */
+    @Nullable public UserData getUserData() {
+        try {
+            final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
+            mService.getUserData(receiver);
+            return receiver.getParcelableResult();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (SyncResultReceiver.TimeoutException e) {
+            throw new RuntimeException("Fail to get user data for field classification.");
+        }
+    }
+
+    /**
+     * Sets the {@link UserData} used for
+     * <a href="AutofillService.html#FieldClassification">field classification</a>
+     *
+     * <p><b>Note:</b> This method should only be called by an app providing an autofill service,
+     * and it's ignored if the caller currently doesn't have an enabled autofill service for
+     * the user.
+     */
+    public void setUserData(@Nullable UserData userData) {
+        try {
+            mService.setUserData(userData);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Checks if <a href="AutofillService.html#FieldClassification">field classification</a> is
+     * enabled.
+     *
+     * <p>As field classification is an expensive operation, it could be disabled, either
+     * temporarily (for example, because the service exceeded a rate-limit threshold) or
+     * permanently (for example, because the device is a low-level device).
+     *
+     * <p><b>Note:</b> This method should only be called by an app providing an autofill service,
+     * and it's ignored if the caller currently doesn't have an enabled autofill service for
+     * the user.
+     */
+    public boolean isFieldClassificationEnabled() {
+        final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
+        try {
+            mService.isFieldClassificationEnabled(receiver);
+            return receiver.getIntResult() == 1;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (SyncResultReceiver.TimeoutException e) {
+            throw new RuntimeException("Fail to get field classification enabled status.");
+        }
+    }
+
+    /**
+     * Gets the name of the default algorithm used for
+     * <a href="AutofillService.html#FieldClassification">field classification</a>.
+     *
+     * <p>The default algorithm is used when the algorithm on {@link UserData} is invalid or not
+     * set.
+     *
+     * <p><b>Note:</b> This method should only be called by an app providing an autofill service,
+     * and it's ignored if the caller currently doesn't have an enabled autofill service for
+     * the user.
+     */
+    @Nullable
+    public String getDefaultFieldClassificationAlgorithm() {
+        final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
+        try {
+            mService.getDefaultFieldClassificationAlgorithm(receiver);
+            return receiver.getStringResult();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (SyncResultReceiver.TimeoutException e) {
+            throw new RuntimeException("Fail to get default field classification algorithm.");
+        }
+    }
+
+    /**
+     * Gets the name of all algorithms currently available for
+     * <a href="AutofillService.html#FieldClassification">field classification</a>.
+     *
+     * <p><b>Note:</b> This method should only be called by an app providing an autofill service,
+     * and it returns an empty list if the caller currently doesn't have an enabled autofill service
+     * for the user.
+     */
+    @NonNull
+    public List<String> getAvailableFieldClassificationAlgorithms() {
+        final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
+        try {
+            mService.getAvailableFieldClassificationAlgorithms(receiver);
+            final String[] algorithms = receiver.getStringArrayResult();
+            return algorithms != null ? Arrays.asList(algorithms) : Collections.emptyList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (SyncResultReceiver.TimeoutException e) {
+            throw new RuntimeException("Fail to get available field classification algorithms.");
+        }
+    }
+
+    /**
+     * Returns {@code true} if autofill is supported by the current device and
+     * is supported for this user.
+     *
+     * <p>Autofill is typically supported, but it could be unsupported in cases like:
+     * <ol>
+     *     <li>Low-end devices.
+     *     <li>Device policy rules that forbid its usage.
+     * </ol>
+     */
+    public boolean isAutofillSupported() {
+        if (mService == null) return false;
+
+        final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
+        try {
+            mService.isServiceSupported(mContext.getUserId(), receiver);
+            return receiver.getIntResult() == 1;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (SyncResultReceiver.TimeoutException e) {
+            throw new RuntimeException("Fail to get autofill supported status.");
+        }
+    }
+
+    // Note: don't need to use locked suffix because mContext is final.
+    private AutofillClient getClient() {
+        final AutofillClient client = mContext.getAutofillClient();
+        if (client == null && sVerbose) {
+            Log.v(TAG, "No AutofillClient for " + mContext.getPackageName() + " on context "
+                    + mContext);
+        }
+        return client;
+    }
+
+    /**
+     * Check if autofill ui is showing, must be called on UI thread.
+     * @hide
+     */
+    public boolean isAutofillUiShowing() {
+        final AutofillClient client = mContext.getAutofillClient();
+        return client != null && client.autofillClientIsFillUiShowing();
+    }
+
+    /** @hide */
+    public void onAuthenticationResult(int authenticationId, Intent data, View focusView) {
+        if (!hasAutofillFeature()) {
+            return;
+        }
+        // TODO: the result code is being ignored, so this method is not reliably
+        // handling the cases where it's not RESULT_OK: it works fine if the service does not
+        // set the EXTRA_AUTHENTICATION_RESULT extra, but it could cause weird results if the
+        // service set the extra and returned RESULT_CANCELED...
+
+        if (sDebug) {
+            Log.d(TAG, "onAuthenticationResult(): id= " + authenticationId + ", data=" + data);
+        }
+
+        synchronized (mLock) {
+            if (!isActiveLocked()) {
+                return;
+            }
+            // If authenticate activity closes itself during onCreate(), there is no onStop/onStart
+            // of app activity.  We enforce enter event to re-show fill ui in such case.
+            // CTS example:
+            //     LoginActivityTest#testDatasetAuthTwoFieldsUserCancelsFirstAttempt
+            //     LoginActivityTest#testFillResponseAuthBothFieldsUserCancelsFirstAttempt
+            if (!mOnInvisibleCalled && focusView != null
+                    && focusView.canNotifyAutofillEnterExitEvent()) {
+                notifyViewExitedLocked(focusView);
+                notifyViewEnteredLocked(focusView, 0);
+            }
+            if (data == null) {
+                // data is set to null when result is not RESULT_OK
+                return;
+            }
+
+            final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT);
+            final Bundle responseData = new Bundle();
+            responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result);
+            final Bundle newClientState = data.getBundleExtra(EXTRA_CLIENT_STATE);
+            if (newClientState != null) {
+                responseData.putBundle(EXTRA_CLIENT_STATE, newClientState);
+            }
+            if (data.getExtras().containsKey(EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET)) {
+                responseData.putBoolean(EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET,
+                        data.getBooleanExtra(EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET,
+                                false));
+            }
+            try {
+                mService.setAuthenticationResult(responseData, mSessionId, authenticationId,
+                        mContext.getUserId());
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error delivering authentication result", e);
+            }
+        }
+    }
+
+    /**
+     * Gets the next unique autofill ID for the activity context.
+     *
+     * <p>Typically used to manage views whose content is recycled - see
+     * {@link View#setAutofillId(AutofillId)} for more info.
+     *
+     * @return An ID that is unique in the activity, or {@code null} if autofill is not supported in
+     * the {@link Context} associated with this {@link AutofillManager}.
+     */
+    @Nullable
+    public AutofillId getNextAutofillId() {
+        final AutofillClient client = getClient();
+        if (client == null) return null;
+
+        final AutofillId id = client.autofillClientGetNextAutofillId();
+
+        if (id == null && sDebug) {
+            Log.d(TAG, "getNextAutofillId(): client " + client + " returned null");
+        }
+
+        return id;
+    }
+
+    private static AutofillId getAutofillId(View parent, int virtualId) {
+        return new AutofillId(parent.getAutofillViewId(), virtualId);
+    }
+
+    @GuardedBy("mLock")
+    private void startSessionLocked(@NonNull AutofillId id, @NonNull Rect bounds,
+            @NonNull AutofillValue value, int flags) {
+        if (mEnteredForAugmentedAutofillIds != null
+                && mEnteredForAugmentedAutofillIds.contains(id)
+                || mEnabledForAugmentedAutofillOnly) {
+            if (sVerbose) Log.v(TAG, "Starting session for augmented autofill on " + id);
+            flags |= FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY;
+        }
+        if (sVerbose) {
+            Log.v(TAG, "startSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value
+                    + ", flags=" + flags + ", state=" + getStateAsStringLocked()
+                    + ", compatMode=" + isCompatibilityModeEnabledLocked()
+                    + ", augmentedOnly=" + mForAugmentedAutofillOnly
+                    + ", enabledAugmentedOnly=" + mEnabledForAugmentedAutofillOnly
+                    + ", enteredIds=" + mEnteredIds);
+        }
+        // We need to reset the augmented-only state when a manual request is made, as it's possible
+        // that the service returned null for the first request and now the user is manually
+        // requesting autofill to trigger a custom UI provided by the service.
+        if (mForAugmentedAutofillOnly && !mEnabledForAugmentedAutofillOnly
+                && (flags & FLAG_MANUAL_REQUEST) != 0) {
+            if (sVerbose) {
+                Log.v(TAG, "resetting mForAugmentedAutofillOnly on manual autofill request");
+            }
+            mForAugmentedAutofillOnly = false;
+        }
+        if (mState != STATE_UNKNOWN && !isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) {
+            if (sVerbose) {
+                Log.v(TAG, "not automatically starting session for " + id
+                        + " on state " + getStateAsStringLocked() + " and flags " + flags);
+            }
+            return;
+        }
+        try {
+            final AutofillClient client = getClient();
+            if (client == null) return; // NOTE: getClient() already logged it..
+
+            final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
+            final ComponentName clientActivity = client.autofillClientGetComponentName();
+
+            if (!mEnabledForAugmentedAutofillOnly && mOptions != null
+                    && mOptions.isAutofillDisabledLocked(clientActivity)) {
+                if (mOptions.isAugmentedAutofillEnabled(mContext)) {
+                    if (sDebug) {
+                        Log.d(TAG, "startSession(" + clientActivity + "): disabled by service but "
+                                + "allowlisted for augmented autofill");
+                        flags |= FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY;
+                    }
+                } else {
+                    if (sDebug) {
+                        Log.d(TAG, "startSession(" + clientActivity + "): ignored because "
+                                + "disabled by service and not allowlisted for augmented autofill");
+                    }
+                    setSessionFinished(AutofillManager.STATE_DISABLED_BY_SERVICE, null);
+                    client.autofillClientResetableStateAvailable();
+                    return;
+                }
+            }
+
+            mService.startSession(client.autofillClientGetActivityToken(),
+                    mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
+                    mCallback != null, flags, clientActivity,
+                    isCompatibilityModeEnabledLocked(), receiver);
+            mSessionId = receiver.getIntResult();
+            if (mSessionId != NO_SESSION) {
+                mState = STATE_ACTIVE;
+            }
+            final int extraFlags = receiver.getOptionalExtraIntResult(0);
+            if ((extraFlags & RECEIVER_FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY) != 0) {
+                if (sDebug) Log.d(TAG, "startSession(" + clientActivity + "): for augmented only");
+                mForAugmentedAutofillOnly = true;
+            }
+            client.autofillClientResetableStateAvailable();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (SyncResultReceiver.TimeoutException e) {
+            // no-op, just log the error message.
+            Log.w(TAG, "Exception getting result from SyncResultReceiver: " + e);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void finishSessionLocked() {
+        if (sVerbose) Log.v(TAG, "finishSessionLocked(): " + getStateAsStringLocked());
+
+        if (!isActiveLocked()) return;
+
+        try {
+            mService.finishSession(mSessionId, mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        resetSessionLocked(/* resetEnteredIds= */ true);
+    }
+
+    @GuardedBy("mLock")
+    private void cancelSessionLocked() {
+        if (sVerbose) Log.v(TAG, "cancelSessionLocked(): " + getStateAsStringLocked());
+
+        if (!isActiveLocked()) return;
+
+        try {
+            mService.cancelSession(mSessionId, mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        resetSessionLocked(/* resetEnteredIds= */ true);
+    }
+
+    @GuardedBy("mLock")
+    private void resetSessionLocked(boolean resetEnteredIds) {
+        mSessionId = NO_SESSION;
+        mState = STATE_UNKNOWN;
+        mTrackedViews = null;
+        mFillableIds = null;
+        mSaveTriggerId = null;
+        mIdShownFillUi = null;
+        if (resetEnteredIds) {
+            mEnteredIds = null;
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value, int action,
+            int flags) {
+        if (sVerbose) {
+            Log.v(TAG, "updateSessionLocked(): id=" + id + ", bounds=" + bounds
+                    + ", value=" + value + ", action=" + action + ", flags=" + flags);
+        }
+        try {
+            mService.updateSession(mSessionId, id, bounds, value, action, flags,
+                    mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Tries to add AutofillManagerClient to service if it does not been added. Returns {@code true}
+     * if the AutofillManagerClient is added successfully or is already added. Otherwise,
+     * returns {@code false}.
+     */
+    @GuardedBy("mLock")
+    private boolean tryAddServiceClientIfNeededLocked() {
+        final AutofillClient client = getClient();
+        if (client == null) {
+            return false;
+        }
+        if (mService == null) {
+            Log.w(TAG, "Autofill service is null!");
+            return false;
+        }
+        if (mServiceClient == null) {
+            mServiceClient = new AutofillManagerClient(this);
+            try {
+                final int userId = mContext.getUserId();
+                final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
+                mService.addClient(mServiceClient, client.autofillClientGetComponentName(),
+                        userId, receiver);
+                int flags = 0;
+                try {
+                    flags = receiver.getIntResult();
+                } catch (SyncResultReceiver.TimeoutException e) {
+                    Log.w(TAG, "Failed to initialize autofill: " + e);
+                    // Reset the states initialized above.
+                    mService.removeClient(mServiceClient, userId);
+                    mServiceClient = null;
+                    return false;
+                }
+                mEnabled = (flags & FLAG_ADD_CLIENT_ENABLED) != 0;
+                sDebug = (flags & FLAG_ADD_CLIENT_DEBUG) != 0;
+                sVerbose = (flags & FLAG_ADD_CLIENT_VERBOSE) != 0;
+                mEnabledForAugmentedAutofillOnly = (flags
+                        & FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY) != 0;
+                if (sVerbose) {
+                    Log.v(TAG, "receiver results: flags=" + flags + " enabled=" + mEnabled
+                            + ", enabledForAugmentedOnly: " + mEnabledForAugmentedAutofillOnly);
+                }
+                final IAutoFillManager service = mService;
+                final IAutoFillManagerClient serviceClient = mServiceClient;
+                mServiceClientCleaner = Cleaner.create(this, () -> {
+                    // TODO(b/123100811): call service to also remove reference to
+                    // mAugmentedAutofillServiceClient
+                    try {
+                        service.removeClient(serviceClient, userId);
+                    } catch (RemoteException e) {
+                    }
+                });
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return true;
+    }
+
+    @GuardedBy("mLock")
+    private boolean startAutofillIfNeededLocked(View view) {
+        if (mState == STATE_UNKNOWN
+                && mSessionId == NO_SESSION
+                && view instanceof EditText
+                && !TextUtils.isEmpty(((EditText) view).getText())
+                && !view.isFocused()
+                && view.isImportantForAutofill()
+                && view.isLaidOut()
+                && view.isVisibleToUser()) {
+
+            final boolean clientAdded = tryAddServiceClientIfNeededLocked();
+
+            if (sVerbose) {
+                Log.v(TAG, "startAutofillIfNeededLocked(): enabled=" + mEnabled + " mServiceClient="
+                        + mServiceClient);
+            }
+            if (clientAdded && mEnabled && !isClientDisablingEnterExitEvent()) {
+                final AutofillId id = view.getAutofillId();
+                final AutofillValue value = view.getAutofillValue();
+                // Starts new session.
+                startSessionLocked(id, /* bounds= */ null, /* value= */ null, /* flags= */ 0);
+                // Updates value.
+                updateSessionLocked(id, /* bounds= */ null, value, ACTION_VALUE_CHANGED,
+                        /* flags= */ 0);
+                addEnteredIdLocked(id);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Registers a {@link AutofillCallback} to receive autofill events.
+     *
+     * @param callback callback to receive events.
+     */
+    public void registerCallback(@Nullable AutofillCallback callback) {
+        if (!hasAutofillFeature()) {
+            return;
+        }
+        synchronized (mLock) {
+            if (callback == null) return;
+
+            final boolean hadCallback = mCallback != null;
+            mCallback = callback;
+
+            if (!hadCallback) {
+                try {
+                    mService.setHasCallback(mSessionId, mContext.getUserId(), true);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+    }
+
+    /**
+     * Unregisters a {@link AutofillCallback} to receive autofill events.
+     *
+     * @param callback callback to stop receiving events.
+     */
+    public void unregisterCallback(@Nullable AutofillCallback callback) {
+        if (!hasAutofillFeature()) {
+            return;
+        }
+        synchronized (mLock) {
+            if (callback == null || mCallback == null || callback != mCallback) return;
+
+            mCallback = null;
+
+            try {
+                mService.setHasCallback(mSessionId, mContext.getUserId(), false);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Explicitly limits augmented autofill to the given packages and activities.
+     *
+     * <p>To reset the allowlist, call it passing {@code null} to both arguments.
+     *
+     * <p>Useful when the service wants to restrict augmented autofill to a category of apps, like
+     * apps that uses addresses. For example, if the service wants to support augmented autofill on
+     * all activities of app {@code AddressApp1} and just activities {@code act1} and {@code act2}
+     * of {@code AddressApp2}, it would call:
+     * {@code setAugmentedAutofillWhitelist(Arrays.asList("AddressApp1"),
+     * Arrays.asList(new ComponentName("AddressApp2", "act1"),
+     * new ComponentName("AddressApp2", "act2")));}
+     *
+     * <p><b>Note:</b> This method should only be called by the app providing the augmented autofill
+     * service, and it's ignored if the caller isn't it.
+     *
+     * @hide
+     */
+    @SystemApi
+    public void setAugmentedAutofillWhitelist(@Nullable Set<String> packages,
+            @Nullable Set<ComponentName> activities) {
+        if (!hasAutofillFeature()) {
+            return;
+        }
+
+        final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
+        int resultCode;
+        try {
+            mService.setAugmentedAutofillWhitelist(toList(packages), toList(activities),
+                    resultReceiver);
+            resultCode = resultReceiver.getIntResult();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (SyncResultReceiver.TimeoutException e) {
+            Log.e(TAG, "Fail to get the result of set AugmentedAutofill whitelist. " + e);
+            return;
+        }
+        switch (resultCode) {
+            case RESULT_OK:
+                return;
+            case RESULT_CODE_NOT_SERVICE:
+                throw new SecurityException("caller is not user's Augmented Autofill Service");
+            default:
+                Log.wtf(TAG, "setAugmentedAutofillWhitelist(): received invalid result: "
+                        + resultCode);
+        }
+    }
+
+    /**
+     * Notifies that a non-autofillable view was entered because the activity is allowlisted for
+     * augmented autofill.
+     *
+     * <p>This method is necessary to set the right flag on start, so the server-side session
+     * doesn't trigger the standard autofill workflow, but the augmented's instead.
+     *
+     * @hide
+     */
+    public void notifyViewEnteredForAugmentedAutofill(@NonNull View view) {
+        final AutofillId id = view.getAutofillId();
+        synchronized (mLock) {
+            if (mEnteredForAugmentedAutofillIds == null) {
+                mEnteredForAugmentedAutofillIds = new ArraySet<>(1);
+            }
+            mEnteredForAugmentedAutofillIds.add(id);
+        }
+    }
+
+    private void requestShowFillUi(int sessionId, AutofillId id, int width, int height,
+            Rect anchorBounds, IAutofillWindowPresenter presenter) {
+        final View anchor = findView(id);
+        if (anchor == null) {
+            return;
+        }
+
+        AutofillCallback callback = null;
+        synchronized (mLock) {
+            if (mSessionId == sessionId) {
+                AutofillClient client = getClient();
+
+                if (client != null) {
+                    if (client.autofillClientRequestShowFillUi(anchor, width, height,
+                            anchorBounds, presenter)) {
+                        callback = mCallback;
+                        mIdShownFillUi = id;
+                    }
+                }
+            }
+        }
+
+        if (callback != null) {
+            if (id.isVirtualInt()) {
+                callback.onAutofillEvent(anchor, id.getVirtualChildIntId(),
+                        AutofillCallback.EVENT_INPUT_SHOWN);
+            } else {
+                callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_SHOWN);
+            }
+        }
+    }
+
+    private void authenticate(int sessionId, int authenticationId, IntentSender intent,
+            Intent fillInIntent, boolean authenticateInline) {
+        synchronized (mLock) {
+            if (sessionId == mSessionId) {
+                final AutofillClient client = getClient();
+                if (client != null) {
+                    // clear mOnInvisibleCalled and we will see if receive onInvisibleForAutofill()
+                    // before onAuthenticationResult()
+                    mOnInvisibleCalled = false;
+                    client.autofillClientAuthenticate(authenticationId, intent, fillInIntent,
+                            authenticateInline);
+                }
+            }
+        }
+    }
+
+    private void dispatchUnhandledKey(int sessionId, AutofillId id, KeyEvent keyEvent) {
+        final View anchor = findView(id);
+        if (anchor == null) {
+            return;
+        }
+
+        synchronized (mLock) {
+            if (mSessionId == sessionId) {
+                AutofillClient client = getClient();
+
+                if (client != null) {
+                    client.autofillClientDispatchUnhandledKey(anchor, keyEvent);
+                }
+            }
+        }
+    }
+
+    /** @hide */
+    public static final int SET_STATE_FLAG_ENABLED = 0x01;
+    /** @hide */
+    public static final int SET_STATE_FLAG_RESET_SESSION = 0x02;
+    /** @hide */
+    public static final int SET_STATE_FLAG_RESET_CLIENT = 0x04;
+    /** @hide */
+    public static final int SET_STATE_FLAG_DEBUG = 0x08;
+    /** @hide */
+    public static final int SET_STATE_FLAG_VERBOSE = 0x10;
+    /** @hide */
+    public static final int SET_STATE_FLAG_FOR_AUTOFILL_ONLY = 0x20;
+
+    private void setState(int flags) {
+        if (sVerbose) {
+            Log.v(TAG, "setState(" + flags + ": " + DebugUtils.flagsToString(AutofillManager.class,
+                    "SET_STATE_FLAG_", flags) + ")");
+        }
+        synchronized (mLock) {
+            if ((flags & SET_STATE_FLAG_FOR_AUTOFILL_ONLY) != 0) {
+                mForAugmentedAutofillOnly = true;
+                // NOTE: returning right away as this is the only flag set, at least currently...
+                return;
+            }
+            mEnabled = (flags & SET_STATE_FLAG_ENABLED) != 0;
+            if (!mEnabled || (flags & SET_STATE_FLAG_RESET_SESSION) != 0) {
+                // Reset the session state
+                resetSessionLocked(/* resetEnteredIds= */ true);
+            }
+            if ((flags & SET_STATE_FLAG_RESET_CLIENT) != 0) {
+                // Reset connection to system
+                mServiceClient = null;
+                mAugmentedAutofillServiceClient = null;
+                if (mServiceClientCleaner != null) {
+                    mServiceClientCleaner.clean();
+                    mServiceClientCleaner = null;
+                }
+                notifyReenableAutofill();
+            }
+        }
+        sDebug = (flags & SET_STATE_FLAG_DEBUG) != 0;
+        sVerbose = (flags & SET_STATE_FLAG_VERBOSE) != 0;
+    }
+
+    /**
+     * Sets a view as autofilled if the current value is the {code targetValue}.
+     *
+     * @param view The view that is to be autofilled
+     * @param targetValue The value we want to fill into view
+     */
+    private void setAutofilledIfValuesIs(@NonNull View view, @Nullable AutofillValue targetValue,
+            boolean hideHighlight) {
+        AutofillValue currentValue = view.getAutofillValue();
+        if (Objects.equals(currentValue, targetValue)) {
+            synchronized (mLock) {
+                if (mLastAutofilledData == null) {
+                    mLastAutofilledData = new ParcelableMap(1);
+                }
+                mLastAutofilledData.put(view.getAutofillId(), targetValue);
+            }
+            view.setAutofilled(true, hideHighlight);
+        }
+    }
+
+    private void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values,
+            boolean hideHighlight) {
+        synchronized (mLock) {
+            if (sessionId != mSessionId) {
+                return;
+            }
+
+            final AutofillClient client = getClient();
+            if (client == null) {
+                return;
+            }
+
+            final int itemCount = ids.size();
+            int numApplied = 0;
+            ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null;
+            final View[] views = client.autofillClientFindViewsByAutofillIdTraversal(
+                    Helper.toArray(ids));
+
+            ArrayList<AutofillId> failedIds = null;
+
+            for (int i = 0; i < itemCount; i++) {
+                final AutofillId id = ids.get(i);
+                final AutofillValue value = values.get(i);
+                final View view = views[i];
+                if (view == null) {
+                    // Most likely view has been removed after the initial request was sent to the
+                    // the service; this is fine, but we need to update the view status in the
+                    // server side so it can be triggered again.
+                    Log.d(TAG, "autofill(): no View with id " + id);
+                    if (failedIds == null) {
+                        failedIds = new ArrayList<>();
+                    }
+                    failedIds.add(id);
+                    continue;
+                }
+                if (id.isVirtualInt()) {
+                    if (virtualValues == null) {
+                        // Most likely there will be just one view with virtual children.
+                        virtualValues = new ArrayMap<>(1);
+                    }
+                    SparseArray<AutofillValue> valuesByParent = virtualValues.get(view);
+                    if (valuesByParent == null) {
+                        // We don't know the size yet, but usually it will be just a few fields...
+                        valuesByParent = new SparseArray<>(5);
+                        virtualValues.put(view, valuesByParent);
+                    }
+                    valuesByParent.put(id.getVirtualChildIntId(), value);
+                } else {
+                    // Mark the view as to be autofilled with 'value'
+                    if (mLastAutofilledData == null) {
+                        mLastAutofilledData = new ParcelableMap(itemCount - i);
+                    }
+                    mLastAutofilledData.put(id, value);
+
+                    view.autofill(value);
+
+                    // Set as autofilled if the values match now, e.g. when the value was updated
+                    // synchronously.
+                    // If autofill happens async, the view is set to autofilled in
+                    // notifyValueChanged.
+                    setAutofilledIfValuesIs(view, value, hideHighlight);
+
+                    numApplied++;
+                }
+            }
+
+            if (failedIds != null) {
+                if (sVerbose) {
+                    Log.v(TAG, "autofill(): total failed views: " + failedIds);
+                }
+                try {
+                    mService.setAutofillFailure(mSessionId, failedIds, mContext.getUserId());
+                } catch (RemoteException e) {
+                    // In theory, we could ignore this error since it's not a big deal, but
+                    // in reality, we rather crash the app anyways, as the failure could be
+                    // a consequence of something going wrong on the server side...
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+
+            if (virtualValues != null) {
+                for (int i = 0; i < virtualValues.size(); i++) {
+                    final View parent = virtualValues.keyAt(i);
+                    final SparseArray<AutofillValue> childrenValues = virtualValues.valueAt(i);
+                    parent.autofill(childrenValues);
+                    numApplied += childrenValues.size();
+                    // TODO: we should provide a callback so the parent can call failures; something
+                    // like notifyAutofillFailed(View view, int[] childrenIds);
+                }
+            }
+
+            mMetricsLogger.write(newLog(MetricsEvent.AUTOFILL_DATASET_APPLIED)
+                    .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount)
+                    .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied));
+        }
+    }
+
+    private void autofillContent(int sessionId, AutofillId id, ClipData clip) {
+        synchronized (mLock) {
+            if (sessionId != mSessionId) {
+                return;
+            }
+            final AutofillClient client = getClient();
+            if (client == null) {
+                return;
+            }
+            final View view = client.autofillClientFindViewByAutofillIdTraversal(id);
+            if (view == null) {
+                // Most likely view has been removed after the initial request was sent to the
+                // the service; this is fine, but we need to update the view status in the
+                // server side so it can be triggered again.
+                Log.d(TAG, "autofillContent(): no view with id " + id);
+                reportAutofillContentFailure(id);
+                return;
+            }
+            ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_AUTOFILL).build();
+            ContentInfo result = view.performReceiveContent(payload);
+            if (result != null) {
+                Log.w(TAG, "autofillContent(): receiver could not insert content: id=" + id
+                        + ", view=" + view + ", clip=" + clip);
+                reportAutofillContentFailure(id);
+                return;
+            }
+            mMetricsLogger.write(newLog(MetricsEvent.AUTOFILL_DATASET_APPLIED)
+                    .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, 1)
+                    .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, 1));
+        }
+    }
+
+    private void reportAutofillContentFailure(AutofillId id) {
+        try {
+            mService.setAutofillFailure(mSessionId, Collections.singletonList(id),
+                    mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private LogMaker newLog(int category) {
+        final LogMaker log = new LogMaker(category)
+                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SESSION_ID, mSessionId);
+
+        if (isCompatibilityModeEnabledLocked()) {
+            log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE, 1);
+        }
+        final AutofillClient client = getClient();
+        if (client == null) {
+            // Client should never be null here, but it doesn't hurt to check...
+            log.setPackageName(mContext.getPackageName());
+        } else {
+            // Remove activity name from logging
+            final ComponentName sanitizedComponentName =
+                    new ComponentName(client.autofillClientGetComponentName().getPackageName(), "");
+            log.setComponentName(sanitizedComponentName);
+        }
+        return log;
+    }
+
+    /**
+     *  Set the tracked views.
+     *
+     * @param trackedIds The views to be tracked.
+     * @param saveOnAllViewsInvisible Finish the session once all tracked views are invisible.
+     * @param saveOnFinish Finish the session once the activity is finished.
+     * @param fillableIds Views that might anchor FillUI.
+     * @param saveTriggerId View that when clicked triggers commit().
+     */
+    private void setTrackedViews(int sessionId, @Nullable AutofillId[] trackedIds,
+            boolean saveOnAllViewsInvisible, boolean saveOnFinish,
+            @Nullable AutofillId[] fillableIds, @Nullable AutofillId saveTriggerId) {
+        if (saveTriggerId != null) {
+            saveTriggerId.resetSessionId();
+        }
+        synchronized (mLock) {
+            if (sVerbose) {
+                Log.v(TAG, "setTrackedViews(): sessionId=" + sessionId
+                        + ", trackedIds=" + Arrays.toString(trackedIds)
+                        + ", saveOnAllViewsInvisible=" + saveOnAllViewsInvisible
+                        + ", saveOnFinish=" + saveOnFinish
+                        + ", fillableIds=" + Arrays.toString(fillableIds)
+                        + ", saveTrigerId=" + saveTriggerId
+                        + ", mFillableIds=" + mFillableIds
+                        + ", mEnabled=" + mEnabled
+                        + ", mSessionId=" + mSessionId);
+
+            }
+            if (mEnabled && mSessionId == sessionId) {
+                if (saveOnAllViewsInvisible) {
+                    mTrackedViews = new TrackedViews(trackedIds);
+                } else {
+                    mTrackedViews = null;
+                }
+                mSaveOnFinish = saveOnFinish;
+                if (fillableIds != null) {
+                    if (mFillableIds == null) {
+                        mFillableIds = new ArraySet<>(fillableIds.length);
+                    }
+                    for (AutofillId id : fillableIds) {
+                        id.resetSessionId();
+                        mFillableIds.add(id);
+                    }
+                }
+
+                if (mSaveTriggerId != null && !mSaveTriggerId.equals(saveTriggerId)) {
+                    // Turn off trigger on previous view id.
+                    setNotifyOnClickLocked(mSaveTriggerId, false);
+                }
+
+                if (saveTriggerId != null && !saveTriggerId.equals(mSaveTriggerId)) {
+                    // Turn on trigger on new view id.
+                    mSaveTriggerId = saveTriggerId;
+                    setNotifyOnClickLocked(mSaveTriggerId, true);
+                }
+            }
+        }
+    }
+
+    private void setNotifyOnClickLocked(@NonNull AutofillId id, boolean notify) {
+        final View view = findView(id);
+        if (view == null) {
+            Log.w(TAG, "setNotifyOnClick(): invalid id: " + id);
+            return;
+        }
+        view.setNotifyAutofillManagerOnClick(notify);
+    }
+
+    private void setSaveUiState(int sessionId, boolean shown) {
+        if (sDebug) Log.d(TAG, "setSaveUiState(" + sessionId + "): " + shown);
+        synchronized (mLock) {
+            if (mSessionId != NO_SESSION) {
+                // Race condition: app triggered a new session after the previous session was
+                // finished but before server called setSaveUiState() - need to cancel the new
+                // session to avoid further inconsistent behavior.
+                Log.w(TAG, "setSaveUiState(" + sessionId + ", " + shown
+                        + ") called on existing session " + mSessionId + "; cancelling it");
+                cancelSessionLocked();
+            }
+            if (shown) {
+                mSessionId = sessionId;
+                mState = STATE_SHOWING_SAVE_UI;
+            } else {
+                mSessionId = NO_SESSION;
+                mState = STATE_UNKNOWN;
+            }
+        }
+    }
+
+    /**
+     * Marks the state of the session as finished.
+     *
+     * @param newState {@link #STATE_FINISHED} (because the autofill service returned a {@code null}
+     *  FillResponse), {@link #STATE_UNKNOWN} (because the session was removed),
+     *  {@link #STATE_UNKNOWN_COMPAT_MODE} (beucase the session was finished when the URL bar
+     *  changed on compat mode), {@link #STATE_UNKNOWN_FAILED} (because the session was finished
+     *  when the service failed to fullfil the request, or {@link #STATE_DISABLED_BY_SERVICE}
+     *  (because the autofill service disabled further autofill requests for the activity).
+     * @param autofillableIds list of ids that could trigger autofill, use to not handle a new
+     *  session when they're entered.
+     */
+    private void setSessionFinished(int newState, @Nullable List<AutofillId> autofillableIds) {
+        if (autofillableIds != null) {
+            for (int i = 0; i < autofillableIds.size(); i++) {
+                autofillableIds.get(i).resetSessionId();
+            }
+        }
+        synchronized (mLock) {
+            if (sVerbose) {
+                Log.v(TAG, "setSessionFinished(): from " + getStateAsStringLocked() + " to "
+                        + getStateAsString(newState) + "; autofillableIds=" + autofillableIds);
+            }
+            if (autofillableIds != null) {
+                mEnteredIds = new ArraySet<>(autofillableIds);
+            }
+            if (newState == STATE_UNKNOWN_COMPAT_MODE || newState == STATE_UNKNOWN_FAILED) {
+                resetSessionLocked(/* resetEnteredIds= */ true);
+                mState = STATE_UNKNOWN;
+            } else {
+                resetSessionLocked(/* resetEnteredIds= */ false);
+                mState = newState;
+            }
+        }
+    }
+
+    /**
+     * Gets a {@link AugmentedAutofillManagerClient} for this {@link AutofillManagerClient}.
+     *
+     * <p>These are 2 distinct objects because we need to restrict what the Augmented Autofill
+     * service can do (which is defined by {@code IAugmentedAutofillManagerClient.aidl}).
+     */
+    private void getAugmentedAutofillClient(@NonNull IResultReceiver result) {
+        synchronized (mLock) {
+            if (mAugmentedAutofillServiceClient == null) {
+                mAugmentedAutofillServiceClient = new AugmentedAutofillManagerClient(this);
+            }
+            final Bundle resultData = new Bundle();
+            resultData.putBinder(EXTRA_AUGMENTED_AUTOFILL_CLIENT,
+                    mAugmentedAutofillServiceClient.asBinder());
+
+            try {
+                result.send(0, resultData);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Could not send AugmentedAutofillClient back: " + e);
+            }
+        }
+    }
+
+    private void requestShowSoftInput(@NonNull AutofillId id) {
+        if (sVerbose) Log.v(TAG, "requestShowSoftInput(" + id + ")");
+        final AutofillClient client = getClient();
+        if (client == null) {
+            return;
+        }
+        final View view = client.autofillClientFindViewByAutofillIdTraversal(id);
+        if (view == null) {
+            if (sVerbose) Log.v(TAG, "View is not found");
+            return;
+        }
+        final Handler handler = view.getHandler();
+        if (handler == null) {
+            if (sVerbose) Log.v(TAG, "Ignoring requestShowSoftInput due to no handler in view");
+            return;
+        }
+        if (handler.getLooper() != Looper.myLooper()) {
+            // The view is running on a different thread than our own, so we need to reschedule
+            // our work for over there.
+            if (sVerbose) Log.v(TAG, "Scheduling showSoftInput() on the view UI thread");
+            handler.post(() -> requestShowSoftInputInViewThread(view));
+        } else {
+            requestShowSoftInputInViewThread(view);
+        }
+    }
+
+    // This method must be called from within the View thread.
+    private static void requestShowSoftInputInViewThread(@NonNull View view) {
+        if (!view.isFocused()) {
+            Log.w(TAG, "Ignoring requestShowSoftInput() due to non-focused view");
+            return;
+        }
+        final InputMethodManager inputMethodManager = view.getContext().getSystemService(
+                InputMethodManager.class);
+        boolean ret = inputMethodManager.showSoftInput(view, /*flags=*/ 0);
+        if (sVerbose) Log.v(TAG, " InputMethodManager.showSoftInput returns " + ret);
+    }
+
+    /** @hide */
+    public void requestHideFillUi() {
+        requestHideFillUi(mIdShownFillUi, true);
+    }
+
+    private void requestHideFillUi(AutofillId id, boolean force) {
+        final View anchor = id == null ? null : findView(id);
+        if (sVerbose) Log.v(TAG, "requestHideFillUi(" + id + "): anchor = " + anchor);
+        if (anchor == null) {
+            if (force) {
+                // When user taps outside autofill window, force to close fill ui even id does
+                // not match.
+                AutofillClient client = getClient();
+                if (client != null) {
+                    client.autofillClientRequestHideFillUi();
+                }
+            }
+            return;
+        }
+        requestHideFillUi(id, anchor);
+    }
+
+    private void requestHideFillUi(AutofillId id, View anchor) {
+
+        AutofillCallback callback = null;
+        synchronized (mLock) {
+            // We do not check the session id for two reasons:
+            // 1. If local and remote session id are off sync the UI would be stuck shown
+            // 2. There is a race between the user state being destroyed due the fill
+            //    service being uninstalled and the UI being dismissed.
+            AutofillClient client = getClient();
+            if (client != null) {
+                if (client.autofillClientRequestHideFillUi()) {
+                    mIdShownFillUi = null;
+                    callback = mCallback;
+                }
+            }
+        }
+
+        if (callback != null) {
+            if (id.isVirtualInt()) {
+                callback.onAutofillEvent(anchor, id.getVirtualChildIntId(),
+                        AutofillCallback.EVENT_INPUT_HIDDEN);
+            } else {
+                callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_HIDDEN);
+            }
+        }
+    }
+
+    private void notifyDisableAutofill(long disableDuration, ComponentName componentName) {
+        synchronized (mLock) {
+            if (mOptions == null) {
+                return;
+            }
+            long expiration = SystemClock.elapsedRealtime() + disableDuration;
+            // Protect it against overflow
+            if (expiration < 0) {
+                expiration = Long.MAX_VALUE;
+            }
+            if (componentName != null) {
+                if (mOptions.disabledActivities == null) {
+                    mOptions.disabledActivities = new ArrayMap<>();
+                }
+                mOptions.disabledActivities.put(componentName.flattenToString(), expiration);
+            } else {
+                mOptions.appDisabledExpiration = expiration;
+            }
+        }
+    }
+
+    void notifyReenableAutofill() {
+        synchronized (mLock) {
+            if (mOptions == null) {
+                return;
+            }
+            mOptions.appDisabledExpiration = 0;
+            mOptions.disabledActivities = null;
+        }
+    }
+
+    private void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) {
+        if (sVerbose) {
+            Log.v(TAG, "notifyNoFillUi(): sessionFinishedState=" + sessionFinishedState);
+        }
+        final View anchor = findView(id);
+        if (anchor == null) {
+            return;
+        }
+
+        notifyCallback(sessionId, id, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
+
+        if (sessionFinishedState != STATE_UNKNOWN) {
+            // Callback call was "hijacked" to also update the session state.
+            setSessionFinished(sessionFinishedState, /* autofillableIds= */ null);
+        }
+    }
+
+    private void notifyCallback(
+            int sessionId, AutofillId id, @AutofillCallback.AutofillEventType int event) {
+        if (sVerbose) {
+            Log.v(TAG, "notifyCallback(): sessionId=" + sessionId + ", autofillId=" + id
+                    + ", event=" + event);
+        }
+        final View anchor = findView(id);
+        if (anchor == null) {
+            return;
+        }
+
+        AutofillCallback callback = null;
+        synchronized (mLock) {
+            if (mSessionId == sessionId && getClient() != null) {
+                callback = mCallback;
+            }
+        }
+
+        if (callback != null) {
+            if (id.isVirtualInt()) {
+                callback.onAutofillEvent(
+                        anchor, id.getVirtualChildIntId(), event);
+            } else {
+                callback.onAutofillEvent(anchor, event);
+            }
+        }
+    }
+
+    /**
+     * Find a single view by its id.
+     *
+     * @param autofillId The autofill id of the view
+     *
+     * @return The view or {@code null} if view was not found
+     */
+    private View findView(@NonNull AutofillId autofillId) {
+        final AutofillClient client = getClient();
+        if (client != null) {
+            return client.autofillClientFindViewByAutofillIdTraversal(autofillId);
+        }
+        return null;
+    }
+
+    /** @hide */
+    public boolean hasAutofillFeature() {
+        return mService != null;
+    }
+
+    /** @hide */
+    public void onPendingSaveUi(int operation, IBinder token) {
+        if (sVerbose) Log.v(TAG, "onPendingSaveUi(" + operation + "): " + token);
+
+        synchronized (mLock) {
+            try {
+                mService.onPendingSaveUi(operation, token);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error in onPendingSaveUi: ", e);
+            }
+        }
+    }
+
+    /** @hide */
+    public void dump(String outerPrefix, PrintWriter pw) {
+        pw.print(outerPrefix); pw.println("AutofillManager:");
+        final String pfx = outerPrefix + "  ";
+        pw.print(pfx); pw.print("sessionId: "); pw.println(mSessionId);
+        pw.print(pfx); pw.print("state: "); pw.println(getStateAsStringLocked());
+        pw.print(pfx); pw.print("context: "); pw.println(mContext);
+        pw.print(pfx); pw.print("service client: "); pw.println(mServiceClient);
+        final AutofillClient client = getClient();
+        if (client != null) {
+            pw.print(pfx); pw.print("client: "); pw.print(client);
+            pw.print(" ("); pw.print(client.autofillClientGetActivityToken()); pw.println(')');
+        }
+        pw.print(pfx); pw.print("enabled: "); pw.println(mEnabled);
+        pw.print(pfx); pw.print("enabledAugmentedOnly: "); pw.println(mForAugmentedAutofillOnly);
+        pw.print(pfx); pw.print("hasService: "); pw.println(mService != null);
+        pw.print(pfx); pw.print("hasCallback: "); pw.println(mCallback != null);
+        pw.print(pfx); pw.print("onInvisibleCalled "); pw.println(mOnInvisibleCalled);
+        pw.print(pfx); pw.print("last autofilled data: "); pw.println(mLastAutofilledData);
+        pw.print(pfx); pw.print("id of last fill UI shown: "); pw.println(mIdShownFillUi);
+        pw.print(pfx); pw.print("tracked views: ");
+        if (mTrackedViews == null) {
+            pw.println("null");
+        } else {
+            final String pfx2 = pfx + "  ";
+            pw.println();
+            pw.print(pfx2); pw.print("visible:"); pw.println(mTrackedViews.mVisibleTrackedIds);
+            pw.print(pfx2); pw.print("invisible:"); pw.println(mTrackedViews.mInvisibleTrackedIds);
+        }
+        pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds);
+        pw.print(pfx); pw.print("entered ids: "); pw.println(mEnteredIds);
+        if (mEnteredForAugmentedAutofillIds != null) {
+            pw.print(pfx); pw.print("entered ids for augmented autofill: ");
+            pw.println(mEnteredForAugmentedAutofillIds);
+        }
+        if (mForAugmentedAutofillOnly) {
+            pw.print(pfx); pw.println("For Augmented Autofill Only");
+        }
+        pw.print(pfx); pw.print("save trigger id: "); pw.println(mSaveTriggerId);
+        pw.print(pfx); pw.print("save on finish(): "); pw.println(mSaveOnFinish);
+        if (mOptions != null) {
+            pw.print(pfx); pw.print("options: "); mOptions.dumpShort(pw); pw.println();
+        }
+        pw.print(pfx); pw.print("compat mode enabled: ");
+        synchronized (mLock) {
+            if (mCompatibilityBridge != null) {
+                final String pfx2 = pfx + "  ";
+                pw.println("true");
+                pw.print(pfx2); pw.print("windowId: ");
+                pw.println(mCompatibilityBridge.mFocusedWindowId);
+                pw.print(pfx2); pw.print("nodeId: ");
+                pw.println(mCompatibilityBridge.mFocusedNodeId);
+                pw.print(pfx2); pw.print("virtualId: ");
+                pw.println(AccessibilityNodeInfo
+                        .getVirtualDescendantId(mCompatibilityBridge.mFocusedNodeId));
+                pw.print(pfx2); pw.print("focusedBounds: ");
+                pw.println(mCompatibilityBridge.mFocusedBounds);
+            } else {
+                pw.println("false");
+            }
+        }
+        pw.print(pfx); pw.print("debug: "); pw.print(sDebug);
+        pw.print(" verbose: "); pw.println(sVerbose);
+    }
+
+    @GuardedBy("mLock")
+    private String getStateAsStringLocked() {
+        return getStateAsString(mState);
+    }
+
+    @NonNull
+    private static String getStateAsString(int state) {
+        switch (state) {
+            case STATE_UNKNOWN:
+                return "UNKNOWN";
+            case STATE_ACTIVE:
+                return "ACTIVE";
+            case STATE_FINISHED:
+                return "FINISHED";
+            case STATE_SHOWING_SAVE_UI:
+                return "SHOWING_SAVE_UI";
+            case STATE_DISABLED_BY_SERVICE:
+                return "DISABLED_BY_SERVICE";
+            case STATE_UNKNOWN_COMPAT_MODE:
+                return "UNKNOWN_COMPAT_MODE";
+            case STATE_UNKNOWN_FAILED:
+                return "UNKNOWN_FAILED";
+            default:
+                return "INVALID:" + state;
+        }
+    }
+
+    /** @hide */
+    public static String getSmartSuggestionModeToString(@SmartSuggestionMode int flags) {
+        switch (flags) {
+            case FLAG_SMART_SUGGESTION_OFF:
+                return "OFF";
+            case FLAG_SMART_SUGGESTION_SYSTEM:
+                return "SYSTEM";
+            default:
+                return "INVALID:" + flags;
+        }
+    }
+
+    @GuardedBy("mLock")
+    private boolean isActiveLocked() {
+        return mState == STATE_ACTIVE;
+    }
+
+    @GuardedBy("mLock")
+    private boolean isDisabledByServiceLocked() {
+        return mState == STATE_DISABLED_BY_SERVICE;
+    }
+
+    @GuardedBy("mLock")
+    private boolean isFinishedLocked() {
+        return mState == STATE_FINISHED;
+    }
+
+    private void post(Runnable runnable) {
+        final AutofillClient client = getClient();
+        if (client == null) {
+            if (sVerbose) Log.v(TAG, "ignoring post() because client is null");
+            return;
+        }
+        client.autofillClientRunOnUiThread(runnable);
+    }
+
+    /**
+     * Implementation of the accessibility based compatibility.
+     */
+    private final class CompatibilityBridge implements AccessibilityManager.AccessibilityPolicy {
+        @GuardedBy("mLock")
+        private final Rect mFocusedBounds = new Rect();
+        @GuardedBy("mLock")
+        private final Rect mTempBounds = new Rect();
+
+        @GuardedBy("mLock")
+        private int mFocusedWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+        @GuardedBy("mLock")
+        private long mFocusedNodeId = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
+
+        // Need to report a fake service in case a11y clients check the service list
+        @NonNull
+        @GuardedBy("mLock")
+        AccessibilityServiceInfo mCompatServiceInfo;
+
+        CompatibilityBridge() {
+            final AccessibilityManager am = AccessibilityManager.getInstance(mContext);
+            am.setAccessibilityPolicy(this);
+        }
+
+        private AccessibilityServiceInfo getCompatServiceInfo() {
+            synchronized (mLock) {
+                if (mCompatServiceInfo != null) {
+                    return mCompatServiceInfo;
+                }
+                final Intent intent = new Intent();
+                intent.setComponent(new ComponentName("android",
+                        "com.android.server.autofill.AutofillCompatAccessibilityService"));
+                final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(
+                        intent, PackageManager.MATCH_SYSTEM_ONLY | PackageManager.GET_META_DATA);
+                try {
+                    mCompatServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext);
+                } catch (XmlPullParserException | IOException e) {
+                    Log.e(TAG, "Cannot find compat autofill service:" + intent);
+                    throw new IllegalStateException("Cannot find compat autofill service");
+                }
+                return mCompatServiceInfo;
+            }
+        }
+
+        @Override
+        public boolean isEnabled(boolean accessibilityEnabled) {
+            return true;
+        }
+
+        @Override
+        public int getRelevantEventTypes(int relevantEventTypes) {
+            return relevantEventTypes | AccessibilityEvent.TYPE_VIEW_FOCUSED
+                    | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED
+                    | AccessibilityEvent.TYPE_VIEW_CLICKED
+                    | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
+        }
+
+        @Override
+        public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(
+               List<AccessibilityServiceInfo> installedServices) {
+            if (installedServices == null) {
+                installedServices = new ArrayList<>();
+            }
+            installedServices.add(getCompatServiceInfo());
+            return installedServices;
+        }
+
+        @Override
+        public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
+                int feedbackTypeFlags, List<AccessibilityServiceInfo> enabledService) {
+            if (enabledService == null) {
+                enabledService = new ArrayList<>();
+            }
+            enabledService.add(getCompatServiceInfo());
+            return enabledService;
+        }
+
+        @Override
+        public AccessibilityEvent onAccessibilityEvent(AccessibilityEvent event,
+                boolean accessibilityEnabled, int relevantEventTypes) {
+            final int type = event.getEventType();
+            if (sVerbose) {
+                // NOTE: this is waaay spammy, but that's life.
+                Log.v(TAG, "onAccessibilityEvent(" + AccessibilityEvent.eventTypeToString(type)
+                        + "): virtualId="
+                        + AccessibilityNodeInfo.getVirtualDescendantId(event.getSourceNodeId())
+                        + ", client=" + getClient());
+            }
+            switch (type) {
+                case AccessibilityEvent.TYPE_VIEW_FOCUSED: {
+                    synchronized (mLock) {
+                        if (mFocusedWindowId == event.getWindowId()
+                                && mFocusedNodeId == event.getSourceNodeId()) {
+                            return event;
+                        }
+                        if (mFocusedWindowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID
+                                && mFocusedNodeId != AccessibilityNodeInfo.UNDEFINED_NODE_ID) {
+                            notifyViewExited(mFocusedWindowId, mFocusedNodeId);
+                            mFocusedWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+                            mFocusedNodeId = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
+                            mFocusedBounds.set(0, 0, 0, 0);
+                        }
+                        final int windowId = event.getWindowId();
+                        final long nodeId = event.getSourceNodeId();
+                        if (notifyViewEntered(windowId, nodeId, mFocusedBounds)) {
+                            mFocusedWindowId = windowId;
+                            mFocusedNodeId = nodeId;
+                        }
+                    }
+                } break;
+
+                case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED: {
+                    synchronized (mLock) {
+                        if (mFocusedWindowId == event.getWindowId()
+                                && mFocusedNodeId == event.getSourceNodeId()) {
+                            notifyValueChanged(event.getWindowId(), event.getSourceNodeId());
+                        }
+                    }
+                } break;
+
+                case AccessibilityEvent.TYPE_VIEW_CLICKED: {
+                    synchronized (mLock) {
+                        notifyViewClicked(event.getWindowId(), event.getSourceNodeId());
+                    }
+                } break;
+
+                case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: {
+                    final AutofillClient client = getClient();
+                    if (client != null) {
+                        synchronized (mLock) {
+                            if (client.autofillClientIsFillUiShowing()) {
+                                notifyViewEntered(mFocusedWindowId, mFocusedNodeId, mFocusedBounds);
+                            }
+                            updateTrackedViewsLocked();
+                        }
+                    }
+                } break;
+            }
+
+            return accessibilityEnabled ? event : null;
+        }
+
+        private boolean notifyViewEntered(int windowId, long nodeId, Rect focusedBounds) {
+            final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(nodeId);
+            if (!isVirtualNode(virtualId)) {
+                return false;
+            }
+            final View view = findViewByAccessibilityId(windowId, nodeId);
+            if (view == null) {
+                return false;
+            }
+            final AccessibilityNodeInfo node = findVirtualNodeByAccessibilityId(view, virtualId);
+            if (node == null) {
+                return false;
+            }
+            if (!node.isEditable()) {
+                return false;
+            }
+            final Rect newBounds = mTempBounds;
+            node.getBoundsInScreen(newBounds);
+            if (newBounds.equals(focusedBounds)) {
+                return false;
+            }
+            focusedBounds.set(newBounds);
+            AutofillManager.this.notifyViewEntered(view, virtualId, newBounds);
+            return true;
+        }
+
+        private void notifyViewExited(int windowId, long nodeId) {
+            final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(nodeId);
+            if (!isVirtualNode(virtualId)) {
+                return;
+            }
+            final View view = findViewByAccessibilityId(windowId, nodeId);
+            if (view == null) {
+                return;
+            }
+            AutofillManager.this.notifyViewExited(view, virtualId);
+        }
+
+        private void notifyValueChanged(int windowId, long nodeId) {
+            final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(nodeId);
+            if (!isVirtualNode(virtualId)) {
+                return;
+            }
+            final View view = findViewByAccessibilityId(windowId, nodeId);
+            if (view == null) {
+                return;
+            }
+            final AccessibilityNodeInfo node = findVirtualNodeByAccessibilityId(view, virtualId);
+            if (node == null) {
+                return;
+            }
+            AutofillManager.this.notifyValueChanged(view, virtualId,
+                    AutofillValue.forText(node.getText()));
+        }
+
+        private void notifyViewClicked(int windowId, long nodeId) {
+            final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(nodeId);
+            if (!isVirtualNode(virtualId)) {
+                return;
+            }
+            final View view = findViewByAccessibilityId(windowId, nodeId);
+            if (view == null) {
+                return;
+            }
+            final AccessibilityNodeInfo node = findVirtualNodeByAccessibilityId(view, virtualId);
+            if (node == null) {
+                return;
+            }
+            AutofillManager.this.notifyViewClicked(view, virtualId);
+        }
+
+        @GuardedBy("mLock")
+        private void updateTrackedViewsLocked() {
+            if (mTrackedViews != null) {
+                mTrackedViews.onVisibleForAutofillChangedLocked();
+            }
+        }
+
+        private View findViewByAccessibilityId(int windowId, long nodeId) {
+            final AutofillClient client = getClient();
+            if (client == null) {
+                return null;
+            }
+            final int viewId = AccessibilityNodeInfo.getAccessibilityViewId(nodeId);
+            return client.autofillClientFindViewByAccessibilityIdTraversal(viewId, windowId);
+        }
+
+        private AccessibilityNodeInfo findVirtualNodeByAccessibilityId(View view, int virtualId) {
+            final AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
+            if (provider == null) {
+                return null;
+            }
+            return provider.createAccessibilityNodeInfo(virtualId);
+        }
+
+        private boolean isVirtualNode(int nodeId) {
+            return nodeId != AccessibilityNodeProvider.HOST_VIEW_ID
+                    && nodeId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
+        }
+    }
+
+    /**
+     * View tracking information. Once all tracked views become invisible the session is finished.
+     */
+    private class TrackedViews {
+        /** Visible tracked views */
+        @Nullable private ArraySet<AutofillId> mVisibleTrackedIds;
+
+        /** Invisible tracked views */
+        @Nullable private ArraySet<AutofillId> mInvisibleTrackedIds;
+
+        /**
+         * Check if set is null or value is in set.
+         *
+         * @param set   The set or null (== empty set)
+         * @param value The value that might be in the set
+         *
+         * @return {@code true} iff set is not empty and value is in set
+         */
+        // TODO: move to Helper as static method
+        private <T> boolean isInSet(@Nullable ArraySet<T> set, T value) {
+            return set != null && set.contains(value);
+        }
+
+        /**
+         * Add a value to a set. If set is null, create a new set.
+         *
+         * @param set        The set or null (== empty set)
+         * @param valueToAdd The value to add
+         *
+         * @return The set including the new value. If set was {@code null}, a set containing only
+         *         the new value.
+         */
+        // TODO: move to Helper as static method
+        @NonNull
+        private <T> ArraySet<T> addToSet(@Nullable ArraySet<T> set, T valueToAdd) {
+            if (set == null) {
+                set = new ArraySet<>(1);
+            }
+
+            set.add(valueToAdd);
+
+            return set;
+        }
+
+        /**
+         * Remove a value from a set.
+         *
+         * @param set           The set or null (== empty set)
+         * @param valueToRemove The value to remove
+         *
+         * @return The set without the removed value. {@code null} if set was null, or is empty
+         *         after removal.
+         */
+        // TODO: move to Helper as static method
+        @Nullable
+        private <T> ArraySet<T> removeFromSet(@Nullable ArraySet<T> set, T valueToRemove) {
+            if (set == null) {
+                return null;
+            }
+
+            set.remove(valueToRemove);
+
+            if (set.isEmpty()) {
+                return null;
+            }
+
+            return set;
+        }
+
+        /**
+         * Set the tracked views.
+         *
+         * @param trackedIds The views to be tracked
+         */
+        TrackedViews(@Nullable AutofillId[] trackedIds) {
+            final AutofillClient client = getClient();
+            if (!ArrayUtils.isEmpty(trackedIds) && client != null) {
+                final boolean[] isVisible;
+
+                if (client.autofillClientIsVisibleForAutofill()) {
+                    if (sVerbose) Log.v(TAG, "client is visible, check tracked ids");
+                    isVisible = client.autofillClientGetViewVisibility(trackedIds);
+                } else {
+                    // All false
+                    isVisible = new boolean[trackedIds.length];
+                }
+
+                final int numIds = trackedIds.length;
+                for (int i = 0; i < numIds; i++) {
+                    final AutofillId id = trackedIds[i];
+                    id.resetSessionId();
+
+                    if (isVisible[i]) {
+                        mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id);
+                    } else {
+                        mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id);
+                    }
+                }
+            }
+
+            if (sVerbose) {
+                Log.v(TAG, "TrackedViews(trackedIds=" + Arrays.toString(trackedIds) + "): "
+                        + " mVisibleTrackedIds=" + mVisibleTrackedIds
+                        + " mInvisibleTrackedIds=" + mInvisibleTrackedIds);
+            }
+
+            if (mVisibleTrackedIds == null) {
+                finishSessionLocked();
+            }
+        }
+
+        /**
+         * Called when a {@link View view's} visibility changes.
+         *
+         * @param id the id of the view/virtual view whose visibility changed.
+         * @param isVisible visible if the view is visible in the view hierarchy.
+         */
+        @GuardedBy("mLock")
+        void notifyViewVisibilityChangedLocked(@NonNull AutofillId id, boolean isVisible) {
+            if (sDebug) {
+                Log.d(TAG, "notifyViewVisibilityChangedLocked(): id=" + id + " isVisible="
+                        + isVisible);
+            }
+
+            if (isClientVisibleForAutofillLocked()) {
+                if (isVisible) {
+                    if (isInSet(mInvisibleTrackedIds, id)) {
+                        mInvisibleTrackedIds = removeFromSet(mInvisibleTrackedIds, id);
+                        mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id);
+                    }
+                } else {
+                    if (isInSet(mVisibleTrackedIds, id)) {
+                        mVisibleTrackedIds = removeFromSet(mVisibleTrackedIds, id);
+                        mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id);
+                    }
+                }
+            }
+
+            if (mVisibleTrackedIds == null) {
+                if (sVerbose) {
+                    Log.v(TAG, "No more visible ids. Invisibile = " + mInvisibleTrackedIds);
+                }
+                finishSessionLocked();
+            }
+        }
+
+        /**
+         * Called once the client becomes visible.
+         *
+         * @see AutofillClient#autofillClientIsVisibleForAutofill()
+         */
+        @GuardedBy("mLock")
+        void onVisibleForAutofillChangedLocked() {
+            // The visibility of the views might have changed while the client was not be visible,
+            // hence update the visibility state for all views.
+            AutofillClient client = getClient();
+            ArraySet<AutofillId> updatedVisibleTrackedIds = null;
+            ArraySet<AutofillId> updatedInvisibleTrackedIds = null;
+            if (client != null) {
+                if (sVerbose) {
+                    Log.v(TAG, "onVisibleForAutofillChangedLocked(): inv= " + mInvisibleTrackedIds
+                            + " vis=" + mVisibleTrackedIds);
+                }
+                if (mInvisibleTrackedIds != null) {
+                    final ArrayList<AutofillId> orderedInvisibleIds =
+                            new ArrayList<>(mInvisibleTrackedIds);
+                    final boolean[] isVisible = client.autofillClientGetViewVisibility(
+                            Helper.toArray(orderedInvisibleIds));
+
+                    final int numInvisibleTrackedIds = orderedInvisibleIds.size();
+                    for (int i = 0; i < numInvisibleTrackedIds; i++) {
+                        final AutofillId id = orderedInvisibleIds.get(i);
+                        if (isVisible[i]) {
+                            updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id);
+
+                            if (sDebug) {
+                                Log.d(TAG, "onVisibleForAutofill() " + id + " became visible");
+                            }
+                        } else {
+                            updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id);
+                        }
+                    }
+                }
+
+                if (mVisibleTrackedIds != null) {
+                    final ArrayList<AutofillId> orderedVisibleIds =
+                            new ArrayList<>(mVisibleTrackedIds);
+                    final boolean[] isVisible = client.autofillClientGetViewVisibility(
+                            Helper.toArray(orderedVisibleIds));
+
+                    final int numVisibleTrackedIds = orderedVisibleIds.size();
+                    for (int i = 0; i < numVisibleTrackedIds; i++) {
+                        final AutofillId id = orderedVisibleIds.get(i);
+
+                        if (isVisible[i]) {
+                            updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id);
+                        } else {
+                            updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id);
+
+                            if (sDebug) {
+                                Log.d(TAG, "onVisibleForAutofill() " + id + " became invisible");
+                            }
+                        }
+                    }
+                }
+
+                mInvisibleTrackedIds = updatedInvisibleTrackedIds;
+                mVisibleTrackedIds = updatedVisibleTrackedIds;
+            }
+
+            if (mVisibleTrackedIds == null) {
+                if (sVerbose) {
+                    Log.v(TAG, "onVisibleForAutofillChangedLocked(): no more visible ids");
+                }
+                finishSessionLocked();
+            }
+        }
+    }
+
+    /**
+     * Callback for autofill related events.
+     *
+     * <p>Typically used for applications that display their own "auto-complete" views, so they can
+     * enable / disable such views when the autofill UI is shown / hidden.
+     */
+    public abstract static class AutofillCallback {
+
+        /** @hide */
+        @IntDef(prefix = { "EVENT_INPUT_" }, value = {
+                EVENT_INPUT_SHOWN,
+                EVENT_INPUT_HIDDEN,
+                EVENT_INPUT_UNAVAILABLE
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface AutofillEventType {}
+
+        /**
+         * The autofill input UI associated with the view was shown.
+         *
+         * <p>If the view provides its own auto-complete UI and its currently shown, it
+         * should be hidden upon receiving this event.
+         */
+        public static final int EVENT_INPUT_SHOWN = 1;
+
+        /**
+         * The autofill input UI associated with the view was hidden.
+         *
+         * <p>If the view provides its own auto-complete UI that was hidden upon a
+         * {@link #EVENT_INPUT_SHOWN} event, it could be shown again now.
+         */
+        public static final int EVENT_INPUT_HIDDEN = 2;
+
+        /**
+         * The autofill input UI associated with the view isn't shown because
+         * autofill is not available.
+         *
+         * <p>If the view provides its own auto-complete UI but was not displaying it
+         * to avoid flickering, it could shown it upon receiving this event.
+         */
+        public static final int EVENT_INPUT_UNAVAILABLE = 3;
+
+        /**
+         * Called after a change in the autofill state associated with a view.
+         *
+         * @param view view associated with the change.
+         *
+         * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}.
+         */
+        public void onAutofillEvent(@NonNull View view, @AutofillEventType int event) {
+        }
+
+        /**
+         * Called after a change in the autofill state associated with a virtual view.
+         *
+         * @param view parent view associated with the change.
+         * @param virtualId id identifying the virtual child inside the parent view.
+         *
+         * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}.
+         */
+        public void onAutofillEvent(@NonNull View view, int virtualId,
+                @AutofillEventType int event) {
+        }
+    }
+
+    private static final class AutofillManagerClient extends IAutoFillManagerClient.Stub {
+        private final WeakReference<AutofillManager> mAfm;
+
+        private AutofillManagerClient(AutofillManager autofillManager) {
+            mAfm = new WeakReference<>(autofillManager);
+        }
+
+        @Override
+        public void setState(int flags) {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                afm.post(() -> afm.setState(flags));
+            }
+        }
+
+        @Override
+        public void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values,
+                boolean hideHighlight) {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                afm.post(() -> afm.autofill(sessionId, ids, values, hideHighlight));
+            }
+        }
+
+        @Override
+        public void autofillContent(int sessionId, AutofillId id, ClipData content) {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                afm.post(() -> afm.autofillContent(sessionId, id, content));
+            }
+        }
+
+        @Override
+        public void authenticate(int sessionId, int authenticationId, IntentSender intent,
+                Intent fillInIntent, boolean authenticateInline) {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                afm.post(() -> afm.authenticate(sessionId, authenticationId, intent, fillInIntent,
+                        authenticateInline));
+            }
+        }
+
+        @Override
+        public void requestShowFillUi(int sessionId, AutofillId id, int width, int height,
+                Rect anchorBounds, IAutofillWindowPresenter presenter) {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                afm.post(() -> afm.requestShowFillUi(sessionId, id, width, height, anchorBounds,
+                        presenter));
+            }
+        }
+
+        @Override
+        public void requestHideFillUi(int sessionId, AutofillId id) {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                afm.post(() -> afm.requestHideFillUi(id, false));
+            }
+        }
+
+        @Override
+        public void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                afm.post(() -> afm.notifyNoFillUi(sessionId, id, sessionFinishedState));
+            }
+        }
+
+        @Override
+        public void notifyFillUiShown(int sessionId, AutofillId id) {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                afm.post(
+                        () -> afm.notifyCallback(
+                                sessionId, id, AutofillCallback.EVENT_INPUT_SHOWN));
+            }
+        }
+
+        @Override
+        public void notifyFillUiHidden(int sessionId, AutofillId id) {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                afm.post(
+                        () -> afm.notifyCallback(
+                                sessionId, id, AutofillCallback.EVENT_INPUT_HIDDEN));
+            }
+        }
+
+        @Override
+        public void notifyDisableAutofill(long disableDuration, ComponentName componentName)
+                throws RemoteException {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                afm.post(() -> afm.notifyDisableAutofill(disableDuration, componentName));
+            }
+        }
+
+        @Override
+        public void dispatchUnhandledKey(int sessionId, AutofillId id, KeyEvent fullScreen) {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                afm.post(() -> afm.dispatchUnhandledKey(sessionId, id, fullScreen));
+            }
+        }
+
+        @Override
+        public void startIntentSender(IntentSender intentSender, Intent intent) {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                afm.post(() -> {
+                    try {
+                        afm.mContext.startIntentSender(intentSender, intent, 0, 0, 0);
+                    } catch (IntentSender.SendIntentException e) {
+                        Log.e(TAG, "startIntentSender() failed for intent:" + intentSender, e);
+                    }
+                });
+            }
+        }
+
+        @Override
+        public void setTrackedViews(int sessionId, AutofillId[] ids,
+                boolean saveOnAllViewsInvisible, boolean saveOnFinish, AutofillId[] fillableIds,
+                AutofillId saveTriggerId) {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                afm.post(() -> afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible,
+                        saveOnFinish, fillableIds, saveTriggerId));
+            }
+        }
+
+        @Override
+        public void setSaveUiState(int sessionId, boolean shown) {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                afm.post(() -> afm.setSaveUiState(sessionId, shown));
+            }
+        }
+
+        @Override
+        public void setSessionFinished(int newState, List<AutofillId> autofillableIds) {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                afm.post(() -> afm.setSessionFinished(newState, autofillableIds));
+            }
+        }
+
+        @Override
+        public void getAugmentedAutofillClient(IResultReceiver result) {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                afm.post(() -> afm.getAugmentedAutofillClient(result));
+            }
+        }
+
+        @Override
+        public void requestShowSoftInput(@NonNull AutofillId id) {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                afm.post(() -> afm.requestShowSoftInput(id));
+            }
+        }
+    }
+
+    private static final class AugmentedAutofillManagerClient
+            extends IAugmentedAutofillManagerClient.Stub {
+        private final WeakReference<AutofillManager> mAfm;
+
+        private AugmentedAutofillManagerClient(AutofillManager autofillManager) {
+            mAfm = new WeakReference<>(autofillManager);
+        }
+
+        @Nullable
+        @Override
+        public ViewNodeParcelable getViewNodeParcelable(@NonNull AutofillId id) {
+            final AutofillManager afm = mAfm.get();
+            if (afm == null) return null;
+
+            final View view = getView(afm, id);
+            if (view == null) {
+                Log.w(TAG, "getViewNodeParcelable(" + id + "): could not find view");
+                return null;
+            }
+            final ViewRootImpl root = view.getViewRootImpl();
+            if (root != null
+                    && (root.getWindowFlags() & WindowManager.LayoutParams.FLAG_SECURE) == 0) {
+                ViewNodeBuilder viewStructure = new ViewNodeBuilder();
+                viewStructure.setAutofillId(view.getAutofillId());
+                view.onProvideAutofillStructure(viewStructure, /* flags= */ 0);
+                // TODO(b/141703532): We don't call View#onProvideAutofillVirtualStructure for
+                //  efficiency reason. But this also means we will return null for virtual views
+                //  for now. We will add a new API to fetch the view node info of the virtual
+                //  child view.
+                ViewNode viewNode = viewStructure.getViewNode();
+                if (viewNode != null && id.equals(viewNode.getAutofillId())) {
+                    return new ViewNodeParcelable(viewNode);
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public Rect getViewCoordinates(@NonNull AutofillId id) {
+            final AutofillManager afm = mAfm.get();
+            if (afm == null) return null;
+
+            final View view = getView(afm, id);
+            if (view == null) {
+                return null;
+            }
+            final Rect windowVisibleDisplayFrame = new Rect();
+            view.getWindowVisibleDisplayFrame(windowVisibleDisplayFrame);
+            final int[] location = new int[2];
+            view.getLocationOnScreen(location);
+            final Rect rect = new Rect(location[0], location[1] - windowVisibleDisplayFrame.top,
+                    location[0] + view.getWidth(),
+                    location[1] - windowVisibleDisplayFrame.top + view.getHeight());
+            if (sVerbose) {
+                Log.v(TAG, "Coordinates for " + id + ": " + rect);
+            }
+            return rect;
+        }
+
+        @Override
+        public void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values,
+                boolean hideHighlight) {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                afm.post(() -> afm.autofill(sessionId, ids, values, hideHighlight));
+            }
+        }
+
+        @Override
+        public void requestShowFillUi(int sessionId, AutofillId id, int width, int height,
+                Rect anchorBounds, IAutofillWindowPresenter presenter) {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                afm.post(() -> afm.requestShowFillUi(sessionId, id, width, height, anchorBounds,
+                        presenter));
+            }
+        }
+
+        @Override
+        public void requestHideFillUi(int sessionId, AutofillId id) {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                afm.post(() -> afm.requestHideFillUi(id, false));
+            }
+        }
+
+        @Override
+        public boolean requestAutofill(int sessionId, AutofillId id) {
+            final AutofillManager afm = mAfm.get();
+            if (afm == null || afm.mSessionId != sessionId) {
+                if (sDebug) {
+                    Slog.d(TAG, "Autofill not available or sessionId doesn't match");
+                }
+                return false;
+            }
+            final View view = getView(afm, id);
+            if (view == null || !view.isFocused()) {
+                if (sDebug) {
+                    Slog.d(TAG, "View not available or is not on focus");
+                }
+                return false;
+            }
+            if (sVerbose) {
+                Log.v(TAG, "requestAutofill() by AugmentedAutofillService.");
+            }
+            afm.post(() -> afm.requestAutofillFromNewSession(view));
+            return true;
+        }
+
+        @Nullable
+        private View getView(@NonNull AutofillManager afm, @NonNull AutofillId id) {
+            final AutofillClient client = afm.getClient();
+            if (client == null) {
+                Log.w(TAG, "getView(" + id + "): no autofill client");
+                return null;
+            }
+            View view = client.autofillClientFindViewByAutofillIdTraversal(id);
+            if (view == null) {
+                Log.w(TAG, "getView(" + id + "): could not find view");
+            }
+            return view;
+        }
+    }
+}
diff --git a/android/view/autofill/AutofillManagerInternal.java b/android/view/autofill/AutofillManagerInternal.java
new file mode 100644
index 0000000..a07d46d
--- /dev/null
+++ b/android/view/autofill/AutofillManagerInternal.java
@@ -0,0 +1,56 @@
+/*
+ * 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.view.autofill;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.AutofillOptions;
+
+/**
+ * Autofill Manager local system service interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class AutofillManagerInternal {
+
+    /**
+     * Notifies the manager that the back key was pressed.
+     */
+    public abstract void onBackKeyPressed();
+
+    /**
+     * Gets autofill options for a package.
+     *
+     * <p><b>NOTE: </b>this method is called by the {@code ActivityManager} service and hence cannot
+     * hold the main service lock.
+     *
+     * @param packageName The package for which to query.
+     * @param versionCode The package version code.
+     * @param userId The user id for which to query.
+     */
+    @Nullable
+    public abstract AutofillOptions getAutofillOptions(@NonNull String packageName,
+            long versionCode, @UserIdInt int userId);
+
+    /**
+     * Checks whether the given {@code uid} owns the
+     * {@link android.service.autofill.augmented.AugmentedAutofillService} implementation associated
+     * with the given {@code userId}.
+     */
+    public abstract boolean isAugmentedAutofillServiceForUser(@NonNull int callingUid,
+            @UserIdInt int userId);
+}
diff --git a/android/view/autofill/AutofillPopupWindow.java b/android/view/autofill/AutofillPopupWindow.java
new file mode 100644
index 0000000..3161c3c
--- /dev/null
+++ b/android/view/autofill/AutofillPopupWindow.java
@@ -0,0 +1,427 @@
+/*
+ * 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.view.autofill;
+
+import static android.view.autofill.Helper.sVerbose;
+
+import android.annotation.NonNull;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.transition.Transition;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.widget.PopupWindow;
+import android.window.WindowMetricsHelper;
+
+/**
+ * Custom {@link PopupWindow} used to isolate its content from the autofilled app - the
+ * UI is rendered in a framework process, but it's controlled by the app.
+ *
+ * TODO(b/34943932): use an app surface control solution.
+ *
+ * @hide
+ */
+public class AutofillPopupWindow extends PopupWindow {
+
+    private static final String TAG = "AutofillPopupWindow";
+
+    private final WindowPresenter mWindowPresenter;
+    private WindowManager.LayoutParams mWindowLayoutParams;
+    private boolean mFullScreen;
+
+    private final View.OnAttachStateChangeListener mOnAttachStateChangeListener =
+            new View.OnAttachStateChangeListener() {
+        @Override
+        public void onViewAttachedToWindow(View v) {
+            /* ignore - handled by the super class */
+        }
+
+        @Override
+        public void onViewDetachedFromWindow(View v) {
+            dismiss();
+        }
+    };
+
+    /**
+     * Creates a popup window with a presenter owning the window and responsible for
+     * showing/hiding/updating the backing window. This can be useful of the window is
+     * being shown by another process while the popup logic is in the process hosting
+     * the anchor view.
+     * <p>
+     * Using this constructor means that the presenter completely owns the content of
+     * the window and the following methods manipulating the window content shouldn't
+     * be used: {@link #getEnterTransition()}, {@link #setEnterTransition(Transition)},
+     * {@link #getExitTransition()}, {@link #setExitTransition(Transition)},
+     * {@link #getContentView()}, {@link #setContentView(View)}, {@link #getBackground()},
+     * {@link #setBackgroundDrawable(Drawable)}, {@link #getElevation()},
+     * {@link #setElevation(float)}, ({@link #getAnimationStyle()},
+     * {@link #setAnimationStyle(int)}, {@link #setTouchInterceptor(OnTouchListener)}.</p>
+     */
+    public AutofillPopupWindow(@NonNull IAutofillWindowPresenter presenter) {
+        mWindowPresenter = new WindowPresenter(presenter);
+
+        setTouchModal(false);
+        setOutsideTouchable(true);
+        setInputMethodMode(INPUT_METHOD_NOT_NEEDED);
+        setFocusable(true);
+    }
+
+    @Override
+    protected boolean hasContentView() {
+        return true;
+    }
+
+    @Override
+    protected boolean hasDecorView() {
+        return true;
+    }
+
+    @Override
+    protected LayoutParams getDecorViewLayoutParams() {
+        return mWindowLayoutParams;
+    }
+
+    /**
+     * The effective {@code update} method that should be called by its clients.
+     */
+    public void update(View anchor, int offsetX, int offsetY, int width, int height,
+            Rect virtualBounds) {
+        mFullScreen = width == LayoutParams.MATCH_PARENT;
+        // For no fullscreen autofill window, we want to show the window as system controlled one
+        // so it covers app windows, but it has to be an application type (so it's contained inside
+        // the application area). Hence, we set it to the application type with the highest z-order,
+        // which currently is TYPE_APPLICATION_ABOVE_SUB_PANEL.
+        // For fullscreen mode, autofill window is at the bottom of screen, it should not be
+        // clipped by app activity window. Fullscreen autofill window does not need to follow app
+        // anchor view position.
+        setWindowLayoutType(mFullScreen ? WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG
+                : WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL);
+        // If we are showing the popup for a virtual view we use a fake view which
+        // delegates to the anchor but present itself with the same bounds as the
+        // virtual view. This ensures that the location logic in popup works
+        // symmetrically when the dropdown is below and above the anchor.
+        final View actualAnchor;
+        if (mFullScreen) {
+            offsetX = 0;
+            offsetY = 0;
+            // If it is not fullscreen height, put window at bottom. Computes absolute position.
+            // Note that we cannot easily change default gravity from Gravity.TOP to
+            // Gravity.BOTTOM because PopupWindow base class does not expose computeGravity().
+            final WindowManager windowManager = anchor.getContext()
+                    .getSystemService(WindowManager.class);
+            final Rect windowBounds = WindowMetricsHelper.getBoundsExcludingNavigationBarAndCutout(
+                    windowManager.getCurrentWindowMetrics());
+            width = windowBounds.width();
+            if (height != LayoutParams.MATCH_PARENT) {
+                offsetY = windowBounds.height() - height;
+            }
+            actualAnchor = anchor;
+        } else if (virtualBounds != null) {
+            final int[] mLocationOnScreen = new int[] {virtualBounds.left, virtualBounds.top};
+            actualAnchor = new View(anchor.getContext()) {
+                @Override
+                public void getLocationOnScreen(int[] location) {
+                    location[0] = mLocationOnScreen[0];
+                    location[1] = mLocationOnScreen[1];
+                }
+
+                @Override
+                public int getAccessibilityViewId() {
+                    return anchor.getAccessibilityViewId();
+                }
+
+                @Override
+                public ViewTreeObserver getViewTreeObserver() {
+                    return anchor.getViewTreeObserver();
+                }
+
+                @Override
+                public IBinder getApplicationWindowToken() {
+                    return anchor.getApplicationWindowToken();
+                }
+
+                @Override
+                public View getRootView() {
+                    return anchor.getRootView();
+                }
+
+                @Override
+                public int getLayoutDirection() {
+                    return anchor.getLayoutDirection();
+                }
+
+                @Override
+                public void getWindowDisplayFrame(Rect outRect) {
+                    anchor.getWindowDisplayFrame(outRect);
+                }
+
+                @Override
+                public void addOnAttachStateChangeListener(
+                        OnAttachStateChangeListener listener) {
+                    anchor.addOnAttachStateChangeListener(listener);
+                }
+
+                @Override
+                public void removeOnAttachStateChangeListener(
+                        OnAttachStateChangeListener listener) {
+                    anchor.removeOnAttachStateChangeListener(listener);
+                }
+
+                @Override
+                public boolean isAttachedToWindow() {
+                    return anchor.isAttachedToWindow();
+                }
+
+                @Override
+                public boolean requestRectangleOnScreen(Rect rectangle, boolean immediate) {
+                    return anchor.requestRectangleOnScreen(rectangle, immediate);
+                }
+
+                @Override
+                public IBinder getWindowToken() {
+                    return anchor.getWindowToken();
+                }
+            };
+
+            actualAnchor.setLeftTopRightBottom(
+                    virtualBounds.left, virtualBounds.top,
+                    virtualBounds.right, virtualBounds.bottom);
+            actualAnchor.setScrollX(anchor.getScrollX());
+            actualAnchor.setScrollY(anchor.getScrollY());
+
+            anchor.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+                mLocationOnScreen[0] = mLocationOnScreen[0] - (scrollX - oldScrollX);
+                mLocationOnScreen[1] = mLocationOnScreen[1] - (scrollY - oldScrollY);
+            });
+            actualAnchor.setWillNotDraw(true);
+        } else {
+            actualAnchor = anchor;
+        }
+
+        if (!mFullScreen) {
+            // No fullscreen window animation is controlled by PopupWindow.
+            setAnimationStyle(-1);
+        } else if (height == LayoutParams.MATCH_PARENT) {
+            // Complete fullscreen autofill window has no animation.
+            setAnimationStyle(0);
+        } else {
+            // Slide half screen height autofill window from bottom.
+            setAnimationStyle(com.android.internal.R.style.AutofillHalfScreenAnimation);
+        }
+        if (!isShowing()) {
+            setWidth(width);
+            setHeight(height);
+            showAsDropDown(actualAnchor, offsetX, offsetY);
+        } else {
+            update(actualAnchor, offsetX, offsetY, width, height);
+        }
+    }
+
+    @Override
+    protected void update(View anchor, WindowManager.LayoutParams params) {
+        final int layoutDirection = anchor != null ? anchor.getLayoutDirection()
+                : View.LAYOUT_DIRECTION_LOCALE;
+        mWindowPresenter.show(params, getTransitionEpicenter(), isLayoutInsetDecor(),
+                layoutDirection);
+    }
+
+    @Override
+    protected boolean findDropDownPosition(View anchor, LayoutParams outParams,
+            int xOffset, int yOffset, int width, int height, int gravity, boolean allowScroll) {
+        if (mFullScreen) {
+            // In fullscreen mode, don't need consider the anchor view.
+            outParams.x = xOffset;
+            outParams.y = yOffset;
+            outParams.width = width;
+            outParams.height = height;
+            outParams.gravity = gravity;
+            return false;
+        }
+        return super.findDropDownPosition(anchor, outParams, xOffset, yOffset,
+                width, height, gravity, allowScroll);
+    }
+
+    @Override
+    public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
+        if (sVerbose) {
+            Log.v(TAG, "showAsDropDown(): anchor=" + anchor + ", xoff=" + xoff + ", yoff=" + yoff
+                    + ", isShowing(): " + isShowing());
+        }
+        if (isShowing()) {
+            return;
+        }
+
+        setShowing(true);
+        setDropDown(true);
+        attachToAnchor(anchor, xoff, yoff, gravity);
+        final WindowManager.LayoutParams p = mWindowLayoutParams = createPopupLayoutParams(
+                anchor.getWindowToken());
+        final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff,
+                p.width, p.height, gravity, getAllowScrollingAnchorParent());
+        updateAboveAnchor(aboveAnchor);
+        p.accessibilityIdOfAnchor = anchor.getAccessibilityViewId();
+        p.packageName = anchor.getContext().getPackageName();
+        mWindowPresenter.show(p, getTransitionEpicenter(), isLayoutInsetDecor(),
+                anchor.getLayoutDirection());
+    }
+
+    @Override
+    protected void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
+        super.attachToAnchor(anchor, xoff, yoff, gravity);
+        anchor.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
+    }
+
+    @Override
+    protected void detachFromAnchor() {
+        final View anchor = getAnchor();
+        if (anchor != null) {
+            anchor.removeOnAttachStateChangeListener(mOnAttachStateChangeListener);
+        }
+        super.detachFromAnchor();
+    }
+
+    @Override
+    public void dismiss() {
+        if (!isShowing() || isTransitioningToDismiss()) {
+            return;
+        }
+
+        setShowing(false);
+        setTransitioningToDismiss(true);
+
+        mWindowPresenter.hide(getTransitionEpicenter());
+        detachFromAnchor();
+        if (getOnDismissListener() != null) {
+            getOnDismissListener().onDismiss();
+        }
+    }
+
+    @Override
+    public int getAnimationStyle() {
+        throw new IllegalStateException("You can't call this!");
+    }
+
+    @Override
+    public Drawable getBackground() {
+        throw new IllegalStateException("You can't call this!");
+    }
+
+    @Override
+    public View getContentView() {
+        throw new IllegalStateException("You can't call this!");
+    }
+
+    @Override
+    public float getElevation() {
+        throw new IllegalStateException("You can't call this!");
+    }
+
+    @Override
+    public Transition getEnterTransition() {
+        throw new IllegalStateException("You can't call this!");
+    }
+
+    @Override
+    public Transition getExitTransition() {
+        throw new IllegalStateException("You can't call this!");
+    }
+
+    @Override
+    public void setBackgroundDrawable(Drawable background) {
+        throw new IllegalStateException("You can't call this!");
+    }
+
+    @Override
+    public void setContentView(View contentView) {
+        if (contentView != null) {
+            throw new IllegalStateException("You can't call this!");
+        }
+    }
+
+    @Override
+    public void setElevation(float elevation) {
+        throw new IllegalStateException("You can't call this!");
+    }
+
+    @Override
+    public void setEnterTransition(Transition enterTransition) {
+        throw new IllegalStateException("You can't call this!");
+    }
+
+    @Override
+    public void setExitTransition(Transition exitTransition) {
+        throw new IllegalStateException("You can't call this!");
+    }
+
+    @Override
+    public void setTouchInterceptor(OnTouchListener l) {
+        throw new IllegalStateException("You can't call this!");
+    }
+
+    /**
+     * Contract between the popup window and a presenter that is responsible for
+     * showing/hiding/updating the actual window.
+     *
+     * <p>This can be useful if the anchor is in one process and the backing window is owned by
+     * another process.
+     */
+    private class WindowPresenter {
+        final IAutofillWindowPresenter mPresenter;
+
+        WindowPresenter(IAutofillWindowPresenter presenter) {
+            mPresenter = presenter;
+        }
+
+        /**
+         * Shows the backing window.
+         *
+         * @param p The window layout params.
+         * @param transitionEpicenter The transition epicenter if animating.
+         * @param fitsSystemWindows Whether the content view should account for system decorations.
+         * @param layoutDirection The content layout direction to be consistent with the anchor.
+         */
+        void show(WindowManager.LayoutParams p, Rect transitionEpicenter, boolean fitsSystemWindows,
+                int layoutDirection) {
+            try {
+                mPresenter.show(p, transitionEpicenter, fitsSystemWindows, layoutDirection);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Error showing fill window", e);
+                e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Hides the backing window.
+         *
+         * @param transitionEpicenter The transition epicenter if animating.
+         */
+        void hide(Rect transitionEpicenter) {
+            try {
+                mPresenter.hide(transitionEpicenter);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Error hiding fill window", e);
+                e.rethrowFromSystemServer();
+            }
+        }
+    }
+}
diff --git a/android/view/autofill/AutofillTestHelper.java b/android/view/autofill/AutofillTestHelper.java
new file mode 100644
index 0000000..0763729
--- /dev/null
+++ b/android/view/autofill/AutofillTestHelper.java
@@ -0,0 +1,81 @@
+/*
+ * 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.view.autofill;
+
+import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
+import android.app.assist.AssistStructure.WindowNode;
+import android.service.autofill.FillContext;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Helper for common funcionalities.
+ */
+public class AutofillTestHelper {
+    private static final String TAG = "AutofillTestHelper";
+
+    /**
+     * Gets a node given its Android resource id, or {@code null} if not found.
+     */
+    public static ViewNode findNodeByResourceId(List<FillContext> contexts, String resourceId) {
+        for (FillContext context : contexts) {
+            ViewNode node = findNodeByResourceId(context.getStructure(), resourceId);
+            if (node != null) {
+                return node;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets a node if it matches the filter criteria for the given id.
+     */
+    private static ViewNode findNodeByResourceId(AssistStructure structure, String id) {
+        Log.v(TAG, "Parsing request for activity " + structure.getActivityComponent());
+        final int nodes = structure.getWindowNodeCount();
+        for (int i = 0; i < nodes; i++) {
+            final WindowNode windowNode = structure.getWindowNodeAt(i);
+            final ViewNode rootNode = windowNode.getRootViewNode();
+            final ViewNode node = findNodeByResourceId(rootNode, id);
+            if (node != null) {
+                return node;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets a node if it matches the filter criteria for the given id.
+     */
+    private static ViewNode findNodeByResourceId(ViewNode node, String id) {
+        if (id.equals(node.getIdEntry())) {
+            return node;
+        }
+        final int childrenSize = node.getChildCount();
+        if (childrenSize > 0) {
+            for (int i = 0; i < childrenSize; i++) {
+                final ViewNode found = findNodeByResourceId(node.getChildAt(i), id);
+                if (found != null) {
+                    return found;
+                }
+            }
+        }
+        return null;
+    }
+}
diff --git a/android/view/autofill/AutofillTestWatcher.java b/android/view/autofill/AutofillTestWatcher.java
new file mode 100644
index 0000000..f65067f
--- /dev/null
+++ b/android/view/autofill/AutofillTestWatcher.java
@@ -0,0 +1,196 @@
+/*
+ * 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.view.autofill;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import android.perftests.utils.SettingsHelper;
+import android.provider.Settings;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.Timeout;
+
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Custom {@link TestWatcher} that does the setup and reset tasks for the tests.
+ */
+final class AutofillTestWatcher extends TestWatcher {
+
+    private static final String TAG = "AutofillTestWatcher";
+    private static final long GENERIC_TIMEOUT_MS = 10_000;
+
+    private static ServiceWatcher sServiceWatcher;
+
+    private String mOriginalLogLevel;
+
+    @Override
+    protected void starting(Description description) {
+        super.starting(description);
+        final String testName = description.getDisplayName();
+        Log.i(TAG, "Starting " + testName);
+
+        prepareDevice();
+        enableVerboseLog();
+        // Prepare the service before each test.
+        // Disable the current AutofillService.
+        resetAutofillService();
+        // Set MyAutofillService status enable, it can start to accept the calls.
+        enableMyAutofillService();
+        setServiceWatcher();
+    }
+
+    @Override
+    protected void finished(Description description) {
+        super.finished(description);
+        final String testName = description.getDisplayName();
+        Log.i(TAG, "Finished " + testName);
+        restoreLogLevel();
+        // Set MyAutofillService status disable, so the calls are ignored.
+        disableMyAutofillService();
+        clearServiceWatcher();
+    }
+
+    void waitServiceConnect() throws InterruptedException {
+        if (sServiceWatcher != null) {
+            Log.d(TAG, "waitServiceConnect()");
+            sServiceWatcher.waitOnConnected();
+        }
+    }
+
+    /**
+     * Uses the {@code settings} binary to set the autofill service.
+     */
+    void setAutofillService() {
+        String serviceName = MyAutofillService.COMPONENT_NAME;
+        SettingsHelper.syncSet(InstrumentationRegistry.getTargetContext(),
+                SettingsHelper.NAMESPACE_SECURE,
+                Settings.Secure.AUTOFILL_SERVICE,
+                serviceName);
+        // Waits until the service is actually enabled.
+        Timeout timeout = new Timeout("CONNECTION_TIMEOUT", GENERIC_TIMEOUT_MS, 2F,
+                GENERIC_TIMEOUT_MS);
+        try {
+            timeout.run("Enabling Autofill service", () -> {
+                return isAutofillServiceEnabled(serviceName) ? serviceName : null;
+            });
+        } catch (Exception e) {
+            throw new AssertionError("Enabling Autofill service failed.");
+        }
+    }
+
+    /**
+     * Uses the {@code settings} binary to reset the autofill service.
+     */
+    void resetAutofillService() {
+        SettingsHelper.syncDelete(InstrumentationRegistry.getTargetContext(),
+                SettingsHelper.NAMESPACE_SECURE,
+                Settings.Secure.AUTOFILL_SERVICE);
+    }
+
+    /**
+     * Checks whether the given service is set as the autofill service for the default user.
+     */
+    private boolean isAutofillServiceEnabled(String serviceName) {
+        String actualName = SettingsHelper.get(SettingsHelper.NAMESPACE_SECURE,
+                Settings.Secure.AUTOFILL_SERVICE);
+        return serviceName.equals(actualName);
+    }
+
+    private void prepareDevice() {
+        // Unlock screen.
+        runShellCommand("input keyevent KEYCODE_WAKEUP");
+
+        // Dismiss keyguard, in case it's set as "Swipe to unlock".
+        runShellCommand("wm dismiss-keyguard");
+
+        // Collapse notifications.
+        runShellCommand("cmd statusbar collapse");
+    }
+
+    private void enableMyAutofillService() {
+        MyAutofillService.resetStaticState();
+        MyAutofillService.setEnabled(true);
+    }
+
+    private void disableMyAutofillService() {
+        // Must disable service so calls are ignored in case of errors during the test case;
+        // otherwise, other tests will fail because these calls are made in the UI thread (as both
+        // the service, the tests, and the app run in the same process).
+        MyAutofillService.setEnabled(false);
+    }
+
+    private void enableVerboseLog() {
+        mOriginalLogLevel = runShellCommand("cmd autofill get log_level");
+        Log.d(TAG, "enableVerboseLog(), mOriginalLogLevel=" + mOriginalLogLevel);
+        if (!mOriginalLogLevel.equals("verbose")) {
+            runShellCommand("cmd autofill set log_level verbose");
+        }
+    }
+
+    private void restoreLogLevel() {
+        Log.d(TAG, "restoreLogLevel to " + mOriginalLogLevel);
+        if (!mOriginalLogLevel.equals("verbose")) {
+            runShellCommand("cmd autofill set log_level %s", mOriginalLogLevel);
+        }
+    }
+
+    private static void setServiceWatcher() {
+        if (sServiceWatcher == null) {
+            sServiceWatcher = new ServiceWatcher();
+        }
+    }
+
+    private static void clearServiceWatcher() {
+        if (sServiceWatcher != null) {
+            sServiceWatcher = null;
+        }
+    }
+
+    public static final class ServiceWatcher {
+        private final CountDownLatch mConnected = new CountDownLatch(1);
+
+        public static void onConnected() {
+            Log.i(TAG, "onConnected:  sServiceWatcher=" + sServiceWatcher);
+
+            sServiceWatcher.mConnected.countDown();
+        }
+
+        @NonNull
+        public void waitOnConnected() throws InterruptedException {
+            await(mConnected, "not connected");
+        }
+
+        private void await(@NonNull CountDownLatch latch, @NonNull String fmt,
+                @Nullable Object... args)
+                throws InterruptedException {
+            final boolean called = latch.await(GENERIC_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            if (!called) {
+                throw new IllegalStateException(String.format(fmt, args)
+                        + " in " + GENERIC_TIMEOUT_MS + "ms");
+            }
+        }
+    }
+}
diff --git a/android/view/autofill/AutofillValue.java b/android/view/autofill/AutofillValue.java
new file mode 100644
index 0000000..f5eef35
--- /dev/null
+++ b/android/view/autofill/AutofillValue.java
@@ -0,0 +1,306 @@
+/*
+ * 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.view.autofill;
+
+import static android.view.View.AUTOFILL_TYPE_DATE;
+import static android.view.View.AUTOFILL_TYPE_LIST;
+import static android.view.View.AUTOFILL_TYPE_TEXT;
+import static android.view.View.AUTOFILL_TYPE_TOGGLE;
+import static android.view.autofill.Helper.sDebug;
+import static android.view.autofill.Helper.sVerbose;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Abstracts how a {@link View} can be autofilled by an
+ * {@link android.service.autofill.AutofillService}.
+ *
+ * <p>Each {@link AutofillValue} is associated with a {@code type}, as defined by
+ * {@link View#getAutofillType()}.
+ */
+public final class AutofillValue implements Parcelable {
+
+    private static final String TAG = "AutofillValue";
+
+    private final @View.AutofillType int mType;
+    private final @NonNull Object mValue;
+
+    private AutofillValue(@View.AutofillType int type, @NonNull Object value) {
+        mType = type;
+        mValue = value;
+    }
+
+    /**
+     * Gets the value to autofill a text field.
+     *
+     * <p>See {@link View#AUTOFILL_TYPE_TEXT} for more info.</p>
+     *
+     * @throws IllegalStateException if the value is not a text value
+     */
+    @NonNull public CharSequence getTextValue() {
+        Preconditions.checkState(isText(), "value must be a text value, not type=%d", mType);
+        return (CharSequence) mValue;
+    }
+
+    /**
+     * Checks if this is a text value.
+     *
+     * <p>See {@link View#AUTOFILL_TYPE_TEXT} for more info.</p>
+     */
+    public boolean isText() {
+        return mType == AUTOFILL_TYPE_TEXT;
+    }
+
+    /**
+     * Gets the value to autofill a toggable field.
+     *
+     * <p>See {@link View#AUTOFILL_TYPE_TOGGLE} for more info.</p>
+     *
+     * @throws IllegalStateException if the value is not a toggle value
+     */
+    public boolean getToggleValue() {
+        Preconditions.checkState(isToggle(), "value must be a toggle value, not type=%d", mType);
+        return (Boolean) mValue;
+    }
+
+    /**
+     * Checks if this is a toggle value.
+     *
+     * <p>See {@link View#AUTOFILL_TYPE_TOGGLE} for more info.</p>
+     */
+    public boolean isToggle() {
+        return mType == AUTOFILL_TYPE_TOGGLE;
+    }
+
+    /**
+     * Gets the value to autofill a selection list field.
+     *
+     * <p>See {@link View#AUTOFILL_TYPE_LIST} for more info.</p>
+     *
+     * @throws IllegalStateException if the value is not a list value
+     */
+    public int getListValue() {
+        Preconditions.checkState(isList(), "value must be a list value, not type=%d", mType);
+        return (Integer) mValue;
+    }
+
+    /**
+     * Checks if this is a list value.
+     *
+     * <p>See {@link View#AUTOFILL_TYPE_LIST} for more info.</p>
+     */
+    public boolean isList() {
+        return mType == AUTOFILL_TYPE_LIST;
+    }
+
+    /**
+     * Gets the value to autofill a date field.
+     *
+     * <p>See {@link View#AUTOFILL_TYPE_DATE} for more info.</p>
+     *
+     * @throws IllegalStateException if the value is not a date value
+     */
+    public long getDateValue() {
+        Preconditions.checkState(isDate(), "value must be a date value, not type=%d", mType);
+        return (Long) mValue;
+    }
+
+    /**
+     * Checks if this is a date value.
+     *
+     * <p>See {@link View#AUTOFILL_TYPE_DATE} for more info.</p>
+     */
+    public boolean isDate() {
+        return mType == AUTOFILL_TYPE_DATE;
+    }
+
+    /**
+     * Used to define whether a field is empty so it's not sent to service on save.
+     *
+     * <p>Only applies to some types, like text.
+     *
+     * @hide
+     */
+    public boolean isEmpty() {
+        return isText() && ((CharSequence) mValue).length() == 0;
+    }
+
+    /////////////////////////////////////
+    //  Object "contract" methods. //
+    /////////////////////////////////////
+
+    @Override
+    public int hashCode() {
+        return mType + mValue.hashCode();
+    }
+
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (this == obj) return true;
+        if (obj == null) return false;
+        if (getClass() != obj.getClass()) return false;
+        final AutofillValue other = (AutofillValue) obj;
+
+        if (mType != other.mType) return false;
+
+        if (isText()) {
+            return mValue.toString().equals(other.mValue.toString());
+        } else {
+            return Objects.equals(mValue, other.mValue);
+        }
+    }
+
+    @Override
+    public String toString() {
+        if (!sDebug) return super.toString();
+
+        final StringBuilder string = new StringBuilder()
+                .append("[type=").append(mType)
+                .append(", value=");
+        if (isText()) {
+            Helper.appendRedacted(string, (CharSequence) mValue);
+        } else {
+            string.append(mValue);
+        }
+        return string.append(']').toString();
+    }
+
+    /////////////////////////////////////
+    //  Parcelable "contract" methods. //
+    /////////////////////////////////////
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeInt(mType);
+
+        switch (mType) {
+            case AUTOFILL_TYPE_TEXT:
+                parcel.writeCharSequence((CharSequence) mValue);
+                break;
+            case AUTOFILL_TYPE_TOGGLE:
+                parcel.writeInt((Boolean) mValue ? 1 : 0);
+                break;
+            case AUTOFILL_TYPE_LIST:
+                parcel.writeInt((Integer) mValue);
+                break;
+            case AUTOFILL_TYPE_DATE:
+                parcel.writeLong((Long) mValue);
+                break;
+        }
+    }
+
+    private AutofillValue(@NonNull Parcel parcel) {
+        mType = parcel.readInt();
+
+        switch (mType) {
+            case AUTOFILL_TYPE_TEXT:
+                mValue = parcel.readCharSequence();
+                break;
+            case AUTOFILL_TYPE_TOGGLE:
+                int rawValue = parcel.readInt();
+                mValue = rawValue != 0;
+                break;
+            case AUTOFILL_TYPE_LIST:
+                mValue = parcel.readInt();
+                break;
+            case AUTOFILL_TYPE_DATE:
+                mValue = parcel.readLong();
+                break;
+            default:
+                throw new IllegalArgumentException("type=" + mType + " not valid");
+        }
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<AutofillValue> CREATOR =
+            new Parcelable.Creator<AutofillValue>() {
+        @Override
+        public AutofillValue createFromParcel(Parcel source) {
+            return new AutofillValue(source);
+        }
+
+        @Override
+        public AutofillValue[] newArray(int size) {
+            return new AutofillValue[size];
+        }
+    };
+
+    ////////////////////
+    // Factory methods //
+    ////////////////////
+
+    /**
+     * Creates a new {@link AutofillValue} to autofill a {@link View} representing a text field.
+     *
+     * <p>See {@link View#AUTOFILL_TYPE_TEXT} for more info.
+     *
+     * <p><b>Note:</b> This method is not thread safe and can throw an exception if the
+     * {@code value} is modified by a different thread before it returns.
+     */
+    public static AutofillValue forText(@Nullable CharSequence value) {
+        if (sVerbose && !Looper.getMainLooper().isCurrentThread()) {
+            Log.v(TAG, "forText() not called on main thread: " + Thread.currentThread());
+        }
+
+        return value == null ? null : new AutofillValue(AUTOFILL_TYPE_TEXT,
+                TextUtils.trimNoCopySpans(value));
+    }
+
+    /**
+     * Creates a new {@link AutofillValue} to autofill a {@link View} representing a toggable
+     * field.
+     *
+     * <p>See {@link View#AUTOFILL_TYPE_TOGGLE} for more info.
+     */
+    public static AutofillValue forToggle(boolean value) {
+        return new AutofillValue(AUTOFILL_TYPE_TOGGLE, value);
+    }
+
+    /**
+     * Creates a new {@link AutofillValue} to autofill a {@link View} representing a selection
+     * list.
+     *
+     * <p>See {@link View#AUTOFILL_TYPE_LIST} for more info.
+     */
+    public static AutofillValue forList(int value) {
+        return new AutofillValue(AUTOFILL_TYPE_LIST, value);
+    }
+
+    /**
+     * Creates a new {@link AutofillValue} to autofill a {@link View} representing a date.
+     *
+     * <p>See {@link View#AUTOFILL_TYPE_DATE} for more info.
+     */
+    public static AutofillValue forDate(long value) {
+        return new AutofillValue(AUTOFILL_TYPE_DATE, value);
+    }
+}
diff --git a/android/view/autofill/Helper.java b/android/view/autofill/Helper.java
new file mode 100644
index 0000000..2f12bb2
--- /dev/null
+++ b/android/view/autofill/Helper.java
@@ -0,0 +1,92 @@
+/*
+ * 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.view.autofill;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Set;
+
+/** @hide */
+public final class Helper {
+
+    // Debug-level flags are defined when service is bound.
+    public static boolean sDebug = false;
+    public static boolean sVerbose = false;
+
+    /**
+     * Appends {@code value} to the {@code builder} redacting its contents.
+     */
+    public static void appendRedacted(@NonNull StringBuilder builder,
+            @Nullable CharSequence value) {
+        builder.append(getRedacted(value));
+    }
+
+    /**
+     * Gets the redacted version of a value.
+     */
+    @NonNull
+    public static String getRedacted(@Nullable CharSequence value) {
+        return (value == null) ? "null" : value.length() + "_chars";
+    }
+
+    /**
+     * Appends {@code values} to the {@code builder} redacting its contents.
+     */
+    public static void appendRedacted(@NonNull StringBuilder builder, @Nullable String[] values) {
+        if (values == null) {
+            builder.append("N/A");
+            return;
+        }
+        builder.append("[");
+        for (String value : values) {
+            builder.append(" '");
+            appendRedacted(builder, value);
+            builder.append("'");
+        }
+        builder.append(" ]");
+    }
+
+    /**
+     * Converts a collaction of {@link AutofillId AutofillIds} to an array.
+     *
+     * @param collection The collection.
+     * @return The array.
+     */
+    public static @NonNull AutofillId[] toArray(Collection<AutofillId> collection) {
+        if (collection == null) {
+            return new AutofillId[0];
+        }
+        final AutofillId[] array = new AutofillId[collection.size()];
+        collection.toArray(array);
+        return array;
+    }
+
+    /**
+     * Converts a Set to a List.
+     */
+    @Nullable
+    public static <T> ArrayList<T> toList(@Nullable Set<T> set) {
+        return set == null ? null : new ArrayList<T>(set);
+    }
+
+    private Helper() {
+        throw new UnsupportedOperationException("contains static members only");
+    }
+}
diff --git a/android/view/autofill/LoginTest.java b/android/view/autofill/LoginTest.java
new file mode 100644
index 0000000..a5d1e00
--- /dev/null
+++ b/android/view/autofill/LoginTest.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.autofill;
+
+import static android.view.autofill.AutofillManager.AutofillCallback.EVENT_INPUT_HIDDEN;
+import static android.view.autofill.AutofillManager.AutofillCallback.EVENT_INPUT_SHOWN;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfTestActivity;
+import android.view.View;
+import android.widget.EditText;
+
+import androidx.test.filters.LargeTest;
+
+import com.android.perftests.autofill.R;
+
+import org.junit.Test;
+
+@LargeTest
+public class LoginTest extends AbstractAutofillPerfTestCase {
+
+    public static final String ID_USERNAME = "username";
+    public static final String ID_PASSWORD = "password";
+
+    private EditText mUsername;
+    private EditText mPassword;
+    private AutofillManager mAfm;
+
+    public LoginTest() {
+        super(R.layout.test_autofill_login);
+    }
+
+    @Override
+    protected void onCreate(PerfTestActivity activity) {
+        View root = activity.getWindow().getDecorView();
+        mUsername = root.findViewById(R.id.username);
+        mPassword = root.findViewById(R.id.password);
+        mAfm = activity.getSystemService(AutofillManager.class);
+    }
+
+    /**
+     * This is the baseline test for focusing the 2 views when autofill is disabled.
+     */
+    @Test
+    public void testFocus_noService() throws Throwable {
+        mTestWatcher.resetAutofillService();
+
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mActivityRule.runOnUiThread(() -> {
+                mUsername.requestFocus();
+                mPassword.requestFocus();
+            });
+        }
+    }
+
+    /**
+     * This time the service is called, but it returns a {@code null} response so the UI behaves
+     * as if autofill was disabled.
+     */
+    @Test
+    public void testFocus_serviceDoesNotAutofill() throws Throwable {
+        MyAutofillService.newCannedResponse().reply();
+        mTestWatcher.setAutofillService();
+
+        // Must first focus in a field to trigger autofill and wait for service response
+        // outside the loop
+        mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
+        mTestWatcher.waitServiceConnect();
+        MyAutofillService.getLastFillRequest();
+        // Then focus on password so loop start with focus away from username
+        mActivityRule.runOnUiThread(() -> mPassword.requestFocus());
+
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mActivityRule.runOnUiThread(() -> {
+                mUsername.requestFocus();
+                mPassword.requestFocus();
+            });
+        }
+    }
+
+    /**
+     * Now the service returns autofill data, for both username and password.
+     */
+    @Test
+    public void testFocus_autofillBothFields() throws Throwable {
+        MyAutofillService.newCannedResponse()
+                .setUsername(ID_USERNAME, "user")
+                .setPassword(ID_PASSWORD, "pass")
+                .reply();
+        mTestWatcher.setAutofillService();
+
+        // Callback is used to slow down the calls made to the autofill server so the
+        // app is not crashed due to binder exhaustion. But the time spent waiting for the callbacks
+        // is not measured here...
+        MyAutofillCallback callback = new MyAutofillCallback();
+        mAfm.registerCallback(callback);
+
+        // Must first trigger autofill and wait for service response outside the loop
+        mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
+        mTestWatcher.waitServiceConnect();
+        MyAutofillService.getLastFillRequest();
+        callback.expectEvent(mUsername, EVENT_INPUT_SHOWN);
+
+        // Then focus on password so loop start with focus away from username
+        mActivityRule.runOnUiThread(() -> mPassword.requestFocus());
+        callback.expectEvent(mUsername, EVENT_INPUT_HIDDEN);
+        callback.expectEvent(mPassword, EVENT_INPUT_SHOWN);
+
+
+        // NOTE: we cannot run the whole loop inside the UI thread, because the autofill callback
+        // is called on it, which would cause a deadlock on expectEvent().
+        try {
+            BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+            while (state.keepRunning()) {
+                mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
+                state.pauseTiming(); // Ignore time spent waiting for callbacks
+                callback.expectEvent(mPassword, EVENT_INPUT_HIDDEN);
+                callback.expectEvent(mUsername, EVENT_INPUT_SHOWN);
+                state.resumeTiming();
+                mActivityRule.runOnUiThread(() -> mPassword.requestFocus());
+                state.pauseTiming(); // Ignore time spent waiting for callbacks
+                callback.expectEvent(mUsername, EVENT_INPUT_HIDDEN);
+                callback.expectEvent(mPassword, EVENT_INPUT_SHOWN);
+                state.resumeTiming();
+            }
+
+            // Sanity check
+            callback.assertNoAsyncErrors();
+        } finally {
+            mAfm.unregisterCallback(callback);
+        }
+    }
+
+    /**
+     * Now the service returns autofill data, but just for username.
+     */
+    @Test
+    public void testFocus_autofillUsernameOnly() throws Throwable {
+        // Must set ignored ids so focus on password does not trigger new requests
+        MyAutofillService.newCannedResponse()
+                .setUsername(ID_USERNAME, "user")
+                .setIgnored(ID_PASSWORD)
+                .reply();
+        mTestWatcher.setAutofillService();
+
+        // Callback is used to slow down the calls made to the autofill server so the
+        // app is not crashed due to binder exhaustion. But the time spent waiting for the callbacks
+        // is not measured here...
+        MyAutofillCallback callback = new MyAutofillCallback();
+        mAfm.registerCallback(callback);
+
+        // Must first trigger autofill and wait for service response outside the loop
+        mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
+        mTestWatcher.waitServiceConnect();
+        MyAutofillService.getLastFillRequest();
+        callback.expectEvent(mUsername, EVENT_INPUT_SHOWN);
+
+        // Then focus on password so loop start with focus away from username
+        mActivityRule.runOnUiThread(() -> mPassword.requestFocus());
+        callback.expectEvent(mUsername, EVENT_INPUT_HIDDEN);
+
+        // NOTE: we cannot run the whole loop inside the UI thread, because the autofill callback
+        // is called on it, which would cause a deadlock on expectEvent().
+        try {
+            BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+            while (state.keepRunning()) {
+                mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
+                state.pauseTiming(); // Ignore time spent waiting for callbacks
+                callback.expectEvent(mUsername, EVENT_INPUT_SHOWN);
+                state.resumeTiming();
+                mActivityRule.runOnUiThread(() -> mPassword.requestFocus());
+                state.pauseTiming(); // Ignore time spent waiting for callbacks
+                callback.expectEvent(mUsername, EVENT_INPUT_HIDDEN);
+                state.resumeTiming();
+            }
+
+            // Sanity check
+            callback.assertNoAsyncErrors();
+        } finally {
+            mAfm.unregisterCallback(callback);
+        }
+    }
+
+    /**
+     * This is the baseline test for changing the 2 views when autofill is disabled.
+     */
+    @Test
+    public void testChange_noService() throws Throwable {
+        mTestWatcher.resetAutofillService();
+
+        changeTest(false);
+    }
+
+    /**
+     * This time the service is called, but it returns a {@code null} response so the UI behaves
+     * as if autofill was disabled.
+     */
+    @Test
+    public void testChange_serviceDoesNotAutofill() throws Throwable {
+        MyAutofillService.newCannedResponse().reply();
+        mTestWatcher.setAutofillService();
+
+        changeTest(true);
+    }
+
+    /**
+     * Now the service returns autofill data, for both username and password.
+     */
+    @Test
+    public void testChange_autofillBothFields() throws Throwable {
+        MyAutofillService.newCannedResponse()
+                .setUsername(ID_USERNAME, "user")
+                .setPassword(ID_PASSWORD, "pass")
+                .reply();
+        mTestWatcher.setAutofillService();
+
+        changeTest(true);
+    }
+
+    /**
+     * Now the service returns autofill data, but just for username.
+     */
+    @Test
+    public void testChange_autofillUsernameOnly() throws Throwable {
+        // Must set ignored ids so focus on password does not trigger new requests
+        MyAutofillService.newCannedResponse()
+                .setUsername(ID_USERNAME, "user")
+                .setIgnored(ID_PASSWORD)
+                .reply();
+        mTestWatcher.setAutofillService();
+
+        changeTest(true);
+    }
+
+    private void changeTest(boolean waitForService) throws Throwable {
+        // Must first focus in a field to trigger autofill and wait for service response
+        // outside the loop
+        mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
+        if (waitForService) {
+            mTestWatcher.waitServiceConnect();
+            MyAutofillService.getLastFillRequest();
+        }
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mActivityRule.runOnUiThread(() -> {
+                mUsername.setText("");
+                mUsername.setText("a");
+                mPassword.setText("");
+                mPassword.setText("x");
+            });
+        }
+    }
+
+    @Test
+    public void testCallbacks() throws Throwable {
+        MyAutofillService.newCannedResponse()
+                .setUsername(ID_USERNAME, "user")
+                .setPassword(ID_PASSWORD, "pass")
+                .reply();
+        mTestWatcher.setAutofillService();
+
+        MyAutofillCallback callback = new MyAutofillCallback();
+        mAfm.registerCallback(callback);
+
+        // Must first focus in a field to trigger autofill and wait for service response
+        // outside the loop
+        mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
+        mTestWatcher.waitServiceConnect();
+        MyAutofillService.getLastFillRequest();
+        callback.expectEvent(mUsername, EVENT_INPUT_SHOWN);
+
+        // Now focus on password to prepare loop state
+        mActivityRule.runOnUiThread(() -> mPassword.requestFocus());
+        callback.expectEvent(mUsername, EVENT_INPUT_HIDDEN);
+        callback.expectEvent(mPassword, EVENT_INPUT_SHOWN);
+
+        // NOTE: we cannot run the whole loop inside the UI thread, because the autofill callback
+        // is called on it, which would cause a deadlock on expectEvent().
+        try {
+            BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+            while (state.keepRunning()) {
+                mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
+                callback.expectEvent(mPassword, EVENT_INPUT_HIDDEN);
+                callback.expectEvent(mUsername, EVENT_INPUT_SHOWN);
+                mActivityRule.runOnUiThread(() -> mPassword.requestFocus());
+                callback.expectEvent(mUsername, EVENT_INPUT_HIDDEN);
+                callback.expectEvent(mPassword, EVENT_INPUT_SHOWN);
+            }
+
+            // Sanity check
+            callback.assertNoAsyncErrors();
+        } finally {
+            mAfm.unregisterCallback(callback);
+        }
+    }
+}
diff --git a/android/view/autofill/MyAutofillCallback.java b/android/view/autofill/MyAutofillCallback.java
new file mode 100644
index 0000000..208d632
--- /dev/null
+++ b/android/view/autofill/MyAutofillCallback.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package android.view.autofill;
+
+import android.view.View;
+import android.view.autofill.AutofillManager.AutofillCallback;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+import static android.view.autofill.AutofillManager.AutofillCallback.EVENT_INPUT_HIDDEN;
+import static android.view.autofill.AutofillManager.AutofillCallback.EVENT_INPUT_SHOWN;
+import static android.view.autofill.AutofillManager.AutofillCallback.EVENT_INPUT_UNAVAILABLE;
+
+import android.os.CancellationSignal;
+import android.service.autofill.FillCallback;
+import android.service.autofill.FillRequest;
+import android.util.Log;
+
+/**
+ * Custom {@link AutofillCallback} used to recover events during tests.
+ */
+public final class MyAutofillCallback extends AutofillCallback {
+
+    private static final String TAG = "MyAutofillCallback";
+    private static final int TIMEOUT_MS = 5000;
+
+    private final BlockingQueue<MyEvent> mEvents = new LinkedBlockingQueue<>(2);
+    private final List<String> mAsyncErrors = new ArrayList<>();
+
+    @Override
+    public void onAutofillEvent(View view, int event) {
+        boolean offered = false;
+        try {
+            offered = mEvents.offer(new MyEvent(view, event), TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
+        if (!offered) {
+            String error = "could not offer " + toString(view, event) + " in " + TIMEOUT_MS + "ms";
+            Log.e(TAG, error);
+            mAsyncErrors.add(error);
+        }
+    }
+
+    /**
+     * Asserts the callback is called for the given view and event, or fail if it times out.
+     */
+    public void expectEvent(@NonNull View view, int event) {
+        MyEvent myEvent;
+        try {
+            myEvent = mEvents.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            if (myEvent == null) {
+                throw new IllegalStateException("no event received in " + TIMEOUT_MS
+                        + "ms while waiting for " + toString(view, event));
+            }
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new IllegalStateException("interrupted waiting for " + toString(view, event));
+        }
+        if (!myEvent.view.equals(view) || myEvent.event != event) {
+            throw new AssertionError("Invalid event: expected " + myEvent + ", got "
+                    + toString(view, event));
+        }
+    }
+
+    /**
+     * Throws an exception if an error happened asynchronously while handing
+     * {@link #onAutofillEvent(View, int)}.
+     */
+    public void assertNoAsyncErrors() {
+       if (!mAsyncErrors.isEmpty()) {
+           throw new IllegalStateException(mAsyncErrors.size() + " errors: " + mAsyncErrors);
+       }
+    }
+
+    private static String eventToString(int event) {
+        switch (event) {
+            case EVENT_INPUT_HIDDEN:
+                return "HIDDEN";
+            case EVENT_INPUT_SHOWN:
+                return "SHOWN";
+            case EVENT_INPUT_UNAVAILABLE:
+                return "UNAVAILABLE";
+            default:
+                throw new IllegalArgumentException("invalid event: " + event);
+        }
+    }
+
+    private static String toString(View view, int event) {
+        return eventToString(event) + ": " + view + ")";
+    }
+
+    private static final class MyEvent {
+        public final View view;
+        public final int event;
+
+        MyEvent(View view, int event) {
+            this.view = view;
+            this.event = event;
+        }
+
+        @Override
+        public String toString() {
+            return MyAutofillCallback.toString(view, event);
+        }
+    }
+}
diff --git a/android/view/autofill/MyAutofillService.java b/android/view/autofill/MyAutofillService.java
new file mode 100644
index 0000000..5db6597
--- /dev/null
+++ b/android/view/autofill/MyAutofillService.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.autofill;
+
+import android.app.assist.AssistStructure.ViewNode;
+import android.os.CancellationSignal;
+import android.service.autofill.AutofillService;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillCallback;
+import android.service.autofill.FillRequest;
+import android.service.autofill.FillResponse;
+import android.service.autofill.SaveCallback;
+import android.service.autofill.SaveRequest;
+import android.util.Log;
+import android.util.Pair;
+import android.widget.RemoteViews;
+
+import androidx.annotation.NonNull;
+
+import com.android.perftests.autofill.R;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An {@link AutofillService} implementation whose replies can be programmed by the test case.
+ */
+public class MyAutofillService extends AutofillService {
+
+    private static final String TAG = "MyAutofillService";
+    private static final int TIMEOUT_MS = 5_000;
+
+    private static final String PACKAGE_NAME = "com.android.perftests.autofill";
+    static final String COMPONENT_NAME = PACKAGE_NAME + "/android.view.autofill.MyAutofillService";
+
+    private static final BlockingQueue<FillRequest> sFillRequests = new LinkedBlockingQueue<>();
+    private static final BlockingQueue<CannedResponse> sCannedResponses =
+            new LinkedBlockingQueue<>();
+
+    private static boolean sEnabled;
+
+    /**
+     * Returns the TestWatcher that was used for the testing.
+     */
+    @NonNull
+    public static AutofillTestWatcher getTestWatcher() {
+        return new AutofillTestWatcher();
+    }
+
+    /**
+     * Resets the static state associated with the service.
+     */
+    static void resetStaticState() {
+        sFillRequests.clear();
+        sCannedResponses.clear();
+        sEnabled = false;
+    }
+
+    /**
+     * Sets whether the service is enabled or not - when disabled, calls to
+     * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} will be ignored.
+     */
+    static void setEnabled(boolean enabled) {
+        sEnabled = enabled;
+    }
+
+    /**
+     * Gets the the last {@link FillRequest} passed to
+     * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} or throws an
+     * exception if that method was not called.
+     */
+    @NonNull
+    static FillRequest getLastFillRequest() {
+        FillRequest request = null;
+        try {
+            request = sFillRequests.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new IllegalStateException("onFillRequest() interrupted");
+        }
+        if (request == null) {
+            throw new IllegalStateException("onFillRequest() not called in " + TIMEOUT_MS + "ms");
+        }
+        return request;
+    }
+
+    @Override
+    public void onConnected() {
+        AutofillTestWatcher.ServiceWatcher.onConnected();
+    }
+
+    @Override
+    public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
+            FillCallback callback) {
+        try {
+            handleRequest(request, callback);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            onError("onFillRequest() interrupted", e, callback);
+        } catch (Exception e) {
+            onError("exception on onFillRequest()", e, callback);
+        }
+    }
+
+
+    private void handleRequest(FillRequest request, FillCallback callback) throws Exception {
+        if (!sEnabled) {
+            onError("ignoring onFillRequest(): service is disabled", callback);
+            return;
+        }
+        CannedResponse response = sCannedResponses.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        if (response == null) {
+            onError("ignoring onFillRequest(): response not set", callback);
+            return;
+        }
+        Dataset.Builder dataset = new Dataset.Builder(newDatasetPresentation("dataset"));
+        boolean hasData = false;
+        if (response.mUsername != null) {
+            hasData = true;
+            AutofillId autofillId = getAutofillIdByResourceId(request, response.mUsername.first);
+            AutofillValue value = AutofillValue.forText(response.mUsername.second);
+            dataset.setValue(autofillId, value, newDatasetPresentation("dataset"));
+        }
+        if (response.mPassword != null) {
+            hasData = true;
+            AutofillId autofillId = getAutofillIdByResourceId(request, response.mPassword.first);
+            AutofillValue value = AutofillValue.forText(response.mPassword.second);
+            dataset.setValue(autofillId, value, newDatasetPresentation("dataset"));
+        }
+        if (hasData) {
+            FillResponse.Builder fillResponse = new FillResponse.Builder();
+            if (response.mIgnoredIds != null) {
+                int length = response.mIgnoredIds.length;
+                AutofillId[] requiredIds = new AutofillId[length];
+                for (int i = 0; i < length; i++) {
+                    String resourceId = response.mIgnoredIds[i];
+                    requiredIds[i] = getAutofillIdByResourceId(request, resourceId);
+                }
+                fillResponse.setIgnoredIds(requiredIds);
+            }
+            callback.onSuccess(fillResponse.addDataset(dataset.build()).build());
+        } else {
+            callback.onSuccess(null);
+        }
+        if (!sFillRequests.offer(request, TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+            Log.w(TAG, "could not offer request in " + TIMEOUT_MS + "ms");
+        }
+    }
+
+    private AutofillId getAutofillIdByResourceId(FillRequest request, String resourceId)
+            throws Exception {
+        ViewNode node = AutofillTestHelper.findNodeByResourceId(request.getFillContexts(),
+                resourceId);
+        if (node == null) {
+            throw new AssertionError("No node with resource id " + resourceId);
+        }
+        return node.getAutofillId();
+    }
+
+    @Override
+    public void onSaveRequest(SaveRequest request, SaveCallback callback) {
+        // No current test should have triggered it...
+        Log.e(TAG, "onSaveRequest() should not have been called");
+        callback.onFailure("onSaveRequest() should not have been called");
+    }
+
+    static final class CannedResponse {
+        private final Pair<String, String> mUsername;
+        private final Pair<String, String> mPassword;
+        private final String[] mIgnoredIds;
+
+        private CannedResponse(@NonNull Builder builder) {
+            mUsername = builder.mUsername;
+            mPassword = builder.mPassword;
+            mIgnoredIds = builder.mIgnoredIds;
+        }
+
+        static class Builder {
+            private Pair<String, String> mUsername;
+            private Pair<String, String> mPassword;
+            private String[] mIgnoredIds;
+
+            @NonNull
+            Builder setUsername(@NonNull String id, @NonNull String value) {
+                mUsername = new Pair<>(id, value);
+                return this;
+            }
+
+            @NonNull
+            Builder setPassword(@NonNull String id, @NonNull String value) {
+                mPassword = new Pair<>(id, value);
+                return this;
+            }
+
+            @NonNull
+            Builder setIgnored(String... ids) {
+                mIgnoredIds = ids;
+                return this;
+            }
+
+            void reply() {
+                sCannedResponses.add(new CannedResponse(this));
+            }
+        }
+    }
+
+    /**
+     * Sets the expected canned {@link FillResponse} for the next
+     * {@link AutofillService#onFillRequest(FillRequest, CancellationSignal, FillCallback)}.
+     */
+    static CannedResponse.Builder newCannedResponse() {
+        return new CannedResponse.Builder();
+    }
+
+    private void onError(@NonNull String msg, @NonNull FillCallback callback) {
+        Log.e(TAG, msg);
+        callback.onFailure(msg);
+    }
+
+    private void onError(@NonNull String msg, @NonNull Exception e,
+            @NonNull FillCallback callback) {
+        Log.e(TAG, msg, e);
+        callback.onFailure(msg);
+    }
+
+    @NonNull
+    private static RemoteViews newDatasetPresentation(@NonNull CharSequence text) {
+        RemoteViews presentation =
+                new RemoteViews(PACKAGE_NAME, R.layout.autofill_dataset_picker_text_only);
+        presentation.setTextViewText(R.id.text, text);
+        return presentation;
+    }
+}
diff --git a/android/view/autofill/ParcelableMap.java b/android/view/autofill/ParcelableMap.java
new file mode 100644
index 0000000..d8459aa
--- /dev/null
+++ b/android/view/autofill/ParcelableMap.java
@@ -0,0 +1,73 @@
+/*
+ * 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.view.autofill;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A parcelable HashMap for {@link AutofillId} and {@link AutofillValue}
+ *
+ * {@hide}
+ */
+class ParcelableMap extends HashMap<AutofillId, AutofillValue> implements Parcelable {
+    ParcelableMap(int size) {
+        super(size);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(size());
+
+        for (Map.Entry<AutofillId, AutofillValue> entry : entrySet()) {
+            dest.writeParcelable(entry.getKey(), 0);
+            dest.writeParcelable(entry.getValue(), 0);
+        }
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<ParcelableMap> CREATOR =
+            new Parcelable.Creator<ParcelableMap>() {
+                @Override
+                public ParcelableMap createFromParcel(Parcel source) {
+                    int size = source.readInt();
+
+                    ParcelableMap map = new ParcelableMap(size);
+
+                    for (int i = 0; i < size; i++) {
+                        AutofillId key = source.readParcelable(null);
+                        AutofillValue value = source.readParcelable(null);
+
+                        map.put(key, value);
+                    }
+
+                    return map;
+                }
+
+                @Override
+                public ParcelableMap[] newArray(int size) {
+                    return new ParcelableMap[size];
+                }
+            };
+}
diff --git a/android/view/contentcapture/AbstractContentCapturePerfTestCase.java b/android/view/contentcapture/AbstractContentCapturePerfTestCase.java
new file mode 100644
index 0000000..9b853fe
--- /dev/null
+++ b/android/view/contentcapture/AbstractContentCapturePerfTestCase.java
@@ -0,0 +1,267 @@
+/*
+ * 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.view.contentcapture;
+
+import static android.view.contentcapture.CustomTestActivity.INTENT_EXTRA_CUSTOM_VIEWS;
+import static android.view.contentcapture.CustomTestActivity.INTENT_EXTRA_LAYOUT_ID;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import android.app.Application;
+import android.content.ContentCaptureOptions;
+import android.content.Context;
+import android.content.Intent;
+import android.os.BatteryManager;
+import android.os.UserHandle;
+import android.perftests.utils.PerfStatusReporter;
+import android.provider.Settings;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.compatibility.common.util.ActivitiesWatcher;
+import com.android.compatibility.common.util.ActivitiesWatcher.ActivityWatcher;
+import com.android.perftests.contentcapture.R;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.rules.TestRule;
+import org.junit.runners.model.Statement;
+
+/**
+ * Base class for all content capture tests.
+ */
+public abstract class AbstractContentCapturePerfTestCase {
+
+    private static final String TAG = AbstractContentCapturePerfTestCase.class.getSimpleName();
+    private static final long GENERIC_TIMEOUT_MS = 10_000;
+
+    private static int sOriginalStayOnWhilePluggedIn;
+    private static Context sContext = getInstrumentation().getTargetContext();
+
+    protected ActivitiesWatcher mActivitiesWatcher;
+
+    private MyContentCaptureService.ServiceWatcher mServiceWatcher;
+
+    @Rule
+    public ActivityTestRule<CustomTestActivity> mActivityRule =
+            new ActivityTestRule<>(CustomTestActivity.class, false, false);
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Rule
+    public TestRule mServiceDisablerRule = (base, description) -> {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                try {
+                    base.evaluate();
+                } finally {
+                    Log.v(TAG, "@mServiceDisablerRule: safelyDisableService()");
+                    safelyDisableService();
+                }
+            }
+        };
+    };
+
+    private void safelyDisableService() {
+        try {
+            resetService();
+            MyContentCaptureService.resetStaticState();
+
+            if (mServiceWatcher != null) {
+                mServiceWatcher.waitOnDestroy();
+            }
+        } catch (Throwable t) {
+            Log.e(TAG, "error disabling service", t);
+        }
+    }
+
+    /**
+     * Sets the content capture service.
+     */
+    private static void setService(@NonNull String service) {
+        final int userId = getCurrentUserId();
+        Log.d(TAG, "Setting service for user " + userId + " to " + service);
+        // TODO(b/123540602): use @TestingAPI to get max duration constant
+        runShellCommand("cmd content_capture set temporary-service %d %s 119000", userId, service);
+    }
+
+    /**
+     * Resets the content capture service.
+     */
+    private static void resetService() {
+        final int userId = getCurrentUserId();
+        Log.d(TAG, "Resetting back user " + userId + " to default service");
+        runShellCommand("cmd content_capture set temporary-service %d", userId);
+    }
+
+    private static int getCurrentUserId() {
+        return UserHandle.myUserId();
+    }
+
+    @BeforeClass
+    public static void setStayAwake() {
+        Log.v(TAG, "@BeforeClass: setStayAwake()");
+        // Some test cases will restart the activity, and stay awake is necessary to ensure that
+        // the test will not screen off during the test.
+        // Keeping the activity screen on is not enough, screen off may occur between the activity
+        // finished and the next start
+        final int stayOnWhilePluggedIn = Settings.Global.getInt(sContext.getContentResolver(),
+                Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
+        sOriginalStayOnWhilePluggedIn = -1;
+        if (stayOnWhilePluggedIn != BatteryManager.BATTERY_PLUGGED_ANY) {
+            sOriginalStayOnWhilePluggedIn = stayOnWhilePluggedIn;
+            // Keep the device awake during testing.
+            setStayOnWhilePluggedIn(BatteryManager.BATTERY_PLUGGED_ANY);
+        }
+    }
+
+    @AfterClass
+    public static void resetStayAwake() {
+        Log.v(TAG, "@AfterClass: resetStayAwake()");
+        if (sOriginalStayOnWhilePluggedIn != -1) {
+            setStayOnWhilePluggedIn(sOriginalStayOnWhilePluggedIn);
+        }
+    }
+
+    private static void setStayOnWhilePluggedIn(int value) {
+        runShellCommand(String.format("settings put global %s %d",
+                Settings.Global.STAY_ON_WHILE_PLUGGED_IN, value));
+    }
+
+    @BeforeClass
+    public static void setAllowSelf() {
+        final ContentCaptureOptions options = new ContentCaptureOptions(null);
+        Log.v(TAG, "@BeforeClass: setAllowSelf(): options=" + options);
+        sContext.getApplicationContext().setContentCaptureOptions(options);
+    }
+
+    @AfterClass
+    public static void unsetAllowSelf() {
+        Log.v(TAG, "@AfterClass: unsetAllowSelf()");
+        clearOptions();
+    }
+
+    protected static void clearOptions() {
+        sContext.getApplicationContext().setContentCaptureOptions(null);
+    }
+
+    @BeforeClass
+    public static void disableDefaultService() {
+        Log.v(TAG, "@BeforeClass: disableDefaultService()");
+        setDefaultServiceEnabled(false);
+    }
+
+    @AfterClass
+    public static void enableDefaultService() {
+        Log.v(TAG, "@AfterClass: enableDefaultService()");
+        setDefaultServiceEnabled(true);
+    }
+
+    /**
+     * Enables / disables the default service.
+     */
+    private static void setDefaultServiceEnabled(boolean enabled) {
+        final int userId = getCurrentUserId();
+        Log.d(TAG, "setDefaultServiceEnabled(user=" + userId + ", enabled= " + enabled + ")");
+        runShellCommand("cmd content_capture set default-service-enabled %d %s", userId,
+                Boolean.toString(enabled));
+    }
+
+    @Before
+    public void prepareDevice() throws Exception {
+        Log.v(TAG, "@Before: prepareDevice()");
+
+        // Unlock screen.
+        runShellCommand("input keyevent KEYCODE_WAKEUP");
+
+        // Dismiss keyguard, in case it's set as "Swipe to unlock".
+        runShellCommand("wm dismiss-keyguard");
+
+        // Collapse notifications.
+        runShellCommand("cmd statusbar collapse");
+    }
+
+    @Before
+    public void registerLifecycleCallback() {
+        Log.v(TAG, "@Before: Registering lifecycle callback");
+        final Application app = (Application) sContext.getApplicationContext();
+        mActivitiesWatcher = new ActivitiesWatcher(GENERIC_TIMEOUT_MS);
+        app.registerActivityLifecycleCallbacks(mActivitiesWatcher);
+    }
+
+    @After
+    public void unregisterLifecycleCallback() {
+        Log.d(TAG, "@After: Unregistering lifecycle callback: " + mActivitiesWatcher);
+        if (mActivitiesWatcher != null) {
+            final Application app = (Application) sContext.getApplicationContext();
+            app.unregisterActivityLifecycleCallbacks(mActivitiesWatcher);
+        }
+    }
+
+    /**
+     * Sets {@link MyContentCaptureService} as the service for the current user and waits until
+     * its created, then add the perf test package into allow list.
+     */
+    public MyContentCaptureService enableService() throws InterruptedException {
+        if (mServiceWatcher != null) {
+            throw new IllegalStateException("There Can Be Only One!");
+        }
+
+        mServiceWatcher = MyContentCaptureService.setServiceWatcher();
+        setService(MyContentCaptureService.SERVICE_NAME);
+        mServiceWatcher.setAllowSelf();
+        return mServiceWatcher.waitOnCreate();
+    }
+
+    @NonNull
+    protected ActivityWatcher startWatcher() {
+        return mActivitiesWatcher.watch(CustomTestActivity.class);
+    }
+
+    /**
+     * Launch test activity with default login layout
+     */
+    protected CustomTestActivity launchActivity() {
+        return launchActivity(R.layout.test_login_activity, 0);
+    }
+
+    /**
+     * Launch test activity with give layout and parameter
+     */
+    protected CustomTestActivity launchActivity(int layoutId, int numViews) {
+        final Intent intent = new Intent(sContext, CustomTestActivity.class);
+        intent.putExtra(INTENT_EXTRA_LAYOUT_ID, layoutId);
+        intent.putExtra(INTENT_EXTRA_CUSTOM_VIEWS, numViews);
+        return mActivityRule.launchActivity(intent);
+    }
+
+    protected void finishActivity() {
+        try {
+            mActivityRule.finishActivity();
+        } catch (IllegalStateException e) {
+            // no op
+        }
+    }
+}
diff --git a/android/view/contentcapture/ChildContentCaptureSession.java b/android/view/contentcapture/ChildContentCaptureSession.java
new file mode 100644
index 0000000..44b4353
--- /dev/null
+++ b/android/view/contentcapture/ChildContentCaptureSession.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.contentcapture;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Insets;
+import android.view.autofill.AutofillId;
+import android.view.contentcapture.ViewNode.ViewStructureImpl;
+
+/**
+ * A session that is explicitly created by the app (and hence is a descendant of
+ * {@link MainContentCaptureSession}).
+ *
+ * @hide
+ */
+final class ChildContentCaptureSession extends ContentCaptureSession {
+
+    @NonNull
+    private final ContentCaptureSession mParent;
+
+    /** @hide */
+    protected ChildContentCaptureSession(@NonNull ContentCaptureSession parent,
+            @NonNull ContentCaptureContext clientContext) {
+        super(clientContext);
+        mParent = parent;
+    }
+
+    @Override
+    MainContentCaptureSession getMainCaptureSession() {
+        if (mParent instanceof MainContentCaptureSession) {
+            return (MainContentCaptureSession) mParent;
+        }
+        return mParent.getMainCaptureSession();
+    }
+
+    @Override
+    ContentCaptureSession newChild(@NonNull ContentCaptureContext clientContext) {
+        final ContentCaptureSession child = new ChildContentCaptureSession(this, clientContext);
+        getMainCaptureSession().notifyChildSessionStarted(mId, child.mId, clientContext);
+        return child;
+    }
+
+    @Override
+    void flush(@FlushReason int reason) {
+        mParent.flush(reason);
+    }
+
+    @Override
+    public void updateContentCaptureContext(@Nullable ContentCaptureContext context) {
+        getMainCaptureSession().notifyContextUpdated(mId, context);
+    }
+
+    @Override
+    void onDestroy() {
+        getMainCaptureSession().notifyChildSessionFinished(mParent.mId, mId);
+    }
+
+    @Override
+    void internalNotifyViewAppeared(@NonNull ViewStructureImpl node) {
+        getMainCaptureSession().notifyViewAppeared(mId, node);
+    }
+
+    @Override
+    void internalNotifyViewDisappeared(@NonNull AutofillId id) {
+        getMainCaptureSession().notifyViewDisappeared(mId, id);
+    }
+
+    @Override
+    void internalNotifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text) {
+        getMainCaptureSession().notifyViewTextChanged(mId, id, text);
+    }
+
+    @Override
+    void internalNotifyViewInsetsChanged(@NonNull Insets viewInsets) {
+        getMainCaptureSession().notifyViewInsetsChanged(mId, viewInsets);
+    }
+
+    @Override
+    public void internalNotifyViewTreeEvent(boolean started) {
+        getMainCaptureSession().notifyViewTreeEvent(mId, started);
+    }
+
+    @Override
+    void internalNotifySessionResumed() {
+        getMainCaptureSession().notifySessionResumed();
+    }
+
+    @Override
+    void internalNotifySessionPaused() {
+        getMainCaptureSession().notifySessionPaused();
+    }
+
+    @Override
+    boolean isContentCaptureEnabled() {
+        return getMainCaptureSession().isContentCaptureEnabled();
+    }
+}
diff --git a/android/view/contentcapture/ContentCaptureCondition.java b/android/view/contentcapture/ContentCaptureCondition.java
new file mode 100644
index 0000000..1adef94
--- /dev/null
+++ b/android/view/contentcapture/ContentCaptureCondition.java
@@ -0,0 +1,146 @@
+/*
+ * 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.view.contentcapture;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.LocusId;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.DebugUtils;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Defines a condition for when content capture should be allowed.
+ *
+ * <p>See {@link ContentCaptureManager#getContentCaptureConditions()} for more.
+ */
+public final class ContentCaptureCondition implements Parcelable {
+
+    /**
+     * When set, package should use the {@link LocusId#getId()} as a regular expression (using the
+     * {@link java.util.regex.Pattern} format).
+     */
+    public static final int FLAG_IS_REGEX = 0x2;
+
+    /** @hide */
+    @IntDef(prefix = { "FLAG" }, flag = true, value = {
+            FLAG_IS_REGEX
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface Flags {}
+
+    private final @NonNull LocusId mLocusId;
+    private final @Flags int mFlags;
+
+    /**
+     * Default constructor.
+     *
+     * @param locusId id of the condition, as defined by
+     * {@link ContentCaptureContext#getLocusId()}.
+     * @param flags either {@link ContentCaptureCondition#FLAG_IS_REGEX} (to use a regular
+     * expression match) or {@code 0} (in which case the {@code LocusId} must be an exact match of
+     * the {@code LocusId} used in the {@link ContentCaptureContext}).
+     */
+    public ContentCaptureCondition(@NonNull LocusId locusId, @Flags int flags) {
+        this.mLocusId = Preconditions.checkNotNull(locusId);
+        this.mFlags = flags;
+    }
+
+    /**
+     * Gets the {@code LocusId} per se.
+     */
+    @NonNull
+    public LocusId getLocusId() {
+        return mLocusId;
+    }
+
+    /**
+     * Gets the flags associates with this condition.
+     *
+     * @return either {@link ContentCaptureCondition#FLAG_IS_REGEX} or {@code 0}.
+     */
+    public @Flags int getFlags() {
+        return mFlags;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + mFlags;
+        result = prime * result + ((mLocusId == null) ? 0 : mLocusId.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (this == obj) return true;
+        if (obj == null) return false;
+        if (getClass() != obj.getClass()) return false;
+        final ContentCaptureCondition other = (ContentCaptureCondition) obj;
+        if (mFlags != other.mFlags) return false;
+        if (mLocusId == null) {
+            if (other.mLocusId != null) return false;
+        } else {
+            if (!mLocusId.equals(other.mLocusId)) return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder string = new StringBuilder(mLocusId.toString()); // LocusID is PII safe
+        if (mFlags != 0) {
+            string
+                .append(" (")
+                .append(DebugUtils.flagsToString(ContentCaptureCondition.class, "FLAG_", mFlags))
+                .append(')');
+        }
+        return string.toString();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel parcel, int flags) {
+        parcel.writeParcelable(mLocusId, flags);
+        parcel.writeInt(mFlags);
+    }
+
+    public static final @NonNull Parcelable.Creator<ContentCaptureCondition> CREATOR =
+            new Parcelable.Creator<ContentCaptureCondition>() {
+
+                @Override
+                public ContentCaptureCondition createFromParcel(@NonNull Parcel parcel) {
+                    return new ContentCaptureCondition(parcel.readParcelable(null),
+                            parcel.readInt());
+                }
+
+                @Override
+                public ContentCaptureCondition[] newArray(int size) {
+                    return new ContentCaptureCondition[size];
+                }
+    };
+}
diff --git a/android/view/contentcapture/ContentCaptureContext.java b/android/view/contentcapture/ContentCaptureContext.java
new file mode 100644
index 0000000..9998fbc
--- /dev/null
+++ b/android/view/contentcapture/ContentCaptureContext.java
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.contentcapture;
+
+import static android.view.contentcapture.ContentCaptureManager.NO_SESSION_ID;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.TaskInfo;
+import android.app.assist.ActivityId;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.LocusId;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.Display;
+import android.view.View;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Context associated with a {@link ContentCaptureSession} - see {@link ContentCaptureManager} for
+ * more info.
+ */
+public final class ContentCaptureContext implements Parcelable {
+
+    /*
+     * IMPLEMENTATION NOTICE:
+     *
+     * This object contains both the info that's explicitly added by apps (hence it's public), but
+     * it also contains info injected by the server (and are accessible through @SystemApi methods).
+     */
+
+    /**
+     * Flag used to indicate that the app explicitly disabled content capture for the activity
+     * (using {@link ContentCaptureManager#setContentCaptureEnabled(boolean)}),
+     * in which case the service will just receive activity-level events.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int FLAG_DISABLED_BY_APP = 0x1;
+
+    /**
+     * Flag used to indicate that the activity's window is tagged with
+     * {@link android.view.Display#FLAG_SECURE}, in which case the service will just receive
+     * activity-level events.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int FLAG_DISABLED_BY_FLAG_SECURE = 0x2;
+
+    /**
+     * Flag used when the event is sent because the Android System reconnected to the service (for
+     * example, after its process died).
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int FLAG_RECONNECTED = 0x4;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "FLAG_" }, value = {
+            FLAG_DISABLED_BY_APP,
+            FLAG_DISABLED_BY_FLAG_SECURE,
+            FLAG_RECONNECTED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ContextCreationFlags{}
+
+    /**
+     * Flag indicating if this object has the app-provided context (which is set on
+     * {@link ContentCaptureSession#createContentCaptureSession(ContentCaptureContext)}).
+     */
+    private final boolean mHasClientContext;
+
+    // Fields below are set by app on Builder
+    private final @Nullable Bundle mExtras;
+    private final @Nullable LocusId mId;
+
+    // Fields below are set by server when the session starts
+    private final @Nullable ComponentName mComponentName;
+    private final int mFlags;
+    private final int mDisplayId;
+    private final ActivityId mActivityId;
+
+    // Fields below are set by the service upon "delivery" and are not marshalled in the parcel
+    private int mParentSessionId = NO_SESSION_ID;
+
+    /** @hide */
+    public ContentCaptureContext(@Nullable ContentCaptureContext clientContext,
+            @NonNull ActivityId activityId, @NonNull ComponentName componentName, int displayId,
+            int flags) {
+        if (clientContext != null) {
+            mHasClientContext = true;
+            mExtras = clientContext.mExtras;
+            mId = clientContext.mId;
+        } else {
+            mHasClientContext = false;
+            mExtras = null;
+            mId = null;
+        }
+        mComponentName = Objects.requireNonNull(componentName);
+        mFlags = flags;
+        mDisplayId = displayId;
+        mActivityId = activityId;
+    }
+
+    private ContentCaptureContext(@NonNull Builder builder) {
+        mHasClientContext = true;
+        mExtras = builder.mExtras;
+        mId = builder.mId;
+
+        mComponentName  = null;
+        mFlags = 0;
+        mDisplayId = Display.INVALID_DISPLAY;
+        mActivityId = null;
+    }
+
+    /** @hide */
+    public ContentCaptureContext(@Nullable ContentCaptureContext original, int extraFlags) {
+        mHasClientContext = original.mHasClientContext;
+        mExtras = original.mExtras;
+        mId = original.mId;
+        mComponentName = original.mComponentName;
+        mFlags = original.mFlags | extraFlags;
+        mDisplayId = original.mDisplayId;
+        mActivityId = original.mActivityId;
+    }
+
+    /**
+     * Gets the (optional) extras set by the app (through {@link Builder#setExtras(Bundle)}).
+     *
+     * <p>It can be used to provide vendor-specific data that can be modified and examined.
+     */
+    @Nullable
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    /**
+     * Gets the context id.
+     */
+    @Nullable
+    public LocusId getLocusId() {
+        return mId;
+    }
+
+    /**
+     * Gets the id of the {@link TaskInfo task} associated with this context.
+     *
+     * @hide
+     */
+    @SystemApi
+    public int getTaskId() {
+        return mHasClientContext ? 0 : mActivityId.getTaskId();
+    }
+
+    /**
+     * Gets the activity associated with this context, or {@code null} when it is a child session.
+     *
+     * @hide
+     */
+    @SystemApi
+    public @Nullable ComponentName getActivityComponent() {
+        return mComponentName;
+    }
+
+    /**
+     * Gets the Activity id information associated with this context, or {@code null} when it is a
+     * child session.
+     *
+     * @hide
+     */
+    @SystemApi
+    @Nullable
+    public ActivityId getActivityId() {
+        return mHasClientContext ? null : mActivityId;
+    }
+
+    /**
+     * Gets the id of the session that originated this session (through
+     * {@link ContentCaptureSession#createContentCaptureSession(ContentCaptureContext)}),
+     * or {@code null} if this is the main session associated with the Activity's {@link Context}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public @Nullable ContentCaptureSessionId getParentSessionId() {
+        return mParentSessionId == NO_SESSION_ID ? null
+                : new ContentCaptureSessionId(mParentSessionId);
+    }
+
+    /** @hide */
+    public void setParentSessionId(int parentSessionId) {
+        mParentSessionId = parentSessionId;
+    }
+
+    /**
+     * Gets the ID of the display associated with this context, as defined by
+     * {G android.hardware.display.DisplayManager#getDisplay(int) DisplayManager.getDisplay()}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public int getDisplayId() {
+        return mDisplayId;
+    }
+
+    /**
+     * Gets the flags associated with this context.
+     *
+     * @return any combination of {@link #FLAG_DISABLED_BY_FLAG_SECURE},
+     * {@link #FLAG_DISABLED_BY_APP} and {@link #FLAG_RECONNECTED}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public @ContextCreationFlags int getFlags() {
+        return mFlags;
+    }
+
+    /**
+     * Helper that creates a {@link ContentCaptureContext} associated with the given {@code id}.
+     */
+    @NonNull
+    public static ContentCaptureContext forLocusId(@NonNull String id) {
+        return new Builder(new LocusId(id)).build();
+    }
+
+    /**
+     * Builder for {@link ContentCaptureContext} objects.
+     */
+    public static final class Builder {
+        private Bundle mExtras;
+        private final LocusId mId;
+        private boolean mDestroyed;
+
+        /**
+         * Creates a new builder.
+         *
+         * <p>The context must have an id, which is usually one of the following:
+         *
+         * <ul>
+         *   <li>A URL representing a web page (or {@code IFRAME}) that's being rendered by the
+         *   activity (See {@link View#setContentCaptureSession(ContentCaptureSession)} for an
+         *   example).
+         *   <li>A unique identifier of the application state (for example, a conversation between
+         *   2 users in a chat app).
+         * </ul>
+         *
+         * <p>See {@link ContentCaptureManager} for more info about the content capture context.
+         *
+         * @param id id associated with this context.
+         */
+        public Builder(@NonNull LocusId id) {
+            mId = Preconditions.checkNotNull(id);
+        }
+
+        /**
+         * Sets extra options associated with this context.
+         *
+         * <p>It can be used to provide vendor-specific data that can be modified and examined.
+         *
+         * @param extras extra options.
+         * @return this builder.
+         *
+         * @throws IllegalStateException if {@link #build()} was already called.
+         */
+        @NonNull
+        public Builder setExtras(@NonNull Bundle extras) {
+            mExtras = Preconditions.checkNotNull(extras);
+            throwIfDestroyed();
+            return this;
+        }
+
+        /**
+         * Builds the {@link ContentCaptureContext}.
+         *
+         * @throws IllegalStateException if {@link #build()} was already called.
+         *
+         * @return the built {@code ContentCaptureContext}
+         */
+        @NonNull
+        public ContentCaptureContext build() {
+            throwIfDestroyed();
+            mDestroyed = true;
+            return new ContentCaptureContext(this);
+        }
+
+        private void throwIfDestroyed() {
+            Preconditions.checkState(!mDestroyed, "Already called #build()");
+        }
+    }
+
+    /**
+     * @hide
+     */
+    // TODO(b/111276913): dump to proto as well
+    public void dump(PrintWriter pw) {
+        if (mComponentName != null) {
+            pw.print("activity="); pw.print(mComponentName.flattenToShortString());
+        }
+        if (mId != null) {
+            pw.print(", id="); mId.dump(pw);
+        }
+        pw.print(", activityId="); pw.print(mActivityId);
+        pw.print(", displayId="); pw.print(mDisplayId);
+        if (mParentSessionId != NO_SESSION_ID) {
+            pw.print(", parentId="); pw.print(mParentSessionId);
+        }
+        if (mFlags > 0) {
+            pw.print(", flags="); pw.print(mFlags);
+        }
+        if (mExtras != null) {
+            // NOTE: cannot dump because it could contain PII
+            pw.print(", hasExtras");
+        }
+    }
+
+    private boolean fromServer() {
+        return mComponentName != null;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder builder = new StringBuilder("Context[");
+
+        if (fromServer()) {
+            builder.append("act=").append(ComponentName.flattenToShortString(mComponentName))
+                .append(", activityId=").append(mActivityId)
+                .append(", displayId=").append(mDisplayId)
+                .append(", flags=").append(mFlags);
+        } else {
+            builder.append("id=").append(mId);
+            if (mExtras != null) {
+                // NOTE: cannot print because it could contain PII
+                builder.append(", hasExtras");
+            }
+        }
+        if (mParentSessionId != NO_SESSION_ID) {
+            builder.append(", parentId=").append(mParentSessionId);
+        }
+        return builder.append(']').toString();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeInt(mHasClientContext ? 1 : 0);
+        if (mHasClientContext) {
+            parcel.writeParcelable(mId, flags);
+            parcel.writeBundle(mExtras);
+        }
+        parcel.writeParcelable(mComponentName, flags);
+        if (fromServer()) {
+            parcel.writeInt(mDisplayId);
+            parcel.writeInt(mFlags);
+            mActivityId.writeToParcel(parcel, flags);
+        }
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<ContentCaptureContext> CREATOR =
+            new Parcelable.Creator<ContentCaptureContext>() {
+
+        @Override
+        @NonNull
+        public ContentCaptureContext createFromParcel(Parcel parcel) {
+            final boolean hasClientContext = parcel.readInt() == 1;
+
+            final ContentCaptureContext clientContext;
+            if (hasClientContext) {
+                // Must reconstruct the client context using the Builder API
+                final LocusId id = parcel.readParcelable(null);
+                final Bundle extras = parcel.readBundle();
+                final Builder builder = new Builder(id);
+                if (extras != null) builder.setExtras(extras);
+                clientContext = new ContentCaptureContext(builder);
+            } else {
+                clientContext = null;
+            }
+            final ComponentName componentName = parcel.readParcelable(null);
+            if (componentName == null) {
+                // Client-state only
+                return clientContext;
+            } else {
+                final int displayId = parcel.readInt();
+                final int flags = parcel.readInt();
+                final ActivityId activityId = new ActivityId(parcel);
+
+                return new ContentCaptureContext(clientContext, activityId, componentName,
+                        displayId, flags);
+            }
+        }
+
+        @Override
+        @NonNull
+        public ContentCaptureContext[] newArray(int size) {
+            return new ContentCaptureContext[size];
+        }
+    };
+}
diff --git a/android/view/contentcapture/ContentCaptureEvent.java b/android/view/contentcapture/ContentCaptureEvent.java
new file mode 100644
index 0000000..ce6d034
--- /dev/null
+++ b/android/view/contentcapture/ContentCaptureEvent.java
@@ -0,0 +1,656 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.contentcapture;
+
+import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString;
+import static android.view.contentcapture.ContentCaptureManager.DEBUG;
+import static android.view.contentcapture.ContentCaptureManager.NO_SESSION_ID;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.graphics.Insets;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.Selection;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+import android.view.inputmethod.BaseInputConnection;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/** @hide */
+@SystemApi
+public final class ContentCaptureEvent implements Parcelable {
+
+    private static final String TAG = ContentCaptureEvent.class.getSimpleName();
+
+    /** @hide */
+    public static final int TYPE_SESSION_FINISHED = -2;
+    /** @hide */
+    public static final int TYPE_SESSION_STARTED = -1;
+
+    /**
+     * Called when a node has been added to the screen and is visible to the user.
+     *
+     * <p>The metadata of the node is available through {@link #getViewNode()}.
+     */
+    public static final int TYPE_VIEW_APPEARED = 1;
+
+    /**
+     * Called when one or more nodes have been removed from the screen and is not visible to the
+     * user anymore.
+     *
+     * <p>To get the id(s), first call {@link #getIds()} - if it returns {@code null}, then call
+     * {@link #getId()}.
+     */
+    public static final int TYPE_VIEW_DISAPPEARED = 2;
+
+    /**
+     * Called when the text of a node has been changed.
+     *
+     * <p>The id of the node is available through {@link #getId()}, and the new text is
+     * available through {@link #getText()}.
+     */
+    public static final int TYPE_VIEW_TEXT_CHANGED = 3;
+
+    /**
+     * Called before events (such as {@link #TYPE_VIEW_APPEARED} and/or
+     * {@link #TYPE_VIEW_DISAPPEARED}) representing a view hierarchy are sent.
+     *
+     * <p><b>NOTE</b>: there is no guarantee this event will be sent. For example, it's not sent
+     * if the initial view hierarchy doesn't initially have any view that's important for content
+     * capture.
+     */
+    public static final int TYPE_VIEW_TREE_APPEARING = 4;
+
+    /**
+     * Called after events (such as {@link #TYPE_VIEW_APPEARED} and/or
+     * {@link #TYPE_VIEW_DISAPPEARED}) representing a view hierarchy were sent.
+     *
+     * <p><b>NOTE</b>: there is no guarantee this event will be sent. For example, it's not sent
+     * if the initial view hierarchy doesn't initially have any view that's important for content
+     * capture.
+     */
+    public static final int TYPE_VIEW_TREE_APPEARED = 5;
+
+    /**
+     * Called after a call to
+     * {@link ContentCaptureSession#setContentCaptureContext(ContentCaptureContext)}.
+     *
+     * <p>The passed context is available through {@link #getContentCaptureContext()}.
+     */
+    public static final int TYPE_CONTEXT_UPDATED = 6;
+
+    /**
+     * Called after the session is ready, typically after the activity resumed and the
+     * initial views appeared
+     */
+    public static final int TYPE_SESSION_RESUMED = 7;
+
+    /**
+     * Called after the session is paused, typically after the activity paused and the
+     * views disappeared.
+     */
+    public static final int TYPE_SESSION_PAUSED = 8;
+
+    /**
+     * Called when the view's insets are changed. The new insets associated with the
+     * event may then be retrieved by calling {@link #getInsets()}
+     */
+    public static final int TYPE_VIEW_INSETS_CHANGED = 9;
+
+    /** @hide */
+    @IntDef(prefix = { "TYPE_" }, value = {
+            TYPE_VIEW_APPEARED,
+            TYPE_VIEW_DISAPPEARED,
+            TYPE_VIEW_TEXT_CHANGED,
+            TYPE_VIEW_TREE_APPEARING,
+            TYPE_VIEW_TREE_APPEARED,
+            TYPE_CONTEXT_UPDATED,
+            TYPE_SESSION_PAUSED,
+            TYPE_SESSION_RESUMED,
+            TYPE_VIEW_INSETS_CHANGED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface EventType{}
+
+    /** @hide */
+    public static final int MAX_INVALID_VALUE = -1;
+
+    private final int mSessionId;
+    private final int mType;
+    private final long mEventTime;
+    private @Nullable AutofillId mId;
+    private @Nullable ArrayList<AutofillId> mIds;
+    private @Nullable ViewNode mNode;
+    private @Nullable CharSequence mText;
+    private int mParentSessionId = NO_SESSION_ID;
+    private @Nullable ContentCaptureContext mClientContext;
+    private @Nullable Insets mInsets;
+
+    private int mComposingStart = MAX_INVALID_VALUE;
+    private int mComposingEnd = MAX_INVALID_VALUE;
+    private int mSelectionStartIndex = MAX_INVALID_VALUE;
+    private int mSelectionEndIndex = MAX_INVALID_VALUE;
+
+    /** Only used in the main Content Capture session, no need to parcel */
+    private boolean mTextHasComposingSpan;
+
+    /** @hide */
+    public ContentCaptureEvent(int sessionId, int type, long eventTime) {
+        mSessionId = sessionId;
+        mType = type;
+        mEventTime = eventTime;
+    }
+
+    /** @hide */
+    public ContentCaptureEvent(int sessionId, int type) {
+        this(sessionId, type, System.currentTimeMillis());
+    }
+
+    /** @hide */
+    public ContentCaptureEvent setAutofillId(@NonNull AutofillId id) {
+        mId = Preconditions.checkNotNull(id);
+        return this;
+    }
+
+    /** @hide */
+    public ContentCaptureEvent setAutofillIds(@NonNull ArrayList<AutofillId> ids) {
+        mIds = Preconditions.checkNotNull(ids);
+        return this;
+    }
+
+    /**
+     * Adds an autofill id to the this event, merging the single id into a list if necessary.
+     *
+     * @hide
+     */
+    public ContentCaptureEvent addAutofillId(@NonNull AutofillId id) {
+        Preconditions.checkNotNull(id);
+        if (mIds == null) {
+            mIds = new ArrayList<>();
+            if (mId == null) {
+                Log.w(TAG, "addAutofillId(" + id + ") called without an initial id");
+            } else {
+                mIds.add(mId);
+                mId = null;
+            }
+        }
+        mIds.add(id);
+        return this;
+    }
+
+    /**
+     * Used by {@link #TYPE_SESSION_STARTED} and {@link #TYPE_SESSION_FINISHED}.
+     *
+     * @hide
+     */
+    public ContentCaptureEvent setParentSessionId(int parentSessionId) {
+        mParentSessionId = parentSessionId;
+        return this;
+    }
+
+    /**
+     * Used by {@link #TYPE_SESSION_STARTED} and {@link #TYPE_SESSION_FINISHED}.
+     *
+     * @hide
+     */
+    public ContentCaptureEvent setClientContext(@NonNull ContentCaptureContext clientContext) {
+        mClientContext = clientContext;
+        return this;
+    }
+
+    /** @hide */
+    @NonNull
+    public int getSessionId() {
+        return mSessionId;
+    }
+
+    /**
+     * Used by {@link #TYPE_SESSION_STARTED} and {@link #TYPE_SESSION_FINISHED}.
+     *
+     * @hide
+     */
+    @Nullable
+    public int getParentSessionId() {
+        return mParentSessionId;
+    }
+
+    /**
+     * Gets the {@link ContentCaptureContext} set calls to
+     * {@link ContentCaptureSession#setContentCaptureContext(ContentCaptureContext)}.
+     *
+     * <p>Only set on {@link #TYPE_CONTEXT_UPDATED} events.
+     */
+    @Nullable
+    public ContentCaptureContext getContentCaptureContext() {
+        return mClientContext;
+    }
+
+    /** @hide */
+    @NonNull
+    public ContentCaptureEvent setViewNode(@NonNull ViewNode node) {
+        mNode = Preconditions.checkNotNull(node);
+        return this;
+    }
+
+    /** @hide */
+    @NonNull
+    public ContentCaptureEvent setText(@Nullable CharSequence text) {
+        mText = text;
+        return this;
+    }
+
+    /** @hide */
+    @NonNull
+    public ContentCaptureEvent setComposingIndex(int start, int end) {
+        mComposingStart = start;
+        mComposingEnd = end;
+        return this;
+    }
+
+    /** @hide */
+    @NonNull
+    public boolean hasComposingSpan() {
+        return mComposingStart > MAX_INVALID_VALUE;
+    }
+
+    /** @hide */
+    @NonNull
+    public ContentCaptureEvent setSelectionIndex(int start, int end) {
+        mSelectionStartIndex = start;
+        mSelectionEndIndex = end;
+        return this;
+    }
+
+    boolean hasSameComposingSpan(@NonNull ContentCaptureEvent other) {
+        return mComposingStart == other.mComposingStart && mComposingEnd == other.mComposingEnd;
+    }
+
+    boolean hasSameSelectionSpan(@NonNull ContentCaptureEvent other) {
+        return mSelectionStartIndex == other.mSelectionStartIndex
+                && mSelectionEndIndex == other.mSelectionEndIndex;
+    }
+
+    private int getComposingStart() {
+        return mComposingStart;
+    }
+
+    private int getComposingEnd() {
+        return mComposingEnd;
+    }
+
+    private int getSelectionStart() {
+        return mSelectionStartIndex;
+    }
+
+    private int getSelectionEnd() {
+        return mSelectionEndIndex;
+    }
+
+    private void restoreComposingSpan() {
+        if (mComposingStart <= MAX_INVALID_VALUE
+                || mComposingEnd <= MAX_INVALID_VALUE) {
+            return;
+        }
+        if (mText instanceof Spannable) {
+            BaseInputConnection.setComposingSpans((Spannable) mText, mComposingStart,
+                    mComposingEnd);
+        } else {
+            Log.w(TAG, "Text is not a Spannable.");
+        }
+    }
+
+    private void restoreSelectionSpans() {
+        if (mSelectionStartIndex <= MAX_INVALID_VALUE
+                || mSelectionEndIndex <= MAX_INVALID_VALUE) {
+            return;
+        }
+
+        if (mText instanceof SpannableString) {
+            SpannableString ss = (SpannableString) mText;
+            ss.setSpan(Selection.SELECTION_START, mSelectionStartIndex, mSelectionStartIndex, 0);
+            ss.setSpan(Selection.SELECTION_END, mSelectionEndIndex, mSelectionEndIndex, 0);
+        } else {
+            Log.w(TAG, "Text is not a SpannableString.");
+        }
+    }
+
+    /** @hide */
+    @NonNull
+    public ContentCaptureEvent setInsets(@NonNull Insets insets) {
+        mInsets = insets;
+        return this;
+    }
+
+    /**
+     * Gets the type of the event.
+     *
+     * @return one of {@link #TYPE_VIEW_APPEARED}, {@link #TYPE_VIEW_DISAPPEARED},
+     * {@link #TYPE_VIEW_TEXT_CHANGED}, {@link #TYPE_VIEW_TREE_APPEARING},
+     * {@link #TYPE_VIEW_TREE_APPEARED}, {@link #TYPE_CONTEXT_UPDATED},
+     * {@link #TYPE_SESSION_RESUMED}, or {@link #TYPE_SESSION_PAUSED}.
+     */
+    public @EventType int getType() {
+        return mType;
+    }
+
+    /**
+     * Gets when the event was generated, in millis since epoch.
+     */
+    public long getEventTime() {
+        return mEventTime;
+    }
+
+    /**
+     * Gets the whole metadata of the node associated with the event.
+     *
+     * <p>Only set on {@link #TYPE_VIEW_APPEARED} events.
+     */
+    @Nullable
+    public ViewNode getViewNode() {
+        return mNode;
+    }
+
+    /**
+     * Gets the {@link AutofillId} of the node associated with the event.
+     *
+     * <p>Only set on {@link #TYPE_VIEW_DISAPPEARED} (when the event contains just one node - if
+     * it contains more than one, this method returns {@code null} and the actual ids should be
+     * retrived by {@link #getIds()}) and {@link #TYPE_VIEW_TEXT_CHANGED} events.
+     */
+    @Nullable
+    public AutofillId getId() {
+        return mId;
+    }
+
+    /**
+     * Gets the {@link AutofillId AutofillIds} of the nodes associated with the event.
+     *
+     * <p>Only set on {@link #TYPE_VIEW_DISAPPEARED}, when the event contains more than one node
+     * (if it contains just one node, it's returned by {@link #getId()} instead.
+     */
+    @Nullable
+    public List<AutofillId> getIds() {
+        return mIds;
+    }
+
+    /**
+     * Gets the current text of the node associated with the event.
+     *
+     * <p>Only set on {@link #TYPE_VIEW_TEXT_CHANGED} events.
+     */
+    @Nullable
+    public CharSequence getText() {
+        return mText;
+    }
+
+    /**
+     * Gets the rectangle of the insets associated with the event. Valid insets will only be
+     * returned if the type of the event is {@link #TYPE_VIEW_INSETS_CHANGED}, otherwise they
+     * will be null.
+     */
+    @Nullable
+    public Insets getInsets() {
+        return mInsets;
+    }
+
+    /**
+     * Merges event of the same type, either {@link #TYPE_VIEW_TEXT_CHANGED}
+     * or {@link #TYPE_VIEW_DISAPPEARED}.
+     *
+     * @hide
+     */
+    public void mergeEvent(@NonNull ContentCaptureEvent event) {
+        Preconditions.checkNotNull(event);
+        final int eventType = event.getType();
+        if (mType != eventType) {
+            Log.e(TAG, "mergeEvent(" + getTypeAsString(eventType) + ") cannot be merged "
+                    + "with different eventType=" + getTypeAsString(mType));
+            return;
+        }
+
+        if (eventType == TYPE_VIEW_DISAPPEARED) {
+            final List<AutofillId> ids = event.getIds();
+            final AutofillId id = event.getId();
+            if (ids != null) {
+                if (id != null) {
+                    Log.w(TAG, "got TYPE_VIEW_DISAPPEARED event with both id and ids: " + event);
+                }
+                for (int i = 0; i < ids.size(); i++) {
+                    addAutofillId(ids.get(i));
+                }
+                return;
+            }
+            if (id != null) {
+                addAutofillId(id);
+                return;
+            }
+            throw new IllegalArgumentException("mergeEvent(): got "
+                    + "TYPE_VIEW_DISAPPEARED event with neither id or ids: " + event);
+        } else if (eventType == TYPE_VIEW_TEXT_CHANGED) {
+            setText(event.getText());
+            setComposingIndex(event.getComposingStart(), event.getComposingEnd());
+            setSelectionIndex(event.getSelectionStart(), event.getSelectionEnd());
+        } else {
+            Log.e(TAG, "mergeEvent(" + getTypeAsString(eventType)
+                    + ") does not support this event type.");
+        }
+    }
+
+    /** @hide */
+    public void dump(@NonNull PrintWriter pw) {
+        pw.print("type="); pw.print(getTypeAsString(mType));
+        pw.print(", time="); pw.print(mEventTime);
+        if (mId != null) {
+            pw.print(", id="); pw.print(mId);
+        }
+        if (mIds != null) {
+            pw.print(", ids="); pw.print(mIds);
+        }
+        if (mNode != null) {
+            pw.print(", mNode.id="); pw.print(mNode.getAutofillId());
+        }
+        if (mSessionId != NO_SESSION_ID) {
+            pw.print(", sessionId="); pw.print(mSessionId);
+        }
+        if (mParentSessionId != NO_SESSION_ID) {
+            pw.print(", parentSessionId="); pw.print(mParentSessionId);
+        }
+        if (mText != null) {
+            pw.print(", text="); pw.println(getSanitizedString(mText));
+        }
+        if (mClientContext != null) {
+            pw.print(", context="); mClientContext.dump(pw); pw.println();
+        }
+        if (mInsets != null) {
+            pw.print(", insets="); pw.println(mInsets);
+        }
+        if (mComposingStart > MAX_INVALID_VALUE) {
+            pw.print(", composing("); pw.print(mComposingStart);
+            pw.print(", "); pw.print(mComposingEnd); pw.print(")");
+        }
+        if (mSelectionStartIndex > MAX_INVALID_VALUE) {
+            pw.print(", selection("); pw.print(mSelectionStartIndex);
+            pw.print(", "); pw.print(mSelectionEndIndex); pw.print(")");
+        }
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        final StringBuilder string = new StringBuilder("ContentCaptureEvent[type=")
+                .append(getTypeAsString(mType));
+        string.append(", session=").append(mSessionId);
+        if (mType == TYPE_SESSION_STARTED && mParentSessionId != NO_SESSION_ID) {
+            string.append(", parent=").append(mParentSessionId);
+        }
+        if (mId != null) {
+            string.append(", id=").append(mId);
+        }
+        if (mIds != null) {
+            string.append(", ids=").append(mIds);
+        }
+        if (mNode != null) {
+            final String className = mNode.getClassName();
+            string.append(", class=").append(className);
+            string.append(", id=").append(mNode.getAutofillId());
+            if (mNode.getText() != null) {
+                string.append(", text=")
+                        .append(DEBUG ? mNode.getText() : getSanitizedString(mNode.getText()));
+            }
+        }
+        if (mText != null) {
+            string.append(", text=")
+                    .append(DEBUG ? mText : getSanitizedString(mText));
+        }
+        if (mClientContext != null) {
+            string.append(", context=").append(mClientContext);
+        }
+        if (mInsets != null) {
+            string.append(", insets=").append(mInsets);
+        }
+        if (mComposingStart > MAX_INVALID_VALUE) {
+            string.append(", composing=[")
+                    .append(mComposingStart).append(",").append(mComposingEnd).append("]");
+        }
+        if (mSelectionStartIndex > MAX_INVALID_VALUE) {
+            string.append(", selection=[")
+                    .append(mSelectionStartIndex).append(",")
+                    .append(mSelectionEndIndex).append("]");
+        }
+        return string.append(']').toString();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeInt(mSessionId);
+        parcel.writeInt(mType);
+        parcel.writeLong(mEventTime);
+        parcel.writeParcelable(mId, flags);
+        parcel.writeTypedList(mIds);
+        ViewNode.writeToParcel(parcel, mNode, flags);
+        parcel.writeCharSequence(mText);
+        if (mType == TYPE_SESSION_STARTED || mType == TYPE_SESSION_FINISHED) {
+            parcel.writeInt(mParentSessionId);
+        }
+        if (mType == TYPE_SESSION_STARTED || mType == TYPE_CONTEXT_UPDATED) {
+            parcel.writeParcelable(mClientContext, flags);
+        }
+        if (mType == TYPE_VIEW_INSETS_CHANGED) {
+            parcel.writeParcelable(mInsets, flags);
+        }
+        if (mType == TYPE_VIEW_TEXT_CHANGED) {
+            parcel.writeInt(mComposingStart);
+            parcel.writeInt(mComposingEnd);
+            parcel.writeInt(mSelectionStartIndex);
+            parcel.writeInt(mSelectionEndIndex);
+        }
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<ContentCaptureEvent> CREATOR =
+            new Parcelable.Creator<ContentCaptureEvent>() {
+
+        @Override
+        @NonNull
+        public ContentCaptureEvent createFromParcel(Parcel parcel) {
+            final int sessionId = parcel.readInt();
+            final int type = parcel.readInt();
+            final long eventTime  = parcel.readLong();
+            final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, type, eventTime);
+            final AutofillId id = parcel.readParcelable(null);
+            if (id != null) {
+                event.setAutofillId(id);
+            }
+            final ArrayList<AutofillId> ids = parcel.createTypedArrayList(AutofillId.CREATOR);
+            if (ids != null) {
+                event.setAutofillIds(ids);
+            }
+            final ViewNode node = ViewNode.readFromParcel(parcel);
+            if (node != null) {
+                event.setViewNode(node);
+            }
+            event.setText(parcel.readCharSequence());
+            if (type == TYPE_SESSION_STARTED || type == TYPE_SESSION_FINISHED) {
+                event.setParentSessionId(parcel.readInt());
+            }
+            if (type == TYPE_SESSION_STARTED || type == TYPE_CONTEXT_UPDATED) {
+                event.setClientContext(parcel.readParcelable(null));
+            }
+            if (type == TYPE_VIEW_INSETS_CHANGED) {
+                event.setInsets(parcel.readParcelable(null));
+            }
+            if (type == TYPE_VIEW_TEXT_CHANGED) {
+                event.setComposingIndex(parcel.readInt(), parcel.readInt());
+                event.restoreComposingSpan();
+                event.setSelectionIndex(parcel.readInt(), parcel.readInt());
+                event.restoreSelectionSpans();
+            }
+            return event;
+        }
+
+        @Override
+        @NonNull
+        public ContentCaptureEvent[] newArray(int size) {
+            return new ContentCaptureEvent[size];
+        }
+    };
+
+    /** @hide */
+    public static String getTypeAsString(@EventType int type) {
+        switch (type) {
+            case TYPE_SESSION_STARTED:
+                return "SESSION_STARTED";
+            case TYPE_SESSION_FINISHED:
+                return "SESSION_FINISHED";
+            case TYPE_SESSION_RESUMED:
+                return "SESSION_RESUMED";
+            case TYPE_SESSION_PAUSED:
+                return "SESSION_PAUSED";
+            case TYPE_VIEW_APPEARED:
+                return "VIEW_APPEARED";
+            case TYPE_VIEW_DISAPPEARED:
+                return "VIEW_DISAPPEARED";
+            case TYPE_VIEW_TEXT_CHANGED:
+                return "VIEW_TEXT_CHANGED";
+            case TYPE_VIEW_TREE_APPEARING:
+                return "VIEW_TREE_APPEARING";
+            case TYPE_VIEW_TREE_APPEARED:
+                return "VIEW_TREE_APPEARED";
+            case TYPE_CONTEXT_UPDATED:
+                return "CONTEXT_UPDATED";
+            case TYPE_VIEW_INSETS_CHANGED:
+                return "VIEW_INSETS_CHANGED";
+            default:
+                return "UKNOWN_TYPE: " + type;
+        }
+    }
+}
diff --git a/android/view/contentcapture/ContentCaptureHelper.java b/android/view/contentcapture/ContentCaptureHelper.java
new file mode 100644
index 0000000..c7ca220
--- /dev/null
+++ b/android/view/contentcapture/ContentCaptureHelper.java
@@ -0,0 +1,128 @@
+/*
+ * 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.view.contentcapture;
+
+import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_LOGGING_LEVEL;
+import static android.view.contentcapture.ContentCaptureManager.LOGGING_LEVEL_DEBUG;
+import static android.view.contentcapture.ContentCaptureManager.LOGGING_LEVEL_OFF;
+import static android.view.contentcapture.ContentCaptureManager.LOGGING_LEVEL_VERBOSE;
+
+import android.annotation.Nullable;
+import android.os.Build;
+import android.provider.DeviceConfig;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.contentcapture.ContentCaptureManager.LoggingLevel;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Helper class for this package and server's.
+ *
+ * @hide
+ */
+public final class ContentCaptureHelper {
+
+    private static final String TAG = ContentCaptureHelper.class.getSimpleName();
+
+    public static boolean sVerbose = false;
+    public static boolean sDebug = true;
+
+    /**
+     * Used to log text that could contain PII.
+     */
+    @Nullable
+    public static String getSanitizedString(@Nullable CharSequence text) {
+        return text == null ? null : text.length() + "_chars";
+    }
+
+    /**
+     * Gets the default logging level for the device.
+     */
+    @LoggingLevel
+    public static int getDefaultLoggingLevel() {
+        return Build.IS_DEBUGGABLE ? LOGGING_LEVEL_DEBUG : LOGGING_LEVEL_OFF;
+    }
+
+    /**
+     * Sets the value of the static logging level constants based on device config.
+     */
+    public static void setLoggingLevel() {
+        final int defaultLevel = getDefaultLoggingLevel();
+        final int level = DeviceConfig.getInt(DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                DEVICE_CONFIG_PROPERTY_LOGGING_LEVEL, defaultLevel);
+        setLoggingLevel(level);
+    }
+
+    /**
+     * Sets the value of the static logging level constants based the given level.
+     */
+    public static void setLoggingLevel(@LoggingLevel int level) {
+        Log.i(TAG, "Setting logging level to " + getLoggingLevelAsString(level));
+        sVerbose = sDebug = false;
+        switch (level) {
+            case LOGGING_LEVEL_VERBOSE:
+                sVerbose = true;
+                // fall through
+            case LOGGING_LEVEL_DEBUG:
+                sDebug = true;
+                return;
+            case LOGGING_LEVEL_OFF:
+                // You log nothing, Jon Snow!
+                return;
+            default:
+                Log.w(TAG, "setLoggingLevel(): invalud level: " + level);
+        }
+    }
+
+    /**
+     * Gets a user-friendly value for a content capture logging level.
+     */
+    public static String getLoggingLevelAsString(@LoggingLevel int level) {
+        switch (level) {
+            case LOGGING_LEVEL_OFF:
+                return "OFF";
+            case LOGGING_LEVEL_DEBUG:
+                return "DEBUG";
+            case LOGGING_LEVEL_VERBOSE:
+                return "VERBOSE";
+            default:
+                return "UNKNOWN-" + level;
+        }
+    }
+
+    /**
+     * Converts a set to a list.
+     */
+    @Nullable
+    public static <T> ArrayList<T> toList(@Nullable Set<T> set) {
+        return set == null ? null : new ArrayList<T>(set);
+    }
+
+    /**
+     * Converts a list to a set.
+     */
+    @Nullable
+    public static <T> ArraySet<T> toSet(@Nullable List<T> list) {
+        return list == null ? null : new ArraySet<T>(list);
+    }
+
+    private ContentCaptureHelper() {
+        throw new UnsupportedOperationException("contains only static methods");
+    }
+}
diff --git a/android/view/contentcapture/ContentCaptureManager.java b/android/view/contentcapture/ContentCaptureManager.java
new file mode 100644
index 0000000..9241c30
--- /dev/null
+++ b/android/view/contentcapture/ContentCaptureManager.java
@@ -0,0 +1,943 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.contentcapture;
+
+import static android.view.contentcapture.ContentCaptureHelper.sDebug;
+import static android.view.contentcapture.ContentCaptureHelper.sVerbose;
+import static android.view.contentcapture.ContentCaptureHelper.toSet;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.annotation.UiThread;
+import android.annotation.UserIdInt;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.ContentCaptureOptions;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+import android.util.Slog;
+import android.view.View;
+import android.view.ViewStructure;
+import android.view.WindowManager;
+import android.view.contentcapture.ContentCaptureSession.FlushReason;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.SyncResultReceiver;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * <p>The {@link ContentCaptureManager} provides additional ways for for apps to
+ * integrate with the content capture subsystem.
+ *
+ * <p>Content capture provides real-time, continuous capture of application activity, display and
+ * events to an intelligence service that is provided by the Android system. The intelligence
+ * service then uses that info to mediate and speed user journey through different apps. For
+ * example, when the user receives a restaurant address in a chat app and switches to a map app
+ * to search for that restaurant, the intelligence service could offer an autofill dialog to
+ * let the user automatically select its address.
+ *
+ * <p>Content capture was designed with two major concerns in mind: privacy and performance.
+ *
+ * <ul>
+ *   <li><b>Privacy:</b> the intelligence service is a trusted component provided that is provided
+ *   by the device manufacturer and that cannot be changed by the user (although the user can
+ *   globaly disable content capture using the Android Settings app). This service can only use the
+ *   data for in-device machine learning, which is enforced both by process isolation and
+ *   <a href="https://source.android.com/compatibility/cdd">CDD requirements</a>.
+ *   <li><b>Performance:</b> content capture is highly optimized to minimize its impact in the app
+ *   jankiness and overall device system health. For example, its only enabled on apps (or even
+ *   specific activities from an app) that were explicitly allowlisted by the intelligence service,
+ *   and it buffers the events so they are sent in a batch to the service (see
+ *   {@link #isContentCaptureEnabled()} for other cases when its disabled).
+ * </ul>
+ *
+ * <p>In fact, before using this manager, the app developer should check if it's available. Example:
+ * <pre><code>
+ *  ContentCaptureManager mgr = context.getSystemService(ContentCaptureManager.class);
+ *  if (mgr != null && mgr.isContentCaptureEnabled()) {
+ *    // ...
+ *  }
+ *  </code></pre>
+ *
+ * <p>App developers usually don't need to explicitly interact with content capture, except when the
+ * app:
+ *
+ * <ul>
+ *   <li>Can define a contextual {@link android.content.LocusId} to identify unique state (such as a
+ *   conversation between 2 chat users).
+ *   <li>Can have multiple view hierarchies with different contextual meaning (for example, a
+ *   browser app with multiple tabs, each representing a different URL).
+ *   <li>Contains custom views (that extend View directly and are not provided by the standard
+ *   Android SDK.
+ *   <li>Contains views that provide their own virtual hierarchy (like a web browser that render the
+ *   HTML elements using a Canvas).
+ * </ul>
+ *
+ * <p>The main integration point with content capture is the {@link ContentCaptureSession}. A "main"
+ * session is automatically created by the Android System when content capture is enabled for the
+ * activity and its used by the standard Android views to notify the content capture service of
+ * events such as views being added, views been removed, and text changed by user input. The session
+ * could have a {@link ContentCaptureContext} to provide more contextual info about it, such as
+ * the locus associated with the view hierarchy (see {@link android.content.LocusId} for more info
+ * about locus). By default, the main session doesn't have a {@code ContentCaptureContext}, but you
+ * can change it after its created. Example:
+ *
+ * <pre><code>
+ * protected void onCreate(Bundle savedInstanceState) {
+ *   // Initialize view structure
+ *   ContentCaptureSession session = rootView.getContentCaptureSession();
+ *   if (session != null) {
+ *     session.setContentCaptureContext(ContentCaptureContext.forLocusId("chat_UserA_UserB"));
+ *   }
+ * }
+ * </code></pre>
+ *
+ * <p>If your activity contains view hierarchies with a different contextual meaning, you should
+ * created child sessions for each view hierarchy root. For example, if your activity is a browser,
+ * you could use the main session for the main URL being rendered, then child sessions for each
+ * {@code IFRAME}:
+ *
+ * <pre><code>
+ * ContentCaptureSession mMainSession;
+ *
+ * protected void onCreate(Bundle savedInstanceState) {
+ *    // Initialize view structure...
+ *    mMainSession = rootView.getContentCaptureSession();
+ *    if (mMainSession != null) {
+ *      mMainSession.setContentCaptureContext(
+ *          ContentCaptureContext.forLocusId("https://example.com"));
+ *    }
+ * }
+ *
+ * private void loadIFrame(View iframeRootView, String url) {
+ *   if (mMainSession != null) {
+ *      ContentCaptureSession iFrameSession = mMainSession.newChild(
+ *          ContentCaptureContext.forLocusId(url));
+ *      }
+ *      iframeRootView.setContentCaptureSession(iFrameSession);
+ *   }
+ *   // Load iframe...
+ * }
+ * </code></pre>
+ *
+ * <p>If your activity has custom views (i.e., views that extend {@link View} directly and provide
+ * just one logical view, not a virtual tree hiearchy) and it provides content that's relevant for
+ * content capture (as of {@link android.os.Build.VERSION_CODES#Q Android Q}, the only relevant
+ * content is text), then your view implementation should:
+ *
+ * <ul>
+ *   <li>Set it as important for content capture.
+ *   <li>Fill {@link ViewStructure} used for content capture.
+ *   <li>Notify the {@link ContentCaptureSession} when the text is changed by user input.
+ * </ul>
+ *
+ * <p>Here's an example of the relevant methods for an {@code EditText}-like view:
+ *
+ * <pre><code>
+ * public class MyEditText extends View {
+ *
+ * public MyEditText(...) {
+ *   if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) {
+ *     setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES);
+ *   }
+ * }
+ *
+ * public void onProvideContentCaptureStructure(@NonNull ViewStructure structure, int flags) {
+ *   super.onProvideContentCaptureStructure(structure, flags);
+ *
+ *   structure.setText(getText(), getSelectionStart(), getSelectionEnd());
+ *   structure.setHint(getHint());
+ *   structure.setInputType(getInputType());
+ *   // set other properties like setTextIdEntry(), setTextLines(), setTextStyle(),
+ *   // setMinTextEms(), setMaxTextEms(), setMaxTextLength()
+ * }
+ *
+ * private void onTextChanged() {
+ *   if (isLaidOut() && isImportantForContentCapture() && isTextEditable()) {
+ *     ContentCaptureManager mgr = mContext.getSystemService(ContentCaptureManager.class);
+ *     if (cm != null && cm.isContentCaptureEnabled()) {
+ *        ContentCaptureSession session = getContentCaptureSession();
+ *        if (session != null) {
+ *          session.notifyViewTextChanged(getAutofillId(), getText());
+ *        }
+ *   }
+ * }
+ * </code></pre>
+ *
+ * <p>If your view provides its own virtual hierarchy (for example, if it's a browser that draws
+ * the HTML using {@link Canvas} or native libraries in a different render process), then the view
+ * is also responsible to notify the session when the virtual elements appear and disappear - see
+ * {@link View#onProvideContentCaptureStructure(ViewStructure, int)} for more info.
+ */
+@SystemService(Context.CONTENT_CAPTURE_MANAGER_SERVICE)
+public final class ContentCaptureManager {
+
+    private static final String TAG = ContentCaptureManager.class.getSimpleName();
+
+    /** @hide */
+    public static final boolean DEBUG = false;
+
+    /** Error happened during the data sharing session. */
+    public static final int DATA_SHARE_ERROR_UNKNOWN = 1;
+
+    /** Request has been rejected, because a concurrent data share sessions is in progress. */
+    public static final int DATA_SHARE_ERROR_CONCURRENT_REQUEST = 2;
+
+    /** Request has been interrupted because of data share session timeout. */
+    public static final int DATA_SHARE_ERROR_TIMEOUT_INTERRUPTED = 3;
+
+    /** @hide */
+    @IntDef(flag = false, value = {
+            DATA_SHARE_ERROR_UNKNOWN,
+            DATA_SHARE_ERROR_CONCURRENT_REQUEST,
+            DATA_SHARE_ERROR_TIMEOUT_INTERRUPTED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DataShareError {}
+
+    /** @hide */
+    public static final int RESULT_CODE_OK = 0;
+    /** @hide */
+    public static final int RESULT_CODE_TRUE = 1;
+    /** @hide */
+    public static final int RESULT_CODE_FALSE = 2;
+    /** @hide */
+    public static final int RESULT_CODE_SECURITY_EXCEPTION = -1;
+
+    /**
+     * ID used to indicate that a session does not exist
+     * @hide
+     */
+    @SystemApi
+    public static final int NO_SESSION_ID = 0;
+
+    /**
+     * Timeout for calls to system_server.
+     */
+    private static final int SYNC_CALLS_TIMEOUT_MS = 5000;
+
+    /**
+     * DeviceConfig property used by {@code com.android.server.SystemServer} on start to decide
+     * whether the content capture service should be created or not
+     *
+     * <p>By default it should *NOT* be set (or set to {@code "default"}, so the decision is based
+     * on whether the OEM provides an implementation for the service), but it can be overridden to:
+     *
+     * <ul>
+     *   <li>Provide a "kill switch" so OEMs can disable it remotely in case of emergency (when
+     *   it's set to {@code "false"}).
+     *   <li>Enable the CTS tests to be run on AOSP builds (when it's set to {@code "true"}).
+     * </ul>
+     *
+     * @hide
+     */
+    @TestApi
+    public static final String DEVICE_CONFIG_PROPERTY_SERVICE_EXPLICITLY_ENABLED =
+            "service_explicitly_enabled";
+
+    /**
+     * Maximum number of events that are buffered before sent to the app.
+     *
+     * @hide
+     */
+    @TestApi
+    public static final String DEVICE_CONFIG_PROPERTY_MAX_BUFFER_SIZE = "max_buffer_size";
+
+    /**
+     * Frequency (in ms) of buffer flushes when no events are received.
+     *
+     * @hide
+     */
+    @TestApi
+    public static final String DEVICE_CONFIG_PROPERTY_IDLE_FLUSH_FREQUENCY = "idle_flush_frequency";
+
+    /**
+     * Frequency (in ms) of buffer flushes when no events are received and the last one was a
+     * text change event.
+     *
+     * @hide
+     */
+    @TestApi
+    public static final String DEVICE_CONFIG_PROPERTY_TEXT_CHANGE_FLUSH_FREQUENCY =
+            "text_change_flush_frequency";
+
+    /**
+     * Size of events that are logging on {@code dump}.
+     *
+     * <p>Set it to {@code 0} or less to disable history.
+     *
+     * @hide
+     */
+    @TestApi
+    public static final String DEVICE_CONFIG_PROPERTY_LOG_HISTORY_SIZE = "log_history_size";
+
+    /**
+     * Sets the logging level for {@code logcat} statements.
+     *
+     * <p>Valid values are: {@link #LOGGING_LEVEL_OFF}, {@value #LOGGING_LEVEL_DEBUG}, and
+     * {@link #LOGGING_LEVEL_VERBOSE}.
+     *
+     * @hide
+     */
+    @TestApi
+    public static final String DEVICE_CONFIG_PROPERTY_LOGGING_LEVEL = "logging_level";
+
+    /**
+     * Sets how long (in ms) the service is bound while idle.
+     *
+     * <p>Use {@code 0} to keep it permanently bound.
+     *
+     * @hide
+     */
+    public static final String DEVICE_CONFIG_PROPERTY_IDLE_UNBIND_TIMEOUT = "idle_unbind_timeout";
+
+    /** @hide */
+    @TestApi
+    public static final int LOGGING_LEVEL_OFF = 0;
+
+    /** @hide */
+    @TestApi
+    public static final int LOGGING_LEVEL_DEBUG = 1;
+
+    /** @hide */
+    @TestApi
+    public static final int LOGGING_LEVEL_VERBOSE = 2;
+
+    /** @hide */
+    @IntDef(flag = false, value = {
+            LOGGING_LEVEL_OFF,
+            LOGGING_LEVEL_DEBUG,
+            LOGGING_LEVEL_VERBOSE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface LoggingLevel {}
+
+
+    /** @hide */
+    public static final int DEFAULT_MAX_BUFFER_SIZE = 500; // Enough for typical busy screen.
+    /** @hide */
+    public static final int DEFAULT_IDLE_FLUSHING_FREQUENCY_MS = 5_000;
+    /** @hide */
+    public static final int DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS = 1_000;
+    /** @hide */
+    public static final int DEFAULT_LOG_HISTORY_SIZE = 10;
+
+    private final Object mLock = new Object();
+
+    @NonNull
+    private final Context mContext;
+
+    @NonNull
+    private final IContentCaptureManager mService;
+
+    @GuardedBy("mLock")
+    private final LocalDataShareAdapterResourceManager mDataShareAdapterResourceManager;
+
+    @NonNull
+    final ContentCaptureOptions mOptions;
+
+    // Flags used for starting session.
+    @GuardedBy("mLock")
+    private int mFlags;
+
+    // TODO(b/119220549): use UI Thread directly (as calls are one-way) or a shared thread / handler
+    // held at the Application level
+    @NonNull
+    private final Handler mHandler;
+
+    @GuardedBy("mLock")
+    private MainContentCaptureSession mMainSession;
+
+    /** @hide */
+    public interface ContentCaptureClient {
+        /**
+         * Gets the component name of the client.
+         */
+        @NonNull
+        ComponentName contentCaptureClientGetComponentName();
+    }
+
+    /** @hide */
+    public ContentCaptureManager(@NonNull Context context,
+            @NonNull IContentCaptureManager service, @NonNull ContentCaptureOptions options) {
+        mContext = Preconditions.checkNotNull(context, "context cannot be null");
+        mService = Preconditions.checkNotNull(service, "service cannot be null");
+        mOptions = Preconditions.checkNotNull(options, "options cannot be null");
+
+        ContentCaptureHelper.setLoggingLevel(mOptions.loggingLevel);
+
+        if (sVerbose) Log.v(TAG, "Constructor for " + context.getPackageName());
+
+        // TODO(b/119220549): we might not even need a handler, as the IPCs are oneway. But if we
+        // do, then we should optimize it to run the tests after the Choreographer finishes the most
+        // important steps of the frame.
+        mHandler = Handler.createAsync(Looper.getMainLooper());
+
+        mDataShareAdapterResourceManager = new LocalDataShareAdapterResourceManager();
+    }
+
+    /**
+     * Gets the main session associated with the context.
+     *
+     * <p>By default there's just one (associated with the activity lifecycle), but apps could
+     * explicitly add more using
+     * {@link ContentCaptureSession#createContentCaptureSession(ContentCaptureContext)}.
+     *
+     * @hide
+     */
+    @NonNull
+    @UiThread
+    public MainContentCaptureSession getMainContentCaptureSession() {
+        synchronized (mLock) {
+            if (mMainSession == null) {
+                mMainSession = new MainContentCaptureSession(mContext, this, mHandler, mService);
+                if (sVerbose) Log.v(TAG, "getMainContentCaptureSession(): created " + mMainSession);
+            }
+            return mMainSession;
+        }
+    }
+
+    /** @hide */
+    @UiThread
+    public void onActivityCreated(@NonNull IBinder applicationToken,
+            @NonNull IBinder shareableActivityToken, @NonNull ComponentName activityComponent) {
+        if (mOptions.lite) return;
+        synchronized (mLock) {
+            getMainContentCaptureSession().start(applicationToken, shareableActivityToken,
+                    activityComponent, mFlags);
+        }
+    }
+
+    /** @hide */
+    @UiThread
+    public void onActivityResumed() {
+        if (mOptions.lite) return;
+        getMainContentCaptureSession().notifySessionResumed();
+    }
+
+    /** @hide */
+    @UiThread
+    public void onActivityPaused() {
+        if (mOptions.lite) return;
+        getMainContentCaptureSession().notifySessionPaused();
+    }
+
+    /** @hide */
+    @UiThread
+    public void onActivityDestroyed() {
+        if (mOptions.lite) return;
+        getMainContentCaptureSession().destroy();
+    }
+
+    /**
+     * Flushes the content of all sessions.
+     *
+     * <p>Typically called by {@code Activity} when it's paused / resumed.
+     *
+     * @hide
+     */
+    @UiThread
+    public void flush(@FlushReason int reason) {
+        if (mOptions.lite) return;
+        getMainContentCaptureSession().flush(reason);
+    }
+
+    /**
+     * Returns the component name of the system service that is consuming the captured events for
+     * the current user.
+     */
+    @Nullable
+    public ComponentName getServiceComponentName() {
+        if (!isContentCaptureEnabled() && !mOptions.lite) return null;
+
+        final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
+        try {
+            mService.getServiceComponentName(resultReceiver);
+            return resultReceiver.getParcelableResult();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (SyncResultReceiver.TimeoutException e) {
+            throw new RuntimeException("Fail to get service componentName.");
+        }
+    }
+
+    /**
+     * Gets the (optional) intent used to launch the service-specific settings.
+     *
+     * <p>This method is static because it's called by Settings, which might not be allowlisted
+     * for content capture (in which case the ContentCaptureManager on its context would be null).
+     *
+     * @hide
+     */
+    // TODO: use "lite" options as it's done by activities from the content capture service
+    @Nullable
+    public static ComponentName getServiceSettingsComponentName() {
+        final IBinder binder = ServiceManager
+                .checkService(Context.CONTENT_CAPTURE_MANAGER_SERVICE);
+        if (binder == null) return null;
+
+        final IContentCaptureManager service = IContentCaptureManager.Stub.asInterface(binder);
+        final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
+        try {
+            service.getServiceSettingsActivity(resultReceiver);
+            final int resultCode = resultReceiver.getIntResult();
+            if (resultCode == RESULT_CODE_SECURITY_EXCEPTION) {
+                throw new SecurityException(resultReceiver.getStringResult());
+            }
+            return resultReceiver.getParcelableResult();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (SyncResultReceiver.TimeoutException e) {
+            Log.e(TAG, "Fail to get service settings componentName: " + e);
+            return null;
+        }
+    }
+
+    /**
+     * Checks whether content capture is enabled for this activity.
+     *
+     * <p>There are many reasons it could be disabled, such as:
+     * <ul>
+     *   <li>App itself disabled content capture through {@link #setContentCaptureEnabled(boolean)}.
+     *   <li>Intelligence service did not allowlist content capture for this activity's package.
+     *   <li>Intelligence service did not allowlist content capture for this specific activity.
+     *   <li>Intelligence service disabled content capture globally.
+     *   <li>User disabled content capture globally through the Android Settings app.
+     *   <li>Device manufacturer (OEM) disabled content capture globally.
+     *   <li>Transient errors, such as intelligence service package being updated.
+     * </ul>
+     */
+    public boolean isContentCaptureEnabled() {
+        if (mOptions.lite) return false;
+
+        final MainContentCaptureSession mainSession;
+        synchronized (mLock) {
+            mainSession = mMainSession;
+        }
+        // The main session is only set when the activity starts, so we need to return true until
+        // then.
+        if (mainSession != null && mainSession.isDisabled()) return false;
+
+        return true;
+    }
+
+    /**
+     * Gets the list of conditions for when content capture should be allowed.
+     *
+     * <p>This method is typically used by web browsers so they don't generate unnecessary content
+     * capture events for websites the content capture service is not interested on.
+     *
+     * @return list of conditions, or {@code null} if the service didn't set any restriction
+     * (in which case content capture events should always be generated). If the list is empty,
+     * then it should not generate any event at all.
+     */
+    @Nullable
+    public Set<ContentCaptureCondition> getContentCaptureConditions() {
+        // NOTE: we could cache the conditions on ContentCaptureOptions, but then it would be stick
+        // to the lifetime of the app. OTOH, by dynamically calling the server every time, we allow
+        // the service to fine tune how long-lived apps (like browsers) are allowlisted.
+        if (!isContentCaptureEnabled() && !mOptions.lite) return null;
+
+        final SyncResultReceiver resultReceiver = syncRun(
+                (r) -> mService.getContentCaptureConditions(mContext.getPackageName(), r));
+
+        try {
+            final ArrayList<ContentCaptureCondition> result = resultReceiver
+                    .getParcelableListResult();
+            return toSet(result);
+        } catch (SyncResultReceiver.TimeoutException e) {
+            throw new RuntimeException("Fail to get content capture conditions.");
+        }
+    }
+
+    /**
+     * Called by apps to explicitly enable or disable content capture.
+     *
+     * <p><b>Note: </b> this call is not persisted accross reboots, so apps should typically call
+     * it on {@link android.app.Activity#onCreate(android.os.Bundle, android.os.PersistableBundle)}.
+     */
+    public void setContentCaptureEnabled(boolean enabled) {
+        if (sDebug) {
+            Log.d(TAG, "setContentCaptureEnabled(): setting to " + enabled + " for " + mContext);
+        }
+
+        MainContentCaptureSession mainSession;
+        synchronized (mLock) {
+            if (enabled) {
+                mFlags &= ~ContentCaptureContext.FLAG_DISABLED_BY_APP;
+            } else {
+                mFlags |= ContentCaptureContext.FLAG_DISABLED_BY_APP;
+            }
+            mainSession = mMainSession;
+        }
+        if (mainSession != null) {
+            mainSession.setDisabled(!enabled);
+        }
+    }
+
+    /**
+     * Called by apps to update flag secure when window attributes change.
+     *
+     * @hide
+     */
+    public void updateWindowAttributes(@NonNull WindowManager.LayoutParams params) {
+        if (sDebug) {
+            Log.d(TAG, "updateWindowAttributes(): window flags=" + params.flags);
+        }
+        final boolean flagSecureEnabled =
+                (params.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0;
+
+        MainContentCaptureSession mainSession;
+        synchronized (mLock) {
+            if (flagSecureEnabled) {
+                mFlags |= ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE;
+            } else {
+                mFlags &= ~ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE;
+            }
+            mainSession = mMainSession;
+        }
+        if (mainSession != null) {
+            mainSession.setDisabled(flagSecureEnabled);
+        }
+    }
+
+    /**
+     * Gets whether content capture is enabled for the given user.
+     *
+     * <p>This method is typically used by the content capture service settings page, so it can
+     * provide a toggle to enable / disable it.
+     *
+     * @throws SecurityException if caller is not the app that owns the content capture service
+     * associated with the user.
+     *
+     * @hide
+     */
+    @SystemApi
+    public boolean isContentCaptureFeatureEnabled() {
+        final SyncResultReceiver resultReceiver = syncRun(
+                (r) -> mService.isContentCaptureFeatureEnabled(r));
+
+        try {
+            final int resultCode = resultReceiver.getIntResult();
+            switch (resultCode) {
+                case RESULT_CODE_TRUE:
+                    return true;
+                case RESULT_CODE_FALSE:
+                    return false;
+                default:
+                    Log.wtf(TAG, "received invalid result: " + resultCode);
+                    return false;
+            }
+        } catch (SyncResultReceiver.TimeoutException e) {
+            Log.e(TAG, "Fail to get content capture feature enable status: " + e);
+            return false;
+        }
+    }
+
+    /**
+     * Called by the app to request the content capture service to remove content capture data
+     * associated with some context.
+     *
+     * @param request object specifying what user data should be removed.
+     */
+    public void removeData(@NonNull DataRemovalRequest request) {
+        Preconditions.checkNotNull(request);
+
+        try {
+            mService.removeData(request);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Called by the app to request data sharing via writing to a file.
+     *
+     * <p>The ContentCaptureService app will receive a read-only file descriptor pointing to the
+     * same file and will be able to read data being shared from it.
+     *
+     * <p>Note: using this API doesn't guarantee the app staying alive and is "best-effort".
+     * Starting a foreground service would minimize the chances of the app getting killed during the
+     * file sharing session.
+     *
+     * @param request object specifying details of the data being shared.
+     */
+    public void shareData(@NonNull DataShareRequest request,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull DataShareWriteAdapter dataShareWriteAdapter) {
+        Preconditions.checkNotNull(request);
+        Preconditions.checkNotNull(dataShareWriteAdapter);
+        Preconditions.checkNotNull(executor);
+
+        try {
+            mService.shareData(request,
+                    new DataShareAdapterDelegate(executor, dataShareWriteAdapter,
+                            mDataShareAdapterResourceManager));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Runs a sync method in the service, properly handling exceptions.
+     *
+     * @throws SecurityException if caller is not allowed to execute the method.
+     */
+    @NonNull
+    private SyncResultReceiver syncRun(@NonNull MyRunnable r) {
+        final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
+        try {
+            r.run(resultReceiver);
+            final int resultCode = resultReceiver.getIntResult();
+            if (resultCode == RESULT_CODE_SECURITY_EXCEPTION) {
+                throw new SecurityException(resultReceiver.getStringResult());
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (SyncResultReceiver.TimeoutException e) {
+            throw new RuntimeException("Fail to get syn run result from SyncResultReceiver.");
+        }
+        return resultReceiver;
+    }
+
+    /** @hide */
+    public void dump(String prefix, PrintWriter pw) {
+        pw.print(prefix); pw.println("ContentCaptureManager");
+        final String prefix2 = prefix + "  ";
+        synchronized (mLock) {
+            pw.print(prefix2); pw.print("isContentCaptureEnabled(): ");
+            pw.println(isContentCaptureEnabled());
+            pw.print(prefix2); pw.print("Debug: "); pw.print(sDebug);
+            pw.print(" Verbose: "); pw.println(sVerbose);
+            pw.print(prefix2); pw.print("Context: "); pw.println(mContext);
+            pw.print(prefix2); pw.print("User: "); pw.println(mContext.getUserId());
+            pw.print(prefix2); pw.print("Service: "); pw.println(mService);
+            pw.print(prefix2); pw.print("Flags: "); pw.println(mFlags);
+            pw.print(prefix2); pw.print("Options: "); mOptions.dumpShort(pw); pw.println();
+            if (mMainSession != null) {
+                final String prefix3 = prefix2 + "  ";
+                pw.print(prefix2); pw.println("Main session:");
+                mMainSession.dump(prefix3, pw);
+            } else {
+                pw.print(prefix2); pw.println("No sessions");
+            }
+        }
+    }
+
+    /**
+     * Resets the temporary content capture service implementation to the default component.
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_CONTENT_CAPTURE)
+    public static void resetTemporaryService(@UserIdInt int userId) {
+        final IContentCaptureManager service = getService();
+        if (service == null) {
+            Log.e(TAG, "IContentCaptureManager is null");
+        }
+        try {
+            service.resetTemporaryService(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Temporarily sets the content capture service implementation.
+     *
+     * @param userId user Id to set the temporary service on.
+     * @param serviceName name of the new component
+     * @param duration how long the change will be valid (the service will be automatically reset
+     * to the default component after this timeout expires).
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_CONTENT_CAPTURE)
+    public static void setTemporaryService(
+            @UserIdInt int userId, @NonNull String serviceName, int duration) {
+        final IContentCaptureManager service = getService();
+        if (service == null) {
+            Log.e(TAG, "IContentCaptureManager is null");
+        }
+        try {
+            service.setTemporaryService(userId, serviceName, duration);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sets whether the default content capture service should be used.
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_CONTENT_CAPTURE)
+    public static void setDefaultServiceEnabled(@UserIdInt int userId, boolean enabled) {
+        final IContentCaptureManager service = getService();
+        if (service == null) {
+            Log.e(TAG, "IContentCaptureManager is null");
+        }
+        try {
+            service.setDefaultServiceEnabled(userId, enabled);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private static IContentCaptureManager getService() {
+        return IContentCaptureManager.Stub.asInterface(ServiceManager.getService(
+                Service.CONTENT_CAPTURE_MANAGER_SERVICE));
+    }
+
+    private interface MyRunnable {
+        void run(@NonNull SyncResultReceiver receiver) throws RemoteException;
+    }
+
+    private static class DataShareAdapterDelegate extends IDataShareWriteAdapter.Stub {
+
+        private final WeakReference<LocalDataShareAdapterResourceManager> mResourceManagerReference;
+
+        private DataShareAdapterDelegate(Executor executor, DataShareWriteAdapter adapter,
+                LocalDataShareAdapterResourceManager resourceManager) {
+            Preconditions.checkNotNull(executor);
+            Preconditions.checkNotNull(adapter);
+            Preconditions.checkNotNull(resourceManager);
+
+            resourceManager.initializeForDelegate(this, adapter, executor);
+            mResourceManagerReference = new WeakReference<>(resourceManager);
+        }
+
+        @Override
+        public void write(ParcelFileDescriptor destination)
+                throws RemoteException {
+            executeAdapterMethodLocked(adapter -> adapter.onWrite(destination), "onWrite");
+        }
+
+        @Override
+        public void error(int errorCode) throws RemoteException {
+            executeAdapterMethodLocked(adapter -> adapter.onError(errorCode), "onError");
+            clearHardReferences();
+        }
+
+        @Override
+        public void rejected() throws RemoteException {
+            executeAdapterMethodLocked(DataShareWriteAdapter::onRejected, "onRejected");
+            clearHardReferences();
+        }
+
+        @Override
+        public void finish() throws RemoteException  {
+            clearHardReferences();
+        }
+
+        private void executeAdapterMethodLocked(Consumer<DataShareWriteAdapter> adapterFn,
+                String methodName) {
+            LocalDataShareAdapterResourceManager resourceManager = mResourceManagerReference.get();
+            if (resourceManager == null) {
+                Slog.w(TAG, "Can't execute " + methodName + "(), resource manager has been GC'ed");
+                return;
+            }
+
+            DataShareWriteAdapter adapter = resourceManager.getAdapter(this);
+            Executor executor = resourceManager.getExecutor(this);
+
+            if (adapter == null || executor == null) {
+                Slog.w(TAG, "Can't execute " + methodName + "(), references are null");
+                return;
+            }
+
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                executor.execute(() -> adapterFn.accept(adapter));
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        private void clearHardReferences() {
+            LocalDataShareAdapterResourceManager resourceManager = mResourceManagerReference.get();
+            if (resourceManager == null) {
+                Slog.w(TAG, "Can't clear references, resource manager has been GC'ed");
+                return;
+            }
+
+            resourceManager.clearHardReferences(this);
+        }
+    }
+
+    /**
+     * Wrapper class making sure dependencies on the current application stay in the application
+     * context.
+     */
+    private static class LocalDataShareAdapterResourceManager {
+
+        // Keeping hard references to the remote objects in the current process (static context)
+        // to prevent them to be gc'ed during the lifetime of the application. This is an
+        // artifact of only operating with weak references remotely: there has to be at least 1
+        // hard reference in order for this to not be killed.
+        private Map<DataShareAdapterDelegate, DataShareWriteAdapter> mWriteAdapterHardReferences =
+                new HashMap<>();
+        private Map<DataShareAdapterDelegate, Executor> mExecutorHardReferences =
+                new HashMap<>();
+
+        void initializeForDelegate(DataShareAdapterDelegate delegate, DataShareWriteAdapter adapter,
+                Executor executor) {
+            mWriteAdapterHardReferences.put(delegate, adapter);
+            mExecutorHardReferences.put(delegate, executor);
+        }
+
+        Executor getExecutor(DataShareAdapterDelegate delegate) {
+            return mExecutorHardReferences.get(delegate);
+        }
+
+        DataShareWriteAdapter getAdapter(DataShareAdapterDelegate delegate) {
+            return mWriteAdapterHardReferences.get(delegate);
+        }
+
+        void clearHardReferences(DataShareAdapterDelegate delegate) {
+            mWriteAdapterHardReferences.remove(delegate);
+            mExecutorHardReferences.remove(delegate);
+        }
+    }
+}
diff --git a/android/view/contentcapture/ContentCaptureSession.java b/android/view/contentcapture/ContentCaptureSession.java
new file mode 100644
index 0000000..cc47f09
--- /dev/null
+++ b/android/view/contentcapture/ContentCaptureSession.java
@@ -0,0 +1,626 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.contentcapture;
+
+import static android.view.contentcapture.ContentCaptureHelper.sDebug;
+import static android.view.contentcapture.ContentCaptureHelper.sVerbose;
+import static android.view.contentcapture.ContentCaptureManager.NO_SESSION_ID;
+
+import android.annotation.CallSuper;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Insets;
+import android.util.DebugUtils;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewStructure;
+import android.view.autofill.AutofillId;
+import android.view.contentcapture.ViewNode.ViewStructureImpl;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+
+/**
+ * Session used when notifying the Android system about events associated with views.
+ */
+public abstract class ContentCaptureSession implements AutoCloseable {
+
+    private static final String TAG = ContentCaptureSession.class.getSimpleName();
+
+    // TODO(b/158778794): to make the session ids truly globally unique across
+    //  processes, we may need to explore other options.
+    private static final SecureRandom ID_GENERATOR = new SecureRandom();
+
+    /**
+     * Initial state, when there is no session.
+     *
+     * @hide
+     */
+    // NOTE: not prefixed by STATE_ so it's not printed on getStateAsString()
+    public static final int UNKNOWN_STATE = 0x0;
+
+    /**
+     * Service's startSession() was called, but server didn't confirm it was created yet.
+     *
+     * @hide
+     */
+    public static final int STATE_WAITING_FOR_SERVER = 0x1;
+
+    /**
+     * Session is active.
+     *
+     * @hide
+     */
+    public static final int STATE_ACTIVE = 0x2;
+
+    /**
+     * Session is disabled because there is no service for this user.
+     *
+     * @hide
+     */
+    public static final int STATE_DISABLED = 0x4;
+
+    /**
+     * Session is disabled because its id already existed on server.
+     *
+     * @hide
+     */
+    public static final int STATE_DUPLICATED_ID = 0x8;
+
+    /**
+     * Session is disabled because service is not set for user.
+     *
+     * @hide
+     */
+    public static final int STATE_NO_SERVICE = 0x10;
+
+    /**
+     * Session is disabled by FLAG_SECURE
+     *
+     * @hide
+     */
+    public static final int STATE_FLAG_SECURE = 0x20;
+
+    /**
+     * Session is disabled manually by the specific app
+     * (through {@link ContentCaptureManager#setContentCaptureEnabled(boolean)}).
+     *
+     * @hide
+     */
+    public static final int STATE_BY_APP = 0x40;
+
+    /**
+     * Session is disabled because session start was never replied.
+     *
+     * @hide
+     */
+    public static final int STATE_NO_RESPONSE = 0x80;
+
+    /**
+     * Session is disabled because an internal error.
+     *
+     * @hide
+     */
+    public static final int STATE_INTERNAL_ERROR = 0x100;
+
+    /**
+     * Session is disabled because service didn't allowlist package or activity.
+     *
+     * @hide
+     */
+    public static final int STATE_NOT_WHITELISTED = 0x200;
+
+    /**
+     * Session is disabled because the service died.
+     *
+     * @hide
+     */
+    public static final int STATE_SERVICE_DIED = 0x400;
+
+    /**
+     * Session is disabled because the service package is being udpated.
+     *
+     * @hide
+     */
+    public static final int STATE_SERVICE_UPDATING = 0x800;
+
+    /**
+     * Session is enabled, after the service died and came back to live.
+     *
+     * @hide
+     */
+    public static final int STATE_SERVICE_RESURRECTED = 0x1000;
+
+    private static final int INITIAL_CHILDREN_CAPACITY = 5;
+
+    /** @hide */
+    public static final int FLUSH_REASON_FULL = 1;
+    /** @hide */
+    public static final int FLUSH_REASON_VIEW_ROOT_ENTERED = 2;
+    /** @hide */
+    public static final int FLUSH_REASON_SESSION_STARTED = 3;
+    /** @hide */
+    public static final int FLUSH_REASON_SESSION_FINISHED = 4;
+    /** @hide */
+    public static final int FLUSH_REASON_IDLE_TIMEOUT = 5;
+    /** @hide */
+    public static final int FLUSH_REASON_TEXT_CHANGE_TIMEOUT = 6;
+    /** @hide */
+    public static final int FLUSH_REASON_SESSION_CONNECTED = 7;
+
+    /** @hide */
+    @IntDef(prefix = { "FLUSH_REASON_" }, value = {
+            FLUSH_REASON_FULL,
+            FLUSH_REASON_VIEW_ROOT_ENTERED,
+            FLUSH_REASON_SESSION_STARTED,
+            FLUSH_REASON_SESSION_FINISHED,
+            FLUSH_REASON_IDLE_TIMEOUT,
+            FLUSH_REASON_TEXT_CHANGE_TIMEOUT,
+            FLUSH_REASON_SESSION_CONNECTED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FlushReason{}
+
+    private final Object mLock = new Object();
+
+    /**
+     * Guard use to ignore events after it's destroyed.
+     */
+    @NonNull
+    @GuardedBy("mLock")
+    private boolean mDestroyed;
+
+    /** @hide */
+    @Nullable
+    protected final int mId;
+
+    private int mState = UNKNOWN_STATE;
+
+    // Lazily created on demand.
+    private ContentCaptureSessionId mContentCaptureSessionId;
+
+    /**
+     * {@link ContentCaptureContext} set by client, or {@code null} when it's the
+     * {@link ContentCaptureManager#getMainContentCaptureSession() default session} for the
+     * context.
+     */
+    @Nullable
+    private ContentCaptureContext mClientContext;
+
+    /**
+     * List of children session.
+     */
+    @Nullable
+    @GuardedBy("mLock")
+    private ArrayList<ContentCaptureSession> mChildren;
+
+    /** @hide */
+    protected ContentCaptureSession() {
+        this(getRandomSessionId());
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    public ContentCaptureSession(int id) {
+        Preconditions.checkArgument(id != NO_SESSION_ID);
+        mId = id;
+    }
+
+    // Used by ChildCOntentCaptureSession
+    ContentCaptureSession(@NonNull ContentCaptureContext initialContext) {
+        this();
+        mClientContext = Preconditions.checkNotNull(initialContext);
+    }
+
+    /** @hide */
+    @NonNull
+    abstract MainContentCaptureSession getMainCaptureSession();
+
+    /**
+     * Gets the id used to identify this session.
+     */
+    @NonNull
+    public final ContentCaptureSessionId getContentCaptureSessionId() {
+        if (mContentCaptureSessionId == null) {
+            mContentCaptureSessionId = new ContentCaptureSessionId(mId);
+        }
+        return mContentCaptureSessionId;
+    }
+
+    /** @hide */
+    @NonNull
+    public int getId() {
+        return mId;
+    }
+
+    /**
+     * Creates a new {@link ContentCaptureSession}.
+     *
+     * <p>See {@link View#setContentCaptureSession(ContentCaptureSession)} for more info.
+     */
+    @NonNull
+    public final ContentCaptureSession createContentCaptureSession(
+            @NonNull ContentCaptureContext context) {
+        final ContentCaptureSession child = newChild(context);
+        if (sDebug) {
+            Log.d(TAG, "createContentCaptureSession(" + context + ": parent=" + mId + ", child="
+                    + child.mId);
+        }
+        synchronized (mLock) {
+            if (mChildren == null) {
+                mChildren = new ArrayList<>(INITIAL_CHILDREN_CAPACITY);
+            }
+            mChildren.add(child);
+        }
+        return child;
+    }
+
+    abstract ContentCaptureSession newChild(@NonNull ContentCaptureContext context);
+
+    /**
+     * Flushes the buffered events to the service.
+     */
+    abstract void flush(@FlushReason int reason);
+
+    /**
+     * Sets the {@link ContentCaptureContext} associated with the session.
+     *
+     * <p>Typically used to change the context associated with the default session from an activity.
+     */
+    public final void setContentCaptureContext(@Nullable ContentCaptureContext context) {
+        mClientContext = context;
+        updateContentCaptureContext(context);
+    }
+
+    abstract void updateContentCaptureContext(@Nullable ContentCaptureContext context);
+
+    /**
+     * Gets the {@link ContentCaptureContext} associated with the session.
+     *
+     * @return context set on constructor or by
+     *         {@link #setContentCaptureContext(ContentCaptureContext)}, or {@code null} if never
+     *         explicitly set.
+     */
+    @Nullable
+    public final ContentCaptureContext getContentCaptureContext() {
+        return mClientContext;
+    }
+
+    /**
+     * Destroys this session, flushing out all pending notifications to the service.
+     *
+     * <p>Once destroyed, any new notification will be dropped.
+     */
+    public final void destroy() {
+        synchronized (mLock) {
+            if (mDestroyed) {
+                if (sDebug) Log.d(TAG, "destroy(" + mId + "): already destroyed");
+                return;
+            }
+            mDestroyed = true;
+
+            // TODO(b/111276913): check state (for example, how to handle if it's waiting for remote
+            // id) and send it to the cache of batched commands
+            if (sVerbose) {
+                Log.v(TAG, "destroy(): state=" + getStateAsString(mState) + ", mId=" + mId);
+            }
+            // Finish children first
+            if (mChildren != null) {
+                final int numberChildren = mChildren.size();
+                if (sVerbose) Log.v(TAG, "Destroying " + numberChildren + " children first");
+                for (int i = 0; i < numberChildren; i++) {
+                    final ContentCaptureSession child = mChildren.get(i);
+                    try {
+                        child.destroy();
+                    } catch (Exception e) {
+                        Log.w(TAG, "exception destroying child session #" + i + ": " + e);
+                    }
+                }
+            }
+        }
+
+        onDestroy();
+    }
+
+    abstract void onDestroy();
+
+    /** @hide */
+    @Override
+    public void close() {
+        destroy();
+    }
+
+    /**
+     * Notifies the Content Capture Service that a node has been added to the view structure.
+     *
+     * <p>Typically called "manually" by views that handle their own virtual view hierarchy, or
+     * automatically by the Android System for views that return {@code true} on
+     * {@link View#onProvideContentCaptureStructure(ViewStructure, int)}.
+     *
+     * @param node node that has been added.
+     */
+    public final void notifyViewAppeared(@NonNull ViewStructure node) {
+        Preconditions.checkNotNull(node);
+        if (!isContentCaptureEnabled()) return;
+
+        if (!(node instanceof ViewNode.ViewStructureImpl)) {
+            throw new IllegalArgumentException("Invalid node class: " + node.getClass());
+        }
+
+        internalNotifyViewAppeared((ViewStructureImpl) node);
+    }
+
+    abstract void internalNotifyViewAppeared(@NonNull ViewNode.ViewStructureImpl node);
+
+    /**
+     * Notifies the Content Capture Service that a node has been removed from the view structure.
+     *
+     * <p>Typically called "manually" by views that handle their own virtual view hierarchy, or
+     * automatically by the Android System for standard views.
+     *
+     * @param id id of the node that has been removed.
+     */
+    public final void notifyViewDisappeared(@NonNull AutofillId id) {
+        Preconditions.checkNotNull(id);
+        if (!isContentCaptureEnabled()) return;
+
+        internalNotifyViewDisappeared(id);
+    }
+
+    abstract void internalNotifyViewDisappeared(@NonNull AutofillId id);
+
+    /**
+     * Notifies the Content Capture Service that many nodes has been removed from a virtual view
+     * structure.
+     *
+     * <p>Should only be called by views that handle their own virtual view hierarchy.
+     *
+     * @param hostId id of the non-virtual view hosting the virtual view hierarchy (it can be
+     * obtained by calling {@link ViewStructure#getAutofillId()}).
+     * @param virtualIds ids of the virtual children.
+     *
+     * @throws IllegalArgumentException if the {@code hostId} is an autofill id for a virtual view.
+     * @throws IllegalArgumentException if {@code virtualIds} is empty
+     */
+    public final void notifyViewsDisappeared(@NonNull AutofillId hostId,
+            @NonNull long[] virtualIds) {
+        Preconditions.checkArgument(hostId.isNonVirtual(), "hostId cannot be virtual: %s", hostId);
+        Preconditions.checkArgument(!ArrayUtils.isEmpty(virtualIds), "virtual ids cannot be empty");
+        if (!isContentCaptureEnabled()) return;
+
+        // TODO(b/123036895): use a internalNotifyViewsDisappeared that optimizes how the event is
+        // parcelized
+        for (long id : virtualIds) {
+            internalNotifyViewDisappeared(new AutofillId(hostId, id, mId));
+        }
+    }
+
+    /**
+     * Notifies the Intelligence Service that the value of a text node has been changed.
+     *
+     * @param id of the node.
+     * @param text new text.
+     */
+    public final void notifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text) {
+        Preconditions.checkNotNull(id);
+
+        if (!isContentCaptureEnabled()) return;
+
+        internalNotifyViewTextChanged(id, text);
+    }
+
+    abstract void internalNotifyViewTextChanged(@NonNull AutofillId id,
+            @Nullable CharSequence text);
+
+    /**
+     * Notifies the Intelligence Service that the insets of a view have changed.
+     */
+    public final void notifyViewInsetsChanged(@NonNull Insets viewInsets) {
+        Preconditions.checkNotNull(viewInsets);
+
+        if (!isContentCaptureEnabled()) return;
+
+        internalNotifyViewInsetsChanged(viewInsets);
+    }
+
+    abstract void internalNotifyViewInsetsChanged(@NonNull Insets viewInsets);
+
+    /** @hide */
+    public abstract void internalNotifyViewTreeEvent(boolean started);
+
+    /**
+     * Notifies the Content Capture Service that a session has resumed.
+     */
+    public final void notifySessionResumed() {
+        if (!isContentCaptureEnabled()) return;
+
+        internalNotifySessionResumed();
+    }
+
+    abstract void internalNotifySessionResumed();
+
+    /**
+     * Notifies the Content Capture Service that a session has paused.
+     */
+    public final void notifySessionPaused() {
+        if (!isContentCaptureEnabled()) return;
+
+        internalNotifySessionPaused();
+    }
+
+    abstract void internalNotifySessionPaused();
+
+    /**
+     * Creates a {@link ViewStructure} for a "standard" view.
+     *
+     * <p>This method should be called after a visible view is laid out; the view then must populate
+     * the structure and pass it to {@link #notifyViewAppeared(ViewStructure)}.
+     *
+     * <b>Note: </b>views that manage a virtual structure under this view must populate just the
+     * node representing this view and return right away, then asynchronously report (not
+     * necessarily in the UI thread) when the children nodes appear, disappear or have their text
+     * changed by calling {@link ContentCaptureSession#notifyViewAppeared(ViewStructure)},
+     * {@link ContentCaptureSession#notifyViewDisappeared(AutofillId)}, and
+     * {@link ContentCaptureSession#notifyViewTextChanged(AutofillId, CharSequence)} respectively.
+     * The structure for the a child must be created using
+     * {@link ContentCaptureSession#newVirtualViewStructure(AutofillId, long)}, and the
+     * {@code autofillId} for a child can be obtained either through
+     * {@code childStructure.getAutofillId()} or
+     * {@link ContentCaptureSession#newAutofillId(AutofillId, long)}.
+     *
+     * <p>When the virtual view hierarchy represents a web page, you should also:
+     *
+     * <ul>
+     * <li>Call {@link ContentCaptureManager#getContentCaptureConditions()} to infer content capture
+     * events should be generate for that URL.
+     * <li>Create a new {@link ContentCaptureSession} child for every HTML element that renders a
+     * new URL (like an {@code IFRAME}) and use that session to notify events from that subtree.
+     * </ul>
+     *
+     * <p><b>Note: </b>the following methods of the {@code structure} will be ignored:
+     * <ul>
+     * <li>{@link ViewStructure#setChildCount(int)}
+     * <li>{@link ViewStructure#addChildCount(int)}
+     * <li>{@link ViewStructure#getChildCount()}
+     * <li>{@link ViewStructure#newChild(int)}
+     * <li>{@link ViewStructure#asyncNewChild(int)}
+     * <li>{@link ViewStructure#asyncCommit()}
+     * <li>{@link ViewStructure#setWebDomain(String)}
+     * <li>{@link ViewStructure#newHtmlInfoBuilder(String)}
+     * <li>{@link ViewStructure#setHtmlInfo(android.view.ViewStructure.HtmlInfo)}
+     * <li>{@link ViewStructure#setDataIsSensitive(boolean)}
+     * <li>{@link ViewStructure#setAlpha(float)}
+     * <li>{@link ViewStructure#setElevation(float)}
+     * <li>{@link ViewStructure#setTransformation(android.graphics.Matrix)}
+     * </ul>
+     */
+    @NonNull
+    public final ViewStructure newViewStructure(@NonNull View view) {
+        return new ViewNode.ViewStructureImpl(view);
+    }
+
+    /**
+     * Creates a new {@link AutofillId} for a virtual child, so it can be used to uniquely identify
+     * the children in the session.
+     *
+     * @param hostId id of the non-virtual view hosting the virtual view hierarchy (it can be
+     * obtained by calling {@link ViewStructure#getAutofillId()}).
+     * @param virtualChildId id of the virtual child, relative to the parent.
+     *
+     * @return if for the virtual child
+     *
+     * @throws IllegalArgumentException if the {@code parentId} is a virtual child id.
+     */
+    public @NonNull AutofillId newAutofillId(@NonNull AutofillId hostId, long virtualChildId) {
+        Preconditions.checkNotNull(hostId);
+        Preconditions.checkArgument(hostId.isNonVirtual(), "hostId cannot be virtual: %s", hostId);
+        return new AutofillId(hostId, virtualChildId, mId);
+    }
+
+    /**
+     * Creates a {@link ViewStructure} for a "virtual" view, so it can be passed to
+     * {@link #notifyViewAppeared(ViewStructure)} by the view managing the virtual view hierarchy.
+     *
+     * @param parentId id of the virtual view parent (it can be obtained by calling
+     * {@link ViewStructure#getAutofillId()} on the parent).
+     * @param virtualId id of the virtual child, relative to the parent.
+     *
+     * @return a new {@link ViewStructure} that can be used for Content Capture purposes.
+     */
+    @NonNull
+    public final ViewStructure newVirtualViewStructure(@NonNull AutofillId parentId,
+            long virtualId) {
+        return new ViewNode.ViewStructureImpl(parentId, virtualId, mId);
+    }
+
+    boolean isContentCaptureEnabled() {
+        synchronized (mLock) {
+            return !mDestroyed;
+        }
+    }
+
+    @CallSuper
+    void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
+        pw.print(prefix); pw.print("id: "); pw.println(mId);
+        if (mClientContext != null) {
+            pw.print(prefix); mClientContext.dump(pw); pw.println();
+        }
+        synchronized (mLock) {
+            pw.print(prefix); pw.print("destroyed: "); pw.println(mDestroyed);
+            if (mChildren != null && !mChildren.isEmpty()) {
+                final String prefix2 = prefix + "  ";
+                final int numberChildren = mChildren.size();
+                pw.print(prefix); pw.print("number children: "); pw.println(numberChildren);
+                for (int i = 0; i < numberChildren; i++) {
+                    final ContentCaptureSession child = mChildren.get(i);
+                    pw.print(prefix); pw.print(i); pw.println(": "); child.dump(prefix2, pw);
+                }
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        return Integer.toString(mId);
+    }
+
+    /** @hide */
+    @NonNull
+    protected static String getStateAsString(int state) {
+        return state + " (" + (state == UNKNOWN_STATE ? "UNKNOWN"
+                : DebugUtils.flagsToString(ContentCaptureSession.class, "STATE_", state)) + ")";
+    }
+
+    /** @hide */
+    @NonNull
+    public static String getFlushReasonAsString(@FlushReason int reason) {
+        switch (reason) {
+            case FLUSH_REASON_FULL:
+                return "FULL";
+            case FLUSH_REASON_VIEW_ROOT_ENTERED:
+                return "VIEW_ROOT";
+            case FLUSH_REASON_SESSION_STARTED:
+                return "STARTED";
+            case FLUSH_REASON_SESSION_FINISHED:
+                return "FINISHED";
+            case FLUSH_REASON_IDLE_TIMEOUT:
+                return "IDLE";
+            case FLUSH_REASON_TEXT_CHANGE_TIMEOUT:
+                return "TEXT_CHANGE";
+            case FLUSH_REASON_SESSION_CONNECTED:
+                return "CONNECTED";
+            default:
+                return "UNKOWN-" + reason;
+        }
+    }
+
+    private static int getRandomSessionId() {
+        int id;
+        do {
+            id = ID_GENERATOR.nextInt();
+        } while (id == NO_SESSION_ID);
+        return id;
+    }
+}
diff --git a/android/view/contentcapture/ContentCaptureSessionId.java b/android/view/contentcapture/ContentCaptureSessionId.java
new file mode 100644
index 0000000..413a2f3
--- /dev/null
+++ b/android/view/contentcapture/ContentCaptureSessionId.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.contentcapture;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.io.PrintWriter;
+
+/**
+ * Identifier for a Content Capture session.
+ */
+public final class ContentCaptureSessionId implements Parcelable {
+
+    private final @NonNull int mValue;
+
+    /**
+     * Creates a new instance.
+     *
+     * @param value The internal value.
+     *
+     * @hide
+     */
+    public ContentCaptureSessionId(@NonNull int value) {
+        mValue = value;
+    }
+
+    /**
+     * @hide
+     */
+    public int getValue() {
+        return mValue;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + mValue;
+        return result;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (this == obj) return true;
+        if (obj == null) return false;
+        if (getClass() != obj.getClass()) return false;
+        final ContentCaptureSessionId other = (ContentCaptureSessionId) obj;
+        if (mValue != other.mValue) return false;
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p><b>NOTE: </b>this method is only useful for debugging purposes and is not guaranteed to
+     * be stable, hence it should not be used to identify the session.
+     */
+    @Override
+    public String toString() {
+        return Integer.toString(mValue);
+    }
+
+
+    /** @hide */
+    // TODO(b/111276913): dump to proto as well
+    public void dump(PrintWriter pw) {
+        pw.print(mValue);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeInt(mValue);
+    }
+
+    public static final @NonNull Parcelable.Creator<ContentCaptureSessionId> CREATOR =
+            new Parcelable.Creator<ContentCaptureSessionId>() {
+
+        @Override
+        @NonNull
+        public ContentCaptureSessionId createFromParcel(Parcel parcel) {
+            return new ContentCaptureSessionId(parcel.readInt());
+        }
+
+        @Override
+        @NonNull
+        public ContentCaptureSessionId[] newArray(int size) {
+            return new ContentCaptureSessionId[size];
+        }
+    };
+}
diff --git a/android/view/contentcapture/CustomTestActivity.java b/android/view/contentcapture/CustomTestActivity.java
new file mode 100644
index 0000000..e509837
--- /dev/null
+++ b/android/view/contentcapture/CustomTestActivity.java
@@ -0,0 +1,88 @@
+/*
+ * 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.view.contentcapture;
+
+import android.app.Activity;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.perftests.contentcapture.R;
+
+/**
+ * A simple activity used for testing, e.g. performance of activity switching, or as a base
+ * container of testing view.
+ */
+public class CustomTestActivity extends Activity {
+    public static final String INTENT_EXTRA_LAYOUT_ID = "layout_id";
+    public static final String INTENT_EXTRA_CUSTOM_VIEWS = "custom_view_number";
+    public static final int MAX_VIEWS = 500;
+    private static final int CUSTOM_CONTAINER_LAYOUT_ID = R.layout.test_container_activity;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        if (getIntent().hasExtra(INTENT_EXTRA_LAYOUT_ID)) {
+            final int layoutId = getIntent().getIntExtra(INTENT_EXTRA_LAYOUT_ID,
+                    /* defaultValue= */0);
+            setContentView(layoutId);
+            if (layoutId == CUSTOM_CONTAINER_LAYOUT_ID) {
+                createCustomViews(findViewById(R.id.root_view),
+                        getIntent().getIntExtra(INTENT_EXTRA_CUSTOM_VIEWS, MAX_VIEWS));
+            }
+        }
+    }
+
+    private void createCustomViews(LinearLayout root, int number) {
+        LinearLayout horizontalLayout = null;
+        for (int i = 0; i < number; i++) {
+            final int j = i % 8;
+            if (horizontalLayout != null && j == 0) {
+                root.addView(horizontalLayout);
+                horizontalLayout = null;
+            }
+            if (horizontalLayout == null) {
+                horizontalLayout = createHorizontalLayout();
+            }
+            horizontalLayout.addView(createItem(null, i));
+        }
+        if (horizontalLayout != null) {
+            root.addView(horizontalLayout);
+        }
+    }
+
+    private LinearLayout createHorizontalLayout() {
+        final LinearLayout layout = new LinearLayout(getApplicationContext());
+        layout.setOrientation(LinearLayout.HORIZONTAL);
+        return layout;
+    }
+
+    private LinearLayout createItem(Drawable drawable, int index) {
+        final LinearLayout group = new LinearLayout(getApplicationContext());
+        group.setOrientation(LinearLayout.VERTICAL);
+        group.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
+                LinearLayout.LayoutParams.WRAP_CONTENT, /* weight= */ 1.0f));
+
+        final TextView text = new TextView(this);
+        text.setText("i = " + index);
+        group.addView(text);
+
+        return group;
+    }
+}
diff --git a/android/view/contentcapture/DataRemovalRequest.java b/android/view/contentcapture/DataRemovalRequest.java
new file mode 100644
index 0000000..f403dac
--- /dev/null
+++ b/android/view/contentcapture/DataRemovalRequest.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.contentcapture;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.ActivityThread;
+import android.content.LocusId;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.IntArray;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class used by apps to request the content capture service to remove data associated with
+ * {@link LocusId LocusIds}.
+ *
+ * <p>An app which has tagged data with a LocusId can therefore delete them later. This is intended
+ * to let apps propagate deletions of user data into the operating system.
+ */
+public final class DataRemovalRequest implements Parcelable {
+
+    /**
+     * When set, service should use the {@link LocusId#getId()} as prefix for the data to be
+     * removed.
+     */
+    public static final int FLAG_IS_PREFIX = 0x1;
+
+    /** @hide */
+    @IntDef(prefix = { "FLAG" }, flag = true, value = {
+            FLAG_IS_PREFIX
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface Flags {}
+
+    private final String mPackageName;
+
+    private final boolean mForEverything;
+    private ArrayList<LocusIdRequest> mLocusIdRequests;
+
+    private DataRemovalRequest(@NonNull Builder builder) {
+        mPackageName = ActivityThread.currentActivityThread().getApplication().getPackageName();
+        mForEverything = builder.mForEverything;
+        if (builder.mLocusIds != null) {
+            final int size = builder.mLocusIds.size();
+            mLocusIdRequests = new ArrayList<>(size);
+            for (int i = 0; i < size; i++) {
+                mLocusIdRequests.add(new LocusIdRequest(builder.mLocusIds.get(i),
+                        builder.mFlags.get(i)));
+            }
+        }
+    }
+
+    private DataRemovalRequest(@NonNull Parcel parcel) {
+        mPackageName = parcel.readString();
+        mForEverything = parcel.readBoolean();
+        if (!mForEverything) {
+            final int size = parcel.readInt();
+            mLocusIdRequests = new ArrayList<>(size);
+            for (int i = 0; i < size; i++) {
+                mLocusIdRequests.add(new LocusIdRequest((LocusId) parcel.readValue(null),
+                        parcel.readInt()));
+            }
+        }
+    }
+
+    /**
+     * Gets the name of the app that's making the request.
+     */
+    @NonNull
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * Checks if app is requesting to remove content capture data associated with its package.
+     */
+    public boolean isForEverything() {
+        return mForEverything;
+    }
+
+    /**
+     * Gets the list of {@code LousId}s the apps is requesting to remove.
+     */
+    @NonNull
+    public List<LocusIdRequest> getLocusIdRequests() {
+        return mLocusIdRequests;
+    }
+
+    /**
+     * Builder for {@link DataRemovalRequest} objects.
+     */
+    public static final class Builder {
+
+        private boolean mForEverything;
+        private ArrayList<LocusId> mLocusIds;
+        private IntArray mFlags;
+
+        private boolean mDestroyed;
+
+        /**
+         * Requests servive to remove all content capture data associated with the app's package.
+         *
+         * @return this builder
+         */
+        @NonNull
+        public Builder forEverything() {
+            throwIfDestroyed();
+            Preconditions.checkState(mLocusIds == null, "Already added LocusIds");
+
+            mForEverything = true;
+            return this;
+        }
+
+        /**
+         * Request service to remove data associated with a given {@link LocusId}.
+         *
+         * @param locusId the {@link LocusId} being requested to be removed.
+         * @param flags either {@link DataRemovalRequest#FLAG_IS_PREFIX} or {@code 0}
+         *
+         * @return this builder
+         */
+        @NonNull
+        public Builder addLocusId(@NonNull LocusId locusId, @Flags int flags) {
+            throwIfDestroyed();
+            Preconditions.checkState(!mForEverything, "Already is for everything");
+            Preconditions.checkNotNull(locusId);
+            // felipeal: check flags
+
+            if (mLocusIds == null) {
+                mLocusIds = new ArrayList<>();
+                mFlags = new IntArray();
+            }
+
+            mLocusIds.add(locusId);
+            mFlags.add(flags);
+            return this;
+        }
+
+        /**
+         * Builds the {@link DataRemovalRequest}.
+         */
+        @NonNull
+        public DataRemovalRequest build() {
+            throwIfDestroyed();
+
+            Preconditions.checkState(mForEverything || mLocusIds != null,
+                    "must call either #forEverything() or add one #addLocusId()");
+
+            mDestroyed = true;
+            return new DataRemovalRequest(this);
+        }
+
+        private void throwIfDestroyed() {
+            Preconditions.checkState(!mDestroyed, "Already destroyed!");
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeString(mPackageName);
+        parcel.writeBoolean(mForEverything);
+        if (!mForEverything) {
+            final int size = mLocusIdRequests.size();
+            parcel.writeInt(size);
+            for (int i = 0; i < size; i++) {
+                final LocusIdRequest request = mLocusIdRequests.get(i);
+                parcel.writeValue(request.getLocusId());
+                parcel.writeInt(request.getFlags());
+            }
+        }
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<DataRemovalRequest> CREATOR =
+            new Parcelable.Creator<DataRemovalRequest>() {
+
+        @Override
+        @NonNull
+        public DataRemovalRequest createFromParcel(Parcel parcel) {
+            return new DataRemovalRequest(parcel);
+        }
+
+        @Override
+        @NonNull
+        public DataRemovalRequest[] newArray(int size) {
+            return new DataRemovalRequest[size];
+        }
+    };
+
+    /**
+     * Representation of a request to remove data associated with a {@link LocusId}.
+     */
+    public final class LocusIdRequest {
+        private final @NonNull LocusId mLocusId;
+        private final @Flags int mFlags;
+
+        private LocusIdRequest(@NonNull LocusId locusId, @Flags int flags) {
+            this.mLocusId = locusId;
+            this.mFlags = flags;
+        }
+
+        /**
+         * Gets the {@code LocusId} per se.
+         */
+        @NonNull
+        public LocusId getLocusId() {
+            return mLocusId;
+        }
+
+        /**
+         * Gets the flags associates with request.
+         *
+         * @return either {@link DataRemovalRequest#FLAG_IS_PREFIX} or {@code 0}.
+         */
+        @NonNull
+        public @Flags int getFlags() {
+            return mFlags;
+        }
+    }
+}
diff --git a/android/view/contentcapture/DataShareRequest.java b/android/view/contentcapture/DataShareRequest.java
new file mode 100644
index 0000000..78c0ef9
--- /dev/null
+++ b/android/view/contentcapture/DataShareRequest.java
@@ -0,0 +1,207 @@
+/*
+ * 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.view.contentcapture;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityThread;
+import android.content.LocusId;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Preconditions;
+
+/** Container class representing a request to share data with Content Capture service. */
+@DataClass(
+        genConstructor = false,
+        genEqualsHashCode = true,
+        genHiddenConstDefs = true,
+        genParcelable = true,
+        genToString = true
+)
+public final class DataShareRequest implements Parcelable {
+
+    /** Name of the package making the request. */
+    @NonNull private final String mPackageName;
+
+    /** Locus id helping to identify what data is being shared. */
+    @Nullable private final LocusId mLocusId;
+
+    /** MIME type of the data being shared. */
+    @NonNull private final String mMimeType;
+
+    /** Constructs a request to share data with the Content Capture Service. */
+    public DataShareRequest(@Nullable LocusId locusId, @NonNull String mimeType) {
+        Preconditions.checkNotNull(mimeType);
+
+        mPackageName = ActivityThread.currentActivityThread().getApplication().getPackageName();
+        mLocusId = locusId;
+        mMimeType = mimeType;
+    }
+
+
+
+    // Code below generated by codegen v1.0.14.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/contentcapture/DataShareRequest.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Name of the package making the request.
+     */
+    @DataClass.Generated.Member
+    public @NonNull String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * Locus id helping to identify what data is being shared.
+     */
+    @DataClass.Generated.Member
+    public @Nullable LocusId getLocusId() {
+        return mLocusId;
+    }
+
+    /**
+     * MIME type of the data being shared.
+     */
+    @DataClass.Generated.Member
+    public @NonNull String getMimeType() {
+        return mMimeType;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "DataShareRequest { " +
+                "packageName = " + mPackageName + ", " +
+                "locusId = " + mLocusId + ", " +
+                "mimeType = " + mMimeType +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(DataShareRequest other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        DataShareRequest that = (DataShareRequest) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && java.util.Objects.equals(mPackageName, that.mPackageName)
+                && java.util.Objects.equals(mLocusId, that.mLocusId)
+                && java.util.Objects.equals(mMimeType, that.mMimeType);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + java.util.Objects.hashCode(mPackageName);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mLocusId);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mMimeType);
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mLocusId != null) flg |= 0x2;
+        dest.writeByte(flg);
+        dest.writeString(mPackageName);
+        if (mLocusId != null) dest.writeTypedObject(mLocusId, flags);
+        dest.writeString(mMimeType);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ DataShareRequest(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        String packageName = in.readString();
+        LocusId locusId = (flg & 0x2) == 0 ? null : (LocusId) in.readTypedObject(LocusId.CREATOR);
+        String mimeType = in.readString();
+
+        this.mPackageName = packageName;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mPackageName);
+        this.mLocusId = locusId;
+        this.mMimeType = mimeType;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mMimeType);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<DataShareRequest> CREATOR
+            = new Parcelable.Creator<DataShareRequest>() {
+        @Override
+        public DataShareRequest[] newArray(int size) {
+            return new DataShareRequest[size];
+        }
+
+        @Override
+        public DataShareRequest createFromParcel(@NonNull Parcel in) {
+            return new DataShareRequest(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1579870254459L,
+            codegenVersion = "1.0.14",
+            sourceFile = "frameworks/base/core/java/android/view/contentcapture/DataShareRequest.java",
+            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable android.content.LocusId mLocusId\nprivate final @android.annotation.NonNull java.lang.String mMimeType\nclass DataShareRequest extends java.lang.Object implements [android.os.Parcelable]\[email protected](genConstructor=false, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/android/view/contentcapture/DataShareWriteAdapter.java b/android/view/contentcapture/DataShareWriteAdapter.java
new file mode 100644
index 0000000..3b5b756
--- /dev/null
+++ b/android/view/contentcapture/DataShareWriteAdapter.java
@@ -0,0 +1,49 @@
+/*
+ * 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.view.contentcapture;
+
+import android.annotation.NonNull;
+import android.os.ParcelFileDescriptor;
+import android.view.contentcapture.ContentCaptureManager.DataShareError;
+
+/** Adapter class used by apps to share data with the Content Capture service. */
+public interface DataShareWriteAdapter {
+
+    /**
+     * Method invoked when the data share session has been started and the app needs to start
+     * writing into the file used for sharing.
+     *
+     * <p>App needs to handle explicitly cases when the file descriptor is closed and handle
+     * gracefully if IOExceptions happen.
+     *
+     * @param destination file descriptor used to write data into.
+     */
+    void onWrite(@NonNull ParcelFileDescriptor destination);
+
+    /** Data share sessions has been rejected by the Content Capture service. */
+    void onRejected();
+
+    /**
+     * Method invoked when an error occurred, for example sessions has not been started or
+     * terminated unsuccessfully.
+     *
+     * @param errorCode the error code corresponding to an ERROR_* value.
+     */
+    default void onError(@DataShareError int errorCode) {
+        /* do nothing - stub */
+    }
+}
diff --git a/android/view/contentcapture/LoginTest.java b/android/view/contentcapture/LoginTest.java
new file mode 100644
index 0000000..7257509
--- /dev/null
+++ b/android/view/contentcapture/LoginTest.java
@@ -0,0 +1,158 @@
+/*
+ * 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.view.contentcapture;
+
+import static com.android.compatibility.common.util.ActivitiesWatcher.ActivityLifecycle.CREATED;
+import static com.android.compatibility.common.util.ActivitiesWatcher.ActivityLifecycle.DESTROYED;
+
+import android.perftests.utils.BenchmarkState;
+import android.view.View;
+
+import androidx.test.filters.LargeTest;
+
+import com.android.compatibility.common.util.ActivitiesWatcher.ActivityWatcher;
+import com.android.perftests.contentcapture.R;
+
+import org.junit.Test;
+
+@LargeTest
+public class LoginTest extends AbstractContentCapturePerfTestCase {
+
+    @Test
+    public void testLaunchActivity() throws Throwable {
+        enableService();
+
+        testActivityLaunchTime(R.layout.test_login_activity, 0);
+    }
+
+    @Test
+    public void testLaunchActivity_contain100Views() throws Throwable {
+        enableService();
+
+        testActivityLaunchTime(R.layout.test_container_activity, 100);
+    }
+
+    @Test
+    public void testLaunchActivity_contain300Views() throws Throwable {
+        enableService();
+
+        testActivityLaunchTime(R.layout.test_container_activity, 300);
+    }
+
+    @Test
+    public void testLaunchActivity_contain500Views() throws Throwable {
+        enableService();
+
+        testActivityLaunchTime(R.layout.test_container_activity, 500);
+    }
+
+    @Test
+    public void testLaunchActivity_noService() throws Throwable {
+        testActivityLaunchTime(R.layout.test_login_activity, 0);
+    }
+
+    @Test
+    public void testLaunchActivity_noService_contain100Views() throws Throwable {
+        testActivityLaunchTime(R.layout.test_container_activity, 100);
+    }
+
+    @Test
+    public void testLaunchActivity_noService_contain300Views() throws Throwable {
+        testActivityLaunchTime(R.layout.test_container_activity, 300);
+    }
+
+    @Test
+    public void testLaunchActivity_noService_contain500Views() throws Throwable {
+        testActivityLaunchTime(R.layout.test_container_activity, 500);
+    }
+
+    private void testActivityLaunchTime(int layoutId, int numViews) throws Throwable {
+        final ActivityWatcher watcher = startWatcher();
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            launchActivity(layoutId, numViews);
+
+            // Ignore the time to finish the activity
+            state.pauseTiming();
+            watcher.waitFor(CREATED);
+            finishActivity();
+            watcher.waitFor(DESTROYED);
+            state.resumeTiming();
+        }
+    }
+
+    @Test
+    public void testOnVisibilityAggregated_visibleChanged() throws Throwable {
+        enableService();
+        final CustomTestActivity activity = launchActivity();
+        final View root = activity.getWindow().getDecorView();
+        final View username = root.findViewById(R.id.username);
+
+        testOnVisibilityAggregated(username);
+    }
+
+    @Test
+    public void testOnVisibilityAggregated_visibleChanged_noService() throws Throwable {
+        final CustomTestActivity activity = launchActivity();
+        final View root = activity.getWindow().getDecorView();
+        final View username = root.findViewById(R.id.username);
+
+        testOnVisibilityAggregated(username);
+    }
+
+    @Test
+    public void testOnVisibilityAggregated_visibleChanged_noOptions() throws Throwable {
+        enableService();
+        clearOptions();
+        final CustomTestActivity activity = launchActivity();
+        final View root = activity.getWindow().getDecorView();
+        final View username = root.findViewById(R.id.username);
+
+        testOnVisibilityAggregated(username);
+    }
+
+    @Test
+    public void testOnVisibilityAggregated_visibleChanged_notImportant() throws Throwable {
+        enableService();
+        final CustomTestActivity activity = launchActivity();
+        final View root = activity.getWindow().getDecorView();
+        final View username = root.findViewById(R.id.username);
+        username.setImportantForContentCapture(View.IMPORTANT_FOR_CONTENT_CAPTURE_NO);
+
+        testOnVisibilityAggregated(username);
+    }
+
+    private void testOnVisibilityAggregated(View view) throws Throwable {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        while (state.keepRunning()) {
+            // Only count the time of onVisibilityAggregated()
+            state.pauseTiming();
+            mActivityRule.runOnUiThread(() -> {
+                state.resumeTiming();
+                view.onVisibilityAggregated(false);
+                state.pauseTiming();
+            });
+            mActivityRule.runOnUiThread(() -> {
+                state.resumeTiming();
+                view.onVisibilityAggregated(true);
+                state.pauseTiming();
+            });
+            state.resumeTiming();
+        }
+    }
+}
diff --git a/android/view/contentcapture/MainContentCaptureSession.java b/android/view/contentcapture/MainContentCaptureSession.java
new file mode 100644
index 0000000..4cf5532
--- /dev/null
+++ b/android/view/contentcapture/MainContentCaptureSession.java
@@ -0,0 +1,856 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.contentcapture;
+
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_CONTEXT_UPDATED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_FINISHED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_PAUSED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_RESUMED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_INSETS_CHANGED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TREE_APPEARED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TREE_APPEARING;
+import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString;
+import static android.view.contentcapture.ContentCaptureHelper.sDebug;
+import static android.view.contentcapture.ContentCaptureHelper.sVerbose;
+import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_FALSE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UiThread;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.graphics.Insets;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.RemoteException;
+import android.text.Selection;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Log;
+import android.util.TimeUtils;
+import android.view.autofill.AutofillId;
+import android.view.contentcapture.ViewNode.ViewStructureImpl;
+import android.view.inputmethod.BaseInputConnection;
+
+import com.android.internal.os.IResultReceiver;
+
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Main session associated with a context.
+ *
+ * <p>This session is created when the activity starts and finished when it stops; clients can use
+ * it to create children activities.
+ *
+ * @hide
+ */
+public final class MainContentCaptureSession extends ContentCaptureSession {
+
+    private static final String TAG = MainContentCaptureSession.class.getSimpleName();
+
+    // For readability purposes...
+    private static final boolean FORCE_FLUSH = true;
+
+    /**
+     * Handler message used to flush the buffer.
+     */
+    private static final int MSG_FLUSH = 1;
+
+    /**
+     * Name of the {@link IResultReceiver} extra used to pass the binder interface to the service.
+     * @hide
+     */
+    public static final String EXTRA_BINDER = "binder";
+
+    /**
+     * Name of the {@link IResultReceiver} extra used to pass the content capture enabled state.
+     * @hide
+     */
+    public static final String EXTRA_ENABLED_STATE = "enabled";
+
+    @NonNull
+    private final AtomicBoolean mDisabled = new AtomicBoolean(false);
+
+    @NonNull
+    private final Context mContext;
+
+    @NonNull
+    private final ContentCaptureManager mManager;
+
+    @NonNull
+    private final Handler mHandler;
+
+    /**
+     * Interface to the system_server binder object - it's only used to start the session (and
+     * notify when the session is finished).
+     */
+    @NonNull
+    private final IContentCaptureManager mSystemServerInterface;
+
+    /**
+     * Direct interface to the service binder object - it's used to send the events, including the
+     * last ones (when the session is finished)
+     */
+    @NonNull
+    private IContentCaptureDirectManager mDirectServiceInterface;
+    @Nullable
+    private DeathRecipient mDirectServiceVulture;
+
+    private int mState = UNKNOWN_STATE;
+
+    @Nullable
+    private IBinder mApplicationToken;
+    @Nullable
+    private IBinder mShareableActivityToken;
+
+    @Nullable
+    private ComponentName mComponentName;
+
+    /**
+     * List of events held to be sent as a batch.
+     */
+    @Nullable
+    private ArrayList<ContentCaptureEvent> mEvents;
+
+    // Used just for debugging purposes (on dump)
+    private long mNextFlush;
+
+    /**
+     * Whether the next buffer flush is queued by a text changed event.
+     */
+    private boolean mNextFlushForTextChanged = false;
+
+    @Nullable
+    private final LocalLog mFlushHistory;
+
+    /**
+     * Binder object used to update the session state.
+     */
+    @NonNull
+    private final SessionStateReceiver mSessionStateReceiver;
+
+    private static class SessionStateReceiver extends IResultReceiver.Stub {
+        private final WeakReference<MainContentCaptureSession> mMainSession;
+
+        SessionStateReceiver(MainContentCaptureSession session) {
+            mMainSession = new WeakReference<>(session);
+        }
+
+        @Override
+        public void send(int resultCode, Bundle resultData) {
+            final MainContentCaptureSession mainSession = mMainSession.get();
+            if (mainSession == null) {
+                Log.w(TAG, "received result after mina session released");
+                return;
+            }
+            final IBinder binder;
+            if (resultData != null) {
+                // Change in content capture enabled.
+                final boolean hasEnabled = resultData.getBoolean(EXTRA_ENABLED_STATE);
+                if (hasEnabled) {
+                    final boolean disabled = (resultCode == RESULT_CODE_FALSE);
+                    mainSession.mDisabled.set(disabled);
+                    return;
+                }
+                binder = resultData.getBinder(EXTRA_BINDER);
+                if (binder == null) {
+                    Log.wtf(TAG, "No " + EXTRA_BINDER + " extra result");
+                    mainSession.mHandler.post(() -> mainSession.resetSession(
+                            STATE_DISABLED | STATE_INTERNAL_ERROR));
+                    return;
+                }
+            } else {
+                binder = null;
+            }
+            mainSession.mHandler.post(() -> mainSession.onSessionStarted(resultCode, binder));
+        }
+    }
+
+    protected MainContentCaptureSession(@NonNull Context context,
+            @NonNull ContentCaptureManager manager, @NonNull Handler handler,
+            @NonNull IContentCaptureManager systemServerInterface) {
+        mContext = context;
+        mManager = manager;
+        mHandler = handler;
+        mSystemServerInterface = systemServerInterface;
+
+        final int logHistorySize = mManager.mOptions.logHistorySize;
+        mFlushHistory = logHistorySize > 0 ? new LocalLog(logHistorySize) : null;
+
+        mSessionStateReceiver = new SessionStateReceiver(this);
+    }
+
+    @Override
+    MainContentCaptureSession getMainCaptureSession() {
+        return this;
+    }
+
+    @Override
+    ContentCaptureSession newChild(@NonNull ContentCaptureContext clientContext) {
+        final ContentCaptureSession child = new ChildContentCaptureSession(this, clientContext);
+        notifyChildSessionStarted(mId, child.mId, clientContext);
+        return child;
+    }
+
+    /**
+     * Starts this session.
+     */
+    @UiThread
+    void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken,
+            @NonNull ComponentName component, int flags) {
+        if (!isContentCaptureEnabled()) return;
+
+        if (sVerbose) {
+            Log.v(TAG, "start(): token=" + token + ", comp="
+                    + ComponentName.flattenToShortString(component));
+        }
+
+        if (hasStarted()) {
+            // TODO(b/122959591): make sure this is expected (and when), or use Log.w
+            if (sDebug) {
+                Log.d(TAG, "ignoring handleStartSession(" + token + "/"
+                        + ComponentName.flattenToShortString(component) + " while on state "
+                        + getStateAsString(mState));
+            }
+            return;
+        }
+        mState = STATE_WAITING_FOR_SERVER;
+        mApplicationToken = token;
+        mShareableActivityToken = shareableActivityToken;
+        mComponentName = component;
+
+        if (sVerbose) {
+            Log.v(TAG, "handleStartSession(): token=" + token + ", act="
+                    + getDebugState() + ", id=" + mId);
+        }
+
+        try {
+            mSystemServerInterface.startSession(mApplicationToken, mShareableActivityToken,
+                    component, mId, flags, mSessionStateReceiver);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Error starting session for " + component.flattenToShortString() + ": " + e);
+        }
+    }
+
+    @Override
+    void onDestroy() {
+        mHandler.removeMessages(MSG_FLUSH);
+        mHandler.post(() -> {
+            try {
+                flush(FLUSH_REASON_SESSION_FINISHED);
+            } finally {
+                destroySession();
+            }
+        });
+    }
+
+    /**
+     * Callback from {@code system_server} after call to
+     * {@link IContentCaptureManager#startSession(IBinder, ComponentName, String, int,
+     * IResultReceiver)}.
+     *
+     * @param resultCode session state
+     * @param binder handle to {@code IContentCaptureDirectManager}
+     */
+    @UiThread
+    private void onSessionStarted(int resultCode, @Nullable IBinder binder) {
+        if (binder != null) {
+            mDirectServiceInterface = IContentCaptureDirectManager.Stub.asInterface(binder);
+            mDirectServiceVulture = () -> {
+                Log.w(TAG, "Keeping session " + mId + " when service died");
+                mState = STATE_SERVICE_DIED;
+                mDisabled.set(true);
+            };
+            try {
+                binder.linkToDeath(mDirectServiceVulture, 0);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to link to death on " + binder + ": " + e);
+            }
+        }
+
+        if ((resultCode & STATE_DISABLED) != 0) {
+            resetSession(resultCode);
+        } else {
+            mState = resultCode;
+            mDisabled.set(false);
+            // Flush any pending data immediately as buffering forced until now.
+            flushIfNeeded(FLUSH_REASON_SESSION_CONNECTED);
+        }
+        if (sVerbose) {
+            Log.v(TAG, "handleSessionStarted() result: id=" + mId + " resultCode=" + resultCode
+                    + ", state=" + getStateAsString(mState) + ", disabled=" + mDisabled.get()
+                    + ", binder=" + binder + ", events=" + (mEvents == null ? 0 : mEvents.size()));
+        }
+    }
+
+    @UiThread
+    private void sendEvent(@NonNull ContentCaptureEvent event) {
+        sendEvent(event, /* forceFlush= */ false);
+    }
+
+    @UiThread
+    private void sendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
+        final int eventType = event.getType();
+        if (sVerbose) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event);
+        if (!hasStarted() && eventType != ContentCaptureEvent.TYPE_SESSION_STARTED
+                && eventType != ContentCaptureEvent.TYPE_CONTEXT_UPDATED) {
+            // TODO(b/120494182): comment when this could happen (dialogs?)
+            if (sVerbose) {
+                Log.v(TAG, "handleSendEvent(" + getDebugState() + ", "
+                        + ContentCaptureEvent.getTypeAsString(eventType)
+                        + "): dropping because session not started yet");
+            }
+            return;
+        }
+        if (mDisabled.get()) {
+            // This happens when the event was queued in the handler before the sesison was ready,
+            // then handleSessionStarted() returned and set it as disabled - we need to drop it,
+            // otherwise it will keep triggering handleScheduleFlush()
+            if (sVerbose) Log.v(TAG, "handleSendEvent(): ignoring when disabled");
+            return;
+        }
+        final int maxBufferSize = mManager.mOptions.maxBufferSize;
+        if (mEvents == null) {
+            if (sVerbose) {
+                Log.v(TAG, "handleSendEvent(): creating buffer for " + maxBufferSize + " events");
+            }
+            mEvents = new ArrayList<>(maxBufferSize);
+        }
+
+        // Some type of events can be merged together
+        boolean addEvent = true;
+
+        if (eventType == TYPE_VIEW_TEXT_CHANGED) {
+            // We determine whether to add or merge the current event by following criteria:
+            // 1. Don't have composing span: always add.
+            // 2. Have composing span:
+            //    2.1 either last or current text is empty: add.
+            //    2.2 last event doesn't have composing span: add.
+            // Otherwise, merge.
+            final CharSequence text = event.getText();
+            final boolean hasComposingSpan = event.hasComposingSpan();
+            if (hasComposingSpan) {
+                ContentCaptureEvent lastEvent = null;
+                for (int index = mEvents.size() - 1; index >= 0; index--) {
+                    final ContentCaptureEvent tmpEvent = mEvents.get(index);
+                    if (event.getId().equals(tmpEvent.getId())) {
+                        lastEvent = tmpEvent;
+                        break;
+                    }
+                }
+                if (lastEvent != null && lastEvent.hasComposingSpan()) {
+                    final CharSequence lastText = lastEvent.getText();
+                    final boolean bothNonEmpty = !TextUtils.isEmpty(lastText)
+                            && !TextUtils.isEmpty(text);
+                    boolean equalContent =
+                            TextUtils.equals(lastText, text)
+                            && lastEvent.hasSameComposingSpan(event)
+                            && lastEvent.hasSameSelectionSpan(event);
+                    if (equalContent) {
+                        addEvent = false;
+                    } else if (bothNonEmpty) {
+                        lastEvent.mergeEvent(event);
+                        addEvent = false;
+                    }
+                    if (!addEvent && sVerbose) {
+                        Log.v(TAG, "Buffering VIEW_TEXT_CHANGED event, updated text="
+                                + getSanitizedString(text));
+                    }
+                }
+            }
+        }
+
+        if (!mEvents.isEmpty() && eventType == TYPE_VIEW_DISAPPEARED) {
+            final ContentCaptureEvent lastEvent = mEvents.get(mEvents.size() - 1);
+            if (lastEvent.getType() == TYPE_VIEW_DISAPPEARED
+                    && event.getSessionId() == lastEvent.getSessionId()) {
+                if (sVerbose) {
+                    Log.v(TAG, "Buffering TYPE_VIEW_DISAPPEARED events for session "
+                            + lastEvent.getSessionId());
+                }
+                lastEvent.mergeEvent(event);
+                addEvent = false;
+            }
+        }
+
+        if (addEvent) {
+            mEvents.add(event);
+        }
+
+        // TODO: we need to change when the flush happens so that we don't flush while the
+        //  composing span hasn't changed. But we might need to keep flushing the events for the
+        //  non-editable views and views that don't have the composing state; otherwise some other
+        //  Content Capture features may be delayed.
+
+        final int numberEvents = mEvents.size();
+
+        final boolean bufferEvent = numberEvents < maxBufferSize;
+
+        if (bufferEvent && !forceFlush) {
+            final int flushReason;
+            if (eventType == TYPE_VIEW_TEXT_CHANGED) {
+                mNextFlushForTextChanged = true;
+                flushReason = FLUSH_REASON_TEXT_CHANGE_TIMEOUT;
+            } else {
+                if (mNextFlushForTextChanged) {
+                    if (sVerbose) {
+                        Log.i(TAG, "Not scheduling flush because next flush is for text changed");
+                    }
+                    return;
+                }
+
+                flushReason = FLUSH_REASON_IDLE_TIMEOUT;
+            }
+            scheduleFlush(flushReason, /* checkExisting= */ true);
+            return;
+        }
+
+        if (mState != STATE_ACTIVE && numberEvents >= maxBufferSize) {
+            // Callback from startSession hasn't been called yet - typically happens on system
+            // apps that are started before the system service
+            // TODO(b/122959591): try to ignore session while system is not ready / boot
+            // not complete instead. Similarly, the manager service should return right away
+            // when the user does not have a service set
+            if (sDebug) {
+                Log.d(TAG, "Closing session for " + getDebugState()
+                        + " after " + numberEvents + " delayed events");
+            }
+            resetSession(STATE_DISABLED | STATE_NO_RESPONSE);
+            // TODO(b/111276913): denylist activity / use special flag to indicate that
+            // when it's launched again
+            return;
+        }
+        final int flushReason;
+        switch (eventType) {
+            case ContentCaptureEvent.TYPE_SESSION_STARTED:
+                flushReason = FLUSH_REASON_SESSION_STARTED;
+                break;
+            case ContentCaptureEvent.TYPE_SESSION_FINISHED:
+                flushReason = FLUSH_REASON_SESSION_FINISHED;
+                break;
+            default:
+                flushReason = FLUSH_REASON_FULL;
+        }
+
+        flush(flushReason);
+    }
+
+    @UiThread
+    private boolean hasStarted() {
+        return mState != UNKNOWN_STATE;
+    }
+
+    @UiThread
+    private void scheduleFlush(@FlushReason int reason, boolean checkExisting) {
+        if (sVerbose) {
+            Log.v(TAG, "handleScheduleFlush(" + getDebugState(reason)
+                    + ", checkExisting=" + checkExisting);
+        }
+        if (!hasStarted()) {
+            if (sVerbose) Log.v(TAG, "handleScheduleFlush(): session not started yet");
+            return;
+        }
+
+        if (mDisabled.get()) {
+            // Should not be called on this state, as handleSendEvent checks.
+            // But we rather add one if check and log than re-schedule and keep the session alive...
+            Log.e(TAG, "handleScheduleFlush(" + getDebugState(reason) + "): should not be called "
+                    + "when disabled. events=" + (mEvents == null ? null : mEvents.size()));
+            return;
+        }
+        if (checkExisting && mHandler.hasMessages(MSG_FLUSH)) {
+            // "Renew" the flush message by removing the previous one
+            mHandler.removeMessages(MSG_FLUSH);
+        }
+
+        final int flushFrequencyMs;
+        if (reason == FLUSH_REASON_TEXT_CHANGE_TIMEOUT) {
+            flushFrequencyMs = mManager.mOptions.textChangeFlushingFrequencyMs;
+        } else {
+            if (reason != FLUSH_REASON_IDLE_TIMEOUT) {
+                if (sDebug) {
+                    Log.d(TAG, "handleScheduleFlush(" + getDebugState(reason) + "): not a timeout "
+                            + "reason because mDirectServiceInterface is not ready yet");
+                }
+            }
+            flushFrequencyMs = mManager.mOptions.idleFlushingFrequencyMs;
+        }
+
+        mNextFlush = System.currentTimeMillis() + flushFrequencyMs;
+        if (sVerbose) {
+            Log.v(TAG, "handleScheduleFlush(): scheduled to flush in "
+                    + flushFrequencyMs + "ms: " + TimeUtils.logTimeOfDay(mNextFlush));
+        }
+        // Post using a Runnable directly to trim a few μs from PooledLambda.obtainMessage()
+        mHandler.postDelayed(() -> flushIfNeeded(reason), MSG_FLUSH, flushFrequencyMs);
+    }
+
+    @UiThread
+    private void flushIfNeeded(@FlushReason int reason) {
+        if (mEvents == null || mEvents.isEmpty()) {
+            if (sVerbose) Log.v(TAG, "Nothing to flush");
+            return;
+        }
+        flush(reason);
+    }
+
+    @Override
+    @UiThread
+    void flush(@FlushReason int reason) {
+        if (mEvents == null) return;
+
+        if (mDisabled.get()) {
+            Log.e(TAG, "handleForceFlush(" + getDebugState(reason) + "): should not be when "
+                    + "disabled");
+            return;
+        }
+
+        if (mDirectServiceInterface == null) {
+            if (sVerbose) {
+                Log.v(TAG, "handleForceFlush(" + getDebugState(reason) + "): hold your horses, "
+                        + "client not ready: " + mEvents);
+            }
+            if (!mHandler.hasMessages(MSG_FLUSH)) {
+                scheduleFlush(reason, /* checkExisting= */ false);
+            }
+            return;
+        }
+
+        mNextFlushForTextChanged = false;
+
+        final int numberEvents = mEvents.size();
+        final String reasonString = getFlushReasonAsString(reason);
+        if (sDebug) {
+            Log.d(TAG, "Flushing " + numberEvents + " event(s) for " + getDebugState(reason));
+        }
+        if (mFlushHistory != null) {
+            // Logs reason, size, max size, idle timeout
+            final String logRecord = "r=" + reasonString + " s=" + numberEvents
+                    + " m=" + mManager.mOptions.maxBufferSize
+                    + " i=" + mManager.mOptions.idleFlushingFrequencyMs;
+            mFlushHistory.log(logRecord);
+        }
+        try {
+            mHandler.removeMessages(MSG_FLUSH);
+
+            final ParceledListSlice<ContentCaptureEvent> events = clearEvents();
+            mDirectServiceInterface.sendEvents(events, reason, mManager.mOptions);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Error sending " + numberEvents + " for " + getDebugState()
+                    + ": " + e);
+        }
+    }
+
+    @Override
+    public void updateContentCaptureContext(@Nullable ContentCaptureContext context) {
+        notifyContextUpdated(mId, context);
+    }
+
+    /**
+     * Resets the buffer and return a {@link ParceledListSlice} with the previous events.
+     */
+    @NonNull
+    @UiThread
+    private ParceledListSlice<ContentCaptureEvent> clearEvents() {
+        // NOTE: we must save a reference to the current mEvents and then set it to to null,
+        // otherwise clearing it would clear it in the receiving side if the service is also local.
+        if (mEvents == null) {
+            return new ParceledListSlice<>(Collections.EMPTY_LIST);
+        }
+
+        final List<ContentCaptureEvent> events = new ArrayList<>(mEvents);
+        mEvents.clear();
+        return new ParceledListSlice<>(events);
+    }
+
+    @UiThread
+    private void destroySession() {
+        if (sDebug) {
+            Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with "
+                    + (mEvents == null ? 0 : mEvents.size()) + " event(s) for "
+                    + getDebugState());
+        }
+
+        try {
+            mSystemServerInterface.finishSession(mId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error destroying system-service session " + mId + " for "
+                    + getDebugState() + ": " + e);
+        }
+
+        if (mDirectServiceInterface != null) {
+            mDirectServiceInterface.asBinder().unlinkToDeath(mDirectServiceVulture, 0);
+        }
+        mDirectServiceInterface = null;
+    }
+
+    // TODO(b/122454205): once we support multiple sessions, we might need to move some of these
+    // clearings out.
+    @UiThread
+    private void resetSession(int newState) {
+        if (sVerbose) {
+            Log.v(TAG, "handleResetSession(" + getActivityName() + "): from "
+                    + getStateAsString(mState) + " to " + getStateAsString(newState));
+        }
+        mState = newState;
+        mDisabled.set((newState & STATE_DISABLED) != 0);
+        // TODO(b/122454205): must reset children (which currently is owned by superclass)
+        mApplicationToken = null;
+        mShareableActivityToken = null;
+        mComponentName = null;
+        mEvents = null;
+        if (mDirectServiceInterface != null) {
+            mDirectServiceInterface.asBinder().unlinkToDeath(mDirectServiceVulture, 0);
+        }
+        mDirectServiceInterface = null;
+        mHandler.removeMessages(MSG_FLUSH);
+    }
+
+    @Override
+    void internalNotifyViewAppeared(@NonNull ViewStructureImpl node) {
+        notifyViewAppeared(mId, node);
+    }
+
+    @Override
+    void internalNotifyViewDisappeared(@NonNull AutofillId id) {
+        notifyViewDisappeared(mId, id);
+    }
+
+    @Override
+    void internalNotifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text) {
+        notifyViewTextChanged(mId, id, text);
+    }
+
+    @Override
+    void internalNotifyViewInsetsChanged(@NonNull Insets viewInsets) {
+        notifyViewInsetsChanged(mId, viewInsets);
+    }
+
+    @Override
+    public void internalNotifyViewTreeEvent(boolean started) {
+        notifyViewTreeEvent(mId, started);
+    }
+
+    @Override
+    public void internalNotifySessionResumed() {
+        notifySessionResumed(mId);
+    }
+
+    @Override
+    public void internalNotifySessionPaused() {
+        notifySessionPaused(mId);
+    }
+
+    @Override
+    boolean isContentCaptureEnabled() {
+        return super.isContentCaptureEnabled() && mManager.isContentCaptureEnabled();
+    }
+
+    // Called by ContentCaptureManager.isContentCaptureEnabled
+    boolean isDisabled() {
+        return mDisabled.get();
+    }
+
+    /**
+     * Sets the disabled state of content capture.
+     *
+     * @return whether disabled state was changed.
+     */
+    boolean setDisabled(boolean disabled) {
+        return mDisabled.compareAndSet(!disabled, disabled);
+    }
+
+    // TODO(b/122454205): refactor "notifyXXXX" methods below to a common "Buffer" object that is
+    // shared between ActivityContentCaptureSession and ChildContentCaptureSession objects. Such
+    // change should also get get rid of the "internalNotifyXXXX" methods above
+    void notifyChildSessionStarted(int parentSessionId, int childSessionId,
+            @NonNull ContentCaptureContext clientContext) {
+        mHandler.post(() -> sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED)
+                .setParentSessionId(parentSessionId).setClientContext(clientContext),
+                FORCE_FLUSH));
+    }
+
+    void notifyChildSessionFinished(int parentSessionId, int childSessionId) {
+        mHandler.post(() -> sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED)
+                .setParentSessionId(parentSessionId), FORCE_FLUSH));
+    }
+
+    void notifyViewAppeared(int sessionId, @NonNull ViewStructureImpl node) {
+        mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED)
+                .setViewNode(node.mNode)));
+    }
+
+    /** Public because is also used by ViewRootImpl */
+    public void notifyViewDisappeared(int sessionId, @NonNull AutofillId id) {
+        mHandler.post(() -> sendEvent(
+                new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED).setAutofillId(id)));
+    }
+
+    void notifyViewTextChanged(int sessionId, @NonNull AutofillId id, @Nullable CharSequence text) {
+        // Since the same CharSequence instance may be reused in the TextView, we need to make
+        // a copy of its content so that its value will not be changed by subsequent updates
+        // in the TextView.
+        final CharSequence eventText = stringOrSpannedStringWithoutNoCopySpans(text);
+
+        final int composingStart;
+        final int composingEnd;
+        if (text instanceof Spannable) {
+            composingStart = BaseInputConnection.getComposingSpanStart((Spannable) text);
+            composingEnd = BaseInputConnection.getComposingSpanEnd((Spannable) text);
+        } else {
+            composingStart = ContentCaptureEvent.MAX_INVALID_VALUE;
+            composingEnd = ContentCaptureEvent.MAX_INVALID_VALUE;
+        }
+
+        final int startIndex = Selection.getSelectionStart(text);
+        final int endIndex = Selection.getSelectionEnd(text);
+        mHandler.post(() -> sendEvent(
+                new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED)
+                        .setAutofillId(id).setText(eventText)
+                        .setComposingIndex(composingStart, composingEnd)
+                        .setSelectionIndex(startIndex, endIndex)));
+    }
+
+    private CharSequence stringOrSpannedStringWithoutNoCopySpans(CharSequence source) {
+        if (source == null) {
+            return null;
+        } else if (source instanceof Spanned) {
+            return new SpannableString(source, /* ignoreNoCopySpan= */ true);
+        } else {
+            return source.toString();
+        }
+    }
+
+    /** Public because is also used by ViewRootImpl */
+    public void notifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) {
+        mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_INSETS_CHANGED)
+                .setInsets(viewInsets)));
+    }
+
+    /** Public because is also used by ViewRootImpl */
+    public void notifyViewTreeEvent(int sessionId, boolean started) {
+        final int type = started ? TYPE_VIEW_TREE_APPEARING : TYPE_VIEW_TREE_APPEARED;
+        mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, type), FORCE_FLUSH));
+    }
+
+    void notifySessionResumed(int sessionId) {
+        mHandler.post(() -> sendEvent(
+                new ContentCaptureEvent(sessionId, TYPE_SESSION_RESUMED), FORCE_FLUSH));
+    }
+
+    void notifySessionPaused(int sessionId) {
+        mHandler.post(() -> sendEvent(
+                new ContentCaptureEvent(sessionId, TYPE_SESSION_PAUSED), FORCE_FLUSH));
+    }
+
+    void notifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) {
+        mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED)
+                .setClientContext(context), FORCE_FLUSH));
+    }
+
+    @Override
+    void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
+        super.dump(prefix, pw);
+
+        pw.print(prefix); pw.print("mContext: "); pw.println(mContext);
+        pw.print(prefix); pw.print("user: "); pw.println(mContext.getUserId());
+        if (mDirectServiceInterface != null) {
+            pw.print(prefix); pw.print("mDirectServiceInterface: ");
+            pw.println(mDirectServiceInterface);
+        }
+        pw.print(prefix); pw.print("mDisabled: "); pw.println(mDisabled.get());
+        pw.print(prefix); pw.print("isEnabled(): "); pw.println(isContentCaptureEnabled());
+        pw.print(prefix); pw.print("state: "); pw.println(getStateAsString(mState));
+        if (mApplicationToken != null) {
+            pw.print(prefix); pw.print("app token: "); pw.println(mApplicationToken);
+        }
+        if (mShareableActivityToken != null) {
+            pw.print(prefix); pw.print("sharable activity token: ");
+            pw.println(mShareableActivityToken);
+        }
+        if (mComponentName != null) {
+            pw.print(prefix); pw.print("component name: ");
+            pw.println(mComponentName.flattenToShortString());
+        }
+        if (mEvents != null && !mEvents.isEmpty()) {
+            final int numberEvents = mEvents.size();
+            pw.print(prefix); pw.print("buffered events: "); pw.print(numberEvents);
+            pw.print('/'); pw.println(mManager.mOptions.maxBufferSize);
+            if (sVerbose && numberEvents > 0) {
+                final String prefix3 = prefix + "  ";
+                for (int i = 0; i < numberEvents; i++) {
+                    final ContentCaptureEvent event = mEvents.get(i);
+                    pw.print(prefix3); pw.print(i); pw.print(": "); event.dump(pw);
+                    pw.println();
+                }
+            }
+            pw.print(prefix); pw.print("mNextFlushForTextChanged: ");
+            pw.println(mNextFlushForTextChanged);
+            pw.print(prefix); pw.print("flush frequency: ");
+            if (mNextFlushForTextChanged) {
+                pw.println(mManager.mOptions.textChangeFlushingFrequencyMs);
+            } else {
+                pw.println(mManager.mOptions.idleFlushingFrequencyMs);
+            }
+            pw.print(prefix); pw.print("next flush: ");
+            TimeUtils.formatDuration(mNextFlush - System.currentTimeMillis(), pw);
+            pw.print(" ("); pw.print(TimeUtils.logTimeOfDay(mNextFlush)); pw.println(")");
+        }
+        if (mFlushHistory != null) {
+            pw.print(prefix); pw.println("flush history:");
+            mFlushHistory.reverseDump(/* fd= */ null, pw, /* args= */ null); pw.println();
+        } else {
+            pw.print(prefix); pw.println("not logging flush history");
+        }
+
+        super.dump(prefix, pw);
+    }
+
+    /**
+     * Gets a string that can be used to identify the activity on logging statements.
+     */
+    private String getActivityName() {
+        return mComponentName == null
+                ? "pkg:" + mContext.getPackageName()
+                : "act:" + mComponentName.flattenToShortString();
+    }
+
+    @NonNull
+    private String getDebugState() {
+        return getActivityName() + " [state=" + getStateAsString(mState) + ", disabled="
+                + mDisabled.get() + "]";
+    }
+
+    @NonNull
+    private String getDebugState(@FlushReason int reason) {
+        return getDebugState() + ", reason=" + getFlushReasonAsString(reason);
+    }
+}
diff --git a/android/view/contentcapture/MyContentCaptureService.java b/android/view/contentcapture/MyContentCaptureService.java
new file mode 100644
index 0000000..d07ed37
--- /dev/null
+++ b/android/view/contentcapture/MyContentCaptureService.java
@@ -0,0 +1,182 @@
+/*
+ * 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.view.contentcapture;
+
+import android.content.ComponentName;
+import android.service.contentcapture.ActivityEvent;
+import android.service.contentcapture.ContentCaptureService;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class MyContentCaptureService extends ContentCaptureService {
+
+    private static final String TAG = MyContentCaptureService.class.getSimpleName();
+    private static final String MY_PACKAGE = "com.android.perftests.contentcapture";
+    public static final String SERVICE_NAME = MY_PACKAGE + "/"
+            + MyContentCaptureService.class.getName();
+
+    private static ServiceWatcher sServiceWatcher;
+
+    @NonNull
+    public static ServiceWatcher setServiceWatcher() {
+        if (sServiceWatcher != null) {
+            throw new IllegalStateException("There Can Be Only One!");
+        }
+        sServiceWatcher = new ServiceWatcher();
+        return sServiceWatcher;
+    }
+
+    public static void resetStaticState() {
+        sServiceWatcher = null;
+    }
+
+    private static void clearServiceWatcher() {
+        final ServiceWatcher sw = sServiceWatcher;
+        if (sw != null) {
+            if (sw.mReadyToClear) {
+                sw.mService = null;
+                sServiceWatcher = null;
+            } else {
+                sw.mReadyToClear = true;
+            }
+        }
+    }
+
+    @Override
+    public void onConnected() {
+        Log.i(TAG, "onConnected: sServiceWatcher=" + sServiceWatcher);
+
+        if (sServiceWatcher == null) {
+            Log.e(TAG, "onConnected() without a watcher");
+            return;
+        }
+
+        if (!sServiceWatcher.mReadyToClear && sServiceWatcher.mService != null) {
+            Log.e(TAG, "onConnected(): already created: " + sServiceWatcher);
+            return;
+        }
+
+        sServiceWatcher.mService = this;
+        sServiceWatcher.mCreated.countDown();
+        sServiceWatcher.mReadyToClear = false;
+    }
+
+    @Override
+    public void onDisconnected() {
+        Log.i(TAG, "onDisconnected: sServiceWatcher=" + sServiceWatcher);
+
+        if (sServiceWatcher == null) {
+            Log.e(TAG, "onDisconnected() without a watcher");
+            return;
+        }
+        if (sServiceWatcher.mService == null) {
+            Log.e(TAG, "onDisconnected(): no service on " + sServiceWatcher);
+            return;
+        }
+
+        sServiceWatcher.mDestroyed.countDown();
+        clearServiceWatcher();
+    }
+
+    @Override
+    public void onCreateContentCaptureSession(ContentCaptureContext context,
+            ContentCaptureSessionId sessionId) {
+        Log.i(TAG, "onCreateContentCaptureSession(ctx=" + context + ", session=" + sessionId);
+    }
+
+    @Override
+    public void onDestroyContentCaptureSession(ContentCaptureSessionId sessionId) {
+        Log.i(TAG, "onDestroyContentCaptureSession(session=" + sessionId + ")");
+    }
+
+    @Override
+    public void onContentCaptureEvent(ContentCaptureSessionId sessionId,
+            ContentCaptureEvent event) {
+        Log.i(TAG, "onContentCaptureEventsRequest(session=" + sessionId + "): " + event);
+    }
+
+    @Override
+    public void onActivityEvent(ActivityEvent event) {
+        Log.i(TAG, "onActivityEvent(): " + event);
+    }
+
+    public static final class ServiceWatcher {
+
+        private static final long GENERIC_TIMEOUT_MS = 10_000;
+        private final CountDownLatch mCreated = new CountDownLatch(1);
+        private final CountDownLatch mDestroyed = new CountDownLatch(1);
+        private boolean mReadyToClear = true;
+        private Pair<Set<String>, Set<ComponentName>> mAllowList;
+
+        private MyContentCaptureService mService;
+
+        @NonNull
+        public MyContentCaptureService waitOnCreate() throws InterruptedException {
+            await(mCreated, "not created");
+
+            if (mService == null) {
+                throw new IllegalStateException("not created");
+            }
+
+            if (mAllowList != null) {
+                Log.d(TAG, "Allow after created: " + mAllowList);
+                mService.setContentCaptureWhitelist(mAllowList.first, mAllowList.second);
+            }
+
+            return mService;
+        }
+
+        public void waitOnDestroy() throws InterruptedException {
+            await(mDestroyed, "not destroyed");
+        }
+
+        /**
+         * Allow just this package.
+         */
+        public void setAllowSelf() {
+            final ArraySet<String> pkgs = new ArraySet<>(1);
+            pkgs.add(MY_PACKAGE);
+            mAllowList = new Pair<>(pkgs, null);
+        }
+
+        @Override
+        public String toString() {
+            return "mService: " + mService + " created: " + (mCreated.getCount() == 0)
+                    + " destroyed: " + (mDestroyed.getCount() == 0);
+        }
+
+        /**
+         * Awaits for a latch to be counted down.
+         */
+        private static void await(@NonNull CountDownLatch latch, @NonNull String fmt,
+                @Nullable Object... args)
+                throws InterruptedException {
+            final boolean called = latch.await(GENERIC_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            if (!called) {
+                throw new IllegalStateException(String.format(fmt, args)
+                        + " in " + GENERIC_TIMEOUT_MS + "ms");
+            }
+        }
+    }
+}
diff --git a/android/view/contentcapture/ViewNode.java b/android/view/contentcapture/ViewNode.java
new file mode 100644
index 0000000..c882c6e
--- /dev/null
+++ b/android/view/contentcapture/ViewNode.java
@@ -0,0 +1,1069 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.contentcapture;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.app.assist.AssistStructure;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewParent;
+import android.view.ViewStructure;
+import android.view.ViewStructure.HtmlInfo.Builder;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+
+import com.android.internal.util.Preconditions;
+
+//TODO(b/122484602): add javadocs / implement Parcelable / implement
+//TODO(b/122484602): for now it's extending ViewNode directly as it needs most of its properties,
+// but it might be better to create a common, abstract android.view.ViewNode class that both extend
+// instead
+/** @hide */
+@SystemApi
+public final class ViewNode extends AssistStructure.ViewNode {
+
+    private static final String TAG = ViewNode.class.getSimpleName();
+
+    private static final long FLAGS_HAS_TEXT = 1L << 0;
+    private static final long FLAGS_HAS_COMPLEX_TEXT = 1L << 1;
+    private static final long FLAGS_VISIBILITY_MASK = View.VISIBLE | View.INVISIBLE | View.GONE;
+    private static final long FLAGS_HAS_CLASSNAME = 1L << 4;
+    private static final long FLAGS_HAS_AUTOFILL_ID = 1L << 5;
+    private static final long FLAGS_HAS_AUTOFILL_PARENT_ID = 1L << 6;
+    private static final long FLAGS_HAS_ID = 1L << 7;
+    private static final long FLAGS_HAS_LARGE_COORDS = 1L << 8;
+    private static final long FLAGS_HAS_SCROLL = 1L << 9;
+    private static final long FLAGS_ASSIST_BLOCKED = 1L << 10;
+    private static final long FLAGS_DISABLED = 1L << 11;
+    private static final long FLAGS_CLICKABLE = 1L << 12;
+    private static final long FLAGS_LONG_CLICKABLE = 1L << 13;
+    private static final long FLAGS_CONTEXT_CLICKABLE = 1L << 14;
+    private static final long FLAGS_FOCUSABLE = 1L << 15;
+    private static final long FLAGS_FOCUSED = 1L << 16;
+    private static final long FLAGS_ACCESSIBILITY_FOCUSED = 1L << 17;
+    private static final long FLAGS_CHECKABLE = 1L << 18;
+    private static final long FLAGS_CHECKED = 1L << 19;
+    private static final long FLAGS_SELECTED = 1L << 20;
+    private static final long FLAGS_ACTIVATED = 1L << 21;
+    private static final long FLAGS_OPAQUE = 1L << 22;
+    private static final long FLAGS_HAS_CONTENT_DESCRIPTION = 1L << 23;
+    private static final long FLAGS_HAS_EXTRAS = 1L << 24;
+    private static final long FLAGS_HAS_LOCALE_LIST = 1L << 25;
+    private static final long FLAGS_HAS_INPUT_TYPE = 1L << 26;
+    private static final long FLAGS_HAS_MIN_TEXT_EMS = 1L << 27;
+    private static final long FLAGS_HAS_MAX_TEXT_EMS = 1L << 28;
+    private static final long FLAGS_HAS_MAX_TEXT_LENGTH = 1L << 29;
+    private static final long FLAGS_HAS_TEXT_ID_ENTRY = 1L << 30;
+    private static final long FLAGS_HAS_AUTOFILL_TYPE = 1L << 31;
+    private static final long FLAGS_HAS_AUTOFILL_VALUE = 1L << 32;
+    private static final long FLAGS_HAS_AUTOFILL_HINTS = 1L << 33;
+    private static final long FLAGS_HAS_AUTOFILL_OPTIONS = 1L << 34;
+    private static final long FLAGS_HAS_HINT_ID_ENTRY = 1L << 35;
+    private static final long FLAGS_HAS_MIME_TYPES = 1L << 36;
+
+    /** Flags used to optimize what's written to the parcel */
+    private long mFlags;
+
+    private AutofillId mParentAutofillId;
+
+    private AutofillId mAutofillId;
+    private ViewNodeText mText;
+    private String mClassName;
+    private int mId = View.NO_ID;
+    private String mIdPackage;
+    private String mIdType;
+    private String mIdEntry;
+    private int mX;
+    private int mY;
+    private int mScrollX;
+    private int mScrollY;
+    private int mWidth;
+    private int mHeight;
+    private CharSequence mContentDescription;
+    private Bundle mExtras;
+    private LocaleList mLocaleList;
+    private int mInputType;
+    private int mMinEms = -1;
+    private int mMaxEms = -1;
+    private int mMaxLength = -1;
+    private String mTextIdEntry;
+    private String mHintIdEntry;
+    private @View.AutofillType int mAutofillType = View.AUTOFILL_TYPE_NONE;
+    private String[] mAutofillHints;
+    private AutofillValue mAutofillValue;
+    private CharSequence[] mAutofillOptions;
+    private String[] mReceiveContentMimeTypes;
+
+    /** @hide */
+    public ViewNode() {
+    }
+
+    private ViewNode(long nodeFlags, @NonNull Parcel parcel) {
+        mFlags = nodeFlags;
+
+        if ((nodeFlags & FLAGS_HAS_AUTOFILL_ID) != 0) {
+            mAutofillId = parcel.readParcelable(null);
+        }
+        if ((nodeFlags & FLAGS_HAS_AUTOFILL_PARENT_ID) != 0) {
+            mParentAutofillId = parcel.readParcelable(null);
+        }
+        if ((nodeFlags & FLAGS_HAS_TEXT) != 0) {
+            mText = new ViewNodeText(parcel, (nodeFlags & FLAGS_HAS_COMPLEX_TEXT) == 0);
+        }
+        if ((nodeFlags & FLAGS_HAS_CLASSNAME) != 0) {
+            mClassName = parcel.readString();
+        }
+        if ((nodeFlags & FLAGS_HAS_ID) != 0) {
+            mId = parcel.readInt();
+            if (mId != View.NO_ID) {
+                mIdEntry = parcel.readString();
+                if (mIdEntry != null) {
+                    mIdType = parcel.readString();
+                    mIdPackage = parcel.readString();
+                }
+            }
+        }
+        if ((nodeFlags & FLAGS_HAS_LARGE_COORDS) != 0) {
+            mX = parcel.readInt();
+            mY = parcel.readInt();
+            mWidth = parcel.readInt();
+            mHeight = parcel.readInt();
+        } else {
+            int val = parcel.readInt();
+            mX = val & 0x7fff;
+            mY = (val >> 16) & 0x7fff;
+            val = parcel.readInt();
+            mWidth = val & 0x7fff;
+            mHeight = (val >> 16) & 0x7fff;
+        }
+        if ((nodeFlags & FLAGS_HAS_SCROLL) != 0) {
+            mScrollX = parcel.readInt();
+            mScrollY = parcel.readInt();
+        }
+        if ((nodeFlags & FLAGS_HAS_CONTENT_DESCRIPTION) != 0) {
+            mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+        }
+        if ((nodeFlags & FLAGS_HAS_EXTRAS) != 0) {
+            mExtras = parcel.readBundle();
+        }
+        if ((nodeFlags & FLAGS_HAS_LOCALE_LIST) != 0) {
+            mLocaleList = parcel.readParcelable(null);
+        }
+        if ((nodeFlags & FLAGS_HAS_MIME_TYPES) != 0) {
+            mReceiveContentMimeTypes = parcel.readStringArray();
+        }
+        if ((nodeFlags & FLAGS_HAS_INPUT_TYPE) != 0) {
+            mInputType = parcel.readInt();
+        }
+        if ((nodeFlags & FLAGS_HAS_MIN_TEXT_EMS) != 0) {
+            mMinEms = parcel.readInt();
+        }
+        if ((nodeFlags & FLAGS_HAS_MAX_TEXT_EMS) != 0) {
+            mMaxEms = parcel.readInt();
+        }
+        if ((nodeFlags & FLAGS_HAS_MAX_TEXT_LENGTH) != 0) {
+            mMaxLength = parcel.readInt();
+        }
+        if ((nodeFlags & FLAGS_HAS_TEXT_ID_ENTRY) != 0) {
+            mTextIdEntry = parcel.readString();
+        }
+        if ((nodeFlags & FLAGS_HAS_AUTOFILL_TYPE) != 0) {
+            mAutofillType = parcel.readInt();
+        }
+        if ((nodeFlags & FLAGS_HAS_AUTOFILL_HINTS) != 0) {
+            mAutofillHints = parcel.readStringArray();
+        }
+        if ((nodeFlags & FLAGS_HAS_AUTOFILL_VALUE) != 0) {
+            mAutofillValue = parcel.readParcelable(null);
+        }
+        if ((nodeFlags & FLAGS_HAS_AUTOFILL_OPTIONS) != 0) {
+            mAutofillOptions = parcel.readCharSequenceArray();
+        }
+        if ((nodeFlags & FLAGS_HAS_HINT_ID_ENTRY) != 0) {
+            mHintIdEntry = parcel.readString();
+        }
+    }
+
+    /**
+     * Returns the {@link AutofillId} of this view's parent, if the parent is also part of the
+     * screen observation tree.
+     */
+    @Nullable
+    public AutofillId getParentAutofillId() {
+        return mParentAutofillId;
+    }
+
+    @Nullable
+    @Override
+    public AutofillId getAutofillId() {
+        return mAutofillId;
+    }
+
+    @Nullable
+    @Override
+    public CharSequence getText() {
+        return mText != null ? mText.mText : null;
+    }
+
+    @Nullable
+    @Override
+    public String getClassName() {
+        return mClassName;
+    }
+
+    @Override
+    public int getId() {
+        return mId;
+    }
+
+    @Nullable
+    @Override
+    public String getIdPackage() {
+        return mIdPackage;
+    }
+
+    @Nullable
+    @Override
+    public String getIdType() {
+        return mIdType;
+    }
+
+    @Nullable
+    @Override
+    public String getIdEntry() {
+        return mIdEntry;
+    }
+
+    @Override
+    public int getLeft() {
+        return mX;
+    }
+
+    @Override
+    public int getTop() {
+        return mY;
+    }
+
+    @Override
+    public int getScrollX() {
+        return mScrollX;
+    }
+
+    @Override
+    public int getScrollY() {
+        return mScrollY;
+    }
+
+    @Override
+    public int getWidth() {
+        return mWidth;
+    }
+
+    @Override
+    public int getHeight() {
+        return mHeight;
+    }
+
+    @Override
+    public boolean isAssistBlocked() {
+        return (mFlags & FLAGS_ASSIST_BLOCKED) != 0;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return (mFlags & FLAGS_DISABLED) == 0;
+    }
+
+    @Override
+    public boolean isClickable() {
+        return (mFlags & FLAGS_CLICKABLE) != 0;
+    }
+
+    @Override
+    public boolean isLongClickable() {
+        return (mFlags & FLAGS_LONG_CLICKABLE) != 0;
+    }
+
+    @Override
+    public boolean isContextClickable() {
+        return (mFlags & FLAGS_CONTEXT_CLICKABLE) != 0;
+    }
+
+    @Override
+    public boolean isFocusable() {
+        return (mFlags & FLAGS_FOCUSABLE) != 0;
+    }
+
+    @Override
+    public boolean isFocused() {
+        return (mFlags & FLAGS_FOCUSED) != 0;
+    }
+
+    @Override
+    public boolean isAccessibilityFocused() {
+        return (mFlags & FLAGS_ACCESSIBILITY_FOCUSED) != 0;
+    }
+
+    @Override
+    public boolean isCheckable() {
+        return (mFlags & FLAGS_CHECKABLE) != 0;
+    }
+
+    @Override
+    public boolean isChecked() {
+        return (mFlags & FLAGS_CHECKED) != 0;
+    }
+
+    @Override
+    public boolean isSelected() {
+        return (mFlags & FLAGS_SELECTED) != 0;
+    }
+
+    @Override
+    public boolean isActivated() {
+        return (mFlags & FLAGS_ACTIVATED) != 0;
+    }
+
+    @Override
+    public boolean isOpaque() {
+        return (mFlags & FLAGS_OPAQUE) != 0;
+    }
+
+    @Nullable
+    @Override
+    public CharSequence getContentDescription() {
+        return mContentDescription;
+    }
+
+    @Nullable
+    @Override
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    @Nullable
+    @Override
+    public String getHint() {
+        return mText != null ? mText.mHint : null;
+    }
+
+    @Nullable
+    @Override
+    public String getHintIdEntry() {
+        return mHintIdEntry;
+    }
+
+    @Override
+    public int getTextSelectionStart() {
+        return mText != null ? mText.mTextSelectionStart : -1;
+    }
+
+    @Override
+    public int getTextSelectionEnd() {
+        return mText != null ? mText.mTextSelectionEnd : -1;
+    }
+
+    @Override
+    public int getTextColor() {
+        return mText != null ? mText.mTextColor : TEXT_COLOR_UNDEFINED;
+    }
+
+    @Override
+    public int getTextBackgroundColor() {
+        return mText != null ? mText.mTextBackgroundColor : TEXT_COLOR_UNDEFINED;
+    }
+
+    @Override
+    public float getTextSize() {
+        return mText != null ? mText.mTextSize : 0;
+    }
+
+    @Override
+    public int getTextStyle() {
+        return mText != null ? mText.mTextStyle : 0;
+    }
+
+    @Nullable
+    @Override
+    public int[] getTextLineCharOffsets() {
+        return mText != null ? mText.mLineCharOffsets : null;
+    }
+
+    @Nullable
+    @Override
+    public int[] getTextLineBaselines() {
+        return mText != null ? mText.mLineBaselines : null;
+    }
+
+    @Override
+    public int getVisibility() {
+        return (int) (mFlags & FLAGS_VISIBILITY_MASK);
+    }
+
+    @Override
+    public int getInputType() {
+        return mInputType;
+    }
+
+    @Override
+    public int getMinTextEms() {
+        return mMinEms;
+    }
+
+    @Override
+    public int getMaxTextEms() {
+        return mMaxEms;
+    }
+
+    @Override
+    public int getMaxTextLength() {
+        return mMaxLength;
+    }
+
+    @Nullable
+    @Override
+    public String getTextIdEntry() {
+        return mTextIdEntry;
+    }
+
+    @Override
+    public @View.AutofillType int getAutofillType() {
+        return mAutofillType;
+    }
+
+    @Override
+    @Nullable public String[] getAutofillHints() {
+        return mAutofillHints;
+    }
+
+    @Override
+    @Nullable public AutofillValue getAutofillValue() {
+        return mAutofillValue;
+    }
+
+    @Override
+    @Nullable public CharSequence[] getAutofillOptions() {
+        return mAutofillOptions;
+    }
+
+    @Override
+    @Nullable
+    public String[] getReceiveContentMimeTypes() {
+        return mReceiveContentMimeTypes;
+    }
+
+    @Nullable
+    @Override
+    public LocaleList getLocaleList() {
+        return mLocaleList;
+    }
+
+    private void writeSelfToParcel(@NonNull Parcel parcel, int parcelFlags) {
+        long nodeFlags = mFlags;
+
+        if (mAutofillId != null) {
+            nodeFlags |= FLAGS_HAS_AUTOFILL_ID;
+        }
+
+        if (mParentAutofillId != null) {
+            nodeFlags |= FLAGS_HAS_AUTOFILL_PARENT_ID;
+        }
+
+        if (mText != null) {
+            nodeFlags |= FLAGS_HAS_TEXT;
+            if (!mText.isSimple()) {
+                nodeFlags |= FLAGS_HAS_COMPLEX_TEXT;
+            }
+        }
+        if (mClassName != null) {
+            nodeFlags |= FLAGS_HAS_CLASSNAME;
+        }
+        if (mId != View.NO_ID) {
+            nodeFlags |= FLAGS_HAS_ID;
+        }
+        if ((mX & ~0x7fff) != 0 || (mY & ~0x7fff) != 0
+                || (mWidth & ~0x7fff) != 0 | (mHeight & ~0x7fff) != 0) {
+            nodeFlags |= FLAGS_HAS_LARGE_COORDS;
+        }
+        if (mScrollX != 0 || mScrollY != 0) {
+            nodeFlags |= FLAGS_HAS_SCROLL;
+        }
+        if (mContentDescription != null) {
+            nodeFlags |= FLAGS_HAS_CONTENT_DESCRIPTION;
+        }
+        if (mExtras != null) {
+            nodeFlags |= FLAGS_HAS_EXTRAS;
+        }
+        if (mLocaleList != null) {
+            nodeFlags |= FLAGS_HAS_LOCALE_LIST;
+        }
+        if (mReceiveContentMimeTypes != null) {
+            nodeFlags |= FLAGS_HAS_MIME_TYPES;
+        }
+        if (mInputType != 0) {
+            nodeFlags |= FLAGS_HAS_INPUT_TYPE;
+        }
+        if (mMinEms > -1) {
+            nodeFlags |= FLAGS_HAS_MIN_TEXT_EMS;
+        }
+        if (mMaxEms > -1) {
+            nodeFlags |= FLAGS_HAS_MAX_TEXT_EMS;
+        }
+        if (mMaxLength > -1) {
+            nodeFlags |= FLAGS_HAS_MAX_TEXT_LENGTH;
+        }
+        if (mTextIdEntry != null) {
+            nodeFlags |= FLAGS_HAS_TEXT_ID_ENTRY;
+        }
+        if (mAutofillValue != null) {
+            nodeFlags |= FLAGS_HAS_AUTOFILL_VALUE;
+        }
+        if (mAutofillType != View.AUTOFILL_TYPE_NONE) {
+            nodeFlags |= FLAGS_HAS_AUTOFILL_TYPE;
+        }
+        if (mAutofillHints != null) {
+            nodeFlags |= FLAGS_HAS_AUTOFILL_HINTS;
+        }
+        if (mAutofillOptions != null) {
+            nodeFlags |= FLAGS_HAS_AUTOFILL_OPTIONS;
+        }
+        if (mHintIdEntry != null) {
+            nodeFlags |= FLAGS_HAS_HINT_ID_ENTRY;
+        }
+        parcel.writeLong(nodeFlags);
+
+        if ((nodeFlags & FLAGS_HAS_AUTOFILL_ID) != 0) {
+            parcel.writeParcelable(mAutofillId, parcelFlags);
+        }
+        if ((nodeFlags & FLAGS_HAS_AUTOFILL_PARENT_ID) != 0) {
+            parcel.writeParcelable(mParentAutofillId, parcelFlags);
+        }
+        if ((nodeFlags & FLAGS_HAS_TEXT) != 0) {
+            mText.writeToParcel(parcel, (nodeFlags & FLAGS_HAS_COMPLEX_TEXT) == 0);
+        }
+        if ((nodeFlags & FLAGS_HAS_CLASSNAME) != 0) {
+            parcel.writeString(mClassName);
+        }
+        if ((nodeFlags & FLAGS_HAS_ID) != 0) {
+            parcel.writeInt(mId);
+            if (mId != View.NO_ID) {
+                parcel.writeString(mIdEntry);
+                if (mIdEntry != null) {
+                    parcel.writeString(mIdType);
+                    parcel.writeString(mIdPackage);
+                }
+            }
+        }
+        if ((nodeFlags & FLAGS_HAS_LARGE_COORDS) != 0) {
+            parcel.writeInt(mX);
+            parcel.writeInt(mY);
+            parcel.writeInt(mWidth);
+            parcel.writeInt(mHeight);
+        } else {
+            parcel.writeInt((mY << 16) | mX);
+            parcel.writeInt((mHeight << 16) | mWidth);
+        }
+        if ((nodeFlags & FLAGS_HAS_SCROLL) != 0) {
+            parcel.writeInt(mScrollX);
+            parcel.writeInt(mScrollY);
+        }
+        if ((nodeFlags & FLAGS_HAS_CONTENT_DESCRIPTION) != 0) {
+            TextUtils.writeToParcel(mContentDescription, parcel, 0);
+        }
+        if ((nodeFlags & FLAGS_HAS_EXTRAS) != 0) {
+            parcel.writeBundle(mExtras);
+        }
+        if ((nodeFlags & FLAGS_HAS_LOCALE_LIST) != 0) {
+            parcel.writeParcelable(mLocaleList, 0);
+        }
+        if ((nodeFlags & FLAGS_HAS_MIME_TYPES) != 0) {
+            parcel.writeStringArray(mReceiveContentMimeTypes);
+        }
+        if ((nodeFlags & FLAGS_HAS_INPUT_TYPE) != 0) {
+            parcel.writeInt(mInputType);
+        }
+        if ((nodeFlags & FLAGS_HAS_MIN_TEXT_EMS) != 0) {
+            parcel.writeInt(mMinEms);
+        }
+        if ((nodeFlags & FLAGS_HAS_MAX_TEXT_EMS) != 0) {
+            parcel.writeInt(mMaxEms);
+        }
+        if ((nodeFlags & FLAGS_HAS_MAX_TEXT_LENGTH) != 0) {
+            parcel.writeInt(mMaxLength);
+        }
+        if ((nodeFlags & FLAGS_HAS_TEXT_ID_ENTRY) != 0) {
+            parcel.writeString(mTextIdEntry);
+        }
+        if ((nodeFlags & FLAGS_HAS_AUTOFILL_TYPE) != 0) {
+            parcel.writeInt(mAutofillType);
+        }
+        if ((nodeFlags & FLAGS_HAS_AUTOFILL_HINTS) != 0) {
+            parcel.writeStringArray(mAutofillHints);
+        }
+        if ((nodeFlags & FLAGS_HAS_AUTOFILL_VALUE) != 0) {
+            parcel.writeParcelable(mAutofillValue, 0);
+        }
+        if ((nodeFlags & FLAGS_HAS_AUTOFILL_OPTIONS) != 0) {
+            parcel.writeCharSequenceArray(mAutofillOptions);
+        }
+        if ((nodeFlags & FLAGS_HAS_HINT_ID_ENTRY) != 0) {
+            parcel.writeString(mHintIdEntry);
+        }
+    }
+
+    /** @hide */
+    @TestApi
+    public static void writeToParcel(@NonNull Parcel parcel, @Nullable ViewNode node, int flags) {
+        if (node == null) {
+            parcel.writeLong(0);
+        } else {
+            node.writeSelfToParcel(parcel, flags);
+        }
+    }
+
+    /** @hide */
+    @TestApi
+    public static @Nullable ViewNode readFromParcel(@NonNull Parcel parcel) {
+        final long nodeFlags = parcel.readLong();
+        return nodeFlags == 0 ? null : new ViewNode(nodeFlags, parcel);
+    }
+
+    /** @hide */
+    @TestApi
+    public static final class ViewStructureImpl extends ViewStructure {
+
+        final ViewNode mNode = new ViewNode();
+
+        /** @hide */
+        @TestApi
+        public ViewStructureImpl(@NonNull View view) {
+            mNode.mAutofillId = Preconditions.checkNotNull(view).getAutofillId();
+            final ViewParent parent = view.getParent();
+            if (parent instanceof View) {
+                mNode.mParentAutofillId = ((View) parent).getAutofillId();
+            }
+        }
+
+        /** @hide */
+        @TestApi
+        public ViewStructureImpl(@NonNull AutofillId parentId, long virtualId, int sessionId) {
+            mNode.mParentAutofillId = Preconditions.checkNotNull(parentId);
+            mNode.mAutofillId = new AutofillId(parentId, virtualId, sessionId);
+        }
+
+        /** @hide */
+        @TestApi
+        public ViewNode getNode() {
+            return mNode;
+        }
+
+        @Override
+        public void setId(int id, String packageName, String typeName, String entryName) {
+            mNode.mId = id;
+            mNode.mIdPackage = packageName;
+            mNode.mIdType = typeName;
+            mNode.mIdEntry = entryName;
+        }
+
+        @Override
+        public void setDimens(int left, int top, int scrollX, int scrollY, int width, int height) {
+            mNode.mX = left;
+            mNode.mY = top;
+            mNode.mScrollX = scrollX;
+            mNode.mScrollY = scrollY;
+            mNode.mWidth = width;
+            mNode.mHeight = height;
+        }
+
+        @Override
+        public void setTransformation(Matrix matrix) {
+            Log.w(TAG, "setTransformation() is not supported");
+        }
+
+        @Override
+        public void setElevation(float elevation) {
+            Log.w(TAG, "setElevation() is not supported");
+        }
+
+        @Override
+        public void setAlpha(float alpha) {
+            Log.w(TAG, "setAlpha() is not supported");
+        }
+
+        @Override
+        public void setVisibility(int visibility) {
+            mNode.mFlags = (mNode.mFlags & ~FLAGS_VISIBILITY_MASK)
+                    | (visibility & FLAGS_VISIBILITY_MASK);
+        }
+
+        @Override
+        public void setAssistBlocked(boolean state) {
+            mNode.mFlags = (mNode.mFlags & ~FLAGS_ASSIST_BLOCKED)
+                    | (state ? FLAGS_ASSIST_BLOCKED : 0);
+        }
+
+        @Override
+        public void setEnabled(boolean state) {
+            mNode.mFlags = (mNode.mFlags & ~FLAGS_DISABLED) | (state ? 0 : FLAGS_DISABLED);
+        }
+
+        @Override
+        public void setClickable(boolean state) {
+            mNode.mFlags = (mNode.mFlags & ~FLAGS_CLICKABLE) | (state ? FLAGS_CLICKABLE : 0);
+        }
+
+        @Override
+        public void setLongClickable(boolean state) {
+            mNode.mFlags = (mNode.mFlags & ~FLAGS_LONG_CLICKABLE)
+                    | (state ? FLAGS_LONG_CLICKABLE : 0);
+        }
+
+        @Override
+        public void setContextClickable(boolean state) {
+            mNode.mFlags = (mNode.mFlags & ~FLAGS_CONTEXT_CLICKABLE)
+                    | (state ? FLAGS_CONTEXT_CLICKABLE : 0);
+        }
+
+        @Override
+        public void setFocusable(boolean state) {
+            mNode.mFlags = (mNode.mFlags & ~FLAGS_FOCUSABLE) | (state ? FLAGS_FOCUSABLE : 0);
+        }
+
+        @Override
+        public void setFocused(boolean state) {
+            mNode.mFlags = (mNode.mFlags & ~FLAGS_FOCUSED) | (state ? FLAGS_FOCUSED : 0);
+        }
+
+        @Override
+        public void setAccessibilityFocused(boolean state) {
+            mNode.mFlags = (mNode.mFlags & ~FLAGS_ACCESSIBILITY_FOCUSED)
+                    | (state ? FLAGS_ACCESSIBILITY_FOCUSED : 0);
+        }
+
+        @Override
+        public void setCheckable(boolean state) {
+            mNode.mFlags = (mNode.mFlags & ~FLAGS_CHECKABLE) | (state ? FLAGS_CHECKABLE : 0);
+        }
+
+        @Override
+        public void setChecked(boolean state) {
+            mNode.mFlags = (mNode.mFlags & ~FLAGS_CHECKED) | (state ? FLAGS_CHECKED : 0);
+        }
+
+        @Override
+        public void setSelected(boolean state) {
+            mNode.mFlags = (mNode.mFlags & ~FLAGS_SELECTED) | (state ? FLAGS_SELECTED : 0);
+        }
+
+        @Override
+        public void setActivated(boolean state) {
+            mNode.mFlags = (mNode.mFlags & ~FLAGS_ACTIVATED) | (state ? FLAGS_ACTIVATED : 0);
+        }
+
+        @Override
+        public void setOpaque(boolean opaque) {
+            mNode.mFlags = (mNode.mFlags & ~FLAGS_OPAQUE) | (opaque ? FLAGS_OPAQUE : 0);
+        }
+
+        @Override
+        public void setClassName(String className) {
+            mNode.mClassName = className;
+        }
+
+        @Override
+        public void setContentDescription(CharSequence contentDescription) {
+            mNode.mContentDescription = contentDescription;
+        }
+
+        @Override
+        public void setText(CharSequence text) {
+            final ViewNodeText t = getNodeText();
+            t.mText = TextUtils.trimNoCopySpans(text);
+            t.mTextSelectionStart = t.mTextSelectionEnd = -1;
+        }
+
+        @Override
+        public void setText(CharSequence text, int selectionStart, int selectionEnd) {
+            final ViewNodeText t = getNodeText();
+            t.mText = TextUtils.trimNoCopySpans(text);
+            t.mTextSelectionStart = selectionStart;
+            t.mTextSelectionEnd = selectionEnd;
+        }
+
+        @Override
+        public void setTextStyle(float size, int fgColor, int bgColor, int style) {
+            final ViewNodeText t = getNodeText();
+            t.mTextColor = fgColor;
+            t.mTextBackgroundColor = bgColor;
+            t.mTextSize = size;
+            t.mTextStyle = style;
+        }
+
+        @Override
+        public void setTextLines(int[] charOffsets, int[] baselines) {
+            final ViewNodeText t = getNodeText();
+            t.mLineCharOffsets = charOffsets;
+            t.mLineBaselines = baselines;
+        }
+
+        @Override
+        public void setTextIdEntry(@NonNull String entryName) {
+            mNode.mTextIdEntry = Preconditions.checkNotNull(entryName);
+        }
+
+        @Override
+        public void setHint(CharSequence hint) {
+            getNodeText().mHint = hint != null ? hint.toString() : null;
+        }
+
+        @Override
+        public void setHintIdEntry(String entryName) {
+            mNode.mHintIdEntry = Preconditions.checkNotNull(entryName);
+        }
+
+        @Override
+        public CharSequence getText() {
+            return mNode.getText();
+        }
+
+        @Override
+        public int getTextSelectionStart() {
+            return mNode.getTextSelectionStart();
+        }
+
+        @Override
+        public int getTextSelectionEnd() {
+            return mNode.getTextSelectionEnd();
+        }
+
+        @Override
+        public CharSequence getHint() {
+            return mNode.getHint();
+        }
+
+        @Override
+        public Bundle getExtras() {
+            if (mNode.mExtras != null) {
+                return mNode.mExtras;
+            }
+            mNode.mExtras = new Bundle();
+            return mNode.mExtras;
+        }
+
+        @Override
+        public boolean hasExtras() {
+            return mNode.mExtras != null;
+        }
+
+        @Override
+        public void setChildCount(int num) {
+            Log.w(TAG, "setChildCount() is not supported");
+        }
+
+        @Override
+        public int addChildCount(int num) {
+            Log.w(TAG, "addChildCount() is not supported");
+            return 0;
+        }
+
+        @Override
+        public int getChildCount() {
+            Log.w(TAG, "getChildCount() is not supported");
+            return 0;
+        }
+
+        @Override
+        public ViewStructure newChild(int index) {
+            Log.w(TAG, "newChild() is not supported");
+            return null;
+        }
+
+        @Override
+        public ViewStructure asyncNewChild(int index) {
+            Log.w(TAG, "asyncNewChild() is not supported");
+            return null;
+        }
+
+        @Override
+        public AutofillId getAutofillId() {
+            return mNode.mAutofillId;
+        }
+
+        @Override
+        public void setAutofillId(AutofillId id) {
+            mNode.mAutofillId = Preconditions.checkNotNull(id);
+        }
+
+
+        @Override
+        public void setAutofillId(AutofillId parentId, int virtualId) {
+            mNode.mParentAutofillId = Preconditions.checkNotNull(parentId);
+            mNode.mAutofillId = new AutofillId(parentId, virtualId);
+        }
+
+        @Override
+        public void setAutofillType(@View.AutofillType int type) {
+            mNode.mAutofillType = type;
+        }
+
+        @Override
+        public void setReceiveContentMimeTypes(@Nullable String[] mimeTypes) {
+            mNode.mReceiveContentMimeTypes = mimeTypes;
+        }
+
+        @Override
+        public void setAutofillHints(String[] hints) {
+            mNode.mAutofillHints = hints;
+        }
+
+        @Override
+        public void setAutofillValue(AutofillValue value) {
+            mNode.mAutofillValue = value;
+        }
+
+        @Override
+        public void setAutofillOptions(CharSequence[] options) {
+            mNode.mAutofillOptions = options;
+        }
+
+        @Override
+        public void setInputType(int inputType) {
+            mNode.mInputType = inputType;
+        }
+
+        @Override
+        public void setMinTextEms(int minEms) {
+            mNode.mMinEms = minEms;
+        }
+
+        @Override
+        public void setMaxTextEms(int maxEms) {
+            mNode.mMaxEms = maxEms;
+        }
+
+        @Override
+        public void setMaxTextLength(int maxLength) {
+            mNode.mMaxLength = maxLength;
+        }
+
+        @Override
+        public void setDataIsSensitive(boolean sensitive) {
+            Log.w(TAG, "setDataIsSensitive() is not supported");
+        }
+
+        @Override
+        public void asyncCommit() {
+            Log.w(TAG, "asyncCommit() is not supported");
+        }
+
+        @Override
+        public Rect getTempRect() {
+            Log.w(TAG, "getTempRect() is not supported");
+            return null;
+        }
+
+        @Override
+        public void setWebDomain(String domain) {
+            Log.w(TAG, "setWebDomain() is not supported");
+        }
+
+        @Override
+        public void setLocaleList(LocaleList localeList) {
+            mNode.mLocaleList = localeList;
+        }
+
+        @Override
+        public Builder newHtmlInfoBuilder(String tagName) {
+            Log.w(TAG, "newHtmlInfoBuilder() is not supported");
+            return null;
+        }
+
+        @Override
+        public void setHtmlInfo(HtmlInfo htmlInfo) {
+            Log.w(TAG, "setHtmlInfo() is not supported");
+        }
+
+        private ViewNodeText getNodeText() {
+            if (mNode.mText != null) {
+                return mNode.mText;
+            }
+            mNode.mText = new ViewNodeText();
+            return mNode.mText;
+        }
+    }
+
+    //TODO(b/122484602): copied 'as-is' from AssistStructure, except for writeSensitive
+    static final class ViewNodeText {
+        CharSequence mText;
+        float mTextSize;
+        int mTextStyle;
+        int mTextColor = ViewNode.TEXT_COLOR_UNDEFINED;
+        int mTextBackgroundColor = ViewNode.TEXT_COLOR_UNDEFINED;
+        int mTextSelectionStart;
+        int mTextSelectionEnd;
+        int[] mLineCharOffsets;
+        int[] mLineBaselines;
+        String mHint;
+
+        ViewNodeText() {
+        }
+
+        boolean isSimple() {
+            return mTextBackgroundColor == ViewNode.TEXT_COLOR_UNDEFINED
+                    && mTextSelectionStart == 0 && mTextSelectionEnd == 0
+                    && mLineCharOffsets == null && mLineBaselines == null && mHint == null;
+        }
+
+        ViewNodeText(Parcel in, boolean simple) {
+            mText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+            mTextSize = in.readFloat();
+            mTextStyle = in.readInt();
+            mTextColor = in.readInt();
+            if (!simple) {
+                mTextBackgroundColor = in.readInt();
+                mTextSelectionStart = in.readInt();
+                mTextSelectionEnd = in.readInt();
+                mLineCharOffsets = in.createIntArray();
+                mLineBaselines = in.createIntArray();
+                mHint = in.readString();
+            }
+        }
+
+        void writeToParcel(Parcel out, boolean simple) {
+            TextUtils.writeToParcel(mText, out, 0);
+            out.writeFloat(mTextSize);
+            out.writeInt(mTextStyle);
+            out.writeInt(mTextColor);
+            if (!simple) {
+                out.writeInt(mTextBackgroundColor);
+                out.writeInt(mTextSelectionStart);
+                out.writeInt(mTextSelectionEnd);
+                out.writeIntArray(mLineCharOffsets);
+                out.writeIntArray(mLineBaselines);
+                out.writeString(mHint);
+            }
+        }
+    }
+}
diff --git a/android/view/displayhash/DisplayHash.java b/android/view/displayhash/DisplayHash.java
new file mode 100644
index 0000000..4ec0a59
--- /dev/null
+++ b/android/view/displayhash/DisplayHash.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.displayhash;
+
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+/**
+ * The DisplayHash used to validate information about what was present on screen.
+ */
+public final class DisplayHash implements Parcelable {
+    /**
+     * The timestamp when the hash was generated.
+     */
+    @CurrentTimeMillisLong
+    private final long mTimeMillis;
+
+    /**
+     * The bounds of the requested area to generate the hash. This is in window space passed in
+     * by the client.
+     */
+    @NonNull
+    private final Rect mBoundsInWindow;
+
+    /**
+     * The selected hash algorithm that generated the image hash.
+     */
+    @NonNull
+    private final String mHashAlgorithm;
+
+    /**
+     * The image hash generated when creating the DisplayHash.
+     */
+    @NonNull
+    private final byte[] mImageHash;
+
+    /**
+     * The hmac generated by the system and used to verify whether this token was generated by
+     * the system.
+     */
+    @NonNull
+    private final byte[] mHmac;
+
+    /**
+     * Creates a new DisplayHash.
+     *
+     * @param timeMillis       The timestamp when the hash was generated.
+     * @param boundsInWindow   The bounds of the requested area to generate the hash. This is
+     *                         in window space passed in by the client.
+     * @param hashAlgorithm The selected hash algorithm that generated the image hash.
+     * @param imageHash        The image hash generated when creating the DisplayHash.
+     * @param hmac             The hmac generated by the system and used to verify whether this
+     *                         token was generated by
+     *                         the system. This should only be accessed by a system process.
+     * @hide
+     */
+    @SystemApi
+    public DisplayHash(@CurrentTimeMillisLong long timeMillis, @NonNull Rect boundsInWindow,
+            @NonNull String hashAlgorithm, @NonNull byte[] imageHash, @NonNull byte[] hmac) {
+        mTimeMillis = timeMillis;
+        mBoundsInWindow = boundsInWindow;
+        AnnotationValidations.validate(NonNull.class, null, mBoundsInWindow);
+        mHashAlgorithm = hashAlgorithm;
+        AnnotationValidations.validate(NonNull.class, null, mHashAlgorithm);
+        mImageHash = imageHash;
+        AnnotationValidations.validate(NonNull.class, null, mImageHash);
+        mHmac = hmac;
+        AnnotationValidations.validate(NonNull.class, null, mHmac);
+    }
+
+    /**
+     * The timestamp when the hash was generated.
+     *
+     * @hide
+     */
+    @SystemApi
+    @CurrentTimeMillisLong
+    public long getTimeMillis() {
+        return mTimeMillis;
+    }
+
+    /**
+     * The bounds of the requested area to to generate the hash. This is in window space passed in
+     * by the client.
+     *
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    public Rect getBoundsInWindow() {
+        return mBoundsInWindow;
+    }
+
+    /**
+     * The selected hash algorithm that generated the image hash.
+     *
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    public String getHashAlgorithm() {
+        return mHashAlgorithm;
+    }
+
+    /**
+     * The image hash generated when creating the DisplayHash.
+     *
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    public byte[] getImageHash() {
+        return mImageHash;
+    }
+
+    /**
+     * The hmac generated by the system and used to verify whether this token was generated by
+     * the system. This should only be accessed by a system process.
+     *
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    public byte[] getHmac() {
+        return mHmac;
+    }
+
+    /** @hide **/
+    @Override
+    public String toString() {
+        return "DisplayHash { "
+                + "timeMillis = " + mTimeMillis + ", "
+                + "boundsInWindow = " + mBoundsInWindow + ", "
+                + "hashAlgorithm = " + mHashAlgorithm + ", "
+                + "imageHash = " + byteArrayToString(mImageHash) + ", "
+                + "hmac = " + byteArrayToString(mHmac)
+                + " }";
+    }
+
+    private String byteArrayToString(byte[] byteArray) {
+        if (byteArray == null) {
+            return "null";
+        }
+        int iMax = byteArray.length - 1;
+        if (iMax == -1) {
+            return "[]";
+        }
+
+        StringBuilder b = new StringBuilder();
+        b.append('[');
+        for (int i = 0; ; i++) {
+            String formatted = String.format("%02X", byteArray[i] & 0xFF);
+            b.append(formatted);
+            if (i == iMax) {
+                return b.append(']').toString();
+            }
+            b.append(", ");
+        }
+    }
+
+    /** @hide **/
+    @SystemApi
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeLong(mTimeMillis);
+        dest.writeTypedObject(mBoundsInWindow, flags);
+        dest.writeString(mHashAlgorithm);
+        dest.writeByteArray(mImageHash);
+        dest.writeByteArray(mHmac);
+    }
+
+    /** @hide **/
+    @SystemApi
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    private DisplayHash(@NonNull Parcel in) {
+        mTimeMillis = in.readLong();
+        Rect boundsInWindow = in.readTypedObject(Rect.CREATOR);
+        String hashAlgorithm = in.readString();
+        byte[] imageHash = in.createByteArray();
+        byte[] hmac = in.createByteArray();
+
+        mBoundsInWindow = boundsInWindow;
+        AnnotationValidations.validate(NonNull.class, null, mBoundsInWindow);
+        mHashAlgorithm = hashAlgorithm;
+        AnnotationValidations.validate(NonNull.class, null, mHashAlgorithm);
+        mImageHash = imageHash;
+        AnnotationValidations.validate(NonNull.class, null, mImageHash);
+        mHmac = hmac;
+        AnnotationValidations.validate(NonNull.class, null, mHmac);
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<DisplayHash> CREATOR =
+            new Parcelable.Creator<DisplayHash>() {
+                @Override
+                public DisplayHash[] newArray(int size) {
+                    return new DisplayHash[size];
+                }
+
+                @Override
+                public DisplayHash createFromParcel(@NonNull Parcel in) {
+                    return new DisplayHash(in);
+                }
+            };
+}
diff --git a/android/view/displayhash/DisplayHashManager.java b/android/view/displayhash/DisplayHashManager.java
new file mode 100644
index 0000000..4f5fef6
--- /dev/null
+++ b/android/view/displayhash/DisplayHashManager.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.displayhash;
+
+
+import static android.content.Context.DISPLAY_HASH_SERVICE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.os.RemoteException;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.WindowManagerGlobal;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Manages DisplayHash requests. The manager object can be retrieved by calling
+ * {@code Context.getSystemService(Context.DISPLAY_HASH_SERVICE)}
+ */
+@SystemService(DISPLAY_HASH_SERVICE)
+public final class DisplayHashManager {
+    private static final String TAG = "DisplayHashManager";
+
+    private final Object mSupportedHashingAlgorithmLock = new Object();
+
+    @GuardedBy("mSupportedAlgorithmLock")
+    private static Set<String> sSupportedHashAlgorithms;
+
+    /**
+     * @hide
+     */
+    public DisplayHashManager() {
+    }
+
+    /**
+     * Get a Set of DisplayHash algorithms that the device supports.
+     *
+     * @return a String Set of supported hashing algorithms. The String value of one
+     * algorithm should be used when requesting to generate the DisplayHash.
+     */
+    @NonNull
+    public Set<String> getSupportedHashAlgorithms() {
+        synchronized (mSupportedHashingAlgorithmLock) {
+            if (sSupportedHashAlgorithms != null) {
+                return sSupportedHashAlgorithms;
+            }
+
+            try {
+                String[] supportedAlgorithms = WindowManagerGlobal.getWindowManagerService()
+                        .getSupportedDisplayHashAlgorithms();
+                if (supportedAlgorithms == null) {
+                    return Collections.emptySet();
+                }
+                sSupportedHashAlgorithms = new ArraySet<>(supportedAlgorithms);
+                return sSupportedHashAlgorithms;
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to send request getSupportedHashingAlgorithms", e);
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Call to verify that the DisplayHash passed in was generated by the system.
+     *
+     * @param displayHash The hash to verify that it was generated by the system.
+     * @return a {@link VerifiedDisplayHash} if the hash was generated by the system or null
+     * if the hash cannot be verified.
+     */
+    @Nullable
+    public VerifiedDisplayHash verifyDisplayHash(@NonNull DisplayHash displayHash) {
+        try {
+            return WindowManagerGlobal.getWindowManagerService().verifyDisplayHash(displayHash);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to send request verifyImpressionToken", e);
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Call to enable or disable the throttling when generating a display hash. This should only be
+     * used for testing. Throttling is enabled by default.
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.READ_FRAME_BUFFER)
+    public void setDisplayHashThrottlingEnabled(boolean enable) {
+        try {
+            WindowManagerGlobal.getWindowManagerService().setDisplayHashThrottlingEnabled(enable);
+        } catch (RemoteException e) {
+        }
+    }
+}
diff --git a/android/view/displayhash/DisplayHashResultCallback.java b/android/view/displayhash/DisplayHashResultCallback.java
new file mode 100644
index 0000000..6e3d9a8
--- /dev/null
+++ b/android/view/displayhash/DisplayHashResultCallback.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.displayhash;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.graphics.Rect;
+import android.view.View;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
+/**
+ * Use when calling {@link View#generateDisplayHash(String, Rect, Executor,
+ * DisplayHashResultCallback)}.
+ *
+ * The callback will only invoke either {@link #onDisplayHashResult} when the system successfully
+ * generated the {@link DisplayHash} or {@link #onDisplayHashError(int)} when it failed.
+ */
+public interface DisplayHashResultCallback {
+    /**
+     * @hide
+     */
+    String EXTRA_DISPLAY_HASH = "DISPLAY_HASH";
+
+    /**
+     * @hide
+     */
+    String EXTRA_DISPLAY_HASH_ERROR_CODE = "DISPLAY_HASH_ERROR_CODE";
+
+    /**
+     * An unknown error occurred.
+     */
+    int DISPLAY_HASH_ERROR_UNKNOWN = -1;
+
+    /**
+     * The bounds used when requesting the hash hash were invalid or empty.
+     */
+    int DISPLAY_HASH_ERROR_INVALID_BOUNDS = -2;
+
+    /**
+     * The window for the view that requested the hash is no longer around. This can happen if the
+     * window is getting torn down.
+     */
+    int DISPLAY_HASH_ERROR_MISSING_WINDOW = -3;
+
+    /**
+     * The view that requested the hash is not visible on screen. This could either mean
+     * that the view bounds are offscreen, window bounds are offscreen, view is not visible, or
+     * window is not visible.
+     */
+    int DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN = -4;
+
+    /**
+     * The hash algorithm sent to generate the hash was invalid. This means the value is not one
+     * of the supported values in {@link DisplayHashManager#getSupportedHashAlgorithms()}
+     */
+    int DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM = -5;
+
+    /**
+     * The caller requested to generate the hash too frequently. The caller should try again at a
+     * after some time has passed to ensure the system isn't overloaded.
+     */
+    int DISPLAY_HASH_ERROR_TOO_MANY_REQUESTS = -6;
+
+    /** @hide */
+    @IntDef(prefix = {"DISPLAY_HASH_ERROR_"}, value = {
+            DISPLAY_HASH_ERROR_UNKNOWN,
+            DISPLAY_HASH_ERROR_INVALID_BOUNDS,
+            DISPLAY_HASH_ERROR_MISSING_WINDOW,
+            DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN,
+            DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM,
+            DISPLAY_HASH_ERROR_TOO_MANY_REQUESTS
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface DisplayHashErrorCode {
+    }
+
+    /**
+     * Callback invoked when calling
+     * {@link android.view.View#generateDisplayHash(String, Rect, Executor,
+     * DisplayHashResultCallback)}
+     *
+     * @param displayHash The DisplayHash generated. If the hash cannot be generated,
+     *                    {@link #onDisplayHashError(int)} will be called instead
+     */
+    void onDisplayHashResult(@NonNull DisplayHash displayHash);
+
+    /**
+     * Callback invoked when
+     * {@link android.view.View#generateDisplayHash(String, Rect, Executor,
+     * DisplayHashResultCallback)} results in an error and cannot generate a display hash.
+     *
+     * @param errorCode One of the values in {@link DisplayHashErrorCode}
+     */
+    void onDisplayHashError(@DisplayHashErrorCode int errorCode);
+}
diff --git a/android/view/displayhash/VerifiedDisplayHash.java b/android/view/displayhash/VerifiedDisplayHash.java
new file mode 100644
index 0000000..b17c068
--- /dev/null
+++ b/android/view/displayhash/VerifiedDisplayHash.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.displayhash;
+
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.NonNull;
+import android.graphics.Rect;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * The verified display hash used to validate information about what was present on screen.
+ */
+@DataClass(genToString = true, genAidl = true)
+public final class VerifiedDisplayHash implements Parcelable {
+    /**
+     * The timestamp when the hash was generated.
+     */
+    @CurrentTimeMillisLong
+    private final long mTimeMillis;
+
+    /**
+     * The bounds of the requested area to generate the hash. This is in window space passed in
+     * by the client.
+     */
+    @NonNull
+    private final Rect mBoundsInWindow;
+
+    /**
+     * The selected hash algorithm that generated the image hash.
+     */
+    @NonNull
+    private final String mHashAlgorithm;
+
+    /**
+     * The image hash generated when creating the DisplayHash.
+     */
+    @NonNull
+    private final byte[] mImageHash;
+
+    private String imageHashToString() {
+        return byteArrayToString(mImageHash);
+    }
+
+    private String byteArrayToString(byte[] byteArray) {
+        if (byteArray == null) {
+            return "null";
+        }
+        int iMax = byteArray.length - 1;
+        if (iMax == -1) {
+            return "[]";
+        }
+
+        StringBuilder b = new StringBuilder();
+        b.append('[');
+        for (int i = 0; ; i++) {
+            String formatted = String.format("%02X", byteArray[i] & 0xFF);
+            b.append(formatted);
+            if (i == iMax) {
+                return b.append(']').toString();
+            }
+            b.append(", ");
+        }
+    }
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/displayhash/VerifiedDisplayHash.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new VerifiedDisplayHash.
+     *
+     * @param timeMillis
+     *   The timestamp when the hash was generated.
+     * @param boundsInWindow
+     *   The bounds of the requested area to generate the hash. This is in window space passed in
+     *   by the client.
+     * @param hashAlgorithm
+     *   The selected hash algorithm that generated the image hash.
+     * @param imageHash
+     *   The image hash generated when creating the DisplayHash.
+     */
+    @DataClass.Generated.Member
+    public VerifiedDisplayHash(
+            @CurrentTimeMillisLong long timeMillis,
+            @NonNull Rect boundsInWindow,
+            @NonNull String hashAlgorithm,
+            @NonNull byte[] imageHash) {
+        this.mTimeMillis = timeMillis;
+        com.android.internal.util.AnnotationValidations.validate(
+                CurrentTimeMillisLong.class, null, mTimeMillis);
+        this.mBoundsInWindow = boundsInWindow;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mBoundsInWindow);
+        this.mHashAlgorithm = hashAlgorithm;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mHashAlgorithm);
+        this.mImageHash = imageHash;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mImageHash);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * The timestamp when the hash was generated.
+     */
+    @DataClass.Generated.Member
+    public @CurrentTimeMillisLong long getTimeMillis() {
+        return mTimeMillis;
+    }
+
+    /**
+     * The bounds of the requested area to generate the hash. This is in window space passed in
+     * by the client.
+     */
+    @DataClass.Generated.Member
+    public @NonNull Rect getBoundsInWindow() {
+        return mBoundsInWindow;
+    }
+
+    /**
+     * The selected hash algorithm that generated the image hash.
+     */
+    @DataClass.Generated.Member
+    public @NonNull String getHashAlgorithm() {
+        return mHashAlgorithm;
+    }
+
+    /**
+     * The image hash generated when creating the DisplayHash.
+     */
+    @DataClass.Generated.Member
+    public @NonNull byte[] getImageHash() {
+        return mImageHash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "VerifiedDisplayHash { " +
+                "timeMillis = " + mTimeMillis + ", " +
+                "boundsInWindow = " + mBoundsInWindow + ", " +
+                "hashAlgorithm = " + mHashAlgorithm + ", " +
+                "imageHash = " + imageHashToString() +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeLong(mTimeMillis);
+        dest.writeTypedObject(mBoundsInWindow, flags);
+        dest.writeString(mHashAlgorithm);
+        dest.writeByteArray(mImageHash);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ VerifiedDisplayHash(@NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        long timeMillis = in.readLong();
+        Rect boundsInWindow = (Rect) in.readTypedObject(Rect.CREATOR);
+        String hashAlgorithm = in.readString();
+        byte[] imageHash = in.createByteArray();
+
+        this.mTimeMillis = timeMillis;
+        com.android.internal.util.AnnotationValidations.validate(
+                CurrentTimeMillisLong.class, null, mTimeMillis);
+        this.mBoundsInWindow = boundsInWindow;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mBoundsInWindow);
+        this.mHashAlgorithm = hashAlgorithm;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mHashAlgorithm);
+        this.mImageHash = imageHash;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mImageHash);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<VerifiedDisplayHash> CREATOR
+            = new Parcelable.Creator<VerifiedDisplayHash>() {
+        @Override
+        public VerifiedDisplayHash[] newArray(int size) {
+            return new VerifiedDisplayHash[size];
+        }
+
+        @Override
+        public VerifiedDisplayHash createFromParcel(@NonNull android.os.Parcel in) {
+            return new VerifiedDisplayHash(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1617747271440L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/view/displayhash/VerifiedDisplayHash.java",
+            inputSignatures = "private final @android.annotation.CurrentTimeMillisLong long mTimeMillis\nprivate final @android.annotation.NonNull android.graphics.Rect mBoundsInWindow\nprivate final @android.annotation.NonNull java.lang.String mHashAlgorithm\nprivate final @android.annotation.NonNull byte[] mImageHash\nprivate  java.lang.String imageHashToString()\nprivate  java.lang.String byteArrayToString(byte[])\nclass VerifiedDisplayHash extends java.lang.Object implements [android.os.Parcelable]\[email protected](genToString=true, genAidl=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/android/view/inputmethod/BaseInputConnection.java b/android/view/inputmethod/BaseInputConnection.java
new file mode 100644
index 0000000..c4540b0
--- /dev/null
+++ b/android/view/inputmethod/BaseInputConnection.java
@@ -0,0 +1,982 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.view.inputmethod;
+
+import static android.view.ContentInfo.SOURCE_INPUT_METHOD;
+
+import android.annotation.CallSuper;
+import android.annotation.IntRange;
+import android.annotation.Nullable;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.text.Editable;
+import android.text.NoCopySpan;
+import android.text.Selection;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.method.MetaKeyKeyListener;
+import android.util.Log;
+import android.util.LogPrinter;
+import android.view.ContentInfo;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.View;
+
+import com.android.internal.util.Preconditions;
+
+class ComposingText implements NoCopySpan {
+}
+
+/**
+ * Base class for implementors of the InputConnection interface, taking care
+ * of most of the common behavior for providing a connection to an Editable.
+ * Implementors of this class will want to be sure to implement
+ * {@link #getEditable} to provide access to their own editable object, and
+ * to refer to the documentation in {@link InputConnection}.
+ */
+public class BaseInputConnection implements InputConnection {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "BaseInputConnection";
+    static final Object COMPOSING = new ComposingText();
+
+    /** @hide */
+    protected final InputMethodManager mIMM;
+    final View mTargetView;
+    final boolean mFallbackMode;
+
+    private Object[] mDefaultComposingSpans;
+
+    Editable mEditable;
+    KeyCharacterMap mKeyCharacterMap;
+
+    BaseInputConnection(InputMethodManager mgr, boolean fullEditor) {
+        mIMM = mgr;
+        mTargetView = null;
+        mFallbackMode = !fullEditor;
+    }
+
+    public BaseInputConnection(View targetView, boolean fullEditor) {
+        mIMM = (InputMethodManager)targetView.getContext().getSystemService(
+                Context.INPUT_METHOD_SERVICE);
+        mTargetView = targetView;
+        mFallbackMode = !fullEditor;
+    }
+
+    public static final void removeComposingSpans(Spannable text) {
+        text.removeSpan(COMPOSING);
+        Object[] sps = text.getSpans(0, text.length(), Object.class);
+        if (sps != null) {
+            for (int i=sps.length-1; i>=0; i--) {
+                Object o = sps[i];
+                if ((text.getSpanFlags(o)&Spanned.SPAN_COMPOSING) != 0) {
+                    text.removeSpan(o);
+                }
+            }
+        }
+    }
+
+    public static void setComposingSpans(Spannable text) {
+        setComposingSpans(text, 0, text.length());
+    }
+
+    /** @hide */
+    public static void setComposingSpans(Spannable text, int start, int end) {
+        final Object[] sps = text.getSpans(start, end, Object.class);
+        if (sps != null) {
+            for (int i=sps.length-1; i>=0; i--) {
+                final Object o = sps[i];
+                if (o == COMPOSING) {
+                    text.removeSpan(o);
+                    continue;
+                }
+
+                final int fl = text.getSpanFlags(o);
+                if ((fl & (Spanned.SPAN_COMPOSING | Spanned.SPAN_POINT_MARK_MASK))
+                        != (Spanned.SPAN_COMPOSING | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)) {
+                    text.setSpan(o, text.getSpanStart(o), text.getSpanEnd(o),
+                            (fl & ~Spanned.SPAN_POINT_MARK_MASK)
+                                    | Spanned.SPAN_COMPOSING
+                                    | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                }
+            }
+        }
+
+        text.setSpan(COMPOSING, start, end,
+                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
+    }
+
+    public static int getComposingSpanStart(Spannable text) {
+        return text.getSpanStart(COMPOSING);
+    }
+
+    public static int getComposingSpanEnd(Spannable text) {
+        return text.getSpanEnd(COMPOSING);
+    }
+
+    /**
+     * Return the target of edit operations.  The default implementation
+     * returns its own fake editable that is just used for composing text;
+     * subclasses that are real text editors should override this and
+     * supply their own.
+     */
+    public Editable getEditable() {
+        if (mEditable == null) {
+            mEditable = Editable.Factory.getInstance().newEditable("");
+            Selection.setSelection(mEditable, 0);
+        }
+        return mEditable;
+    }
+
+    /**
+     * Default implementation does nothing.
+     */
+    public boolean beginBatchEdit() {
+        return false;
+    }
+
+    /**
+     * Default implementation does nothing.
+     */
+    public boolean endBatchEdit() {
+        return false;
+    }
+
+    /**
+     * Called after only the composing region is modified (so it isn't called if the text also
+     * changes).
+     * <p>
+     * Default implementation does nothing.
+     *
+     * @hide
+     */
+    public void endComposingRegionEditInternal() {
+    }
+
+    /**
+     * Default implementation calls {@link #finishComposingText()} and
+     * {@code setImeConsumesInput(false)}.
+     */
+    @CallSuper
+    public void closeConnection() {
+        finishComposingText();
+        setImeConsumesInput(false);
+    }
+
+    /**
+     * Default implementation uses
+     * {@link MetaKeyKeyListener#clearMetaKeyState(long, int)
+     * MetaKeyKeyListener.clearMetaKeyState(long, int)} to clear the state.
+     */
+    public boolean clearMetaKeyStates(int states) {
+        final Editable content = getEditable();
+        if (content == null) return false;
+        MetaKeyKeyListener.clearMetaKeyState(content, states);
+        return true;
+    }
+
+    /**
+     * Default implementation does nothing and returns false.
+     */
+    public boolean commitCompletion(CompletionInfo text) {
+        return false;
+    }
+
+    /**
+     * Default implementation does nothing and returns false.
+     */
+    public boolean commitCorrection(CorrectionInfo correctionInfo) {
+        return false;
+    }
+
+    /**
+     * Default implementation replaces any existing composing text with
+     * the given text.  In addition, only if fallback mode, a key event is
+     * sent for the new text and the current editable buffer cleared.
+     */
+    public boolean commitText(CharSequence text, int newCursorPosition) {
+        if (DEBUG) Log.v(TAG, "commitText " + text);
+        replaceText(text, newCursorPosition, false);
+        sendCurrentText();
+        return true;
+    }
+
+    /**
+     * The default implementation performs the deletion around the current selection position of the
+     * editable text.
+     *
+     * @param beforeLength The number of characters before the cursor to be deleted, in code unit.
+     *        If this is greater than the number of existing characters between the beginning of the
+     *        text and the cursor, then this method does not fail but deletes all the characters in
+     *        that range.
+     * @param afterLength The number of characters after the cursor to be deleted, in code unit.
+     *        If this is greater than the number of existing characters between the cursor and
+     *        the end of the text, then this method does not fail but deletes all the characters in
+     *        that range.
+     *
+     * @return {@code true} when selected text is deleted, {@code false} when either the
+     *         selection is invalid or not yet attached (i.e. selection start or end is -1),
+     *         or the editable text is {@code null}.
+     */
+    public boolean deleteSurroundingText(int beforeLength, int afterLength) {
+        if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength
+                + " / " + afterLength);
+        final Editable content = getEditable();
+        if (content == null) return false;
+
+        beginBatchEdit();
+
+        int a = Selection.getSelectionStart(content);
+        int b = Selection.getSelectionEnd(content);
+
+        if (a > b) {
+            int tmp = a;
+            a = b;
+            b = tmp;
+        }
+
+        // Skip when the selection is not yet attached.
+        if (a == -1 || b == -1) {
+            endBatchEdit();
+            return false;
+        }
+
+        // Ignore the composing text.
+        int ca = getComposingSpanStart(content);
+        int cb = getComposingSpanEnd(content);
+        if (cb < ca) {
+            int tmp = ca;
+            ca = cb;
+            cb = tmp;
+        }
+        if (ca != -1 && cb != -1) {
+            if (ca < a) a = ca;
+            if (cb > b) b = cb;
+        }
+
+        int deleted = 0;
+
+        if (beforeLength > 0) {
+            int start = a - beforeLength;
+            if (start < 0) start = 0;
+
+            final int numDeleteBefore = a - start;
+            if (a >= 0 && numDeleteBefore > 0) {
+                content.delete(start, a);
+                deleted = numDeleteBefore;
+            }
+        }
+
+        if (afterLength > 0) {
+            b = b - deleted;
+
+            int end = b + afterLength;
+            if (end > content.length()) end = content.length();
+
+            final int numDeleteAfter = end - b;
+            if (b >= 0 && numDeleteAfter > 0) {
+                content.delete(b, end);
+            }
+        }
+
+        endBatchEdit();
+
+        return true;
+    }
+
+    private static int INVALID_INDEX = -1;
+    private static int findIndexBackward(final CharSequence cs, final int from,
+            final int numCodePoints) {
+        int currentIndex = from;
+        boolean waitingHighSurrogate = false;
+        final int N = cs.length();
+        if (currentIndex < 0 || N < currentIndex) {
+            return INVALID_INDEX;  // The starting point is out of range.
+        }
+        if (numCodePoints < 0) {
+            return INVALID_INDEX;  // Basically this should not happen.
+        }
+        int remainingCodePoints = numCodePoints;
+        while (true) {
+            if (remainingCodePoints == 0) {
+                return currentIndex;  // Reached to the requested length in code points.
+            }
+
+            --currentIndex;
+            if (currentIndex < 0) {
+                if (waitingHighSurrogate) {
+                    return INVALID_INDEX;  // An invalid surrogate pair is found.
+                }
+                return 0;  // Reached to the beginning of the text w/o any invalid surrogate pair.
+            }
+            final char c = cs.charAt(currentIndex);
+            if (waitingHighSurrogate) {
+                if (!java.lang.Character.isHighSurrogate(c)) {
+                    return INVALID_INDEX;  // An invalid surrogate pair is found.
+                }
+                waitingHighSurrogate = false;
+                --remainingCodePoints;
+                continue;
+            }
+            if (!java.lang.Character.isSurrogate(c)) {
+                --remainingCodePoints;
+                continue;
+            }
+            if (java.lang.Character.isHighSurrogate(c)) {
+                return INVALID_INDEX;  // A invalid surrogate pair is found.
+            }
+            waitingHighSurrogate = true;
+        }
+    }
+
+    private static int findIndexForward(final CharSequence cs, final int from,
+            final int numCodePoints) {
+        int currentIndex = from;
+        boolean waitingLowSurrogate = false;
+        final int N = cs.length();
+        if (currentIndex < 0 || N < currentIndex) {
+            return INVALID_INDEX;  // The starting point is out of range.
+        }
+        if (numCodePoints < 0) {
+            return INVALID_INDEX;  // Basically this should not happen.
+        }
+        int remainingCodePoints = numCodePoints;
+
+        while (true) {
+            if (remainingCodePoints == 0) {
+                return currentIndex;  // Reached to the requested length in code points.
+            }
+
+            if (currentIndex >= N) {
+                if (waitingLowSurrogate) {
+                    return INVALID_INDEX;  // An invalid surrogate pair is found.
+                }
+                return N;  // Reached to the end of the text w/o any invalid surrogate pair.
+            }
+            final char c = cs.charAt(currentIndex);
+            if (waitingLowSurrogate) {
+                if (!java.lang.Character.isLowSurrogate(c)) {
+                    return INVALID_INDEX;  // An invalid surrogate pair is found.
+                }
+                --remainingCodePoints;
+                waitingLowSurrogate = false;
+                ++currentIndex;
+                continue;
+            }
+            if (!java.lang.Character.isSurrogate(c)) {
+                --remainingCodePoints;
+                ++currentIndex;
+                continue;
+            }
+            if (java.lang.Character.isLowSurrogate(c)) {
+                return INVALID_INDEX;  // A invalid surrogate pair is found.
+            }
+            waitingLowSurrogate = true;
+            ++currentIndex;
+        }
+    }
+
+    /**
+     * The default implementation performs the deletion around the current selection position of the
+     * editable text.
+     * @param beforeLength The number of characters before the cursor to be deleted, in code points.
+     *        If this is greater than the number of existing characters between the beginning of the
+     *        text and the cursor, then this method does not fail but deletes all the characters in
+     *        that range.
+     * @param afterLength The number of characters after the cursor to be deleted, in code points.
+     *        If this is greater than the number of existing characters between the cursor and
+     *        the end of the text, then this method does not fail but deletes all the characters in
+     *        that range.
+     */
+    public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
+        if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength
+                + " / " + afterLength);
+        final Editable content = getEditable();
+        if (content == null) return false;
+
+        beginBatchEdit();
+
+        int a = Selection.getSelectionStart(content);
+        int b = Selection.getSelectionEnd(content);
+
+        if (a > b) {
+            int tmp = a;
+            a = b;
+            b = tmp;
+        }
+
+        // Ignore the composing text.
+        int ca = getComposingSpanStart(content);
+        int cb = getComposingSpanEnd(content);
+        if (cb < ca) {
+            int tmp = ca;
+            ca = cb;
+            cb = tmp;
+        }
+        if (ca != -1 && cb != -1) {
+            if (ca < a) a = ca;
+            if (cb > b) b = cb;
+        }
+
+        if (a >= 0 && b >= 0) {
+            final int start = findIndexBackward(content, a, Math.max(beforeLength, 0));
+            if (start != INVALID_INDEX) {
+                final int end = findIndexForward(content, b, Math.max(afterLength, 0));
+                if (end != INVALID_INDEX) {
+                    final int numDeleteBefore = a - start;
+                    if (numDeleteBefore > 0) {
+                        content.delete(start, a);
+                    }
+                    final int numDeleteAfter = end - b;
+                    if (numDeleteAfter > 0) {
+                        content.delete(b - numDeleteBefore, end - numDeleteBefore);
+                    }
+                }
+            }
+            // NOTE: You may think we should return false here if start and/or end is INVALID_INDEX,
+            // but the truth is that IInputConnectionWrapper running in the middle of IPC calls
+            // always returns true to the IME without waiting for the completion of this method as
+            // IInputConnectionWrapper#isAtive() returns true.  This is actually why some methods
+            // including this method look like asynchronous calls from the IME.
+        }
+
+        endBatchEdit();
+
+        return true;
+    }
+
+    /**
+     * The default implementation removes the composing state from the
+     * current editable text.  In addition, only if fallback mode, a key event is
+     * sent for the new text and the current editable buffer cleared.
+     */
+    public boolean finishComposingText() {
+        if (DEBUG) Log.v(TAG, "finishComposingText");
+        final Editable content = getEditable();
+        if (content != null) {
+            beginBatchEdit();
+            removeComposingSpans(content);
+            // Note: sendCurrentText does nothing unless mFallbackMode is set
+            sendCurrentText();
+            endBatchEdit();
+            endComposingRegionEditInternal();
+        }
+        return true;
+    }
+
+    /**
+     * The default implementation uses TextUtils.getCapsMode to get the
+     * cursor caps mode for the current selection position in the editable
+     * text, unless in fallback mode in which case 0 is always returned.
+     */
+    public int getCursorCapsMode(int reqModes) {
+        if (mFallbackMode) return 0;
+
+        final Editable content = getEditable();
+        if (content == null) return 0;
+
+        int a = Selection.getSelectionStart(content);
+        int b = Selection.getSelectionEnd(content);
+
+        if (a > b) {
+            int tmp = a;
+            a = b;
+            b = tmp;
+        }
+
+        return TextUtils.getCapsMode(content, a, reqModes);
+    }
+
+    /**
+     * The default implementation always returns null.
+     */
+    public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
+        return null;
+    }
+
+    /**
+     * The default implementation returns the given amount of text from the
+     * current cursor position in the buffer.
+     */
+    @Nullable
+    public CharSequence getTextBeforeCursor(@IntRange(from = 0) int length, int flags) {
+        Preconditions.checkArgumentNonnegative(length);
+
+        final Editable content = getEditable();
+        if (content == null) return null;
+
+        int a = Selection.getSelectionStart(content);
+        int b = Selection.getSelectionEnd(content);
+
+        if (a > b) {
+            int tmp = a;
+            a = b;
+            b = tmp;
+        }
+
+        if (a <= 0) {
+            return "";
+        }
+
+        if (length > a) {
+            length = a;
+        }
+
+        if ((flags&GET_TEXT_WITH_STYLES) != 0) {
+            return content.subSequence(a - length, a);
+        }
+        return TextUtils.substring(content, a - length, a);
+    }
+
+    /**
+     * The default implementation returns the text currently selected, or null if none is
+     * selected.
+     */
+    public CharSequence getSelectedText(int flags) {
+        final Editable content = getEditable();
+        if (content == null) return null;
+
+        int a = Selection.getSelectionStart(content);
+        int b = Selection.getSelectionEnd(content);
+
+        if (a > b) {
+            int tmp = a;
+            a = b;
+            b = tmp;
+        }
+
+        if (a == b || a < 0) return null;
+
+        if ((flags&GET_TEXT_WITH_STYLES) != 0) {
+            return content.subSequence(a, b);
+        }
+        return TextUtils.substring(content, a, b);
+    }
+
+    /**
+     * The default implementation returns the given amount of text from the
+     * current cursor position in the buffer.
+     */
+    @Nullable
+    public CharSequence getTextAfterCursor(@IntRange(from = 0) int length, int flags) {
+        Preconditions.checkArgumentNonnegative(length);
+
+        final Editable content = getEditable();
+        if (content == null) return null;
+
+        int a = Selection.getSelectionStart(content);
+        int b = Selection.getSelectionEnd(content);
+
+        if (a > b) {
+            int tmp = a;
+            a = b;
+            b = tmp;
+        }
+
+        // Guard against the case where the cursor has not been positioned yet.
+        if (b < 0) {
+            b = 0;
+        }
+
+        if (b + length > content.length()) {
+            length = content.length() - b;
+        }
+
+
+        if ((flags&GET_TEXT_WITH_STYLES) != 0) {
+            return content.subSequence(b, b + length);
+        }
+        return TextUtils.substring(content, b, b + length);
+    }
+
+    /**
+     * The default implementation returns the given amount of text around the current cursor
+     * position in the buffer.
+     */
+    @Nullable
+    public SurroundingText getSurroundingText(
+            @IntRange(from = 0) int beforeLength, @IntRange(from = 0)  int afterLength, int flags) {
+        Preconditions.checkArgumentNonnegative(beforeLength);
+        Preconditions.checkArgumentNonnegative(afterLength);
+
+        final Editable content = getEditable();
+        // If {@link #getEditable()} is null or {@code mEditable} is equal to {@link #getEditable()}
+        // (a.k.a, a fake editable), it means we cannot get valid content from the editable, so
+        // fallback to retrieve surrounding text from other APIs.
+        if (content == null || mEditable == content) {
+            return InputConnection.super.getSurroundingText(beforeLength, afterLength, flags);
+        }
+
+        int selStart = Selection.getSelectionStart(content);
+        int selEnd = Selection.getSelectionEnd(content);
+
+        // Guard against the case where the cursor has not been positioned yet.
+        if (selStart < 0 || selEnd < 0) {
+            return null;
+        }
+
+        if (selStart > selEnd) {
+            int tmp = selStart;
+            selStart = selEnd;
+            selEnd = tmp;
+        }
+
+        int contentLength = content.length();
+        int startPos = selStart - beforeLength;
+        int endPos = selEnd + afterLength;
+
+        // Guards the start and end pos within range [0, contentLength].
+        startPos = Math.max(0, startPos);
+        endPos = Math.min(contentLength, endPos);
+
+        CharSequence surroundingText;
+        if ((flags & GET_TEXT_WITH_STYLES) != 0) {
+            surroundingText = content.subSequence(startPos, endPos);
+        } else {
+            surroundingText = TextUtils.substring(content, startPos, endPos);
+        }
+        return new SurroundingText(
+                surroundingText, selStart - startPos, selEnd - startPos, startPos);
+    }
+
+    /**
+     * The default implementation turns this into the enter key.
+     */
+    public boolean performEditorAction(int actionCode) {
+        long eventTime = SystemClock.uptimeMillis();
+        sendKeyEvent(new KeyEvent(eventTime, eventTime,
+                KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
+                KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
+                KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
+                | KeyEvent.FLAG_EDITOR_ACTION));
+        sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
+                KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
+                KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
+                KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
+                | KeyEvent.FLAG_EDITOR_ACTION));
+        return true;
+    }
+
+    /**
+     * The default implementation does nothing.
+     */
+    public boolean performContextMenuAction(int id) {
+        return false;
+    }
+
+    /**
+     * The default implementation does nothing.
+     */
+    public boolean performPrivateCommand(String action, Bundle data) {
+        return false;
+    }
+
+    /**
+     * The default implementation does nothing.
+     */
+    public boolean requestCursorUpdates(int cursorUpdateMode) {
+        return false;
+    }
+
+    public Handler getHandler() {
+        return null;
+    }
+
+    /**
+     * The default implementation places the given text into the editable,
+     * replacing any existing composing text.  The new text is marked as
+     * in a composing state with the composing style.
+     */
+    public boolean setComposingText(CharSequence text, int newCursorPosition) {
+        if (DEBUG) Log.v(TAG, "setComposingText " + text);
+        replaceText(text, newCursorPosition, true);
+        return true;
+    }
+
+    public boolean setComposingRegion(int start, int end) {
+        final Editable content = getEditable();
+        if (content != null) {
+            beginBatchEdit();
+            removeComposingSpans(content);
+            int a = start;
+            int b = end;
+            if (a > b) {
+                int tmp = a;
+                a = b;
+                b = tmp;
+            }
+            // Clip the end points to be within the content bounds.
+            final int length = content.length();
+            if (a < 0) a = 0;
+            if (b < 0) b = 0;
+            if (a > length) a = length;
+            if (b > length) b = length;
+
+            ensureDefaultComposingSpans();
+            if (mDefaultComposingSpans != null) {
+                for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
+                    content.setSpan(mDefaultComposingSpans[i], a, b,
+                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
+                }
+            }
+
+            content.setSpan(COMPOSING, a, b,
+                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
+
+            // Note: sendCurrentText does nothing unless mFallbackMode is set
+            sendCurrentText();
+            endBatchEdit();
+            endComposingRegionEditInternal();
+        }
+        return true;
+    }
+
+    /**
+     * The default implementation changes the selection position in the
+     * current editable text.
+     */
+    public boolean setSelection(int start, int end) {
+        if (DEBUG) Log.v(TAG, "setSelection " + start + ", " + end);
+        final Editable content = getEditable();
+        if (content == null) return false;
+        int len = content.length();
+        if (start > len || end > len || start < 0 || end < 0) {
+            // If the given selection is out of bounds, just ignore it.
+            // Most likely the text was changed out from under the IME,
+            // and the IME is going to have to update all of its state
+            // anyway.
+            return true;
+        }
+        if (start == end && MetaKeyKeyListener.getMetaState(content,
+                MetaKeyKeyListener.META_SELECTING) != 0) {
+            // If we are in selection mode, then we want to extend the
+            // selection instead of replacing it.
+            Selection.extendSelection(content, start);
+        } else {
+            Selection.setSelection(content, start, end);
+        }
+        return true;
+    }
+
+    /**
+     * Provides standard implementation for sending a key event to the window
+     * attached to the input connection's view.
+     */
+    public boolean sendKeyEvent(KeyEvent event) {
+        mIMM.dispatchKeyEventFromInputMethod(mTargetView, event);
+        return false;
+    }
+
+    /**
+     * Updates InputMethodManager with the current fullscreen mode.
+     */
+    public boolean reportFullscreenMode(boolean enabled) {
+        return true;
+    }
+
+    private void sendCurrentText() {
+        if (!mFallbackMode) {
+            return;
+        }
+
+        Editable content = getEditable();
+        if (content != null) {
+            final int N = content.length();
+            if (N == 0) {
+                return;
+            }
+            if (N == 1) {
+                // If it's 1 character, we have a chance of being
+                // able to generate normal key events...
+                if (mKeyCharacterMap == null) {
+                    mKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
+                }
+                char[] chars = new char[1];
+                content.getChars(0, 1, chars, 0);
+                KeyEvent[] events = mKeyCharacterMap.getEvents(chars);
+                if (events != null) {
+                    for (int i=0; i<events.length; i++) {
+                        if (DEBUG) Log.v(TAG, "Sending: " + events[i]);
+                        sendKeyEvent(events[i]);
+                    }
+                    content.clear();
+                    return;
+                }
+            }
+
+            // Otherwise, revert to the special key event containing
+            // the actual characters.
+            KeyEvent event = new KeyEvent(SystemClock.uptimeMillis(),
+                    content.toString(), KeyCharacterMap.VIRTUAL_KEYBOARD, 0);
+            sendKeyEvent(event);
+            content.clear();
+        }
+    }
+
+    private void ensureDefaultComposingSpans() {
+        if (mDefaultComposingSpans == null) {
+            Context context;
+            if (mTargetView != null) {
+                context = mTargetView.getContext();
+            } else if (mIMM.mCurRootView != null) {
+                final View servedView = mIMM.mCurRootView.getImeFocusController().getServedView();
+                context = servedView != null ? servedView.getContext() : null;
+            } else {
+                context = null;
+            }
+            if (context != null) {
+                TypedArray ta = context.getTheme()
+                        .obtainStyledAttributes(new int[] {
+                                com.android.internal.R.attr.candidatesTextStyleSpans
+                        });
+                CharSequence style = ta.getText(0);
+                ta.recycle();
+                if (style != null && style instanceof Spanned) {
+                    mDefaultComposingSpans = ((Spanned)style).getSpans(
+                            0, style.length(), Object.class);
+                }
+            }
+        }
+    }
+
+    private void replaceText(CharSequence text, int newCursorPosition,
+            boolean composing) {
+        final Editable content = getEditable();
+        if (content == null) {
+            return;
+        }
+
+        beginBatchEdit();
+
+        // delete composing text set previously.
+        int a = getComposingSpanStart(content);
+        int b = getComposingSpanEnd(content);
+
+        if (DEBUG) Log.v(TAG, "Composing span: " + a + " to " + b);
+
+        if (b < a) {
+            int tmp = a;
+            a = b;
+            b = tmp;
+        }
+
+        if (a != -1 && b != -1) {
+            removeComposingSpans(content);
+        } else {
+            a = Selection.getSelectionStart(content);
+            b = Selection.getSelectionEnd(content);
+            if (a < 0) a = 0;
+            if (b < 0) b = 0;
+            if (b < a) {
+                int tmp = a;
+                a = b;
+                b = tmp;
+            }
+        }
+
+        if (composing) {
+            Spannable sp = null;
+            if (!(text instanceof Spannable)) {
+                sp = new SpannableStringBuilder(text);
+                text = sp;
+                ensureDefaultComposingSpans();
+                if (mDefaultComposingSpans != null) {
+                    for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
+                        sp.setSpan(mDefaultComposingSpans[i], 0, sp.length(),
+                                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
+                    }
+                }
+            } else {
+                sp = (Spannable)text;
+            }
+            setComposingSpans(sp);
+        }
+
+        if (DEBUG) Log.v(TAG, "Replacing from " + a + " to " + b + " with \""
+                + text + "\", composing=" + composing
+                + ", type=" + text.getClass().getCanonicalName());
+
+        if (DEBUG) {
+            LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
+            lp.println("Current text:");
+            TextUtils.dumpSpans(content, lp, "  ");
+            lp.println("Composing text:");
+            TextUtils.dumpSpans(text, lp, "  ");
+        }
+
+        // Position the cursor appropriately, so that after replacing the
+        // desired range of text it will be located in the correct spot.
+        // This allows us to deal with filters performing edits on the text
+        // we are providing here.
+        if (newCursorPosition > 0) {
+            newCursorPosition += b - 1;
+        } else {
+            newCursorPosition += a;
+        }
+        if (newCursorPosition < 0) newCursorPosition = 0;
+        if (newCursorPosition > content.length())
+            newCursorPosition = content.length();
+        Selection.setSelection(content, newCursorPosition);
+
+        content.replace(a, b, text);
+
+        if (DEBUG) {
+            LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
+            lp.println("Final text:");
+            TextUtils.dumpSpans(content, lp, "  ");
+        }
+
+        endBatchEdit();
+    }
+
+    /**
+     * Default implementation which invokes {@link View#performReceiveContent} on the target
+     * view if the view {@link View#getReceiveContentMimeTypes allows} content insertion;
+     * otherwise returns false without any side effects.
+     */
+    public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
+        ClipDescription description = inputContentInfo.getDescription();
+        if (mTargetView.getReceiveContentMimeTypes() == null) {
+            if (DEBUG) {
+                Log.d(TAG, "Can't insert content from IME: content=" + description);
+            }
+            return false;
+        }
+        if ((flags & InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
+            try {
+                inputContentInfo.requestPermission();
+            } catch (Exception e) {
+                Log.w(TAG, "Can't insert content from IME; requestPermission() failed", e);
+                return false;
+            }
+        }
+        final ClipData clip = new ClipData(inputContentInfo.getDescription(),
+                new ClipData.Item(inputContentInfo.getContentUri()));
+        final ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_INPUT_METHOD)
+                .setLinkUri(inputContentInfo.getLinkUri())
+                .setExtras(opts)
+                .setInputContentInfo(inputContentInfo)
+                .build();
+        return mTargetView.performReceiveContent(payload) == null;
+    }
+}
diff --git a/android/view/inputmethod/CompletionInfo.java b/android/view/inputmethod/CompletionInfo.java
new file mode 100644
index 0000000..eee8a62
--- /dev/null
+++ b/android/view/inputmethod/CompletionInfo.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2007-2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.view.inputmethod;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Information about a single text completion that an editor has reported to
+ * an input method.
+ *
+ * <p>This class encapsulates a completion offered by an application
+ * that wants it to be presented to the user by the IME. Usually, apps
+ * present their completions directly in a scrolling list for example
+ * (UI developers will usually use or extend
+ * {@see android.widget.AutoCompleteTextView} to implement this).
+ * However, in some cases, the editor may not be visible, as in the
+ * case in extract mode where the IME has taken over the full
+ * screen. In this case, the editor can choose to send their
+ * completions to the IME for display.
+ *
+ * <p>Most applications who want to send completions to an IME should use
+ * {@link android.widget.AutoCompleteTextView} as this class makes this
+ * process easy. In this case, the application would not have to deal directly
+ * with this class.
+ *
+ * <p>An application who implements its own editor and wants direct control
+ * over this would create an array of CompletionInfo objects, and send it to the IME using
+ * {@link InputMethodManager#displayCompletions(View, CompletionInfo[])}.
+ * The IME would present the completions however they see fit, and
+ * call back to the editor through
+ * {@link InputConnection#commitCompletion(CompletionInfo)}.
+ * The application can then pick up the commit event by overriding
+ * {@link android.widget.TextView#onCommitCompletion(CompletionInfo)}.
+ */
+public final class CompletionInfo implements Parcelable {
+    private final long mId;
+    private final int mPosition;
+    private final CharSequence mText;
+    private final CharSequence mLabel;
+
+    /**
+     * Create a simple completion with just text, no label.
+     *
+     * @param id An id that get passed as is (to the editor's discretion)
+     * @param index An index that get passed as is. Typically this is the
+     * index in the list of completions inside the editor.
+     * @param text The text that should be inserted into the editor when
+     * this completion is chosen.
+     */
+    public CompletionInfo(long id, int index, CharSequence text) {
+        mId = id;
+        mPosition = index;
+        mText = text;
+        mLabel = null;
+    }
+
+    /**
+     * Create a full completion with both text and label. The text is
+     * what will get inserted into the editor, while the label is what
+     * the IME should display. If they are the same, use the version
+     * of the constructor without a `label' argument.
+     *
+     * @param id An id that get passed as is (to the editor's discretion)
+     * @param index An index that get passed as is. Typically this is the
+     * index in the list of completions inside the editor.
+     * @param text The text that should be inserted into the editor when
+     * this completion is chosen.
+     * @param label The text that the IME should be showing among the
+     * completions list.
+     */
+    public CompletionInfo(long id, int index, CharSequence text, CharSequence label) {
+        mId = id;
+        mPosition = index;
+        mText = text;
+        mLabel = label;
+    }
+
+    private CompletionInfo(Parcel source) {
+        mId = source.readLong();
+        mPosition = source.readInt();
+        mText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+        mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+    }
+
+    /**
+     * Return the abstract identifier for this completion, typically
+     * corresponding to the id associated with it in the original adapter.
+     */
+    public long getId() {
+        return mId;
+    }
+
+    /**
+     * Return the original position of this completion, typically
+     * corresponding to its position in the original adapter.
+     */
+    public int getPosition() {
+        return mPosition;
+    }
+
+    /**
+     * Return the actual text associated with this completion.  This is the
+     * real text that will be inserted into the editor if the user selects it.
+     */
+    public CharSequence getText() {
+        return mText;
+    }
+
+    /**
+     * Return the user-visible label for the completion, or null if the plain
+     * text should be shown.  If non-null, this will be what the user sees as
+     * the completion option instead of the actual text.
+     */
+    public CharSequence getLabel() {
+        return mLabel;
+    }
+
+    @Override
+    public String toString() {
+        return "CompletionInfo{#" + mPosition + " \"" + mText
+                + "\" id=" + mId + " label=" + mLabel + "}";
+    }
+
+    /**
+     * Used to package this object into a {@link Parcel}.
+     *
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(mId);
+        dest.writeInt(mPosition);
+        TextUtils.writeToParcel(mText, dest, flags);
+        TextUtils.writeToParcel(mLabel, dest, flags);
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final @android.annotation.NonNull Parcelable.Creator<CompletionInfo> CREATOR
+            = new Parcelable.Creator<CompletionInfo>() {
+        public CompletionInfo createFromParcel(Parcel source) {
+            return new CompletionInfo(source);
+        }
+
+        public CompletionInfo[] newArray(int size) {
+            return new CompletionInfo[size];
+        }
+    };
+
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/android/view/inputmethod/CorrectionInfo.java b/android/view/inputmethod/CorrectionInfo.java
new file mode 100644
index 0000000..6db5784
--- /dev/null
+++ b/android/view/inputmethod/CorrectionInfo.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2007-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.view.inputmethod;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Information about a single text correction that an editor has reported to
+ * an input method.
+ */
+public final class CorrectionInfo implements Parcelable {
+    private final int mOffset;
+    private final CharSequence mOldText;
+    private final CharSequence mNewText;
+
+    /**
+     * @param offset The offset in the edited text where the old and new text start.
+     * @param oldText The old text that has been replaced.
+     * @param newText The replacement text.
+     */
+    public CorrectionInfo(int offset, CharSequence oldText, CharSequence newText) {
+        mOffset = offset;
+        mOldText = oldText;
+        mNewText = newText;
+    }
+
+    private CorrectionInfo(Parcel source) {
+        mOffset = source.readInt();
+        mOldText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+        mNewText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+    }
+
+    /**
+     * Return the offset position of this correction in the text. Both the {@link #getOldText()} and
+     * {@link #getNewText()} start at this offset.
+     */
+    public int getOffset() {
+        return mOffset;
+    }
+
+    /**
+     * Return the text that has actually been typed by the user, and which has been corrected.
+     */
+    public CharSequence getOldText() {
+        return mOldText;
+    }
+
+    /**
+     * Return the new text that corrects what was typed by the user.
+     */
+    public CharSequence getNewText() {
+        return mNewText;
+    }
+
+    @Override
+    public String toString() {
+        return "CorrectionInfo{#" + mOffset + " \"" + mOldText + "\" -> \"" + mNewText + "\"}";
+    }
+
+    /**
+     * Used to package this object into a {@link Parcel}.
+     *
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mOffset);
+        TextUtils.writeToParcel(mOldText, dest, flags);
+        TextUtils.writeToParcel(mNewText, dest, flags);
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final @android.annotation.NonNull Parcelable.Creator<CorrectionInfo> CREATOR =
+            new Parcelable.Creator<CorrectionInfo>() {
+                public CorrectionInfo createFromParcel(Parcel source) {
+                    return new CorrectionInfo(source);
+                }
+                public CorrectionInfo[] newArray(int size) {
+                    return new CorrectionInfo[size];
+                }
+            };
+
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/android/view/inputmethod/CursorAnchorInfo.java b/android/view/inputmethod/CursorAnchorInfo.java
new file mode 100644
index 0000000..fbc9470
--- /dev/null
+++ b/android/view/inputmethod/CursorAnchorInfo.java
@@ -0,0 +1,580 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.view.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.Layout;
+import android.text.SpannedString;
+import android.text.TextUtils;
+import android.view.inputmethod.SparseRectFArray.SparseRectFArrayBuilder;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Positional information about the text insertion point and characters in the composition string.
+ *
+ * <p>This class encapsulates locations of the text insertion point and the composition string in
+ * the screen coordinates so that IMEs can render their UI components near where the text is
+ * actually inserted.</p>
+ */
+public final class CursorAnchorInfo implements Parcelable {
+    /**
+     * The pre-computed hash code.
+     */
+    private final int mHashCode;
+
+    /**
+     * The index of the first character of the selected text (inclusive). {@code -1} when there is
+     * no text selection.
+     */
+    private final int mSelectionStart;
+    /**
+     * The index of the first character of the selected text (exclusive). {@code -1} when there is
+     * no text selection.
+     */
+    private final int mSelectionEnd;
+
+    /**
+     * The index of the first character of the composing text (inclusive). {@code -1} when there is
+     * no composing text.
+     */
+    private final int mComposingTextStart;
+    /**
+     * The text, tracked as a composing region.
+     */
+    private final CharSequence mComposingText;
+
+    /**
+     * Flags of the insertion marker. See {@link #FLAG_HAS_VISIBLE_REGION} for example.
+     */
+    private final int mInsertionMarkerFlags;
+    /**
+     * Horizontal position of the insertion marker, in the local coordinates that will be
+     * transformed with the transformation matrix when rendered on the screen. This should be
+     * calculated or compatible with {@link Layout#getPrimaryHorizontal(int)}. This can be
+     * {@code java.lang.Float.NaN} when no value is specified.
+     */
+    private final float mInsertionMarkerHorizontal;
+    /**
+     * Vertical position of the insertion marker, in the local coordinates that will be
+     * transformed with the transformation matrix when rendered on the screen. This should be
+     * calculated or compatible with {@link Layout#getLineTop(int)}. This can be
+     * {@code java.lang.Float.NaN} when no value is specified.
+     */
+    private final float mInsertionMarkerTop;
+    /**
+     * Vertical position of the insertion marker, in the local coordinates that will be
+     * transformed with the transformation matrix when rendered on the screen. This should be
+     * calculated or compatible with {@link Layout#getLineBaseline(int)}. This can be
+     * {@code java.lang.Float.NaN} when no value is specified.
+     */
+    private final float mInsertionMarkerBaseline;
+    /**
+     * Vertical position of the insertion marker, in the local coordinates that will be
+     * transformed with the transformation matrix when rendered on the screen. This should be
+     * calculated or compatible with {@link Layout#getLineBottom(int)}. This can be
+     * {@code java.lang.Float.NaN} when no value is specified.
+     */
+    private final float mInsertionMarkerBottom;
+
+    /**
+     * Container of rectangular position of characters, keyed with character index in a unit of
+     * Java chars, in the local coordinates that will be transformed with the transformation matrix
+     * when rendered on the screen.
+     */
+    private final SparseRectFArray mCharacterBoundsArray;
+
+    /**
+     * Transformation matrix that is applied to any positional information of this class to
+     * transform local coordinates into screen coordinates.
+     */
+    @NonNull
+    private final float[] mMatrixValues;
+
+    /**
+     * Flag for {@link #getInsertionMarkerFlags()} and {@link #getCharacterBoundsFlags(int)}: the
+     * insertion marker or character bounds have at least one visible region.
+     */
+    public static final int FLAG_HAS_VISIBLE_REGION = 0x01;
+
+    /**
+     * Flag for {@link #getInsertionMarkerFlags()} and {@link #getCharacterBoundsFlags(int)}: the
+     * insertion marker or character bounds have at least one invisible (clipped) region.
+     */
+    public static final int FLAG_HAS_INVISIBLE_REGION = 0x02;
+
+    /**
+     * Flag for {@link #getInsertionMarkerFlags()} and {@link #getCharacterBoundsFlags(int)}: the
+     * insertion marker or character bounds is placed at right-to-left (RTL) character.
+     */
+    public static final int FLAG_IS_RTL = 0x04;
+
+    public CursorAnchorInfo(final Parcel source) {
+        mHashCode = source.readInt();
+        mSelectionStart = source.readInt();
+        mSelectionEnd = source.readInt();
+        mComposingTextStart = source.readInt();
+        mComposingText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+        mInsertionMarkerFlags = source.readInt();
+        mInsertionMarkerHorizontal = source.readFloat();
+        mInsertionMarkerTop = source.readFloat();
+        mInsertionMarkerBaseline = source.readFloat();
+        mInsertionMarkerBottom = source.readFloat();
+        mCharacterBoundsArray = source.readParcelable(SparseRectFArray.class.getClassLoader());
+        mMatrixValues = source.createFloatArray();
+    }
+
+    /**
+     * Used to package this object into a {@link Parcel}.
+     *
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mHashCode);
+        dest.writeInt(mSelectionStart);
+        dest.writeInt(mSelectionEnd);
+        dest.writeInt(mComposingTextStart);
+        TextUtils.writeToParcel(mComposingText, dest, flags);
+        dest.writeInt(mInsertionMarkerFlags);
+        dest.writeFloat(mInsertionMarkerHorizontal);
+        dest.writeFloat(mInsertionMarkerTop);
+        dest.writeFloat(mInsertionMarkerBaseline);
+        dest.writeFloat(mInsertionMarkerBottom);
+        dest.writeParcelable(mCharacterBoundsArray, flags);
+        dest.writeFloatArray(mMatrixValues);
+    }
+
+    @Override
+    public int hashCode(){
+        return mHashCode;
+    }
+
+    /**
+     * Compares two float values. Returns {@code true} if {@code a} and {@code b} are
+     * {@link Float#NaN} at the same time.
+     */
+    private static boolean areSameFloatImpl(final float a, final float b) {
+        if (Float.isNaN(a) && Float.isNaN(b)) {
+            return true;
+        }
+        return a == b;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object obj){
+        if (obj == null) {
+            return false;
+        }
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof CursorAnchorInfo)) {
+            return false;
+        }
+        final CursorAnchorInfo that = (CursorAnchorInfo) obj;
+        if (hashCode() != that.hashCode()) {
+            return false;
+        }
+
+        // Check fields that are not covered by hashCode() first.
+
+        if (mSelectionStart != that.mSelectionStart || mSelectionEnd != that.mSelectionEnd) {
+            return false;
+        }
+
+        if (mInsertionMarkerFlags != that.mInsertionMarkerFlags
+                || !areSameFloatImpl(mInsertionMarkerHorizontal, that.mInsertionMarkerHorizontal)
+                || !areSameFloatImpl(mInsertionMarkerTop, that.mInsertionMarkerTop)
+                || !areSameFloatImpl(mInsertionMarkerBaseline, that.mInsertionMarkerBaseline)
+                || !areSameFloatImpl(mInsertionMarkerBottom, that.mInsertionMarkerBottom)) {
+            return false;
+        }
+
+        if (!Objects.equals(mCharacterBoundsArray, that.mCharacterBoundsArray)) {
+            return false;
+        }
+
+        // Following fields are (partially) covered by hashCode().
+
+        if (mComposingTextStart != that.mComposingTextStart
+                || !Objects.equals(mComposingText, that.mComposingText)) {
+            return false;
+        }
+
+        // We do not use Arrays.equals(float[], float[]) to keep the previous behavior regarding
+        // NaN, 0.0f, and -0.0f.
+        if (mMatrixValues.length != that.mMatrixValues.length) {
+            return false;
+        }
+        for (int i = 0; i < mMatrixValues.length; ++i) {
+            if (mMatrixValues[i] != that.mMatrixValues[i]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "CursorAnchorInfo{mHashCode=" + mHashCode
+                + " mSelection=" + mSelectionStart + "," + mSelectionEnd
+                + " mComposingTextStart=" + mComposingTextStart
+                + " mComposingText=" + Objects.toString(mComposingText)
+                + " mInsertionMarkerFlags=" + mInsertionMarkerFlags
+                + " mInsertionMarkerHorizontal=" + mInsertionMarkerHorizontal
+                + " mInsertionMarkerTop=" + mInsertionMarkerTop
+                + " mInsertionMarkerBaseline=" + mInsertionMarkerBaseline
+                + " mInsertionMarkerBottom=" + mInsertionMarkerBottom
+                + " mCharacterBoundsArray=" + Objects.toString(mCharacterBoundsArray)
+                + " mMatrix=" + Arrays.toString(mMatrixValues)
+                + "}";
+    }
+
+    /**
+     * Builder for {@link CursorAnchorInfo}. This class is not designed to be thread-safe.
+     */
+    public static final class Builder {
+        private int mSelectionStart = -1;
+        private int mSelectionEnd = -1;
+        private int mComposingTextStart = -1;
+        private CharSequence mComposingText = null;
+        private float mInsertionMarkerHorizontal = Float.NaN;
+        private float mInsertionMarkerTop = Float.NaN;
+        private float mInsertionMarkerBaseline = Float.NaN;
+        private float mInsertionMarkerBottom = Float.NaN;
+        private int mInsertionMarkerFlags = 0;
+        private SparseRectFArrayBuilder mCharacterBoundsArrayBuilder = null;
+        private float[] mMatrixValues = null;
+        private boolean mMatrixInitialized = false;
+
+        /**
+         * Sets the text range of the selection. Calling this can be skipped if there is no
+         * selection.
+         */
+        public Builder setSelectionRange(final int newStart, final int newEnd) {
+            mSelectionStart = newStart;
+            mSelectionEnd = newEnd;
+            return this;
+        }
+
+        /**
+         * Sets the text range of the composing text. Calling this can be skipped if there is
+         * no composing text.
+         * @param composingTextStart index where the composing text starts.
+         * @param composingText the entire composing text.
+         */
+        public Builder setComposingText(final int composingTextStart,
+            final CharSequence composingText) {
+            mComposingTextStart = composingTextStart;
+            if (composingText == null) {
+                mComposingText = null;
+            } else {
+                // Make a snapshot of the given char sequence.
+                mComposingText = new SpannedString(composingText);
+            }
+            return this;
+        }
+
+        /**
+         * Sets the location of the text insertion point (zero width cursor) as a rectangle in
+         * local coordinates. Calling this can be skipped when there is no text insertion point;
+         * however if there is an insertion point, editors must call this method.
+         * @param horizontalPosition horizontal position of the insertion marker, in the local
+         * coordinates that will be transformed with the transformation matrix when rendered on the
+         * screen. This should be calculated or compatible with
+         * {@link Layout#getPrimaryHorizontal(int)}.
+         * @param lineTop vertical position of the insertion marker, in the local coordinates that
+         * will be transformed with the transformation matrix when rendered on the screen. This
+         * should be calculated or compatible with {@link Layout#getLineTop(int)}.
+         * @param lineBaseline vertical position of the insertion marker, in the local coordinates
+         * that will be transformed with the transformation matrix when rendered on the screen. This
+         * should be calculated or compatible with {@link Layout#getLineBaseline(int)}.
+         * @param lineBottom vertical position of the insertion marker, in the local coordinates
+         * that will be transformed with the transformation matrix when rendered on the screen. This
+         * should be calculated or compatible with {@link Layout#getLineBottom(int)}.
+         * @param flags flags of the insertion marker. See {@link #FLAG_HAS_VISIBLE_REGION} for
+         * example.
+         */
+        public Builder setInsertionMarkerLocation(final float horizontalPosition,
+                final float lineTop, final float lineBaseline, final float lineBottom,
+                final int flags){
+            mInsertionMarkerHorizontal = horizontalPosition;
+            mInsertionMarkerTop = lineTop;
+            mInsertionMarkerBaseline = lineBaseline;
+            mInsertionMarkerBottom = lineBottom;
+            mInsertionMarkerFlags = flags;
+            return this;
+        }
+
+        /**
+         * Adds the bounding box of the character specified with the index.
+         *
+         * @param index index of the character in Java chars units. Must be specified in
+         * ascending order across successive calls.
+         * @param left x coordinate of the left edge of the character in local coordinates.
+         * @param top y coordinate of the top edge of the character in local coordinates.
+         * @param right x coordinate of the right edge of the character in local coordinates.
+         * @param bottom y coordinate of the bottom edge of the character in local coordinates.
+         * @param flags flags for this character bounds. See {@link #FLAG_HAS_VISIBLE_REGION},
+         * {@link #FLAG_HAS_INVISIBLE_REGION} and {@link #FLAG_IS_RTL}. These flags must be
+         * specified when necessary.
+         * @throws IllegalArgumentException If the index is a negative value, or not greater than
+         * all of the previously called indices.
+         */
+        public Builder addCharacterBounds(final int index, final float left, final float top,
+                final float right, final float bottom, final int flags) {
+            if (index < 0) {
+                throw new IllegalArgumentException("index must not be a negative integer.");
+            }
+            if (mCharacterBoundsArrayBuilder == null) {
+                mCharacterBoundsArrayBuilder = new SparseRectFArrayBuilder();
+            }
+            mCharacterBoundsArrayBuilder.append(index, left, top, right, bottom, flags);
+            return this;
+        }
+
+        /**
+         * Sets the matrix that transforms local coordinates into screen coordinates.
+         * @param matrix transformation matrix from local coordinates into screen coordinates. null
+         * is interpreted as an identity matrix.
+         */
+        public Builder setMatrix(final Matrix matrix) {
+            if (mMatrixValues == null) {
+                mMatrixValues = new float[9];
+            }
+            (matrix != null ? matrix : Matrix.IDENTITY_MATRIX).getValues(mMatrixValues);
+            mMatrixInitialized = true;
+            return this;
+        }
+
+        /**
+         * @return {@link CursorAnchorInfo} using parameters in this {@link Builder}.
+         * @throws IllegalArgumentException if one or more positional parameters are specified but
+         * the coordinate transformation matrix is not provided via {@link #setMatrix(Matrix)}.
+         */
+        public CursorAnchorInfo build() {
+            if (!mMatrixInitialized) {
+                // Coordinate transformation matrix is mandatory when at least one positional
+                // parameter is specified.
+                final boolean hasCharacterBounds = (mCharacterBoundsArrayBuilder != null
+                        && !mCharacterBoundsArrayBuilder.isEmpty());
+                if (hasCharacterBounds
+                        || !Float.isNaN(mInsertionMarkerHorizontal)
+                        || !Float.isNaN(mInsertionMarkerTop)
+                        || !Float.isNaN(mInsertionMarkerBaseline)
+                        || !Float.isNaN(mInsertionMarkerBottom)) {
+                    throw new IllegalArgumentException("Coordinate transformation matrix is " +
+                            "required when positional parameters are specified.");
+                }
+            }
+            return new CursorAnchorInfo(this);
+        }
+
+        /**
+         * Resets the internal state so that this instance can be reused to build another
+         * instance of {@link CursorAnchorInfo}.
+         */
+        public void reset() {
+            mSelectionStart = -1;
+            mSelectionEnd = -1;
+            mComposingTextStart = -1;
+            mComposingText = null;
+            mInsertionMarkerFlags = 0;
+            mInsertionMarkerHorizontal = Float.NaN;
+            mInsertionMarkerTop = Float.NaN;
+            mInsertionMarkerBaseline = Float.NaN;
+            mInsertionMarkerBottom = Float.NaN;
+            mMatrixInitialized = false;
+            if (mCharacterBoundsArrayBuilder != null) {
+                mCharacterBoundsArrayBuilder.reset();
+            }
+        }
+    }
+
+    private CursorAnchorInfo(final Builder builder) {
+        mSelectionStart = builder.mSelectionStart;
+        mSelectionEnd = builder.mSelectionEnd;
+        mComposingTextStart = builder.mComposingTextStart;
+        mComposingText = builder.mComposingText;
+        mInsertionMarkerFlags = builder.mInsertionMarkerFlags;
+        mInsertionMarkerHorizontal = builder.mInsertionMarkerHorizontal;
+        mInsertionMarkerTop = builder.mInsertionMarkerTop;
+        mInsertionMarkerBaseline = builder.mInsertionMarkerBaseline;
+        mInsertionMarkerBottom = builder.mInsertionMarkerBottom;
+        mCharacterBoundsArray = builder.mCharacterBoundsArrayBuilder != null
+                ? builder.mCharacterBoundsArrayBuilder.build() : null;
+        mMatrixValues = new float[9];
+        if (builder.mMatrixInitialized) {
+            System.arraycopy(builder.mMatrixValues, 0, mMatrixValues, 0, 9);
+        } else {
+            Matrix.IDENTITY_MATRIX.getValues(mMatrixValues);
+        }
+
+        // To keep hash function simple, we only use some complex objects for hash.
+        int hash = Objects.hashCode(mComposingText);
+        hash *= 31;
+        hash += Arrays.hashCode(mMatrixValues);
+        mHashCode = hash;
+    }
+
+    /**
+     * Returns the index where the selection starts.
+     * @return {@code -1} if there is no selection.
+     */
+    public int getSelectionStart() {
+        return mSelectionStart;
+    }
+
+    /**
+     * Returns the index where the selection ends.
+     * @return {@code -1} if there is no selection.
+     */
+    public int getSelectionEnd() {
+        return mSelectionEnd;
+    }
+
+    /**
+     * Returns the index where the composing text starts.
+     * @return {@code -1} if there is no composing text.
+     */
+    public int getComposingTextStart() {
+        return mComposingTextStart;
+    }
+
+    /**
+     * Returns the entire composing text.
+     * @return {@code null} if there is no composition.
+     */
+    public CharSequence getComposingText() {
+        return mComposingText;
+    }
+
+    /**
+     * Returns the flag of the insertion marker.
+     * @return the flag of the insertion marker. {@code 0} if no flag is specified.
+     */
+    public int getInsertionMarkerFlags() {
+        return mInsertionMarkerFlags;
+    }
+
+    /**
+     * Returns the horizontal start of the insertion marker, in the local coordinates that will
+     * be transformed with {@link #getMatrix()} when rendered on the screen.
+     * @return x coordinate that is compatible with {@link Layout#getPrimaryHorizontal(int)}.
+     * Pay special care to RTL/LTR handling.
+     * {@code java.lang.Float.NaN} if not specified.
+     * @see Layout#getPrimaryHorizontal(int)
+     */
+    public float getInsertionMarkerHorizontal() {
+        return mInsertionMarkerHorizontal;
+    }
+
+    /**
+     * Returns the vertical top position of the insertion marker, in the local coordinates that
+     * will be transformed with {@link #getMatrix()} when rendered on the screen.
+     * @return y coordinate that is compatible with {@link Layout#getLineTop(int)}.
+     * {@code java.lang.Float.NaN} if not specified.
+     */
+    public float getInsertionMarkerTop() {
+        return mInsertionMarkerTop;
+    }
+
+    /**
+     * Returns the vertical baseline position of the insertion marker, in the local coordinates
+     * that will be transformed with {@link #getMatrix()} when rendered on the screen.
+     * @return y coordinate that is compatible with {@link Layout#getLineBaseline(int)}.
+     * {@code java.lang.Float.NaN} if not specified.
+     */
+    public float getInsertionMarkerBaseline() {
+        return mInsertionMarkerBaseline;
+    }
+
+    /**
+     * Returns the vertical bottom position of the insertion marker, in the local coordinates
+     * that will be transformed with {@link #getMatrix()} when rendered on the screen.
+     * @return y coordinate that is compatible with {@link Layout#getLineBottom(int)}.
+     * {@code java.lang.Float.NaN} if not specified.
+     */
+    public float getInsertionMarkerBottom() {
+        return mInsertionMarkerBottom;
+    }
+
+    /**
+     * Returns a new instance of {@link RectF} that indicates the location of the character
+     * specified with the index.
+     * @param index index of the character in a Java chars.
+     * @return the character bounds in local coordinates as a new instance of {@link RectF}.
+     */
+    public RectF getCharacterBounds(final int index) {
+        if (mCharacterBoundsArray == null) {
+            return null;
+        }
+        return mCharacterBoundsArray.get(index);
+    }
+
+    /**
+     * Returns the flags associated with the character bounds specified with the index.
+     * @param index index of the character in a Java chars.
+     * @return {@code 0} if no flag is specified.
+     */
+    public int getCharacterBoundsFlags(final int index) {
+        if (mCharacterBoundsArray == null) {
+            return 0;
+        }
+        return mCharacterBoundsArray.getFlags(index, 0);
+    }
+
+    /**
+     * Returns a new instance of {@link android.graphics.Matrix} that indicates the transformation
+     * matrix that is to be applied other positional data in this class.
+     * @return a new instance (copy) of the transformation matrix.
+     */
+    public Matrix getMatrix() {
+        final Matrix matrix = new Matrix();
+        matrix.setValues(mMatrixValues);
+        return matrix;
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final @android.annotation.NonNull Parcelable.Creator<CursorAnchorInfo> CREATOR
+            = new Parcelable.Creator<CursorAnchorInfo>() {
+        @Override
+        public CursorAnchorInfo createFromParcel(Parcel source) {
+            return new CursorAnchorInfo(source);
+        }
+
+        @Override
+        public CursorAnchorInfo[] newArray(int size) {
+            return new CursorAnchorInfo[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/android/view/inputmethod/DumpableInputConnection.java b/android/view/inputmethod/DumpableInputConnection.java
new file mode 100644
index 0000000..9819a57
--- /dev/null
+++ b/android/view/inputmethod/DumpableInputConnection.java
@@ -0,0 +1,32 @@
+/*
+ * 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.view.inputmethod;
+
+import android.annotation.NonNull;
+import android.util.proto.ProtoOutputStream;
+
+/** @hide */
+public interface DumpableInputConnection {
+
+    /**
+     * Method used to dump state of InputConnection implementations of interest.
+     *
+     * @param proto Stream to write the state to
+     * @param fieldId FieldId of DumpableInputConnection as defined in the parent message
+     */
+    void dumpDebug(@NonNull ProtoOutputStream proto, long fieldId);
+}
diff --git a/android/view/inputmethod/EditorInfo.java b/android/view/inputmethod/EditorInfo.java
new file mode 100644
index 0000000..4a52b1f
--- /dev/null
+++ b/android/view/inputmethod/EditorInfo.java
@@ -0,0 +1,1057 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.view.inputmethod.EditorInfoProto.FIELD_ID;
+import static android.view.inputmethod.EditorInfoProto.IME_OPTIONS;
+import static android.view.inputmethod.EditorInfoProto.INPUT_TYPE;
+import static android.view.inputmethod.EditorInfoProto.PACKAGE_NAME;
+import static android.view.inputmethod.EditorInfoProto.PRIVATE_IME_OPTIONS;
+import static android.view.inputmethod.EditorInfoProto.TARGET_INPUT_METHOD_USER_ID;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.content.res.Configuration;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.util.Printer;
+import android.util.proto.ProtoOutputStream;
+import android.view.View;
+import android.view.autofill.AutofillId;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * An EditorInfo describes several attributes of a text editing object
+ * that an input method is communicating with (typically an EditText), most
+ * importantly the type of text content it contains and the current cursor position.
+ */
+public class EditorInfo implements InputType, Parcelable {
+    /**
+     * Masks for {@link inputType}
+     *
+     * <pre>
+     * |-------|-------|-------|-------|
+     *                              1111 TYPE_MASK_CLASS
+     *                      11111111     TYPE_MASK_VARIATION
+     *          111111111111             TYPE_MASK_FLAGS
+     * |-------|-------|-------|-------|
+     *                                   TYPE_NULL
+     * |-------|-------|-------|-------|
+     *                                 1 TYPE_CLASS_TEXT
+     *                             1     TYPE_TEXT_VARIATION_URI
+     *                            1      TYPE_TEXT_VARIATION_EMAIL_ADDRESS
+     *                            11     TYPE_TEXT_VARIATION_EMAIL_SUBJECT
+     *                           1       TYPE_TEXT_VARIATION_SHORT_MESSAGE
+     *                           1 1     TYPE_TEXT_VARIATION_LONG_MESSAGE
+     *                           11      TYPE_TEXT_VARIATION_PERSON_NAME
+     *                           111     TYPE_TEXT_VARIATION_POSTAL_ADDRESS
+     *                          1        TYPE_TEXT_VARIATION_PASSWORD
+     *                          1  1     TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
+     *                          1 1      TYPE_TEXT_VARIATION_WEB_EDIT_TEXT
+     *                          1 11     TYPE_TEXT_VARIATION_FILTER
+     *                          11       TYPE_TEXT_VARIATION_PHONETIC
+     *                          11 1     TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS
+     *                          111      TYPE_TEXT_VARIATION_WEB_PASSWORD
+     *                     1             TYPE_TEXT_FLAG_CAP_CHARACTERS
+     *                    1              TYPE_TEXT_FLAG_CAP_WORDS
+     *                   1               TYPE_TEXT_FLAG_CAP_SENTENCES
+     *                  1                TYPE_TEXT_FLAG_AUTO_CORRECT
+     *                 1                 TYPE_TEXT_FLAG_AUTO_COMPLETE
+     *                1                  TYPE_TEXT_FLAG_MULTI_LINE
+     *               1                   TYPE_TEXT_FLAG_IME_MULTI_LINE
+     *              1                    TYPE_TEXT_FLAG_NO_SUGGESTIONS
+     * |-------|-------|-------|-------|
+     *                                1  TYPE_CLASS_NUMBER
+     *                             1     TYPE_NUMBER_VARIATION_PASSWORD
+     *                     1             TYPE_NUMBER_FLAG_SIGNED
+     *                    1              TYPE_NUMBER_FLAG_DECIMAL
+     * |-------|-------|-------|-------|
+     *                                11 TYPE_CLASS_PHONE
+     * |-------|-------|-------|-------|
+     *                               1   TYPE_CLASS_DATETIME
+     *                             1     TYPE_DATETIME_VARIATION_DATE
+     *                            1      TYPE_DATETIME_VARIATION_TIME
+     * |-------|-------|-------|-------|</pre>
+     */
+
+    /**
+     * The content type of the text box, whose bits are defined by
+     * {@link InputType}.
+     *
+     * @see InputType
+     * @see #TYPE_MASK_CLASS
+     * @see #TYPE_MASK_VARIATION
+     * @see #TYPE_MASK_FLAGS
+     */
+    public int inputType = TYPE_NULL;
+
+    /**
+     * Set of bits in {@link #imeOptions} that provide alternative actions
+     * associated with the "enter" key.  This both helps the IME provide
+     * better feedback about what the enter key will do, and also allows it
+     * to provide alternative mechanisms for providing that command.
+     */
+    public static final int IME_MASK_ACTION = 0x000000ff;
+
+    /**
+     * Bits of {@link #IME_MASK_ACTION}: no specific action has been
+     * associated with this editor, let the editor come up with its own if
+     * it can.
+     */
+    public static final int IME_ACTION_UNSPECIFIED = 0x00000000;
+
+    /**
+     * Bits of {@link #IME_MASK_ACTION}: there is no available action.
+     */
+    public static final int IME_ACTION_NONE = 0x00000001;
+
+    /**
+     * Bits of {@link #IME_MASK_ACTION}: the action key performs a "go"
+     * operation to take the user to the target of the text they typed.
+     * Typically used, for example, when entering a URL.
+     */
+    public static final int IME_ACTION_GO = 0x00000002;
+
+    /**
+     * Bits of {@link #IME_MASK_ACTION}: the action key performs a "search"
+     * operation, taking the user to the results of searching for the text
+     * they have typed (in whatever context is appropriate).
+     */
+    public static final int IME_ACTION_SEARCH = 0x00000003;
+
+    /**
+     * Bits of {@link #IME_MASK_ACTION}: the action key performs a "send"
+     * operation, delivering the text to its target.  This is typically used
+     * when composing a message in IM or SMS where sending is immediate.
+     */
+    public static final int IME_ACTION_SEND = 0x00000004;
+
+    /**
+     * Bits of {@link #IME_MASK_ACTION}: the action key performs a "next"
+     * operation, taking the user to the next field that will accept text.
+     */
+    public static final int IME_ACTION_NEXT = 0x00000005;
+
+    /**
+     * Bits of {@link #IME_MASK_ACTION}: the action key performs a "done"
+     * operation, typically meaning there is nothing more to input and the
+     * IME will be closed.
+     */
+    public static final int IME_ACTION_DONE = 0x00000006;
+
+    /**
+     * Bits of {@link #IME_MASK_ACTION}: like {@link #IME_ACTION_NEXT}, but
+     * for moving to the previous field.  This will normally not be used to
+     * specify an action (since it precludes {@link #IME_ACTION_NEXT}), but
+     * can be returned to the app if it sets {@link #IME_FLAG_NAVIGATE_PREVIOUS}.
+     */
+    public static final int IME_ACTION_PREVIOUS = 0x00000007;
+
+    /**
+     * Flag of {@link #imeOptions}: used to request that the IME should not update any personalized
+     * data such as typing history and personalized language model based on what the user typed on
+     * this text editing object.  Typical use cases are:
+     * <ul>
+     *     <li>When the application is in a special mode, where user's activities are expected to be
+     *     not recorded in the application's history.  Some web browsers and chat applications may
+     *     have this kind of modes.</li>
+     *     <li>When storing typing history does not make much sense.  Specifying this flag in typing
+     *     games may help to avoid typing history from being filled up with words that the user is
+     *     less likely to type in their daily life.  Another example is that when the application
+     *     already knows that the expected input is not a valid word (e.g. a promotion code that is
+     *     not a valid word in any natural language).</li>
+     * </ul>
+     *
+     * <p>Applications need to be aware that the flag is not a guarantee, and some IMEs may not
+     * respect it.</p>
+     */
+    public static final int IME_FLAG_NO_PERSONALIZED_LEARNING = 0x1000000;
+
+    /**
+     * Flag of {@link #imeOptions}: used to request that the IME never go
+     * into fullscreen mode.
+     * By default, IMEs may go into full screen mode when they think
+     * it's appropriate, for example on small screens in landscape
+     * orientation where displaying a software keyboard may occlude
+     * such a large portion of the screen that the remaining part is
+     * too small to meaningfully display the application UI.
+     * If this flag is set, compliant IMEs will never go into full screen mode,
+     * and always leave some space to display the application UI.
+     * Applications need to be aware that the flag is not a guarantee, and
+     * some IMEs may ignore it.
+     */
+    public static final int IME_FLAG_NO_FULLSCREEN = 0x2000000;
+
+    /**
+     * Flag of {@link #imeOptions}: like {@link #IME_FLAG_NAVIGATE_NEXT}, but
+     * specifies there is something interesting that a backward navigation
+     * can focus on.  If the user selects the IME's facility to backward
+     * navigate, this will show up in the application as an {@link #IME_ACTION_PREVIOUS}
+     * at {@link InputConnection#performEditorAction(int)
+     * InputConnection.performEditorAction(int)}.
+     */
+    public static final int IME_FLAG_NAVIGATE_PREVIOUS = 0x4000000;
+
+    /**
+     * Flag of {@link #imeOptions}: used to specify that there is something
+     * interesting that a forward navigation can focus on. This is like using
+     * {@link #IME_ACTION_NEXT}, except allows the IME to be multiline (with
+     * an enter key) as well as provide forward navigation.  Note that some
+     * IMEs may not be able to do this, especially when running on a small
+     * screen where there is little space.  In that case it does not need to
+     * present a UI for this option.  Like {@link #IME_ACTION_NEXT}, if the
+     * user selects the IME's facility to forward navigate, this will show up
+     * in the application at {@link InputConnection#performEditorAction(int)
+     * InputConnection.performEditorAction(int)}.
+     */
+    public static final int IME_FLAG_NAVIGATE_NEXT = 0x8000000;
+
+    /**
+     * Flag of {@link #imeOptions}: used to specify that the IME does not need
+     * to show its extracted text UI.  For input methods that may be fullscreen,
+     * often when in landscape mode, this allows them to be smaller and let part
+     * of the application be shown behind, through transparent UI parts in the
+     * fullscreen IME. The part of the UI visible to the user may not be responsive
+     * to touch because the IME will receive touch events, which may confuse the
+     * user; use {@link #IME_FLAG_NO_FULLSCREEN} instead for a better experience.
+     * Using this flag is discouraged and it may become deprecated in the future.
+     * Its meaning is unclear in some situations and it may not work appropriately
+     * on older versions of the platform.
+     */
+    public static final int IME_FLAG_NO_EXTRACT_UI = 0x10000000;
+
+    /**
+     * Flag of {@link #imeOptions}: used in conjunction with one of the actions
+     * masked by {@link #IME_MASK_ACTION}, this indicates that the action
+     * should not be available as an accessory button on the right of the extracted
+     * text when the input method is full-screen. Note that by setting this flag,
+     * there can be cases where the action is simply never available to the
+     * user. Setting this generally means that you think that in fullscreen mode,
+     * where there is little space to show the text, it's not worth taking some
+     * screen real estate to display the action and it should be used instead
+     * to show more text.
+     */
+    public static final int IME_FLAG_NO_ACCESSORY_ACTION = 0x20000000;
+
+    /**
+     * Flag of {@link #imeOptions}: used in conjunction with one of the actions
+     * masked by {@link #IME_MASK_ACTION}. If this flag is not set, IMEs will
+     * normally replace the "enter" key with the action supplied. This flag
+     * indicates that the action should not be available in-line as a replacement
+     * for the "enter" key. Typically this is because the action has such a
+     * significant impact or is not recoverable enough that accidentally hitting
+     * it should be avoided, such as sending a message. Note that
+     * {@link android.widget.TextView} will automatically set this flag for you
+     * on multi-line text views.
+     */
+    public static final int IME_FLAG_NO_ENTER_ACTION = 0x40000000;
+
+    /**
+     * Flag of {@link #imeOptions}: used to request an IME that is capable of
+     * inputting ASCII characters.  The intention of this flag is to ensure that
+     * the user can type Roman alphabet characters in a {@link android.widget.TextView}.
+     * It is typically used for an account ID or password input. A lot of the time,
+     * IMEs are already able to input ASCII even without being told so (such IMEs
+     * already respect this flag in a sense), but there are cases when this is not
+     * the default. For instance, users of languages using a different script like
+     * Arabic, Greek, Hebrew or Russian typically have a keyboard that can't
+     * input ASCII characters by default. Applications need to be
+     * aware that the flag is not a guarantee, and some IMEs may not respect it.
+     * However, it is strongly recommended for IME authors to respect this flag
+     * especially when their IME could end up with a state where only languages
+     * using non-ASCII are enabled.
+     */
+    public static final int IME_FLAG_FORCE_ASCII = 0x80000000;
+
+    /**
+     * Flag of {@link #internalImeOptions}: flag is set when app window containing this
+     * {@link EditorInfo} is using {@link Configuration#ORIENTATION_PORTRAIT} mode.
+     * @hide
+     */
+    public static final int IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT = 0x00000001;
+
+    /**
+     * Generic unspecified type for {@link #imeOptions}.
+     */
+    public static final int IME_NULL = 0x00000000;
+
+    /**
+     * Masks for {@link imeOptions}
+     *
+     * <pre>
+     * |-------|-------|-------|-------|
+     *                              1111 IME_MASK_ACTION
+     * |-------|-------|-------|-------|
+     *                                   IME_ACTION_UNSPECIFIED
+     *                                 1 IME_ACTION_NONE
+     *                                1  IME_ACTION_GO
+     *                                11 IME_ACTION_SEARCH
+     *                               1   IME_ACTION_SEND
+     *                               1 1 IME_ACTION_NEXT
+     *                               11  IME_ACTION_DONE
+     *                               111 IME_ACTION_PREVIOUS
+     *         1                         IME_FLAG_NO_PERSONALIZED_LEARNING
+     *        1                          IME_FLAG_NO_FULLSCREEN
+     *       1                           IME_FLAG_NAVIGATE_PREVIOUS
+     *      1                            IME_FLAG_NAVIGATE_NEXT
+     *     1                             IME_FLAG_NO_EXTRACT_UI
+     *    1                              IME_FLAG_NO_ACCESSORY_ACTION
+     *   1                               IME_FLAG_NO_ENTER_ACTION
+     *  1                                IME_FLAG_FORCE_ASCII
+     * |-------|-------|-------|-------|</pre>
+     */
+
+    /**
+     * Extended type information for the editor, to help the IME better
+     * integrate with it.
+     */
+    public int imeOptions = IME_NULL;
+
+    /**
+     * A string supplying additional information options that are
+     * private to a particular IME implementation.  The string must be
+     * scoped to a package owned by the implementation, to ensure there are
+     * no conflicts between implementations, but other than that you can put
+     * whatever you want in it to communicate with the IME.  For example,
+     * you could have a string that supplies an argument like
+     * <code>"com.example.myapp.SpecialMode=3"</code>.  This field is can be
+     * filled in from the {@link android.R.attr#privateImeOptions}
+     * attribute of a TextView.
+     */
+    public String privateImeOptions = null;
+
+    /**
+     * Masks for {@link internalImeOptions}
+     *
+     * <pre>
+     *                                 1 IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT
+     * |-------|-------|-------|-------|</pre>
+     */
+
+    /**
+     * Same as {@link android.R.attr#imeOptions} but for framework's internal-use only.
+     * @hide
+     */
+    public int internalImeOptions = IME_NULL;
+
+    /**
+     * In some cases an IME may be able to display an arbitrary label for
+     * a command the user can perform, which you can specify here. This is
+     * typically used as the label for the action to use in-line as a replacement
+     * for the "enter" key (see {@link #actionId}). Remember the key where
+     * this will be displayed is typically very small, and there are significant
+     * localization challenges to make this fit in all supported languages. Also
+     * you can not count absolutely on this being used, as some IMEs may
+     * ignore this.
+     */
+    public CharSequence actionLabel = null;
+
+    /**
+     * If {@link #actionLabel} has been given, this is the id for that command
+     * when the user presses its button that is delivered back with
+     * {@link InputConnection#performEditorAction(int)
+     * InputConnection.performEditorAction()}.
+     */
+    public int actionId = 0;
+
+    /**
+     * The text offset of the start of the selection at the time editing
+     * begins; -1 if not known. Keep in mind that, without knowing the cursor
+     * position, many IMEs will not be able to offer their full feature set and
+     * may even behave in unpredictable ways: pass the actual cursor position
+     * here if possible at all.
+     *
+     * <p>Also, this needs to be the cursor position <strong>right now</strong>,
+     * not at some point in the past, even if input is starting in the same text field
+     * as before. When the app is filling this object, input is about to start by
+     * definition, and this value will override any value the app may have passed to
+     * {@link InputMethodManager#updateSelection(android.view.View, int, int, int, int)}
+     * before.</p>
+     */
+    public int initialSelStart = -1;
+
+    /**
+     * <p>The text offset of the end of the selection at the time editing
+     * begins; -1 if not known. Keep in mind that, without knowing the cursor
+     * position, many IMEs will not be able to offer their full feature set and
+     * may behave in unpredictable ways: pass the actual cursor position
+     * here if possible at all.</p>
+     *
+     * <p>Also, this needs to be the cursor position <strong>right now</strong>,
+     * not at some point in the past, even if input is starting in the same text field
+     * as before. When the app is filling this object, input is about to start by
+     * definition, and this value will override any value the app may have passed to
+     * {@link InputMethodManager#updateSelection(android.view.View, int, int, int, int)}
+     * before.</p>
+     */
+    public int initialSelEnd = -1;
+
+    /**
+     * The capitalization mode of the first character being edited in the
+     * text.  Values may be any combination of
+     * {@link TextUtils#CAP_MODE_CHARACTERS TextUtils.CAP_MODE_CHARACTERS},
+     * {@link TextUtils#CAP_MODE_WORDS TextUtils.CAP_MODE_WORDS}, and
+     * {@link TextUtils#CAP_MODE_SENTENCES TextUtils.CAP_MODE_SENTENCES}, though
+     * you should generally just take a non-zero value to mean "start out in
+     * caps mode".
+     */
+    public int initialCapsMode = 0;
+
+    /**
+     * The "hint" text of the text view, typically shown in-line when the
+     * text is empty to tell the user what to enter.
+     */
+    public CharSequence hintText;
+
+    /**
+     * A label to show to the user describing the text they are writing.
+     */
+    public CharSequence label;
+
+    /**
+     * Name of the package that owns this editor.
+     *
+     * <p><strong>IME authors:</strong> In API level 22
+     * {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1} and prior, do not trust this package
+     * name. The system had not verified the consistency between the package name here and
+     * application's uid. Consider to use {@link InputBinding#getUid()}, which is trustworthy.
+     * Starting from {@link android.os.Build.VERSION_CODES#M}, the system verifies the consistency
+     * between this package name and application uid before {@link EditorInfo} is passed to the
+     * input method.</p>
+     *
+     * <p><strong>Editor authors:</strong> Starting from {@link android.os.Build.VERSION_CODES#M},
+     * the application is no longer
+     * able to establish input connections if the package name provided here is inconsistent with
+     * application's uid.</p>
+     */
+    public String packageName;
+
+    /**
+     * Autofill Id for the field that's currently on focus.
+     *
+     * <p> Marked as hide since it's only used by framework.</p>
+     * @hide
+     */
+    public AutofillId autofillId;
+
+    /**
+     * Identifier for the editor's field.  This is optional, and may be
+     * 0.  By default it is filled in with the result of
+     * {@link android.view.View#getId() View.getId()} on the View that
+     * is being edited.
+     */
+    public int fieldId;
+
+    /**
+     * Additional name for the editor's field.  This can supply additional
+     * name information for the field.  By default it is null.  The actual
+     * contents have no meaning.
+     */
+    public String fieldName;
+
+    /**
+     * Any extra data to supply to the input method.  This is for extended
+     * communication with specific input methods; the name fields in the
+     * bundle should be scoped (such as "com.mydomain.im.SOME_FIELD") so
+     * that they don't conflict with others.  This field can be
+     * filled in from the {@link android.R.attr#editorExtras}
+     * attribute of a TextView.
+     */
+    public Bundle extras;
+
+    /**
+     * List of the languages that the user is supposed to switch to no matter what input method
+     * subtype is currently used.  This special "hint" can be used mainly for, but not limited to,
+     * multilingual users who want IMEs to switch language context automatically.
+     *
+     * <p>{@code null} means that no special language "hint" is needed.</p>
+     *
+     * <p><strong>Editor authors:</strong> Specify this only when you are confident that the user
+     * will switch to certain languages in this context no matter what input method subtype is
+     * currently selected.  Otherwise, keep this {@code null}.  Explicit user actions and/or
+     * preferences would be good signals to specify this special "hint",  For example, a chat
+     * application may be able to put the last used language at the top of {@link #hintLocales}
+     * based on whom the user is going to talk, by remembering what language is used in the last
+     * conversation.  Do not specify {@link android.widget.TextView#getTextLocales()} only because
+     * it is used for text rendering.</p>
+     *
+     * @see android.widget.TextView#setImeHintLocales(LocaleList)
+     * @see android.widget.TextView#getImeHintLocales()
+     */
+    @Nullable
+    public LocaleList hintLocales = null;
+
+
+    /**
+     * List of acceptable MIME types for
+     * {@link InputConnection#commitContent(InputContentInfo, int, Bundle)}.
+     *
+     * <p>{@code null} or an empty array means that
+     * {@link InputConnection#commitContent(InputContentInfo, int, Bundle)} is not supported in this
+     * editor.</p>
+     */
+    @Nullable
+    public String[] contentMimeTypes = null;
+
+    /**
+     * If not {@code null}, this editor needs to talk to IMEs that run for the specified user, no
+     * matter what user ID the calling process has.
+     *
+     * <p>Note: This field will be silently ignored when
+     * {@link com.android.server.inputmethod.InputMethodSystemProperty#MULTI_CLIENT_IME_ENABLED} is
+     * {@code true}.</p>
+     *
+     * <p>Note also that pseudo handles such as {@link UserHandle#ALL} are not supported.</p>
+     *
+     * @hide
+     */
+    @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
+    @Nullable
+    public UserHandle targetInputMethodUser = null;
+
+    @IntDef({TrimPolicy.HEAD, TrimPolicy.TAIL})
+    @Retention(RetentionPolicy.SOURCE)
+    @interface TrimPolicy {
+        int HEAD = 0;
+        int TAIL = 1;
+    }
+
+    /**
+     * The maximum length of initialSurroundingText. When the input text from
+     * {@code setInitialSurroundingText(CharSequence)} is longer than this, trimming shall be
+     * performed to keep memory efficiency.
+     */
+    @VisibleForTesting
+    static final int MEMORY_EFFICIENT_TEXT_LENGTH = 2048;
+    /**
+     * When the input text is longer than {@code #MEMORY_EFFICIENT_TEXT_LENGTH}, we start trimming
+     * the input text into three parts: BeforeCursor, Selection, and AfterCursor. We don't want to
+     * trim the Selection but we also don't want it consumes all available space. Therefore, the
+     * maximum acceptable Selection length is half of {@code #MEMORY_EFFICIENT_TEXT_LENGTH}.
+     */
+    @VisibleForTesting
+    static final int MAX_INITIAL_SELECTION_LENGTH =  MEMORY_EFFICIENT_TEXT_LENGTH / 2;
+
+    @Nullable
+    private SurroundingText mInitialSurroundingText = null;
+
+
+    /**
+     * Editors may use this method to provide initial input text to IMEs. As the surrounding text
+     * could be used to provide various input assistance, we recommend editors to provide the
+     * complete initial input text in its {@link View#onCreateInputConnection(EditorInfo)} callback.
+     * The supplied text will then be processed to serve {@code #getInitialTextBeforeCursor},
+     * {@code #getInitialSelectedText}, and {@code #getInitialTextBeforeCursor}. System is allowed
+     * to trim {@code sourceText} for various reasons while keeping the most valuable data to IMEs.
+     *
+     * Starting from {@link VERSION_CODES#S}, spans that do not implement {@link Parcelable} will
+     * be automatically dropped.
+     *
+     * <p><strong>Editor authors: </strong>Providing the initial input text helps reducing IPC calls
+     * for IMEs to provide many modern features right after the connection setup. We recommend
+     * calling this method in your implementation.
+     *
+     * @param sourceText The complete input text.
+     */
+    public void setInitialSurroundingText(@NonNull CharSequence sourceText) {
+        setInitialSurroundingSubText(sourceText, /* subTextStart = */ 0);
+    }
+
+    /**
+     * Editors may use this method to provide initial input text to IMEs. As the surrounding text
+     * could be used to provide various input assistance, we recommend editors to provide the
+     * complete initial input text in its {@link View#onCreateInputConnection(EditorInfo)} callback.
+     * When trimming the input text is needed, call this method instead of
+     * {@code setInitialSurroundingText(CharSequence)} and provide the trimmed position info. Always
+     * try to include the selected text within {@code subText} to give the system best flexibility
+     * to choose where and how to trim {@code subText} when necessary.
+     *
+     * Starting from {@link VERSION_CODES#S}, spans that do not implement {@link Parcelable} will
+     * be automatically dropped.
+     *
+     * @param subText The input text. When it was trimmed, {@code subTextStart} must be provided
+     *                correctly.
+     * @param subTextStart  The position that the input text got trimmed. For example, when the
+     *                      editor wants to trim out the first 10 chars, subTextStart should be 10.
+     */
+    public void setInitialSurroundingSubText(@NonNull CharSequence subText, int subTextStart) {
+        Objects.requireNonNull(subText);
+
+        // For privacy protection reason, we don't carry password inputs to IMEs.
+        if (isPasswordInputType(inputType)) {
+            mInitialSurroundingText = null;
+            return;
+        }
+
+        // Swap selection start and end if necessary.
+        final int subTextSelStart = initialSelStart > initialSelEnd
+                ? initialSelEnd - subTextStart : initialSelStart - subTextStart;
+        final int subTextSelEnd = initialSelStart > initialSelEnd
+                ? initialSelStart - subTextStart : initialSelEnd - subTextStart;
+
+        final int subTextLength = subText.length();
+        // Unknown or invalid selection.
+        if (subTextStart < 0 || subTextSelStart < 0 || subTextSelEnd > subTextLength) {
+            mInitialSurroundingText = null;
+            return;
+        }
+
+        if (subTextLength <= MEMORY_EFFICIENT_TEXT_LENGTH) {
+            mInitialSurroundingText = new SurroundingText(subText, subTextSelStart,
+                    subTextSelEnd, subTextStart);
+            return;
+        }
+
+        trimLongSurroundingText(subText, subTextSelStart, subTextSelEnd, subTextStart);
+    }
+
+    /**
+     * Trims the initial surrounding text when it is over sized. Fundamental trimming rules are:
+     * - The text before the cursor is the most important information to IMEs.
+     * - The text after the cursor is the second important information to IMEs.
+     * - The selected text is the least important information but it shall NEVER be truncated. When
+     *    it is too long, just drop it.
+     *<p><pre>
+     * For example, the subText can be viewed as
+     *     TextBeforeCursor + Selection + TextAfterCursor
+     * The result could be
+     *     1. (maybeTrimmedAtHead)TextBeforeCursor + Selection + TextAfterCursor(maybeTrimmedAtTail)
+     *     2. (maybeTrimmedAtHead)TextBeforeCursor + TextAfterCursor(maybeTrimmedAtTail)</pre>
+     *
+     * @param subText The long text that needs to be trimmed.
+     * @param selStart The text offset of the start of the selection.
+     * @param selEnd The text offset of the end of the selection
+     * @param subTextStart The position that the input text got trimmed.
+     */
+    private void trimLongSurroundingText(CharSequence subText, int selStart, int selEnd,
+            int subTextStart) {
+        final int sourceSelLength = selEnd - selStart;
+        // When the selected text is too long, drop it.
+        final int newSelLength = (sourceSelLength > MAX_INITIAL_SELECTION_LENGTH)
+                ? 0 : sourceSelLength;
+
+        // Distribute rest of length quota to TextBeforeCursor and TextAfterCursor in 4:1 ratio.
+        final int subTextBeforeCursorLength = selStart;
+        final int subTextAfterCursorLength = subText.length() - selEnd;
+        final int maxLengthMinusSelection = MEMORY_EFFICIENT_TEXT_LENGTH - newSelLength;
+        final int possibleMaxBeforeCursorLength =
+                Math.min(subTextBeforeCursorLength, (int) (0.8 * maxLengthMinusSelection));
+        int newAfterCursorLength = Math.min(subTextAfterCursorLength,
+                maxLengthMinusSelection - possibleMaxBeforeCursorLength);
+        int newBeforeCursorLength = Math.min(subTextBeforeCursorLength,
+                maxLengthMinusSelection - newAfterCursorLength);
+
+        // As trimming may happen at the head of TextBeforeCursor, calculate new starting position.
+        int newBeforeCursorHead = subTextBeforeCursorLength - newBeforeCursorLength;
+
+        // We don't want to cut surrogate pairs in the middle. Exam that at the new head and tail.
+        if (isCutOnSurrogate(subText,
+                selStart - newBeforeCursorLength, TrimPolicy.HEAD)) {
+            newBeforeCursorHead = newBeforeCursorHead + 1;
+            newBeforeCursorLength = newBeforeCursorLength - 1;
+        }
+        if (isCutOnSurrogate(subText,
+                selEnd + newAfterCursorLength - 1, TrimPolicy.TAIL)) {
+            newAfterCursorLength = newAfterCursorLength - 1;
+        }
+
+        // Now we know where to trim, compose the initialSurroundingText.
+        final int newTextLength = newBeforeCursorLength + newSelLength + newAfterCursorLength;
+        final CharSequence newInitialSurroundingText;
+        if (newSelLength != sourceSelLength) {
+            final CharSequence beforeCursor = subText.subSequence(newBeforeCursorHead,
+                    newBeforeCursorHead + newBeforeCursorLength);
+            final CharSequence afterCursor = subText.subSequence(selEnd,
+                    selEnd + newAfterCursorLength);
+
+            newInitialSurroundingText = TextUtils.concat(beforeCursor, afterCursor);
+        } else {
+            newInitialSurroundingText = subText
+                    .subSequence(newBeforeCursorHead, newBeforeCursorHead + newTextLength);
+        }
+
+        // As trimming may happen at the head, adjust cursor position in the initialSurroundingText
+        // obj.
+        newBeforeCursorHead = 0;
+        final int newSelHead = newBeforeCursorHead + newBeforeCursorLength;
+        final int newOffset = subTextStart + selStart - newSelHead;
+        mInitialSurroundingText = new SurroundingText(
+                newInitialSurroundingText, newSelHead, newSelHead + newSelLength,
+                newOffset);
+    }
+
+
+    /**
+     * Get <var>length</var> characters of text before the current cursor position. May be
+     * {@code null} when the protocol is not supported.
+     *
+     * @param length The expected length of the text.
+     * @param flags Supplies additional options controlling how the text is returned. May be
+     * either 0 or {@link InputConnection#GET_TEXT_WITH_STYLES}.
+     * @return the text before the cursor position; the length of the returned text might be less
+     * than <var>length</var>. When there is no text before the cursor, an empty string will be
+     * returned. It could also be {@code null} when the editor or system could not support this
+     * protocol.
+     */
+    @Nullable
+    public CharSequence getInitialTextBeforeCursor(
+            @IntRange(from = 0) int length, @InputConnection.GetTextType int flags) {
+        if (mInitialSurroundingText == null) {
+            return null;
+        }
+
+        int selStart = Math.min(mInitialSurroundingText.getSelectionStart(),
+                mInitialSurroundingText.getSelectionEnd());
+        int n = Math.min(length, selStart);
+        return ((flags & InputConnection.GET_TEXT_WITH_STYLES) != 0)
+                ? mInitialSurroundingText.getText().subSequence(selStart - n, selStart)
+                : TextUtils.substring(mInitialSurroundingText.getText(), selStart - n,
+                        selStart);
+    }
+
+    /**
+     * Gets the selected text, if any. May be {@code null} when the protocol is not supported or the
+     * selected text is way too long.
+     *
+     * @param flags Supplies additional options controlling how the text is returned. May be
+     * either 0 or {@link InputConnection#GET_TEXT_WITH_STYLES}.
+     * @return the text that is currently selected, if any. It could be an empty string when there
+     * is no text selected. When {@code null} is returned, the selected text might be too long or
+     * this protocol is not supported.
+     */
+    @Nullable
+    public CharSequence getInitialSelectedText(@InputConnection.GetTextType int flags) {
+        if (mInitialSurroundingText == null) {
+            return null;
+        }
+
+        // Swap selection start and end if necessary.
+        final int correctedTextSelStart = initialSelStart > initialSelEnd
+                ? initialSelEnd : initialSelStart;
+        final int correctedTextSelEnd = initialSelStart > initialSelEnd
+                ? initialSelStart : initialSelEnd;
+
+        final int sourceSelLength = correctedTextSelEnd - correctedTextSelStart;
+        int selStart = mInitialSurroundingText.getSelectionStart();
+        int selEnd = mInitialSurroundingText.getSelectionEnd();
+        if (selStart > selEnd) {
+            int tmp = selStart;
+            selStart = selEnd;
+            selEnd = tmp;
+        }
+        final int selLength = selEnd - selStart;
+        if (initialSelStart < 0 || initialSelEnd < 0 || selLength != sourceSelLength) {
+            return null;
+        }
+
+        return ((flags & InputConnection.GET_TEXT_WITH_STYLES) != 0)
+                ? mInitialSurroundingText.getText().subSequence(selStart, selEnd)
+                : TextUtils.substring(mInitialSurroundingText.getText(), selStart, selEnd);
+    }
+
+    /**
+     * Get <var>length</var> characters of text after the current cursor position. May be
+     * {@code null} when the protocol is not supported.
+     *
+     * @param length The expected length of the text.
+     * @param flags Supplies additional options controlling how the text is returned. May be
+     * either 0 or {@link InputConnection#GET_TEXT_WITH_STYLES}.
+     * @return the text after the cursor position; the length of the returned text might be less
+     * than <var>length</var>. When there is no text after the cursor, an empty string will be
+     * returned. It could also be {@code null} when the editor or system could not support this
+     * protocol.
+     */
+    @Nullable
+    public CharSequence getInitialTextAfterCursor(
+            @IntRange(from = 0) int length, @InputConnection.GetTextType  int flags) {
+        if (mInitialSurroundingText == null) {
+            return null;
+        }
+
+        int surroundingTextLength = mInitialSurroundingText.getText().length();
+        int selEnd = Math.max(mInitialSurroundingText.getSelectionStart(),
+                mInitialSurroundingText.getSelectionEnd());
+        int n = Math.min(length, surroundingTextLength - selEnd);
+        return ((flags & InputConnection.GET_TEXT_WITH_STYLES) != 0)
+                ? mInitialSurroundingText.getText().subSequence(selEnd, selEnd + n)
+                : TextUtils.substring(mInitialSurroundingText.getText(), selEnd, selEnd + n);
+    }
+
+    /**
+     * Gets the surrounding text around the current cursor, with <var>beforeLength</var> characters
+     * of text before the cursor (start of the selection), <var>afterLength</var> characters of text
+     * after the cursor (end of the selection), and all of the selected text.
+     *
+     * <p>The initial surrounding text for return could be trimmed if oversize. Fundamental trimming
+     * rules are:</p>
+     * <ul>
+     *     <li>The text before the cursor is the most important information to IMEs.</li>
+     *     <li>The text after the cursor is the second important information to IMEs.</li>
+     *     <li>The selected text is the least important information but it shall NEVER be truncated.
+     *     When it is too long, just drop it.</li>
+     * </ul>
+     *
+     * <p>For example, the subText can be viewed as TextBeforeCursor + Selection + TextAfterCursor.
+     * The result could be:</p>
+     * <ol>
+     *     <li>(maybeTrimmedAtHead)TextBeforeCursor + Selection
+     *     + TextAfterCursor(maybeTrimmedAtTail)</li>
+     *     <li>(maybeTrimmedAtHead)TextBeforeCursor + TextAfterCursor(maybeTrimmedAtTail)</li>
+     * </ol>
+     *
+     * @param beforeLength The expected length of the text before the cursor.
+     * @param afterLength The expected length of the text after the cursor.
+     * @param flags Supplies additional options controlling how the text is returned. May be either
+     * {@code 0} or {@link InputConnection#GET_TEXT_WITH_STYLES}.
+     * @return an {@link android.view.inputmethod.SurroundingText} object describing the surrounding
+     * text and state of selection, or  {@code null} if the editor or system could not support this
+     * protocol.
+     * @throws IllegalArgumentException if {@code beforeLength} or {@code afterLength} is negative.
+     */
+    @Nullable
+    public SurroundingText getInitialSurroundingText(
+            @IntRange(from = 0) int beforeLength, @IntRange(from = 0)  int afterLength,
+            @InputConnection.GetTextType int flags) {
+        Preconditions.checkArgumentNonnegative(beforeLength);
+        Preconditions.checkArgumentNonnegative(afterLength);
+
+        if (mInitialSurroundingText == null) {
+            return null;
+        }
+
+        int length = mInitialSurroundingText.getText().length();
+        int selStart = mInitialSurroundingText.getSelectionStart();
+        int selEnd = mInitialSurroundingText.getSelectionEnd();
+        if (selStart > selEnd) {
+            int tmp = selStart;
+            selStart = selEnd;
+            selEnd = tmp;
+        }
+
+        int before = Math.min(beforeLength, selStart);
+        int after = Math.min(selEnd + afterLength, length);
+        int offset = selStart - before;
+        CharSequence newText = ((flags & InputConnection.GET_TEXT_WITH_STYLES) != 0)
+                ? mInitialSurroundingText.getText().subSequence(offset, after)
+                : TextUtils.substring(mInitialSurroundingText.getText(), offset, after);
+        int newSelEnd = Math.min(selEnd - offset, length);
+        return new SurroundingText(newText, before, newSelEnd,
+                mInitialSurroundingText.getOffset() + offset);
+    }
+
+    private static boolean isCutOnSurrogate(CharSequence sourceText, int cutPosition,
+            @TrimPolicy int policy) {
+        switch (policy) {
+            case TrimPolicy.HEAD:
+                return Character.isLowSurrogate(sourceText.charAt(cutPosition));
+            case TrimPolicy.TAIL:
+                return Character.isHighSurrogate(sourceText.charAt(cutPosition));
+            default:
+                return false;
+        }
+    }
+
+    private static boolean isPasswordInputType(int inputType) {
+        final int variation =
+                inputType & (TYPE_MASK_CLASS | TYPE_MASK_VARIATION);
+        return variation
+                == (TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_PASSWORD)
+                || variation
+                == (TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_WEB_PASSWORD)
+                || variation
+                == (TYPE_CLASS_NUMBER | TYPE_NUMBER_VARIATION_PASSWORD);
+    }
+
+    /**
+     * Ensure that the data in this EditorInfo is compatible with an application
+     * that was developed against the given target API version.  This can
+     * impact the following input types:
+     * {@link InputType#TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS},
+     * {@link InputType#TYPE_TEXT_VARIATION_WEB_PASSWORD},
+     * {@link InputType#TYPE_NUMBER_VARIATION_NORMAL},
+     * {@link InputType#TYPE_NUMBER_VARIATION_PASSWORD}.
+     *
+     * <p>This is called by the framework for input method implementations;
+     * you should not generally need to call it yourself.
+     *
+     * @param targetSdkVersion The API version number that the compatible
+     * application was developed against.
+     */
+    public final void makeCompatible(int targetSdkVersion) {
+        if (targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
+            switch (inputType&(TYPE_MASK_CLASS|TYPE_MASK_VARIATION)) {
+                case TYPE_CLASS_TEXT|TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS:
+                    inputType = TYPE_CLASS_TEXT|TYPE_TEXT_VARIATION_EMAIL_ADDRESS
+                            | (inputType&TYPE_MASK_FLAGS);
+                    break;
+                case TYPE_CLASS_TEXT|TYPE_TEXT_VARIATION_WEB_PASSWORD:
+                    inputType = TYPE_CLASS_TEXT|TYPE_TEXT_VARIATION_PASSWORD
+                            | (inputType&TYPE_MASK_FLAGS);
+                    break;
+                case TYPE_CLASS_NUMBER|TYPE_NUMBER_VARIATION_NORMAL:
+                case TYPE_CLASS_NUMBER|TYPE_NUMBER_VARIATION_PASSWORD:
+                    inputType = TYPE_CLASS_NUMBER
+                            | (inputType&TYPE_MASK_FLAGS);
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Export the state of {@link EditorInfo} into a protocol buffer output stream.
+     *
+     * @param proto Stream to write the state to
+     * @param fieldId FieldId of ViewRootImpl as defined in the parent message
+     * @hide
+     */
+    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(INPUT_TYPE, inputType);
+        proto.write(IME_OPTIONS, imeOptions);
+        proto.write(PRIVATE_IME_OPTIONS, privateImeOptions);
+        proto.write(PACKAGE_NAME, packageName);
+        proto.write(FIELD_ID, this.fieldId);
+        if (targetInputMethodUser != null) {
+            proto.write(TARGET_INPUT_METHOD_USER_ID, targetInputMethodUser.getIdentifier());
+        }
+        proto.end(token);
+    }
+
+    /**
+     * Write debug output of this object.
+     */
+    public void dump(Printer pw, String prefix) {
+        pw.println(prefix + "inputType=0x" + Integer.toHexString(inputType)
+                + " imeOptions=0x" + Integer.toHexString(imeOptions)
+                + " privateImeOptions=" + privateImeOptions);
+        pw.println(prefix + "actionLabel=" + actionLabel
+                + " actionId=" + actionId);
+        pw.println(prefix + "initialSelStart=" + initialSelStart
+                + " initialSelEnd=" + initialSelEnd
+                + " initialCapsMode=0x"
+                + Integer.toHexString(initialCapsMode));
+        pw.println(prefix + "hintText=" + hintText
+                + " label=" + label);
+        pw.println(prefix + "packageName=" + packageName
+                + " autofillId=" + autofillId
+                + " fieldId=" + fieldId
+                + " fieldName=" + fieldName);
+        pw.println(prefix + "extras=" + extras);
+        pw.println(prefix + "hintLocales=" + hintLocales);
+        pw.println(prefix + "contentMimeTypes=" + Arrays.toString(contentMimeTypes));
+        if (targetInputMethodUser != null) {
+            pw.println(prefix + "targetInputMethodUserId=" + targetInputMethodUser.getIdentifier());
+        }
+    }
+
+    /**
+     * Used to package this object into a {@link Parcel}.
+     *
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(inputType);
+        dest.writeInt(imeOptions);
+        dest.writeString(privateImeOptions);
+        dest.writeInt(internalImeOptions);
+        TextUtils.writeToParcel(actionLabel, dest, flags);
+        dest.writeInt(actionId);
+        dest.writeInt(initialSelStart);
+        dest.writeInt(initialSelEnd);
+        dest.writeInt(initialCapsMode);
+        TextUtils.writeToParcel(hintText, dest, flags);
+        TextUtils.writeToParcel(label, dest, flags);
+        dest.writeString(packageName);
+        dest.writeParcelable(autofillId, flags);
+        dest.writeInt(fieldId);
+        dest.writeString(fieldName);
+        dest.writeBundle(extras);
+        dest.writeBoolean(mInitialSurroundingText != null);
+        if (mInitialSurroundingText != null) {
+            mInitialSurroundingText.writeToParcel(dest, flags);
+        }
+        if (hintLocales != null) {
+            hintLocales.writeToParcel(dest, flags);
+        } else {
+            LocaleList.getEmptyLocaleList().writeToParcel(dest, flags);
+        }
+        dest.writeStringArray(contentMimeTypes);
+        UserHandle.writeToParcel(targetInputMethodUser, dest);
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final @android.annotation.NonNull Parcelable.Creator<EditorInfo> CREATOR =
+            new Parcelable.Creator<EditorInfo>() {
+                public EditorInfo createFromParcel(Parcel source) {
+                    EditorInfo res = new EditorInfo();
+                    res.inputType = source.readInt();
+                    res.imeOptions = source.readInt();
+                    res.privateImeOptions = source.readString();
+                    res.internalImeOptions = source.readInt();
+                    res.actionLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+                    res.actionId = source.readInt();
+                    res.initialSelStart = source.readInt();
+                    res.initialSelEnd = source.readInt();
+                    res.initialCapsMode = source.readInt();
+                    res.hintText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+                    res.label = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+                    res.packageName = source.readString();
+                    res.autofillId = source.readParcelable(AutofillId.class.getClassLoader());
+                    res.fieldId = source.readInt();
+                    res.fieldName = source.readString();
+                    res.extras = source.readBundle();
+                    boolean hasInitialSurroundingText = source.readBoolean();
+                    if (hasInitialSurroundingText) {
+                        res.mInitialSurroundingText =
+                                SurroundingText.CREATOR.createFromParcel(source);
+                    }
+                    LocaleList hintLocales = LocaleList.CREATOR.createFromParcel(source);
+                    res.hintLocales = hintLocales.isEmpty() ? null : hintLocales;
+                    res.contentMimeTypes = source.readStringArray();
+                    res.targetInputMethodUser = UserHandle.readFromParcel(source);
+                    return res;
+                }
+
+                public EditorInfo[] newArray(int size) {
+                    return new EditorInfo[size];
+                }
+            };
+
+    public int describeContents() {
+        return 0;
+    }
+}
\ No newline at end of file
diff --git a/android/view/inputmethod/ExtractedText.java b/android/view/inputmethod/ExtractedText.java
new file mode 100644
index 0000000..159d2aa
--- /dev/null
+++ b/android/view/inputmethod/ExtractedText.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Information about text that has been extracted for use by an input method.
+ *
+ * This contains information about a portion of the currently edited text,
+ * that the IME should display into its own interface while in extracted mode.
+ */
+public class ExtractedText implements Parcelable {
+    /**
+     * The text that has been extracted.
+     *
+     * @see android.widget.TextView#getText()
+     */
+    public CharSequence text;
+
+    /**
+     * The offset in the overall text at which the extracted text starts.
+     */
+    public int startOffset;
+
+    /**
+     * If the content is a report of a partial text change, this is the
+     * offset where the change starts and it runs until
+     * {@link #partialEndOffset}.  If the content is the full text, this
+     * field is -1.
+     */
+    public int partialStartOffset;
+
+    /**
+     * If the content is a report of a partial text change, this is the offset
+     * where the change ends.  Note that the actual text may be larger or
+     * smaller than the difference between this and {@link #partialStartOffset},
+     * meaning a reduction or increase, respectively, in the total text.
+     */
+    public int partialEndOffset;
+
+    /**
+     * The offset where the selection currently starts within the extracted
+     * text.  The real selection start position is at
+     * <var>startOffset</var>+<var>selectionStart</var>.
+     */
+    public int selectionStart;
+
+    /**
+     * The offset where the selection currently ends within the extracted
+     * text.  The real selection end position is at
+     * <var>startOffset</var>+<var>selectionEnd</var>.
+     */
+    public int selectionEnd;
+
+    /**
+     * Bit for {@link #flags}: set if the text being edited can only be on
+     * a single line.
+     */
+    public static final int FLAG_SINGLE_LINE = 0x0001;
+
+    /**
+     * Bit for {@link #flags}: set if the editor is currently in selection mode.
+     *
+     * This happens when a hardware keyboard with latched keys is attached and
+     * the shift key is currently latched.
+     */
+    public static final int FLAG_SELECTING = 0x0002;
+
+    /**
+     * Additional bit flags of information about the edited text.
+     */
+    public int flags;
+
+    /**
+     * The hint that has been extracted.
+     *
+     * @see android.widget.TextView#getHint()
+     */
+    public CharSequence hint;
+
+    /**
+     * Used to package this object into a {@link Parcel}.
+     *
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    public void writeToParcel(Parcel dest, int flags) {
+        TextUtils.writeToParcel(text, dest, flags);
+        dest.writeInt(startOffset);
+        dest.writeInt(partialStartOffset);
+        dest.writeInt(partialEndOffset);
+        dest.writeInt(selectionStart);
+        dest.writeInt(selectionEnd);
+        dest.writeInt(this.flags);
+        TextUtils.writeToParcel(hint, dest, flags);
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final @android.annotation.NonNull Parcelable.Creator<ExtractedText> CREATOR
+            = new Parcelable.Creator<ExtractedText>() {
+                public ExtractedText createFromParcel(Parcel source) {
+                    ExtractedText res = new ExtractedText();
+                    res.text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+                    res.startOffset = source.readInt();
+                    res.partialStartOffset = source.readInt();
+                    res.partialEndOffset = source.readInt();
+                    res.selectionStart = source.readInt();
+                    res.selectionEnd = source.readInt();
+                    res.flags = source.readInt();
+                    res.hint = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+                    return res;
+                }
+
+        public ExtractedText[] newArray(int size) {
+            return new ExtractedText[size];
+        }
+    };
+
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/android/view/inputmethod/ExtractedTextRequest.java b/android/view/inputmethod/ExtractedTextRequest.java
new file mode 100644
index 0000000..e442185
--- /dev/null
+++ b/android/view/inputmethod/ExtractedTextRequest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Description of what an input method would like from an application when
+ * extract text from its input editor.
+ */
+public class ExtractedTextRequest implements Parcelable {
+    /**
+     * Arbitrary integer that can be supplied in the request, which will be
+     * delivered back when reporting updates.
+     */
+    public int token;
+    
+    /**
+     * Additional request flags, having the same possible values as the
+     * flags parameter of {@link InputConnection#getTextBeforeCursor
+     * InputConnection.getTextBeforeCursor()}.
+     */
+    public int flags;
+    
+    /**
+     * Hint for the maximum number of lines to return.
+     */
+    public int hintMaxLines;
+    
+    /**
+     * Hint for the maximum number of characters to return.
+     */
+    public int hintMaxChars;
+    
+    /**
+     * Used to package this object into a {@link Parcel}.
+     * 
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(token);
+        dest.writeInt(this.flags);
+        dest.writeInt(hintMaxLines);
+        dest.writeInt(hintMaxChars);
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final @android.annotation.NonNull Parcelable.Creator<ExtractedTextRequest> CREATOR
+            = new Parcelable.Creator<ExtractedTextRequest>() {
+        public ExtractedTextRequest createFromParcel(Parcel source) {
+            ExtractedTextRequest res = new ExtractedTextRequest();
+            res.token = source.readInt();
+            res.flags = source.readInt();
+            res.hintMaxLines = source.readInt();
+            res.hintMaxChars = source.readInt();
+            return res;
+        }
+
+        public ExtractedTextRequest[] newArray(int size) {
+            return new ExtractedTextRequest[size];
+        }
+    };
+
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/android/view/inputmethod/InlineSuggestion.java b/android/view/inputmethod/InlineSuggestion.java
new file mode 100644
index 0000000..27c637b
--- /dev/null
+++ b/android/view/inputmethod/InlineSuggestion.java
@@ -0,0 +1,704 @@
+/*
+ * 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.view.inputmethod;
+
+import android.annotation.BinderThread;
+import android.annotation.CallbackExecutor;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Size;
+import android.util.Slog;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.inline.InlineContentView;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
+import com.android.internal.view.inline.IInlineContentCallback;
+import com.android.internal.view.inline.IInlineContentProvider;
+import com.android.internal.view.inline.InlineTooltipUi;
+
+import java.lang.ref.WeakReference;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * This class represents an inline suggestion which is made by one app and can be embedded into the
+ * UI of another. Suggestions may contain sensitive information not known to the host app which
+ * needs to be protected from spoofing. To address that the suggestion view inflated on demand for
+ * embedding is created in such a way that the hosting app cannot introspect its content and cannot
+ * interact with it.
+ */
+@DataClass(genEqualsHashCode = true, genToString = true, genHiddenConstDefs = true,
+        genHiddenConstructor = true)
+public final class InlineSuggestion implements Parcelable {
+
+    private static final String TAG = "InlineSuggestion";
+
+    @NonNull
+    private final InlineSuggestionInfo mInfo;
+
+    /**
+     * @hide
+     */
+    @Nullable
+    private final IInlineContentProvider mContentProvider;
+
+    /**
+     * Used to keep a strong reference to the callback so it doesn't get garbage collected.
+     *
+     * @hide
+     */
+    @DataClass.ParcelWith(InlineContentCallbackImplParceling.class)
+    @Nullable
+    private InlineContentCallbackImpl mInlineContentCallback;
+
+    /**
+     * Used to show up the inline suggestion tooltip.
+     *
+     * @hide
+     */
+    @Nullable
+    @DataClass.ParcelWith(InlineTooltipUiParceling.class)
+    private InlineTooltipUi mInlineTooltipUi;
+
+    /**
+     * Creates a new {@link InlineSuggestion}, for testing purpose.
+     *
+     * @hide
+     */
+    @TestApi
+    @NonNull
+    public static InlineSuggestion newInlineSuggestion(@NonNull InlineSuggestionInfo info) {
+        return new InlineSuggestion(info, null, /* inlineContentCallback */ null,
+                /* inlineTooltipUi */ null);
+    }
+
+    /**
+     * Creates a new {@link InlineSuggestion}.
+     *
+     * @hide
+     */
+    public InlineSuggestion(@NonNull InlineSuggestionInfo info,
+            @Nullable IInlineContentProvider contentProvider) {
+        this(info, contentProvider, /* inlineContentCallback */ null, /* inlineTooltipUi */ null);
+    }
+
+    /**
+     * Inflates a view with the content of this suggestion at a specific size.
+     *
+     * <p> Each dimension of the size must satisfy one of the following conditions:
+     *
+     * <ol>
+     *     <li>between {@link android.widget.inline.InlinePresentationSpec#getMinSize()} and
+     * {@link android.widget.inline.InlinePresentationSpec#getMaxSize()} of the presentation spec
+     * from {@code mInfo}
+     *     <li>{@link ViewGroup.LayoutParams#WRAP_CONTENT}
+     * </ol>
+     *
+     * If the size is set to {@link
+     * ViewGroup.LayoutParams#WRAP_CONTENT}, then the size of the inflated view will be just large
+     * enough to fit the content, while still conforming to the min / max size specified by the
+     * {@link android.widget.inline.InlinePresentationSpec}.
+     *
+     * <p> The caller can attach an {@link android.view.View.OnClickListener} and/or an
+     * {@link android.view.View.OnLongClickListener} to the view in the {@code callback} to receive
+     * click and long click events on the view.
+     *
+     * @param context  Context in which to inflate the view.
+     * @param size     The size at which to inflate the suggestion. For each dimension, it maybe an
+     *                 exact value or {@link ViewGroup.LayoutParams#WRAP_CONTENT}.
+     * @param callback Callback for receiving the inflated view, where the {@link
+     *                 ViewGroup.LayoutParams} of the view is set as the actual size of the
+     *                 underlying remote view.
+     * @throws IllegalArgumentException If an invalid argument is passed.
+     * @throws IllegalStateException    If this method is already called.
+     */
+    public void inflate(@NonNull Context context, @NonNull Size size,
+            @NonNull @CallbackExecutor Executor callbackExecutor,
+            @NonNull Consumer<InlineContentView> callback) {
+        final Size minSize = mInfo.getInlinePresentationSpec().getMinSize();
+        final Size maxSize = mInfo.getInlinePresentationSpec().getMaxSize();
+        if (!isValid(size.getWidth(), minSize.getWidth(), maxSize.getWidth())
+                || !isValid(size.getHeight(), minSize.getHeight(), maxSize.getHeight())) {
+            throw new IllegalArgumentException(
+                    "size is neither between min:" + minSize + " and max:" + maxSize
+                            + ", nor wrap_content");
+        }
+
+        InlineSuggestion toolTip = mInfo.getTooltip();
+        if (toolTip != null) {
+            if (mInlineTooltipUi == null) {
+                mInlineTooltipUi = new InlineTooltipUi(context);
+            }
+        } else {
+            mInlineTooltipUi = null;
+        }
+
+        mInlineContentCallback = getInlineContentCallback(context, callbackExecutor, callback,
+                mInlineTooltipUi);
+        if (mContentProvider == null) {
+            callbackExecutor.execute(() -> callback.accept(/* view */ null));
+            mInlineTooltipUi = null;
+            return;
+        }
+        try {
+            mContentProvider.provideContent(size.getWidth(), size.getHeight(),
+                    new InlineContentCallbackWrapper(mInlineContentCallback));
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Error creating suggestion content surface: " + e);
+            callbackExecutor.execute(() -> callback.accept(/* view */ null));
+        }
+        if (toolTip == null) return;
+
+        final Size tooltipSize = new Size(ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT);
+        mInfo.getTooltip().inflate(context, tooltipSize, callbackExecutor, view -> {
+            Handler.getMain().post(() -> mInlineTooltipUi.setTooltipView(view));
+        });
+    }
+
+    /**
+     * Returns true if the {@code actual} length is within [min, max] or is {@link
+     * ViewGroup.LayoutParams#WRAP_CONTENT}.
+     */
+    private static boolean isValid(int actual, int min, int max) {
+        if (actual == ViewGroup.LayoutParams.WRAP_CONTENT) {
+            return true;
+        }
+        return actual >= min && actual <= max;
+    }
+
+    private synchronized InlineContentCallbackImpl getInlineContentCallback(Context context,
+            Executor callbackExecutor, Consumer<InlineContentView> callback,
+            InlineTooltipUi inlineTooltipUi) {
+        if (mInlineContentCallback != null) {
+            throw new IllegalStateException("Already called #inflate()");
+        }
+        return new InlineContentCallbackImpl(context, mContentProvider, callbackExecutor,
+                callback, inlineTooltipUi);
+    }
+
+    /**
+     * A wrapper class around the {@link InlineContentCallbackImpl} to ensure it's not strongly
+     * reference by the remote system server process.
+     */
+    private static final class InlineContentCallbackWrapper extends IInlineContentCallback.Stub {
+
+        private final WeakReference<InlineContentCallbackImpl> mCallbackImpl;
+
+        InlineContentCallbackWrapper(InlineContentCallbackImpl callbackImpl) {
+            mCallbackImpl = new WeakReference<>(callbackImpl);
+        }
+
+        @Override
+        @BinderThread
+        public void onContent(SurfaceControlViewHost.SurfacePackage content, int width,
+                int height) {
+            final InlineContentCallbackImpl callbackImpl = mCallbackImpl.get();
+            if (callbackImpl != null) {
+                callbackImpl.onContent(content, width, height);
+            }
+        }
+
+        @Override
+        @BinderThread
+        public void onClick() {
+            final InlineContentCallbackImpl callbackImpl = mCallbackImpl.get();
+            if (callbackImpl != null) {
+                callbackImpl.onClick();
+            }
+        }
+
+        @Override
+        @BinderThread
+        public void onLongClick() {
+            final InlineContentCallbackImpl callbackImpl = mCallbackImpl.get();
+            if (callbackImpl != null) {
+                callbackImpl.onLongClick();
+            }
+        }
+    }
+
+    /**
+     * Handles the communication between the inline suggestion view in current (IME) process and
+     * the remote view provided from the system server.
+     *
+     * <p>This class is thread safe, because all the outside calls are piped into a single
+     * handler thread to be processed.
+     */
+    private static final class InlineContentCallbackImpl {
+
+        @NonNull
+        private final Handler mMainHandler = new Handler(Looper.getMainLooper());
+
+        @NonNull
+        private final Context mContext;
+        @Nullable
+        private final IInlineContentProvider mInlineContentProvider;
+        @NonNull
+        private final Executor mCallbackExecutor;
+
+        /**
+         * Callback from the client (IME) that will receive the inflated suggestion view. It'll
+         * only be called once when the view SurfacePackage is first sent back to the client. Any
+         * updates to the view due to attach to window and detach from window events will be
+         * handled under the hood, transparent from the client.
+         */
+        @NonNull
+        private final Consumer<InlineContentView> mCallback;
+
+        /**
+         * Indicates whether the first content has been received or not.
+         */
+        private boolean mFirstContentReceived = false;
+
+        /**
+         * The client (IME) side view which internally wraps a remote view. It'll be set when
+         * {@link #onContent(SurfaceControlViewHost.SurfacePackage, int, int)} is called, which
+         * should only happen once in the lifecycle of this inline suggestion instance.
+         */
+        @Nullable
+        private InlineContentView mView;
+
+        /**
+         * The SurfacePackage pointing to the remote view. It's cached here to be sent to the next
+         * available consumer.
+         */
+        @Nullable
+        private SurfaceControlViewHost.SurfacePackage mSurfacePackage;
+
+        /**
+         * The callback (from the {@link InlineContentView}) which consumes the surface package.
+         * It's cached here to be called when the SurfacePackage is returned from the remote
+         * view owning process.
+         */
+        @Nullable
+        private Consumer<SurfaceControlViewHost.SurfacePackage> mSurfacePackageConsumer;
+
+        @Nullable
+        private InlineTooltipUi mInlineTooltipUi;
+
+        InlineContentCallbackImpl(@NonNull Context context,
+                @Nullable IInlineContentProvider inlineContentProvider,
+                @NonNull @CallbackExecutor Executor callbackExecutor,
+                @NonNull Consumer<InlineContentView> callback,
+                @Nullable InlineTooltipUi inlineTooltipUi) {
+            mContext = context;
+            mInlineContentProvider = inlineContentProvider;
+            mCallbackExecutor = callbackExecutor;
+            mCallback = callback;
+            mInlineTooltipUi = inlineTooltipUi;
+        }
+
+        @BinderThread
+        public void onContent(SurfaceControlViewHost.SurfacePackage content, int width,
+                int height) {
+            mMainHandler.post(() -> handleOnContent(content, width, height));
+        }
+
+        @MainThread
+        private void handleOnContent(SurfaceControlViewHost.SurfacePackage content, int width,
+                int height) {
+            if (!mFirstContentReceived) {
+                handleOnFirstContentReceived(content, width, height);
+                mFirstContentReceived = true;
+            } else {
+                handleOnSurfacePackage(content);
+            }
+        }
+
+        /**
+         * Called when the view content is returned for the first time.
+         */
+        @MainThread
+        private void handleOnFirstContentReceived(SurfaceControlViewHost.SurfacePackage content,
+                int width, int height) {
+            mSurfacePackage = content;
+            if (mSurfacePackage == null) {
+                mCallbackExecutor.execute(() -> mCallback.accept(/* view */null));
+            } else {
+                mView = new InlineContentView(mContext);
+                if (mInlineTooltipUi != null) {
+                    mView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+                        @Override
+                        public void onLayoutChange(View v, int left, int top, int right,
+                                int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
+                            if (mInlineTooltipUi != null) {
+                                mInlineTooltipUi.update(mView);
+                            }
+                        }
+                    });
+                }
+                mView.setLayoutParams(new ViewGroup.LayoutParams(width, height));
+                mView.setChildSurfacePackageUpdater(getSurfacePackageUpdater());
+                mCallbackExecutor.execute(() -> mCallback.accept(mView));
+            }
+        }
+
+        /**
+         * Called when any subsequent SurfacePackage is returned from the remote view owning
+         * process.
+         */
+        @MainThread
+        private void handleOnSurfacePackage(SurfaceControlViewHost.SurfacePackage surfacePackage) {
+            if (surfacePackage == null) {
+                return;
+            }
+            if (mSurfacePackage != null || mSurfacePackageConsumer == null) {
+                // The surface package is not consumed, release it immediately.
+                surfacePackage.release();
+                try {
+                    mInlineContentProvider.onSurfacePackageReleased();
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Error calling onSurfacePackageReleased(): " + e);
+                }
+                return;
+            }
+            mSurfacePackage = surfacePackage;
+            if (mSurfacePackage == null) {
+                return;
+            }
+            if (mSurfacePackageConsumer != null) {
+                mSurfacePackageConsumer.accept(mSurfacePackage);
+                mSurfacePackageConsumer = null;
+            }
+        }
+
+        @MainThread
+        private void handleOnSurfacePackageReleased() {
+            if (mSurfacePackage != null) {
+                try {
+                    mInlineContentProvider.onSurfacePackageReleased();
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Error calling onSurfacePackageReleased(): " + e);
+                }
+                mSurfacePackage = null;
+            }
+            // Clear the pending surface package consumer, if any. This can happen if the IME
+            // attaches the view to window and then quickly detaches it from the window, before
+            // the surface package requested upon attaching to window was returned.
+            mSurfacePackageConsumer = null;
+        }
+
+        @MainThread
+        private void handleGetSurfacePackage(
+                Consumer<SurfaceControlViewHost.SurfacePackage> consumer) {
+            if (mSurfacePackage != null) {
+                consumer.accept(mSurfacePackage);
+            } else {
+                mSurfacePackageConsumer = consumer;
+                try {
+                    mInlineContentProvider.requestSurfacePackage();
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Error calling getSurfacePackage(): " + e);
+                    consumer.accept(null);
+                    mSurfacePackageConsumer = null;
+                }
+            }
+        }
+
+        private InlineContentView.SurfacePackageUpdater getSurfacePackageUpdater() {
+            return new InlineContentView.SurfacePackageUpdater() {
+                @Override
+                public void onSurfacePackageReleased() {
+                    mMainHandler.post(
+                            () -> InlineContentCallbackImpl.this.handleOnSurfacePackageReleased());
+                }
+
+                @Override
+                public void getSurfacePackage(
+                        Consumer<SurfaceControlViewHost.SurfacePackage> consumer) {
+                    mMainHandler.post(
+                            () -> InlineContentCallbackImpl.this.handleGetSurfacePackage(consumer));
+                }
+            };
+        }
+
+        @BinderThread
+        public void onClick() {
+            mMainHandler.post(() -> {
+                if (mView != null && mView.hasOnClickListeners()) {
+                    mView.callOnClick();
+                }
+            });
+        }
+
+        @BinderThread
+        public void onLongClick() {
+            mMainHandler.post(() -> {
+                if (mView != null && mView.hasOnLongClickListeners()) {
+                    mView.performLongClick();
+                }
+            });
+        }
+    }
+
+    /**
+     * This class used to provide parcelling logic for InlineContentCallbackImpl. It's intended to
+     * make this parcelling a no-op, since it can't be parceled and we don't need to parcel it.
+     */
+    private static class InlineContentCallbackImplParceling implements
+            Parcelling<InlineContentCallbackImpl> {
+        @Override
+        public void parcel(InlineContentCallbackImpl item, Parcel dest, int parcelFlags) {
+        }
+
+        @Override
+        public InlineContentCallbackImpl unparcel(Parcel source) {
+            return null;
+        }
+    }
+
+    /**
+     * This class used to provide parcelling logic for InlineContentCallbackImpl. It's intended to
+     * make this parcelling a no-op, since it can't be parceled and we don't need to parcel it.
+     */
+    private static class InlineTooltipUiParceling implements
+            Parcelling<InlineTooltipUi> {
+        @Override
+        public void parcel(InlineTooltipUi item, Parcel dest, int parcelFlags) {
+        }
+
+        @Override
+        public InlineTooltipUi unparcel(Parcel source) {
+            return null;
+        }
+    }
+
+
+
+    // Code below generated by codegen v1.0.22.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/inputmethod/InlineSuggestion.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new InlineSuggestion.
+     *
+     * @param inlineContentCallback
+     *   Used to keep a strong reference to the callback so it doesn't get garbage collected.
+     * @param inlineTooltipUi
+     *   Used to show up the inline suggestion tooltip.
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public InlineSuggestion(
+            @NonNull InlineSuggestionInfo info,
+            @Nullable IInlineContentProvider contentProvider,
+            @Nullable InlineContentCallbackImpl inlineContentCallback,
+            @Nullable InlineTooltipUi inlineTooltipUi) {
+        this.mInfo = info;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mInfo);
+        this.mContentProvider = contentProvider;
+        this.mInlineContentCallback = inlineContentCallback;
+        this.mInlineTooltipUi = inlineTooltipUi;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull InlineSuggestionInfo getInfo() {
+        return mInfo;
+    }
+
+    /**
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public @Nullable IInlineContentProvider getContentProvider() {
+        return mContentProvider;
+    }
+
+    /**
+     * Used to keep a strong reference to the callback so it doesn't get garbage collected.
+     *
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public @Nullable InlineContentCallbackImpl getInlineContentCallback() {
+        return mInlineContentCallback;
+    }
+
+    /**
+     * Used to show up the inline suggestion tooltip.
+     *
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public @Nullable InlineTooltipUi getInlineTooltipUi() {
+        return mInlineTooltipUi;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "InlineSuggestion { " +
+                "info = " + mInfo + ", " +
+                "contentProvider = " + mContentProvider + ", " +
+                "inlineContentCallback = " + mInlineContentCallback + ", " +
+                "inlineTooltipUi = " + mInlineTooltipUi +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(InlineSuggestion other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        InlineSuggestion that = (InlineSuggestion) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && java.util.Objects.equals(mInfo, that.mInfo)
+                && java.util.Objects.equals(mContentProvider, that.mContentProvider)
+                && java.util.Objects.equals(mInlineContentCallback, that.mInlineContentCallback)
+                && java.util.Objects.equals(mInlineTooltipUi, that.mInlineTooltipUi);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + java.util.Objects.hashCode(mInfo);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mContentProvider);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mInlineContentCallback);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mInlineTooltipUi);
+        return _hash;
+    }
+
+    @DataClass.Generated.Member
+    static Parcelling<InlineContentCallbackImpl> sParcellingForInlineContentCallback =
+            Parcelling.Cache.get(
+                    InlineContentCallbackImplParceling.class);
+    static {
+        if (sParcellingForInlineContentCallback == null) {
+            sParcellingForInlineContentCallback = Parcelling.Cache.put(
+                    new InlineContentCallbackImplParceling());
+        }
+    }
+
+    @DataClass.Generated.Member
+    static Parcelling<InlineTooltipUi> sParcellingForInlineTooltipUi =
+            Parcelling.Cache.get(
+                    InlineTooltipUiParceling.class);
+    static {
+        if (sParcellingForInlineTooltipUi == null) {
+            sParcellingForInlineTooltipUi = Parcelling.Cache.put(
+                    new InlineTooltipUiParceling());
+        }
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mContentProvider != null) flg |= 0x2;
+        if (mInlineContentCallback != null) flg |= 0x4;
+        if (mInlineTooltipUi != null) flg |= 0x8;
+        dest.writeByte(flg);
+        dest.writeTypedObject(mInfo, flags);
+        if (mContentProvider != null) dest.writeStrongInterface(mContentProvider);
+        sParcellingForInlineContentCallback.parcel(mInlineContentCallback, dest, flags);
+        sParcellingForInlineTooltipUi.parcel(mInlineTooltipUi, dest, flags);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ InlineSuggestion(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        InlineSuggestionInfo info = (InlineSuggestionInfo) in.readTypedObject(InlineSuggestionInfo.CREATOR);
+        IInlineContentProvider contentProvider = (flg & 0x2) == 0 ? null : IInlineContentProvider.Stub.asInterface(in.readStrongBinder());
+        InlineContentCallbackImpl inlineContentCallback = sParcellingForInlineContentCallback.unparcel(in);
+        InlineTooltipUi inlineTooltipUi = sParcellingForInlineTooltipUi.unparcel(in);
+
+        this.mInfo = info;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mInfo);
+        this.mContentProvider = contentProvider;
+        this.mInlineContentCallback = inlineContentCallback;
+        this.mInlineTooltipUi = inlineTooltipUi;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<InlineSuggestion> CREATOR
+            = new Parcelable.Creator<InlineSuggestion>() {
+        @Override
+        public InlineSuggestion[] newArray(int size) {
+            return new InlineSuggestion[size];
+        }
+
+        @Override
+        public InlineSuggestion createFromParcel(@NonNull Parcel in) {
+            return new InlineSuggestion(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1615562097666L,
+            codegenVersion = "1.0.22",
+            sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestion.java",
+            inputSignatures = "private static final  java.lang.String TAG\nprivate final @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo mInfo\nprivate final @android.annotation.Nullable com.android.internal.view.inline.IInlineContentProvider mContentProvider\nprivate @com.android.internal.util.DataClass.ParcelWith(android.view.inputmethod.InlineSuggestion.InlineContentCallbackImplParceling.class) @android.annotation.Nullable android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl mInlineContentCallback\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(android.view.inputmethod.InlineSuggestion.InlineTooltipUiParceling.class) com.android.internal.view.inline.InlineTooltipUi mInlineTooltipUi\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestion newInlineSuggestion(android.view.inputmethod.InlineSuggestionInfo)\npublic  void inflate(android.content.Context,android.util.Size,java.util.concurrent.Executor,java.util.function.Consumer<android.widget.inline.InlineContentView>)\nprivate static  boolean isValid(int,int,int)\nprivate synchronized  android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl getInlineContentCallback(android.content.Context,java.util.concurrent.Executor,java.util.function.Consumer<android.widget.inline.InlineContentView>,com.android.internal.view.inline.InlineTooltipUi)\nclass InlineSuggestion extends java.lang.Object implements [android.os.Parcelable]\[email protected](genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/android/view/inputmethod/InlineSuggestionInfo.java b/android/view/inputmethod/InlineSuggestionInfo.java
new file mode 100644
index 0000000..5798614
--- /dev/null
+++ b/android/view/inputmethod/InlineSuggestionInfo.java
@@ -0,0 +1,401 @@
+/*
+ * 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.view.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
+import android.os.Parcelable;
+import android.widget.inline.InlinePresentationSpec;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * This class represents the description of an inline suggestion. It contains information to help
+ * the IME to decide where and when to show the suggestions. See {@link InlineSuggestion} for more
+ * information.
+ */
+@DataClass(
+        genEqualsHashCode = true,
+        genToString = true,
+        genHiddenConstDefs = true,
+        genHiddenConstructor = true)
+public final class InlineSuggestionInfo implements Parcelable {
+
+    /**
+     * Suggestion source: the suggestion is made by the user selected autofill service.
+     */
+    public static final @Source String SOURCE_AUTOFILL = "android:autofill";
+    /**
+     * Suggestion source: the suggestion is made by the platform.
+     */
+    public static final @Source String SOURCE_PLATFORM = "android:platform";
+
+    /**
+     * UI type: the UI contains an Autofill suggestion that will autofill the fields when tapped.
+     */
+    public static final @Type String TYPE_SUGGESTION = "android:autofill:suggestion";
+
+    /**
+     * UI type: the UI contains an widget that will launch an intent when tapped.
+     */
+    @SuppressLint({"IntentName"})
+    public static final @Type String TYPE_ACTION = "android:autofill:action";
+
+    /** The presentation spec to which the inflated suggestion view abides. */
+    private final @NonNull InlinePresentationSpec mInlinePresentationSpec;
+
+    /** The source from which the suggestion is provided. */
+    private final @NonNull @Source String mSource;
+
+    /** Hints for the type of data being suggested. */
+    private final @Nullable String[] mAutofillHints;
+
+    /** The type of the UI. */
+    private final @NonNull @Type String mType;
+
+    /** Whether the suggestion should be pinned or not. */
+    private final boolean mPinned;
+
+    /**
+     * @hide
+     */
+    private final @Nullable InlineSuggestion mTooltip;
+
+    /**
+     * Creates a new {@link InlineSuggestionInfo}, for testing purpose.
+     *
+     * @hide
+     */
+    @TestApi
+    @NonNull
+    public static InlineSuggestionInfo newInlineSuggestionInfo(
+            @NonNull InlinePresentationSpec presentationSpec,
+            @NonNull @Source String source,
+            @SuppressLint("NullableCollection")
+            @Nullable String[] autofillHints, @NonNull @Type String type, boolean isPinned) {
+        return new InlineSuggestionInfo(presentationSpec, source, autofillHints, type, isPinned,
+                null);
+    }
+
+    /**
+     * Creates a new {@link InlineSuggestionInfo}, for testing purpose.
+     *
+     * @hide
+     */
+    @NonNull
+    public static InlineSuggestionInfo newInlineSuggestionInfo(
+            @NonNull InlinePresentationSpec presentationSpec,
+            @NonNull @Source String source,
+            @Nullable String[] autofillHints, @NonNull @Type String type, boolean isPinned,
+            @Nullable InlineSuggestion tooltip) {
+        return new InlineSuggestionInfo(presentationSpec, source, autofillHints, type, isPinned,
+                tooltip);
+    }
+
+
+
+
+
+    // Code below generated by codegen v1.0.22.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/inputmethod/InlineSuggestionInfo.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /** @hide */
+    @android.annotation.StringDef(prefix = "SOURCE_", value = {
+        SOURCE_AUTOFILL,
+        SOURCE_PLATFORM
+    })
+    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface Source {}
+
+    /** @hide */
+    @android.annotation.StringDef(prefix = "TYPE_", value = {
+        TYPE_SUGGESTION,
+        TYPE_ACTION
+    })
+    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface Type {}
+
+    /**
+     * Creates a new InlineSuggestionInfo.
+     *
+     * @param inlinePresentationSpec
+     *   The presentation spec to which the inflated suggestion view abides.
+     * @param source
+     *   The source from which the suggestion is provided.
+     * @param autofillHints
+     *   Hints for the type of data being suggested.
+     * @param type
+     *   The type of the UI.
+     * @param pinned
+     *   Whether the suggestion should be pinned or not.
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public InlineSuggestionInfo(
+            @NonNull InlinePresentationSpec inlinePresentationSpec,
+            @NonNull @Source String source,
+            @Nullable String[] autofillHints,
+            @NonNull @Type String type,
+            boolean pinned,
+            @Nullable InlineSuggestion tooltip) {
+        this.mInlinePresentationSpec = inlinePresentationSpec;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mInlinePresentationSpec);
+        this.mSource = source;
+
+        if (!(java.util.Objects.equals(mSource, SOURCE_AUTOFILL))
+                && !(java.util.Objects.equals(mSource, SOURCE_PLATFORM))) {
+            throw new java.lang.IllegalArgumentException(
+                    "source was " + mSource + " but must be one of: "
+                            + "SOURCE_AUTOFILL(" + SOURCE_AUTOFILL + "), "
+                            + "SOURCE_PLATFORM(" + SOURCE_PLATFORM + ")");
+        }
+
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mSource);
+        this.mAutofillHints = autofillHints;
+        this.mType = type;
+
+        if (!(java.util.Objects.equals(mType, TYPE_SUGGESTION))
+                && !(java.util.Objects.equals(mType, TYPE_ACTION))) {
+            throw new java.lang.IllegalArgumentException(
+                    "type was " + mType + " but must be one of: "
+                            + "TYPE_SUGGESTION(" + TYPE_SUGGESTION + "), "
+                            + "TYPE_ACTION(" + TYPE_ACTION + ")");
+        }
+
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mType);
+        this.mPinned = pinned;
+        this.mTooltip = tooltip;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * The presentation spec to which the inflated suggestion view abides.
+     */
+    @DataClass.Generated.Member
+    public @NonNull InlinePresentationSpec getInlinePresentationSpec() {
+        return mInlinePresentationSpec;
+    }
+
+    /**
+     * The source from which the suggestion is provided.
+     */
+    @DataClass.Generated.Member
+    public @NonNull @Source String getSource() {
+        return mSource;
+    }
+
+    /**
+     * Hints for the type of data being suggested.
+     */
+    @DataClass.Generated.Member
+    public @Nullable String[] getAutofillHints() {
+        return mAutofillHints;
+    }
+
+    /**
+     * The type of the UI.
+     */
+    @DataClass.Generated.Member
+    public @NonNull @Type String getType() {
+        return mType;
+    }
+
+    /**
+     * Whether the suggestion should be pinned or not.
+     */
+    @DataClass.Generated.Member
+    public boolean isPinned() {
+        return mPinned;
+    }
+
+    /**
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public @Nullable InlineSuggestion getTooltip() {
+        return mTooltip;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "InlineSuggestionInfo { " +
+                "inlinePresentationSpec = " + mInlinePresentationSpec + ", " +
+                "source = " + mSource + ", " +
+                "autofillHints = " + java.util.Arrays.toString(mAutofillHints) + ", " +
+                "type = " + mType + ", " +
+                "pinned = " + mPinned + ", " +
+                "tooltip = " + mTooltip +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(InlineSuggestionInfo other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        InlineSuggestionInfo that = (InlineSuggestionInfo) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && java.util.Objects.equals(mInlinePresentationSpec, that.mInlinePresentationSpec)
+                && java.util.Objects.equals(mSource, that.mSource)
+                && java.util.Arrays.equals(mAutofillHints, that.mAutofillHints)
+                && java.util.Objects.equals(mType, that.mType)
+                && mPinned == that.mPinned
+                && java.util.Objects.equals(mTooltip, that.mTooltip);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + java.util.Objects.hashCode(mInlinePresentationSpec);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mSource);
+        _hash = 31 * _hash + java.util.Arrays.hashCode(mAutofillHints);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mType);
+        _hash = 31 * _hash + Boolean.hashCode(mPinned);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mTooltip);
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mPinned) flg |= 0x10;
+        if (mAutofillHints != null) flg |= 0x4;
+        if (mTooltip != null) flg |= 0x20;
+        dest.writeByte(flg);
+        dest.writeTypedObject(mInlinePresentationSpec, flags);
+        dest.writeString(mSource);
+        if (mAutofillHints != null) dest.writeStringArray(mAutofillHints);
+        dest.writeString(mType);
+        if (mTooltip != null) dest.writeTypedObject(mTooltip, flags);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ InlineSuggestionInfo(@NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        boolean pinned = (flg & 0x10) != 0;
+        InlinePresentationSpec inlinePresentationSpec = (InlinePresentationSpec) in.readTypedObject(InlinePresentationSpec.CREATOR);
+        String source = in.readString();
+        String[] autofillHints = (flg & 0x4) == 0 ? null : in.createStringArray();
+        String type = in.readString();
+        InlineSuggestion tooltip = (flg & 0x20) == 0 ? null : (InlineSuggestion) in.readTypedObject(InlineSuggestion.CREATOR);
+
+        this.mInlinePresentationSpec = inlinePresentationSpec;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mInlinePresentationSpec);
+        this.mSource = source;
+
+        if (!(java.util.Objects.equals(mSource, SOURCE_AUTOFILL))
+                && !(java.util.Objects.equals(mSource, SOURCE_PLATFORM))) {
+            throw new java.lang.IllegalArgumentException(
+                    "source was " + mSource + " but must be one of: "
+                            + "SOURCE_AUTOFILL(" + SOURCE_AUTOFILL + "), "
+                            + "SOURCE_PLATFORM(" + SOURCE_PLATFORM + ")");
+        }
+
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mSource);
+        this.mAutofillHints = autofillHints;
+        this.mType = type;
+
+        if (!(java.util.Objects.equals(mType, TYPE_SUGGESTION))
+                && !(java.util.Objects.equals(mType, TYPE_ACTION))) {
+            throw new java.lang.IllegalArgumentException(
+                    "type was " + mType + " but must be one of: "
+                            + "TYPE_SUGGESTION(" + TYPE_SUGGESTION + "), "
+                            + "TYPE_ACTION(" + TYPE_ACTION + ")");
+        }
+
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mType);
+        this.mPinned = pinned;
+        this.mTooltip = tooltip;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<InlineSuggestionInfo> CREATOR
+            = new Parcelable.Creator<InlineSuggestionInfo>() {
+        @Override
+        public InlineSuggestionInfo[] newArray(int size) {
+            return new InlineSuggestionInfo[size];
+        }
+
+        @Override
+        public InlineSuggestionInfo createFromParcel(@NonNull android.os.Parcel in) {
+            return new InlineSuggestionInfo(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1614287616672L,
+            codegenVersion = "1.0.22",
+            sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionInfo.java",
+            inputSignatures = "public static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_AUTOFILL\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_PLATFORM\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String TYPE_SUGGESTION\npublic static final @android.annotation.SuppressLint @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String TYPE_ACTION\nprivate final @android.annotation.NonNull android.widget.inline.InlinePresentationSpec mInlinePresentationSpec\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String mSource\nprivate final @android.annotation.Nullable java.lang.String[] mAutofillHints\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String mType\nprivate final  boolean mPinned\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestion mTooltip\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo newInlineSuggestionInfo(android.widget.inline.InlinePresentationSpec,java.lang.String,java.lang.String[],java.lang.String,boolean)\npublic static @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo newInlineSuggestionInfo(android.widget.inline.InlinePresentationSpec,java.lang.String,java.lang.String[],java.lang.String,boolean,android.view.inputmethod.InlineSuggestion)\nclass InlineSuggestionInfo extends java.lang.Object implements [android.os.Parcelable]\[email protected](genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/android/view/inputmethod/InlineSuggestionsRequest.java b/android/view/inputmethod/InlineSuggestionsRequest.java
new file mode 100644
index 0000000..1eb1a93
--- /dev/null
+++ b/android/view/inputmethod/InlineSuggestionsRequest.java
@@ -0,0 +1,700 @@
+/*
+ * 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.view.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityThread;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.Display;
+import android.widget.inline.InlinePresentationSpec;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Preconditions;
+import com.android.internal.widget.InlinePresentationStyleUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class represents an inline suggestion request made by one app to get suggestions from the
+ * other source. See {@link InlineSuggestion} for more information.
+ */
+@DataClass(genEqualsHashCode = true, genToString = true, genBuilder = true)
+public final class InlineSuggestionsRequest implements Parcelable {
+
+    /** Constant used to indicate not putting a cap on the number of suggestions to return. */
+    public static final int SUGGESTION_COUNT_UNLIMITED = Integer.MAX_VALUE;
+
+    /**
+     * Max number of suggestions expected from the response. It must be a positive value.
+     * Defaults to {@code SUGGESTION_COUNT_UNLIMITED} if not set.
+     *
+     * <p>In practice, it is recommended that the max suggestion count does not exceed <b>5</b>
+     * for performance reasons.</p>
+     */
+    private final int mMaxSuggestionCount;
+
+    /**
+     * The {@link InlinePresentationSpec} for each suggestion in the response. If the max suggestion
+     * count is larger than the number of specs in the list, then the last spec is used for the
+     * remainder of the suggestions. The list should not be empty.
+     */
+    private final @NonNull List<InlinePresentationSpec> mInlinePresentationSpecs;
+
+    /**
+     * The package name of the app that requests for the inline suggestions and will host the
+     * embedded suggestion views. The app does not have to set the value for the field because
+     * it'll be set by the system for safety reasons.
+     */
+    private @NonNull String mHostPackageName;
+
+    /**
+     * The IME provided locales for the request. If non-empty, the inline suggestions should
+     * return languages from the supported locales. If not provided, it'll default to be empty if
+     * target SDK is S or above, and default to system locale otherwise.
+     *
+     * <p>Note for Autofill Providers: It is <b>recommended</b> for the returned inline suggestions
+     * to have one locale to guarantee consistent UI rendering.</p>
+     */
+    private @NonNull LocaleList mSupportedLocales;
+
+    /**
+     * The extras state propagated from the IME to pass extra data.
+     *
+     * <p>Note: There should be no remote objects in the bundle, all included remote objects will
+     * be removed from the bundle before transmission.</p>
+     */
+    private @NonNull Bundle mExtras;
+
+    /**
+     * The host input token of the IME that made the request. This will be set by the system for
+     * safety reasons.
+     *
+     * @hide
+     */
+    private @Nullable IBinder mHostInputToken;
+
+    /**
+     * The host display id of the IME that made the request. This will be set by the system for
+     * safety reasons.
+     *
+     * @hide
+     */
+    private int mHostDisplayId;
+
+    /**
+     * Specifies the UI specification for the inline suggestion tooltip in the response.
+     */
+    private @Nullable InlinePresentationSpec mInlineTooltipPresentationSpec;
+
+    /**
+     * @hide
+     * @see {@link #mHostInputToken}.
+     */
+    public void setHostInputToken(IBinder hostInputToken) {
+        mHostInputToken = hostInputToken;
+    }
+
+    private boolean extrasEquals(@NonNull Bundle extras) {
+        return InlinePresentationStyleUtils.bundleEquals(mExtras, extras);
+    }
+
+    // TODO(b/149609075): remove once IBinder parcelling is natively supported
+    private void parcelHostInputToken(@NonNull Parcel parcel, int flags) {
+        parcel.writeStrongBinder(mHostInputToken);
+    }
+
+    // TODO(b/149609075): remove once IBinder parcelling is natively supported
+    private @Nullable IBinder unparcelHostInputToken(Parcel parcel) {
+        return parcel.readStrongBinder();
+    }
+
+    /**
+     * @hide
+     * @see {@link #mHostDisplayId}.
+     */
+    public void setHostDisplayId(int hostDisplayId) {
+        mHostDisplayId = hostDisplayId;
+    }
+
+    private void onConstructed() {
+        Preconditions.checkState(!mInlinePresentationSpecs.isEmpty());
+        Preconditions.checkState(mMaxSuggestionCount >= mInlinePresentationSpecs.size());
+    }
+
+    /**
+     * Removes the remote objects from the bundles within the {@Code mExtras} and the
+     * {@code mInlinePresentationSpecs}.
+     *
+     * @hide
+     */
+    public void filterContentTypes() {
+        InlinePresentationStyleUtils.filterContentTypes(mExtras);
+        for (int i = 0; i < mInlinePresentationSpecs.size(); i++) {
+            mInlinePresentationSpecs.get(i).filterContentTypes();
+        }
+
+        if (mInlineTooltipPresentationSpec != null) {
+            mInlineTooltipPresentationSpec.filterContentTypes();
+        }
+    }
+
+    private static int defaultMaxSuggestionCount() {
+        return SUGGESTION_COUNT_UNLIMITED;
+    }
+
+    private static String defaultHostPackageName() {
+        return ActivityThread.currentPackageName();
+    }
+
+    private static InlinePresentationSpec defaultInlineTooltipPresentationSpec() {
+        return null;
+    }
+
+    /**
+     * The {@link InlineSuggestionsRequest#getSupportedLocales()} now returns empty locale list when
+     * it's not set, instead of the default system locale.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S)
+    private static final long IME_AUTOFILL_DEFAULT_SUPPORTED_LOCALES_IS_EMPTY = 169273070L;
+
+    private static LocaleList defaultSupportedLocales() {
+        if (CompatChanges.isChangeEnabled(IME_AUTOFILL_DEFAULT_SUPPORTED_LOCALES_IS_EMPTY)) {
+            return LocaleList.getEmptyLocaleList();
+        }
+        return LocaleList.getDefault();
+    }
+
+    @Nullable
+    private static IBinder defaultHostInputToken() {
+        return null;
+    }
+
+    @Nullable
+    private static int defaultHostDisplayId() {
+        return Display.INVALID_DISPLAY;
+    }
+
+    @NonNull
+    private static Bundle defaultExtras() {
+        return Bundle.EMPTY;
+    }
+
+    /** @hide */
+    abstract static class BaseBuilder {
+        abstract Builder setInlinePresentationSpecs(
+                @NonNull List<android.widget.inline.InlinePresentationSpec> specs);
+
+        abstract Builder setHostPackageName(@Nullable String value);
+
+        abstract Builder setHostInputToken(IBinder hostInputToken);
+
+        abstract Builder setHostDisplayId(int value);
+    }
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/./frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    @DataClass.Generated.Member
+    /* package-private */ InlineSuggestionsRequest(
+            int maxSuggestionCount,
+            @NonNull List<InlinePresentationSpec> inlinePresentationSpecs,
+            @NonNull String hostPackageName,
+            @NonNull LocaleList supportedLocales,
+            @NonNull Bundle extras,
+            @Nullable IBinder hostInputToken,
+            int hostDisplayId,
+            @Nullable InlinePresentationSpec inlineTooltipPresentationSpec) {
+        this.mMaxSuggestionCount = maxSuggestionCount;
+        this.mInlinePresentationSpecs = inlinePresentationSpecs;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mInlinePresentationSpecs);
+        this.mHostPackageName = hostPackageName;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mHostPackageName);
+        this.mSupportedLocales = supportedLocales;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mSupportedLocales);
+        this.mExtras = extras;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mExtras);
+        this.mHostInputToken = hostInputToken;
+        this.mHostDisplayId = hostDisplayId;
+        this.mInlineTooltipPresentationSpec = inlineTooltipPresentationSpec;
+
+        onConstructed();
+    }
+
+    /**
+     * Max number of suggestions expected from the response. It must be a positive value.
+     * Defaults to {@code SUGGESTION_COUNT_UNLIMITED} if not set.
+     *
+     * <p>In practice, it is recommended that the max suggestion count does not exceed <b>5</b>
+     * for performance reasons.</p>
+     */
+    @DataClass.Generated.Member
+    public int getMaxSuggestionCount() {
+        return mMaxSuggestionCount;
+    }
+
+    /**
+     * The {@link InlinePresentationSpec} for each suggestion in the response. If the max suggestion
+     * count is larger than the number of specs in the list, then the last spec is used for the
+     * remainder of the suggestions. The list should not be empty.
+     */
+    @DataClass.Generated.Member
+    public @NonNull List<InlinePresentationSpec> getInlinePresentationSpecs() {
+        return mInlinePresentationSpecs;
+    }
+
+    /**
+     * The package name of the app that requests for the inline suggestions and will host the
+     * embedded suggestion views. The app does not have to set the value for the field because
+     * it'll be set by the system for safety reasons.
+     */
+    @DataClass.Generated.Member
+    public @NonNull String getHostPackageName() {
+        return mHostPackageName;
+    }
+
+    /**
+     * The IME provided locales for the request. If non-empty, the inline suggestions should
+     * return languages from the supported locales. If not provided, it'll default to be empty if
+     * target SDK is S or above, and default to system locale otherwise.
+     *
+     * <p>Note for Autofill Providers: It is <b>recommended</b> for the returned inline suggestions
+     * to have one locale to guarantee consistent UI rendering.</p>
+     */
+    @DataClass.Generated.Member
+    public @NonNull LocaleList getSupportedLocales() {
+        return mSupportedLocales;
+    }
+
+    /**
+     * The extras state propagated from the IME to pass extra data.
+     *
+     * <p>Note: There should be no remote objects in the bundle, all included remote objects will
+     * be removed from the bundle before transmission.</p>
+     */
+    @DataClass.Generated.Member
+    public @NonNull Bundle getExtras() {
+        return mExtras;
+    }
+
+    /**
+     * The host input token of the IME that made the request. This will be set by the system for
+     * safety reasons.
+     *
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public @Nullable IBinder getHostInputToken() {
+        return mHostInputToken;
+    }
+
+    /**
+     * The host display id of the IME that made the request. This will be set by the system for
+     * safety reasons.
+     *
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public int getHostDisplayId() {
+        return mHostDisplayId;
+    }
+
+    /**
+     * Specifies the UI specification for the inline suggestion tooltip in the response.
+     */
+    @DataClass.Generated.Member
+    public @Nullable InlinePresentationSpec getInlineTooltipPresentationSpec() {
+        return mInlineTooltipPresentationSpec;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "InlineSuggestionsRequest { " +
+                "maxSuggestionCount = " + mMaxSuggestionCount + ", " +
+                "inlinePresentationSpecs = " + mInlinePresentationSpecs + ", " +
+                "hostPackageName = " + mHostPackageName + ", " +
+                "supportedLocales = " + mSupportedLocales + ", " +
+                "extras = " + mExtras + ", " +
+                "hostInputToken = " + mHostInputToken + ", " +
+                "hostDisplayId = " + mHostDisplayId + ", " +
+                "inlineTooltipPresentationSpec = " + mInlineTooltipPresentationSpec +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(InlineSuggestionsRequest other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        InlineSuggestionsRequest that = (InlineSuggestionsRequest) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && mMaxSuggestionCount == that.mMaxSuggestionCount
+                && java.util.Objects.equals(mInlinePresentationSpecs, that.mInlinePresentationSpecs)
+                && java.util.Objects.equals(mHostPackageName, that.mHostPackageName)
+                && java.util.Objects.equals(mSupportedLocales, that.mSupportedLocales)
+                && extrasEquals(that.mExtras)
+                && java.util.Objects.equals(mHostInputToken, that.mHostInputToken)
+                && mHostDisplayId == that.mHostDisplayId
+                && java.util.Objects.equals(mInlineTooltipPresentationSpec, that.mInlineTooltipPresentationSpec);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + mMaxSuggestionCount;
+        _hash = 31 * _hash + java.util.Objects.hashCode(mInlinePresentationSpecs);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mHostPackageName);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mSupportedLocales);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mExtras);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mHostInputToken);
+        _hash = 31 * _hash + mHostDisplayId;
+        _hash = 31 * _hash + java.util.Objects.hashCode(mInlineTooltipPresentationSpec);
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        int flg = 0;
+        if (mHostInputToken != null) flg |= 0x20;
+        if (mInlineTooltipPresentationSpec != null) flg |= 0x80;
+        dest.writeInt(flg);
+        dest.writeInt(mMaxSuggestionCount);
+        dest.writeParcelableList(mInlinePresentationSpecs, flags);
+        dest.writeString(mHostPackageName);
+        dest.writeTypedObject(mSupportedLocales, flags);
+        dest.writeBundle(mExtras);
+        parcelHostInputToken(dest, flags);
+        dest.writeInt(mHostDisplayId);
+        if (mInlineTooltipPresentationSpec != null) dest.writeTypedObject(mInlineTooltipPresentationSpec, flags);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ InlineSuggestionsRequest(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        int flg = in.readInt();
+        int maxSuggestionCount = in.readInt();
+        List<InlinePresentationSpec> inlinePresentationSpecs = new ArrayList<>();
+        in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader());
+        String hostPackageName = in.readString();
+        LocaleList supportedLocales = (LocaleList) in.readTypedObject(LocaleList.CREATOR);
+        Bundle extras = in.readBundle();
+        IBinder hostInputToken = unparcelHostInputToken(in);
+        int hostDisplayId = in.readInt();
+        InlinePresentationSpec inlineTooltipPresentationSpec = (flg & 0x80) == 0 ? null : (InlinePresentationSpec) in.readTypedObject(InlinePresentationSpec.CREATOR);
+
+        this.mMaxSuggestionCount = maxSuggestionCount;
+        this.mInlinePresentationSpecs = inlinePresentationSpecs;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mInlinePresentationSpecs);
+        this.mHostPackageName = hostPackageName;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mHostPackageName);
+        this.mSupportedLocales = supportedLocales;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mSupportedLocales);
+        this.mExtras = extras;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mExtras);
+        this.mHostInputToken = hostInputToken;
+        this.mHostDisplayId = hostDisplayId;
+        this.mInlineTooltipPresentationSpec = inlineTooltipPresentationSpec;
+
+        onConstructed();
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<InlineSuggestionsRequest> CREATOR
+            = new Parcelable.Creator<InlineSuggestionsRequest>() {
+        @Override
+        public InlineSuggestionsRequest[] newArray(int size) {
+            return new InlineSuggestionsRequest[size];
+        }
+
+        @Override
+        public InlineSuggestionsRequest createFromParcel(@NonNull Parcel in) {
+            return new InlineSuggestionsRequest(in);
+        }
+    };
+
+    /**
+     * A builder for {@link InlineSuggestionsRequest}
+     */
+    @SuppressWarnings("WeakerAccess")
+    @DataClass.Generated.Member
+    public static final class Builder extends BaseBuilder {
+
+        private int mMaxSuggestionCount;
+        private @NonNull List<InlinePresentationSpec> mInlinePresentationSpecs;
+        private @NonNull String mHostPackageName;
+        private @NonNull LocaleList mSupportedLocales;
+        private @NonNull Bundle mExtras;
+        private @Nullable IBinder mHostInputToken;
+        private int mHostDisplayId;
+        private @Nullable InlinePresentationSpec mInlineTooltipPresentationSpec;
+
+        private long mBuilderFieldsSet = 0L;
+
+        /**
+         * Creates a new Builder.
+         *
+         * @param inlinePresentationSpecs
+         *   The {@link InlinePresentationSpec} for each suggestion in the response. If the max suggestion
+         *   count is larger than the number of specs in the list, then the last spec is used for the
+         *   remainder of the suggestions. The list should not be empty.
+         */
+        public Builder(
+                @NonNull List<InlinePresentationSpec> inlinePresentationSpecs) {
+            mInlinePresentationSpecs = inlinePresentationSpecs;
+            com.android.internal.util.AnnotationValidations.validate(
+                    NonNull.class, null, mInlinePresentationSpecs);
+        }
+
+        /**
+         * Max number of suggestions expected from the response. It must be a positive value.
+         * Defaults to {@code SUGGESTION_COUNT_UNLIMITED} if not set.
+         *
+         * <p>In practice, it is recommended that the max suggestion count does not exceed <b>5</b>
+         * for performance reasons.</p>
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setMaxSuggestionCount(int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x1;
+            mMaxSuggestionCount = value;
+            return this;
+        }
+
+        /**
+         * The {@link InlinePresentationSpec} for each suggestion in the response. If the max suggestion
+         * count is larger than the number of specs in the list, then the last spec is used for the
+         * remainder of the suggestions. The list should not be empty.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setInlinePresentationSpecs(@NonNull List<InlinePresentationSpec> value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x2;
+            mInlinePresentationSpecs = value;
+            return this;
+        }
+
+        /** @see #setInlinePresentationSpecs */
+        @DataClass.Generated.Member
+        public @NonNull Builder addInlinePresentationSpecs(@NonNull InlinePresentationSpec value) {
+            // You can refine this method's name by providing item's singular name, e.g.:
+            // @DataClass.PluralOf("item")) mItems = ...
+
+            if (mInlinePresentationSpecs == null) setInlinePresentationSpecs(new ArrayList<>());
+            mInlinePresentationSpecs.add(value);
+            return this;
+        }
+
+        /**
+         * The package name of the app that requests for the inline suggestions and will host the
+         * embedded suggestion views. The app does not have to set the value for the field because
+         * it'll be set by the system for safety reasons.
+         */
+        @DataClass.Generated.Member
+        @Override
+        @NonNull Builder setHostPackageName(@NonNull String value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x4;
+            mHostPackageName = value;
+            return this;
+        }
+
+        /**
+         * The IME provided locales for the request. If non-empty, the inline suggestions should
+         * return languages from the supported locales. If not provided, it'll default to be empty if
+         * target SDK is S or above, and default to system locale otherwise.
+         *
+         * <p>Note for Autofill Providers: It is <b>recommended</b> for the returned inline suggestions
+         * to have one locale to guarantee consistent UI rendering.</p>
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setSupportedLocales(@NonNull LocaleList value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x8;
+            mSupportedLocales = value;
+            return this;
+        }
+
+        /**
+         * The extras state propagated from the IME to pass extra data.
+         *
+         * <p>Note: There should be no remote objects in the bundle, all included remote objects will
+         * be removed from the bundle before transmission.</p>
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setExtras(@NonNull Bundle value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x10;
+            mExtras = value;
+            return this;
+        }
+
+        /**
+         * The host input token of the IME that made the request. This will be set by the system for
+         * safety reasons.
+         *
+         * @hide
+         */
+        @DataClass.Generated.Member
+        @Override
+        @NonNull Builder setHostInputToken(@NonNull IBinder value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x20;
+            mHostInputToken = value;
+            return this;
+        }
+
+        /**
+         * The host display id of the IME that made the request. This will be set by the system for
+         * safety reasons.
+         *
+         * @hide
+         */
+        @DataClass.Generated.Member
+        @Override
+        @NonNull Builder setHostDisplayId(int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x40;
+            mHostDisplayId = value;
+            return this;
+        }
+
+        /**
+         * Specifies the UI specification for the inline suggestion tooltip in the response.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setInlineTooltipPresentationSpec(@NonNull InlinePresentationSpec value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x80;
+            mInlineTooltipPresentationSpec = value;
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        public @NonNull InlineSuggestionsRequest build() {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x100; // Mark builder used
+
+            if ((mBuilderFieldsSet & 0x1) == 0) {
+                mMaxSuggestionCount = defaultMaxSuggestionCount();
+            }
+            if ((mBuilderFieldsSet & 0x4) == 0) {
+                mHostPackageName = defaultHostPackageName();
+            }
+            if ((mBuilderFieldsSet & 0x8) == 0) {
+                mSupportedLocales = defaultSupportedLocales();
+            }
+            if ((mBuilderFieldsSet & 0x10) == 0) {
+                mExtras = defaultExtras();
+            }
+            if ((mBuilderFieldsSet & 0x20) == 0) {
+                mHostInputToken = defaultHostInputToken();
+            }
+            if ((mBuilderFieldsSet & 0x40) == 0) {
+                mHostDisplayId = defaultHostDisplayId();
+            }
+            if ((mBuilderFieldsSet & 0x80) == 0) {
+                mInlineTooltipPresentationSpec = defaultInlineTooltipPresentationSpec();
+            }
+            InlineSuggestionsRequest o = new InlineSuggestionsRequest(
+                    mMaxSuggestionCount,
+                    mInlinePresentationSpecs,
+                    mHostPackageName,
+                    mSupportedLocales,
+                    mExtras,
+                    mHostInputToken,
+                    mHostDisplayId,
+                    mInlineTooltipPresentationSpec);
+            return o;
+        }
+
+        private void checkNotUsed() {
+            if ((mBuilderFieldsSet & 0x100) != 0) {
+                throw new IllegalStateException(
+                        "This Builder should not be reused. Use a new Builder instance instead");
+            }
+        }
+    }
+
+    @DataClass.Generated(
+            time = 1621415989607L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java",
+            inputSignatures = "public static final  int SUGGESTION_COUNT_UNLIMITED\nprivate final  int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate  int mHostDisplayId\nprivate @android.annotation.Nullable android.widget.inline.InlinePresentationSpec mInlineTooltipPresentationSpec\nprivate static final @android.compat.annotation.ChangeId @android.compat.annotation.EnabledSince long IME_AUTOFILL_DEFAULT_SUPPORTED_LOCALES_IS_EMPTY\npublic  void setHostInputToken(android.os.IBinder)\nprivate  boolean extrasEquals(android.os.Bundle)\nprivate  void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic  void setHostDisplayId(int)\nprivate  void onConstructed()\npublic  void filterContentTypes()\nprivate static  int defaultMaxSuggestionCount()\nprivate static  java.lang.String defaultHostPackageName()\nprivate static  android.widget.inline.InlinePresentationSpec defaultInlineTooltipPresentationSpec()\nprivate static  android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []\[email protected](genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/android/view/inputmethod/InlineSuggestionsResponse.java b/android/view/inputmethod/InlineSuggestionsResponse.java
new file mode 100644
index 0000000..b393c67
--- /dev/null
+++ b/android/view/inputmethod/InlineSuggestionsResponse.java
@@ -0,0 +1,208 @@
+/*
+ * 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.view.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class represents an inline suggestion response. See {@link InlineSuggestion} for more
+ * information.
+ */
+@DataClass(genEqualsHashCode = true, genToString = true, genHiddenConstructor = true)
+public final class InlineSuggestionsResponse implements Parcelable {
+    /**
+     * List of {@link InlineSuggestion}s returned as a part of this response.
+     *
+     * <p>When the host app requests to inflate this <b>ordered</b> list of inline suggestions by
+     * calling {@link InlineSuggestion#inflate}, it is the host's responsibility to track the
+     * order of the inflated {@link android.view.View}s. These views are to be added in
+     * order to the view hierarchy, because the inflation calls will return asynchronously.</p>
+     *
+     * <p>The inflation ordering does not apply to the pinned icon.</p>
+     */
+    @NonNull
+    private final List<InlineSuggestion> mInlineSuggestions;
+
+    /**
+     * Creates a new {@link InlineSuggestionsResponse}, for testing purpose.
+     *
+     * @hide
+     */
+    @TestApi
+    @NonNull
+    public static InlineSuggestionsResponse newInlineSuggestionsResponse(
+            @NonNull List<InlineSuggestion> inlineSuggestions) {
+        return new InlineSuggestionsResponse(inlineSuggestions);
+    }
+
+
+
+    // Code below generated by codegen v1.0.15.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsResponse.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new InlineSuggestionsResponse.
+     *
+     * @param inlineSuggestions
+     *   List of {@link InlineSuggestion}s returned as a part of this response.
+     *
+     *   <p>When the host app requests to inflate this <b>ordered</b> list of inline suggestions by
+     *   calling {@link InlineSuggestion#inflate}, it is the host's responsibility to track the
+     *   order of the inflated {@link android.view.View}s. These views are to be added in
+     *   order to the view hierarchy, because the inflation calls will return asynchronously.</p>
+     *
+     *   <p>The inflation ordering does not apply to the pinned icon.</p>
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public InlineSuggestionsResponse(
+            @NonNull List<InlineSuggestion> inlineSuggestions) {
+        this.mInlineSuggestions = inlineSuggestions;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mInlineSuggestions);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * List of {@link InlineSuggestion}s returned as a part of this response.
+     *
+     * <p>When the host app requests to inflate this <b>ordered</b> list of inline suggestions by
+     * calling {@link InlineSuggestion#inflate}, it is the host's responsibility to track the
+     * order of the inflated {@link android.view.View}s. These views are to be added in
+     * order to the view hierarchy, because the inflation calls will return asynchronously.</p>
+     *
+     * <p>The inflation ordering does not apply to the pinned icon.</p>
+     */
+    @DataClass.Generated.Member
+    public @NonNull List<InlineSuggestion> getInlineSuggestions() {
+        return mInlineSuggestions;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "InlineSuggestionsResponse { " +
+                "inlineSuggestions = " + mInlineSuggestions +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(InlineSuggestionsResponse other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        InlineSuggestionsResponse that = (InlineSuggestionsResponse) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && java.util.Objects.equals(mInlineSuggestions, that.mInlineSuggestions);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + java.util.Objects.hashCode(mInlineSuggestions);
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeParcelableList(mInlineSuggestions, flags);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ InlineSuggestionsResponse(@NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        List<InlineSuggestion> inlineSuggestions = new ArrayList<>();
+        in.readParcelableList(inlineSuggestions, InlineSuggestion.class.getClassLoader());
+
+        this.mInlineSuggestions = inlineSuggestions;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mInlineSuggestions);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<InlineSuggestionsResponse> CREATOR
+            = new Parcelable.Creator<InlineSuggestionsResponse>() {
+        @Override
+        public InlineSuggestionsResponse[] newArray(int size) {
+            return new InlineSuggestionsResponse[size];
+        }
+
+        @Override
+        public InlineSuggestionsResponse createFromParcel(@NonNull android.os.Parcel in) {
+            return new InlineSuggestionsResponse(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1595891876037L,
+            codegenVersion = "1.0.15",
+            sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsResponse.java",
+            inputSignatures = "private final @android.annotation.NonNull java.util.List<android.view.inputmethod.InlineSuggestion> mInlineSuggestions\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestionsResponse newInlineSuggestionsResponse(java.util.List<android.view.inputmethod.InlineSuggestion>)\nclass InlineSuggestionsResponse extends java.lang.Object implements [android.os.Parcelable]\[email protected](genEqualsHashCode=true, genToString=true, genHiddenConstructor=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/android/view/inputmethod/InputBinding.java b/android/view/inputmethod/InputBinding.java
new file mode 100644
index 0000000..2bfeb5a
--- /dev/null
+++ b/android/view/inputmethod/InputBinding.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2007-2008 The Android Open Source Project
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.view.inputmethod;
+
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Information given to an {@link InputMethod} about a client connecting
+ * to it.
+ */
+public final class InputBinding implements Parcelable {
+    static final String TAG = "InputBinding";
+    
+    /**
+     * The connection back to the client.
+     */
+    final InputConnection mConnection;
+    
+    /**
+     * A remotable token for the connection back to the client.
+     */
+    final IBinder mConnectionToken;
+    
+    /**
+     * The UID where this binding came from.
+     */
+    final int mUid;
+    
+    /**
+     * The PID where this binding came from.
+     */
+    final int mPid;
+    
+    /**
+     * Constructor.
+     * 
+     * @param conn The interface for communicating back with the application.
+     * @param connToken A remoteable token for communicating across processes.
+     * @param uid The user id of the client of this binding.
+     * @param pid The process id of where the binding came from.
+     */
+    public InputBinding(InputConnection conn, IBinder connToken,
+            int uid, int pid) {
+        mConnection = conn;
+        mConnectionToken = connToken;
+        mUid = uid;
+        mPid = pid;
+    }
+
+    /**
+     * Constructor from an existing InputBinding taking a new local input
+     * connection interface.
+     * 
+     * @param conn The new connection interface.
+     * @param binding Existing binding to copy.
+     */
+    public InputBinding(InputConnection conn, InputBinding binding) {
+        mConnection = conn;
+        mConnectionToken = binding.getConnectionToken();
+        mUid = binding.getUid();
+        mPid = binding.getPid();
+    }
+
+    InputBinding(Parcel source) {
+        mConnection = null;
+        mConnectionToken = source.readStrongBinder();
+        mUid = source.readInt();
+        mPid = source.readInt();
+    }
+    
+    /**
+     * Return the connection for interacting back with the application.
+     */
+    public InputConnection getConnection() {
+        return mConnection;
+    }
+    
+    /**
+     * Return the token for the connection back to the application.  You can
+     * not use this directly, it must be converted to a {@link InputConnection}
+     * for you.
+     */
+    public IBinder getConnectionToken() {
+        return mConnectionToken;
+    }
+    
+    /**
+     * Return the user id of the client associated with this binding.
+     */
+    public int getUid() {
+        return mUid;
+    }
+    
+    /**
+     * Return the process id where this binding came from.
+     */
+    public int getPid() {
+        return mPid;
+    }
+    
+    @Override
+    public String toString() {
+        return "InputBinding{" + mConnectionToken
+                + " / uid " + mUid + " / pid " + mPid + "}";
+    }
+
+    /**
+     * Used to package this object into a {@link Parcel}.
+     * 
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStrongBinder(mConnectionToken);
+        dest.writeInt(mUid);
+        dest.writeInt(mPid);
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final @android.annotation.NonNull Parcelable.Creator<InputBinding> CREATOR = new Parcelable.Creator<InputBinding>() {
+        public InputBinding createFromParcel(Parcel source) {
+            return new InputBinding(source);
+        }
+
+        public InputBinding[] newArray(int size) {
+            return new InputBinding[size];
+        }
+    };
+
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/android/view/inputmethod/InputConnection.java b/android/view/inputmethod/InputConnection.java
new file mode 100644
index 0000000..5f036a3
--- /dev/null
+++ b/android/view/inputmethod/InputConnection.java
@@ -0,0 +1,1024 @@
+/*
+ * 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.view.inputmethod;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.inputmethodservice.InputMethodService;
+import android.os.Bundle;
+import android.os.Handler;
+import android.text.TextUtils;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * The InputConnection interface is the communication channel from an
+ * {@link InputMethod} back to the application that is receiving its
+ * input. It is used to perform such things as reading text around the
+ * cursor, committing text to the text box, and sending raw key events
+ * to the application.
+ *
+ * <p>Starting from API Level {@link android.os.Build.VERSION_CODES#N},
+ * the system can deal with the situation where the application directly
+ * implements this class but one or more of the following methods are
+ * not implemented.</p>
+ * <ul>
+ *     <li>{@link #getSelectedText(int)}, which was introduced in
+ *     {@link android.os.Build.VERSION_CODES#GINGERBREAD}.</li>
+ *     <li>{@link #setComposingRegion(int, int)}, which was introduced
+ *     in {@link android.os.Build.VERSION_CODES#GINGERBREAD}.</li>
+ *     <li>{@link #commitCorrection(CorrectionInfo)}, which was introduced
+ *     in {@link android.os.Build.VERSION_CODES#HONEYCOMB}.</li>
+ *     <li>{@link #requestCursorUpdates(int)}, which was introduced in
+ *     {@link android.os.Build.VERSION_CODES#LOLLIPOP}.</li>
+ *     <li>{@link #deleteSurroundingTextInCodePoints(int, int)}, which
+ *     was introduced in {@link android.os.Build.VERSION_CODES#N}.</li>
+ *     <li>{@link #getHandler()}, which was introduced in
+ *     {@link android.os.Build.VERSION_CODES#N}.</li>
+ *     <li>{@link #closeConnection()}, which was introduced in
+ *     {@link android.os.Build.VERSION_CODES#N}.</li>
+ *     <li>{@link #commitContent(InputContentInfo, int, Bundle)}, which was
+ *     introduced in {@link android.os.Build.VERSION_CODES#N_MR1}.</li>
+ * </ul>
+ *
+ * <h3>Implementing an IME or an editor</h3>
+ * <p>Text input is the result of the synergy of two essential components:
+ * an Input Method Engine (IME) and an editor. The IME can be a
+ * software keyboard, a handwriting interface, an emoji palette, a
+ * speech-to-text engine, and so on. There are typically several IMEs
+ * installed on any given Android device. In Android, IMEs extend
+ * {@link android.inputmethodservice.InputMethodService}.
+ * For more information about how to create an IME, see the
+ * <a href="{@docRoot}guide/topics/text/creating-input-method.html">
+ * Creating an input method</a> guide.
+ *
+ * The editor is the component that receives text and displays it.
+ * Typically, this is an {@link android.widget.EditText} instance, but
+ * some applications may choose to implement their own editor for
+ * various reasons. This is a large and complicated task, and an
+ * application that does this needs to make sure the behavior is
+ * consistent with standard EditText behavior in Android. An editor
+ * needs to interact with the IME, receiving commands through
+ * this InputConnection interface, and sending commands through
+ * {@link android.view.inputmethod.InputMethodManager}. An editor
+ * should start by implementing
+ * {@link android.view.View#onCreateInputConnection(EditorInfo)}
+ * to return its own input connection.</p>
+ *
+ * <p>If you are implementing your own IME, you will need to call the
+ * methods in this interface to interact with the application. Be sure
+ * to test your IME with a wide range of applications, including
+ * browsers and rich text editors, as some may have peculiarities you
+ * need to deal with. Remember your IME may not be the only source of
+ * changes on the text, and try to be as conservative as possible in
+ * the data you send and as liberal as possible in the data you
+ * receive.</p>
+ *
+ * <p>If you are implementing your own editor, you will probably need
+ * to provide your own subclass of {@link BaseInputConnection} to
+ * answer to the commands from IMEs. Please be sure to test your
+ * editor with as many IMEs as you can as their behavior can vary a
+ * lot. Also be sure to test with various languages, including CJK
+ * languages and right-to-left languages like Arabic, as these may
+ * have different input requirements. When in doubt about the
+ * behavior you should adopt for a particular call, please mimic the
+ * default TextView implementation in the latest Android version, and
+ * if you decide to drift from it, please consider carefully that
+ * inconsistencies in text editor behavior is almost universally felt
+ * as a bad thing by users.</p>
+ *
+ * <h3>Cursors, selections and compositions</h3>
+ * <p>In Android, the cursor and the selection are one and the same
+ * thing. A "cursor" is just the special case of a zero-sized
+ * selection. As such, this documentation uses them
+ * interchangeably. Any method acting "before the cursor" would act
+ * before the start of the selection if there is one, and any method
+ * acting "after the cursor" would act after the end of the
+ * selection.</p>
+ *
+ * <p>An editor needs to be able to keep track of a currently
+ * "composing" region, like the standard edition widgets do. The
+ * composition is marked in a specific style: see
+ * {@link android.text.Spanned#SPAN_COMPOSING}. IMEs use this to help
+ * the user keep track of what part of the text they are currently
+ * focusing on, and interact with the editor using
+ * {@link InputConnection#setComposingText(CharSequence, int)},
+ * {@link InputConnection#setComposingRegion(int, int)} and
+ * {@link InputConnection#finishComposingText()}.
+ * The composing region and the selection are completely independent
+ * of each other, and the IME may use them however they see fit.</p>
+ */
+public interface InputConnection {
+    /** @hide */
+    @IntDef(flag = true, prefix = { "GET_TEXT_" }, value = {
+            GET_TEXT_WITH_STYLES,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface GetTextType {}
+
+    /**
+     * Flag for use with {@link #getTextAfterCursor}, {@link #getTextBeforeCursor} and
+     * {@link #getSurroundingText} to have style information returned along with the text. If not
+     * set, {@link #getTextAfterCursor} sends only the raw text, without style or other spans. If
+     * set, it may return a complex CharSequence of both text and style spans.
+     * <strong>Editor authors</strong>: you should strive to send text with styles if possible, but
+     * it is not required.
+     */
+    int GET_TEXT_WITH_STYLES = 0x0001;
+
+    /**
+     * Flag for use with {@link #getExtractedText} to indicate you
+     * would like to receive updates when the extracted text changes.
+     */
+    int GET_EXTRACTED_TEXT_MONITOR = 0x0001;
+
+    /**
+     * Get <var>n</var> characters of text before the current cursor
+     * position.
+     *
+     * <p>This method may fail either if the input connection has
+     * become invalid (such as its process crashing) or the editor is
+     * taking too long to respond with the text (it is given a couple
+     * seconds to return). In either case, null is returned. This
+     * method does not affect the text in the editor in any way, nor
+     * does it affect the selection or composing spans.</p>
+     *
+     * <p>If {@link #GET_TEXT_WITH_STYLES} is supplied as flags, the
+     * editor should return a {@link android.text.SpannableString}
+     * with all the spans set on the text.</p>
+     *
+     * <p><strong>IME authors:</strong> please consider this will
+     * trigger an IPC round-trip that will take some time. Assume this
+     * method consumes a lot of time. Also, please keep in mind the
+     * Editor may choose to return less characters than requested even
+     * if they are available for performance reasons. If you are using
+     * this to get the initial text around the cursor, you may consider
+     * using {@link EditorInfo#getInitialTextBeforeCursor(int, int)},
+     * {@link EditorInfo#getInitialSelectedText(int)}, and
+     * {@link EditorInfo#getInitialTextAfterCursor(int, int)} to prevent IPC costs.</p>
+     *
+     * <p><strong>Editor authors:</strong> please be careful of race
+     * conditions in implementing this call. An IME can make a change
+     * to the text and use this method right away; you need to make
+     * sure the returned value is consistent with the result of the
+     * latest edits. Also, you may return less than n characters if performance
+     * dictates so, but keep in mind IMEs are relying on this for many
+     * functions: you should not, for example, limit the returned value to
+     * the current line, and specifically do not return 0 characters unless
+     * the cursor is really at the start of the text.</p>
+     *
+     * @param n The expected length of the text. This must be non-negative.
+     * @param flags Supplies additional options controlling how the text is
+     * returned. May be either {@code 0} or {@link #GET_TEXT_WITH_STYLES}.
+     * @return the text before the cursor position; the length of the
+     * returned text might be less than <var>n</var>.
+     * @throws IllegalArgumentException if {@code n} is negative.
+     */
+    @Nullable
+    CharSequence getTextBeforeCursor(@IntRange(from = 0) int n, int flags);
+
+    /**
+     * Get <var>n</var> characters of text after the current cursor
+     * position.
+     *
+     * <p>This method may fail either if the input connection has
+     * become invalid (such as its process crashing) or the client is
+     * taking too long to respond with the text (it is given a couple
+     * seconds to return). In either case, null is returned.
+     *
+     * <p>This method does not affect the text in the editor in any
+     * way, nor does it affect the selection or composing spans.</p>
+     *
+     * <p>If {@link #GET_TEXT_WITH_STYLES} is supplied as flags, the
+     * editor should return a {@link android.text.SpannableString}
+     * with all the spans set on the text.</p>
+     *
+     * <p><strong>IME authors:</strong> please consider this will
+     * trigger an IPC round-trip that will take some time. Assume this
+     * method consumes a lot of time. If you are using this to get the
+     * initial text around the cursor, you may consider using
+     * {@link EditorInfo#getInitialTextBeforeCursor(int, int)},
+     * {@link EditorInfo#getInitialSelectedText(int)}, and
+     * {@link EditorInfo#getInitialTextAfterCursor(int, int)} to prevent IPC costs.</p>
+     *
+     * <p><strong>Editor authors:</strong> please be careful of race
+     * conditions in implementing this call. An IME can make a change
+     * to the text and use this method right away; you need to make
+     * sure the returned value is consistent with the result of the
+     * latest edits. Also, you may return less than n characters if performance
+     * dictates so, but keep in mind IMEs are relying on this for many
+     * functions: you should not, for example, limit the returned value to
+     * the current line, and specifically do not return 0 characters unless
+     * the cursor is really at the end of the text.</p>
+     *
+     * @param n The expected length of the text. This must be non-negative.
+     * @param flags Supplies additional options controlling how the text is
+     * returned. May be either {@code 0} or {@link #GET_TEXT_WITH_STYLES}.
+     *
+     * @return the text after the cursor position; the length of the
+     * returned text might be less than <var>n</var>.
+     * @throws IllegalArgumentException if {@code n} is negative.
+     */
+    @Nullable
+    CharSequence getTextAfterCursor(@IntRange(from = 0) int n, int flags);
+
+    /**
+     * Gets the selected text, if any.
+     *
+     * <p>This method may fail if either the input connection has
+     * become invalid (such as its process crashing) or the client is
+     * taking too long to respond with the text (it is given a couple
+     * of seconds to return). In either case, null is returned.</p>
+     *
+     * <p>This method must not cause any changes in the editor's
+     * state.</p>
+     *
+     * <p>If {@link #GET_TEXT_WITH_STYLES} is supplied as flags, the
+     * editor should return a {@link android.text.SpannableString}
+     * with all the spans set on the text.</p>
+     *
+     * <p><strong>IME authors:</strong> please consider this will
+     * trigger an IPC round-trip that will take some time. Assume this
+     * method consumes a lot of time. If you are using this to get the
+     * initial text around the cursor, you may consider using
+     * {@link EditorInfo#getInitialTextBeforeCursor(int, int)},
+     * {@link EditorInfo#getInitialSelectedText(int)}, and
+     * {@link EditorInfo#getInitialTextAfterCursor(int, int)} to prevent IPC costs.</p>
+     *
+     * <p><strong>Editor authors:</strong> please be careful of race
+     * conditions in implementing this call. An IME can make a change
+     * to the text or change the selection position and use this
+     * method right away; you need to make sure the returned value is
+     * consistent with the results of the latest edits.</p>
+     *
+     * @param flags Supplies additional options controlling how the text is
+     * returned. May be either {@code 0} or {@link #GET_TEXT_WITH_STYLES}.
+     * @return the text that is currently selected, if any, or null if
+     * no text is selected. In {@link android.os.Build.VERSION_CODES#N} and
+     * later, returns false when the target application does not implement
+     * this method.
+     */
+    CharSequence getSelectedText(int flags);
+
+    /**
+     * Gets the surrounding text around the current cursor, with <var>beforeLength</var> characters
+     * of text before the cursor (start of the selection), <var>afterLength</var> characters of text
+     * after the cursor (end of the selection), and all of the selected text. The range are for java
+     * characters, not glyphs that can be multiple characters.
+     *
+     * <p>This method may fail either if the input connection has become invalid (such as its
+     * process crashing), or the client is taking too long to respond with the text (it is given a
+     * couple seconds to return), or the protocol is not supported. In any of these cases, null is
+     * returned.
+     *
+     * <p>This method does not affect the text in the editor in any way, nor does it affect the
+     * selection or composing spans.</p>
+     *
+     * <p>If {@link #GET_TEXT_WITH_STYLES} is supplied as flags, the editor should return a
+     * {@link android.text.Spanned} with all the spans set on the text.</p>
+     *
+     * <p><strong>IME authors:</strong> please consider this will trigger an IPC round-trip that
+     * will take some time. Assume this method consumes a lot of time. If you are using this to get
+     * the initial surrounding text around the cursor, you may consider using
+     * {@link EditorInfo#getInitialTextBeforeCursor(int, int)},
+     * {@link EditorInfo#getInitialSelectedText(int)}, and
+     * {@link EditorInfo#getInitialTextAfterCursor(int, int)} to prevent IPC costs.</p>
+     *
+     * @param beforeLength The expected length of the text before the cursor.
+     * @param afterLength The expected length of the text after the cursor.
+     * @param flags Supplies additional options controlling how the text is returned. May be either
+     *              {@code 0} or {@link #GET_TEXT_WITH_STYLES}.
+     * @return an {@link android.view.inputmethod.SurroundingText} object describing the surrounding
+     * text and state of selection, or null if the input connection is no longer valid, or the
+     * editor can't comply with the request for some reason, or the application does not implement
+     * this method. The length of the returned text might be less than the sum of
+     * <var>beforeLength</var> and <var>afterLength</var> .
+     * @throws IllegalArgumentException if {@code beforeLength} or {@code afterLength} is negative.
+     */
+    @Nullable
+    default SurroundingText getSurroundingText(
+            @IntRange(from = 0) int beforeLength, @IntRange(from = 0) int afterLength,
+            @GetTextType int flags) {
+        Preconditions.checkArgumentNonnegative(beforeLength);
+        Preconditions.checkArgumentNonnegative(afterLength);
+
+        CharSequence textBeforeCursor = getTextBeforeCursor(beforeLength, flags);
+        if (textBeforeCursor == null) {
+            return null;
+        }
+        CharSequence textAfterCursor = getTextAfterCursor(afterLength, flags);
+        if (textAfterCursor == null) {
+            return null;
+        }
+        CharSequence selectedText = getSelectedText(flags);
+        if (selectedText == null) {
+            selectedText = "";
+        }
+        CharSequence surroundingText =
+                TextUtils.concat(textBeforeCursor, selectedText, textAfterCursor);
+        return new SurroundingText(surroundingText, textBeforeCursor.length(),
+                textBeforeCursor.length() + selectedText.length(), -1);
+    }
+
+    /**
+     * Retrieve the current capitalization mode in effect at the
+     * current cursor position in the text. See
+     * {@link android.text.TextUtils#getCapsMode TextUtils.getCapsMode}
+     * for more information.
+     *
+     * <p>This method may fail either if the input connection has
+     * become invalid (such as its process crashing) or the client is
+     * taking too long to respond with the text (it is given a couple
+     * seconds to return). In either case, 0 is returned.</p>
+     *
+     * <p>This method does not affect the text in the editor in any
+     * way, nor does it affect the selection or composing spans.</p>
+     *
+     * <p><strong>Editor authors:</strong> please be careful of race
+     * conditions in implementing this call. An IME can change the
+     * cursor position and use this method right away; you need to make
+     * sure the returned value is consistent with the results of the
+     * latest edits and changes to the cursor position.</p>
+     *
+     * @param reqModes The desired modes to retrieve, as defined by
+     * {@link android.text.TextUtils#getCapsMode TextUtils.getCapsMode}. These
+     * constants are defined so that you can simply pass the current
+     * {@link EditorInfo#inputType TextBoxAttribute.contentType} value
+     * directly in to here.
+     * @return the caps mode flags that are in effect at the current
+     * cursor position. See TYPE_TEXT_FLAG_CAPS_* in {@link android.text.InputType}.
+     */
+    int getCursorCapsMode(int reqModes);
+
+    /**
+     * Retrieve the current text in the input connection's editor, and
+     * monitor for any changes to it. This function returns with the
+     * current text, and optionally the input connection can send
+     * updates to the input method when its text changes.
+     *
+     * <p>This method may fail either if the input connection has
+     * become invalid (such as its process crashing) or the client is
+     * taking too long to respond with the text (it is given a couple
+     * seconds to return). In either case, null is returned.</p>
+     *
+     * <p>Editor authors: as a general rule, try to comply with the
+     * fields in <code>request</code> for how many chars to return,
+     * but if performance or convenience dictates otherwise, please
+     * feel free to do what is most appropriate for your case. Also,
+     * if the
+     * {@link #GET_EXTRACTED_TEXT_MONITOR} flag is set, you should be
+     * calling
+     * {@link InputMethodManager#updateExtractedText(View, int, ExtractedText)}
+     * whenever you call
+     * {@link InputMethodManager#updateSelection(View, int, int, int, int)}.</p>
+     *
+     * @param request Description of how the text should be returned.
+     * {@link android.view.inputmethod.ExtractedTextRequest}
+     * @param flags Additional options to control the client, either {@code 0} or
+     * {@link #GET_EXTRACTED_TEXT_MONITOR}.
+
+     * @return an {@link android.view.inputmethod.ExtractedText}
+     * object describing the state of the text view and containing the
+     * extracted text itself, or null if the input connection is no
+     * longer valid of the editor can't comply with the request for
+     * some reason.
+     */
+    ExtractedText getExtractedText(ExtractedTextRequest request, int flags);
+
+    /**
+     * Delete <var>beforeLength</var> characters of text before the
+     * current cursor position, and delete <var>afterLength</var>
+     * characters of text after the current cursor position, excluding
+     * the selection. Before and after refer to the order of the
+     * characters in the string, not to their visual representation:
+     * this means you don't have to figure out the direction of the
+     * text and can just use the indices as-is.
+     *
+     * <p>The lengths are supplied in Java chars, not in code points
+     * or in glyphs.</p>
+     *
+     * <p>Since this method only operates on text before and after the
+     * selection, it can't affect the contents of the selection. This
+     * may affect the composing span if the span includes characters
+     * that are to be deleted, but otherwise will not change it. If
+     * some characters in the composing span are deleted, the
+     * composing span will persist but get shortened by however many
+     * chars inside it have been removed.</p>
+     *
+     * <p><strong>IME authors:</strong> please be careful not to
+     * delete only half of a surrogate pair. Also take care not to
+     * delete more characters than are in the editor, as that may have
+     * ill effects on the application. Calling this method will cause
+     * the editor to call
+     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+     * int, int)} on your service after the batch input is over.</p>
+     *
+     * <p><strong>Editor authors:</strong> please be careful of race
+     * conditions in implementing this call. An IME can make a change
+     * to the text or change the selection position and use this
+     * method right away; you need to make sure the effects are
+     * consistent with the results of the latest edits. Also, although
+     * the IME should not send lengths bigger than the contents of the
+     * string, you should check the values for overflows and trim the
+     * indices to the size of the contents to avoid crashes. Since
+     * this changes the contents of the editor, you need to make the
+     * changes known to the input method by calling
+     * {@link InputMethodManager#updateSelection(View, int, int, int, int)},
+     * but be careful to wait until the batch edit is over if one is
+     * in progress.</p>
+     *
+     * @param beforeLength The number of characters before the cursor to be deleted, in code unit.
+     *        If this is greater than the number of existing characters between the beginning of the
+     *        text and the cursor, then this method does not fail but deletes all the characters in
+     *        that range.
+     * @param afterLength The number of characters after the cursor to be deleted, in code unit.
+     *        If this is greater than the number of existing characters between the cursor and
+     *        the end of the text, then this method does not fail but deletes all the characters in
+     *        that range.
+     * @return true on success, false if the input connection is no longer valid.
+     */
+    boolean deleteSurroundingText(int beforeLength, int afterLength);
+
+    /**
+     * A variant of {@link #deleteSurroundingText(int, int)}. Major differences are:
+     *
+     * <ul>
+     *     <li>The lengths are supplied in code points, not in Java chars or in glyphs.</>
+     *     <li>This method does nothing if there are one or more invalid surrogate pairs in the
+     *     requested range.</li>
+     * </ul>
+     *
+     * <p><strong>Editor authors:</strong> In addition to the requirement in
+     * {@link #deleteSurroundingText(int, int)}, make sure to do nothing when one ore more invalid
+     * surrogate pairs are found in the requested range.</p>
+     *
+     * @see #deleteSurroundingText(int, int)
+     *
+     * @param beforeLength The number of characters before the cursor to be deleted, in code points.
+     *        If this is greater than the number of existing characters between the beginning of the
+     *        text and the cursor, then this method does not fail but deletes all the characters in
+     *        that range.
+     * @param afterLength The number of characters after the cursor to be deleted, in code points.
+     *        If this is greater than the number of existing characters between the cursor and
+     *        the end of the text, then this method does not fail but deletes all the characters in
+     *        that range.
+     * @return true on success, false if the input connection is no longer valid.  Returns
+     * {@code false} when the target application does not implement this method.
+     */
+    boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength);
+
+    /**
+     * Replace the currently composing text with the given text, and
+     * set the new cursor position. Any composing text set previously
+     * will be removed automatically.
+     *
+     * <p>If there is any composing span currently active, all
+     * characters that it comprises are removed. The passed text is
+     * added in its place, and a composing span is added to this
+     * text. If there is no composing span active, the passed text is
+     * added at the cursor position (removing selected characters
+     * first if any), and a composing span is added on the new text.
+     * Finally, the cursor is moved to the location specified by
+     * <code>newCursorPosition</code>.</p>
+     *
+     * <p>This is usually called by IMEs to add or remove or change
+     * characters in the composing span. Calling this method will
+     * cause the editor to call
+     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+     * int, int)} on the current IME after the batch input is over.</p>
+     *
+     * <p><strong>Editor authors:</strong> please keep in mind the
+     * text may be very similar or completely different than what was
+     * in the composing span at call time, or there may not be a
+     * composing span at all. Please note that although it's not
+     * typical use, the string may be empty. Treat this normally,
+     * replacing the currently composing text with an empty string.
+     * Also, be careful with the cursor position. IMEs rely on this
+     * working exactly as described above. Since this changes the
+     * contents of the editor, you need to make the changes known to
+     * the input method by calling
+     * {@link InputMethodManager#updateSelection(View, int, int, int, int)},
+     * but be careful to wait until the batch edit is over if one is
+     * in progress. Note that this method can set the cursor position
+     * on either edge of the composing text or entirely outside it,
+     * but the IME may also go on to move the cursor position to
+     * within the composing text in a subsequent call so you should
+     * make no assumption at all: the composing text and the selection
+     * are entirely independent.</p>
+     *
+     * @param text The composing text with styles if necessary. If no style
+     *        object attached to the text, the default style for composing text
+     *        is used. See {@link android.text.Spanned} for how to attach style
+     *        object to the text. {@link android.text.SpannableString} and
+     *        {@link android.text.SpannableStringBuilder} are two
+     *        implementations of the interface {@link android.text.Spanned}.
+     * @param newCursorPosition The new cursor position around the text. If
+     *        > 0, this is relative to the end of the text - 1; if <= 0, this
+     *        is relative to the start of the text. So a value of 1 will
+     *        always advance you to the position after the full text being
+     *        inserted. Note that this means you can't position the cursor
+     *        within the text, because the editor can make modifications to
+     *        the text you are providing so it is not possible to correctly
+     *        specify locations there.
+     * @return true on success, false if the input connection is no longer
+     * valid.
+     */
+    boolean setComposingText(CharSequence text, int newCursorPosition);
+
+    /**
+     * Mark a certain region of text as composing text. If there was a
+     * composing region, the characters are left as they were and the
+     * composing span removed, as if {@link #finishComposingText()}
+     * has been called. The default style for composing text is used.
+     *
+     * <p>The passed indices are clipped to the contents bounds. If
+     * the resulting region is zero-sized, no region is marked and the
+     * effect is the same as that of calling {@link #finishComposingText()}.
+     * The order of start and end is not important. In effect, the
+     * region from start to end and the region from end to start is
+     * the same. Editor authors, be ready to accept a start that is
+     * greater than end.</p>
+     *
+     * <p>Since this does not change the contents of the text, editors should not call
+     * {@link InputMethodManager#updateSelection(View, int, int, int, int)} and
+     * IMEs should not receive
+     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+     * int, int)}.</p>
+     *
+     * <p>This has no impact on the cursor/selection position. It may
+     * result in the cursor being anywhere inside or outside the
+     * composing region, including cases where the selection and the
+     * composing region overlap partially or entirely.</p>
+     *
+     * @param start the position in the text at which the composing region begins
+     * @param end the position in the text at which the composing region ends
+     * @return true on success, false if the input connection is no longer
+     * valid. In {@link android.os.Build.VERSION_CODES#N} and later, false is returned when the
+     * target application does not implement this method.
+     */
+    boolean setComposingRegion(int start, int end);
+
+    /**
+     * Have the text editor finish whatever composing text is
+     * currently active. This simply leaves the text as-is, removing
+     * any special composing styling or other state that was around
+     * it. The cursor position remains unchanged.
+     *
+     * <p><strong>IME authors:</strong> be aware that this call may be
+     * expensive with some editors.</p>
+     *
+     * <p><strong>Editor authors:</strong> please note that the cursor
+     * may be anywhere in the contents when this is called, including
+     * in the middle of the composing span or in a completely
+     * unrelated place. It must not move.</p>
+     *
+     * @return true on success, false if the input connection
+     * is no longer valid.
+     */
+    boolean finishComposingText();
+
+    /**
+     * Commit text to the text box and set the new cursor position.
+     *
+     * <p>This method removes the contents of the currently composing
+     * text and replaces it with the passed CharSequence, and then
+     * moves the cursor according to {@code newCursorPosition}. If there
+     * is no composing text when this method is called, the new text is
+     * inserted at the cursor position, removing text inside the selection
+     * if any. This behaves like calling
+     * {@link #setComposingText(CharSequence, int) setComposingText(text, newCursorPosition)}
+     * then {@link #finishComposingText()}.</p>
+     *
+     * <p>Calling this method will cause the editor to call
+     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+     * int, int)} on the current IME after the batch input is over.
+     * <strong>Editor authors</strong>, for this to happen you need to
+     * make the changes known to the input method by calling
+     * {@link InputMethodManager#updateSelection(View, int, int, int, int)},
+     * but be careful to wait until the batch edit is over if one is
+     * in progress.</p>
+     *
+     * @param text The text to commit. This may include styles.
+     * @param newCursorPosition The new cursor position around the text,
+     *        in Java characters. If > 0, this is relative to the end
+     *        of the text - 1; if <= 0, this is relative to the start
+     *        of the text. So a value of 1 will always advance the cursor
+     *        to the position after the full text being inserted. Note that
+     *        this means you can't position the cursor within the text,
+     *        because the editor can make modifications to the text
+     *        you are providing so it is not possible to correctly specify
+     *        locations there.
+     * @return true on success, false if the input connection is no longer
+     * valid.
+     */
+    boolean commitText(CharSequence text, int newCursorPosition);
+
+    /**
+     * Commit a completion the user has selected from the possible ones
+     * previously reported to {@link InputMethodSession#displayCompletions
+     * InputMethodSession#displayCompletions(CompletionInfo[])} or
+     * {@link InputMethodManager#displayCompletions
+     * InputMethodManager#displayCompletions(View, CompletionInfo[])}.
+     * This will result in the same behavior as if the user had
+     * selected the completion from the actual UI. In all other
+     * respects, this behaves like {@link #commitText(CharSequence, int)}.
+     *
+     * <p><strong>IME authors:</strong> please take care to send the
+     * same object that you received through
+     * {@link android.inputmethodservice.InputMethodService#onDisplayCompletions(CompletionInfo[])}.
+     * </p>
+     *
+     * <p><strong>Editor authors:</strong> if you never call
+     * {@link InputMethodSession#displayCompletions(CompletionInfo[])} or
+     * {@link InputMethodManager#displayCompletions(View, CompletionInfo[])} then
+     * a well-behaved IME should never call this on your input
+     * connection, but be ready to deal with misbehaving IMEs without
+     * crashing.</p>
+     *
+     * <p>Calling this method (with a valid {@link CompletionInfo} object)
+     * will cause the editor to call
+     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+     * int, int)} on the current IME after the batch input is over.
+     * <strong>Editor authors</strong>, for this to happen you need to
+     * make the changes known to the input method by calling
+     * {@link InputMethodManager#updateSelection(View, int, int, int, int)},
+     * but be careful to wait until the batch edit is over if one is
+     * in progress.</p>
+     *
+     * @param text The committed completion.
+     * @return true on success, false if the input connection is no longer
+     * valid.
+     */
+    boolean commitCompletion(CompletionInfo text);
+
+    /**
+     * Commit a correction automatically performed on the raw user's input. A
+     * typical example would be to correct typos using a dictionary.
+     *
+     * <p>Calling this method will cause the editor to call
+     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+     * int, int)} on the current IME after the batch input is over.
+     * <strong>Editor authors</strong>, for this to happen you need to
+     * make the changes known to the input method by calling
+     * {@link InputMethodManager#updateSelection(View, int, int, int, int)},
+     * but be careful to wait until the batch edit is over if one is
+     * in progress.</p>
+     *
+     * @param correctionInfo Detailed information about the correction.
+     * @return true on success, false if the input connection is no longer valid.
+     * In {@link android.os.Build.VERSION_CODES#N} and later, returns false
+     * when the target application does not implement this method.
+     */
+    boolean commitCorrection(CorrectionInfo correctionInfo);
+
+    /**
+     * Set the selection of the text editor. To set the cursor
+     * position, start and end should have the same value.
+     *
+     * <p>Since this moves the cursor, calling this method will cause
+     * the editor to call
+     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+     * int, int)} on the current IME after the batch input is over.
+     * <strong>Editor authors</strong>, for this to happen you need to
+     * make the changes known to the input method by calling
+     * {@link InputMethodManager#updateSelection(View, int, int, int, int)},
+     * but be careful to wait until the batch edit is over if one is
+     * in progress.</p>
+     *
+     * <p>This has no effect on the composing region which must stay
+     * unchanged. The order of start and end is not important. In
+     * effect, the region from start to end and the region from end to
+     * start is the same. Editor authors, be ready to accept a start
+     * that is greater than end.</p>
+     *
+     * @param start the character index where the selection should start.
+     * @param end the character index where the selection should end.
+     * @return true on success, false if the input connection is no longer
+     * valid.
+     */
+    boolean setSelection(int start, int end);
+
+    /**
+     * Have the editor perform an action it has said it can do.
+     *
+     * <p>This is typically used by IMEs when the user presses the key
+     * associated with the action.</p>
+     *
+     * @param editorAction This must be one of the action constants for
+     * {@link EditorInfo#imeOptions EditorInfo.editorType}, such as
+     * {@link EditorInfo#IME_ACTION_GO EditorInfo.EDITOR_ACTION_GO}.
+     * @return true on success, false if the input connection is no longer
+     * valid.
+     */
+    boolean performEditorAction(int editorAction);
+
+    /**
+     * Perform a context menu action on the field. The given id may be one of:
+     * {@link android.R.id#selectAll},
+     * {@link android.R.id#startSelectingText}, {@link android.R.id#stopSelectingText},
+     * {@link android.R.id#cut}, {@link android.R.id#copy},
+     * {@link android.R.id#paste}, {@link android.R.id#copyUrl},
+     * or {@link android.R.id#switchInputMethod}
+     */
+    boolean performContextMenuAction(int id);
+
+    /**
+     * Tell the editor that you are starting a batch of editor
+     * operations. The editor will try to avoid sending you updates
+     * about its state until {@link #endBatchEdit} is called. Batch
+     * edits nest.
+     *
+     * <p><strong>IME authors:</strong> use this to avoid getting
+     * calls to
+     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+     * int, int)} corresponding to intermediate state. Also, use this to avoid
+     * flickers that may arise from displaying intermediate state. Be
+     * sure to call {@link #endBatchEdit} for each call to this, or
+     * you may block updates in the editor.</p>
+     *
+     * <p><strong>Editor authors:</strong> while a batch edit is in
+     * progress, take care not to send updates to the input method and
+     * not to update the display. IMEs use this intensively to this
+     * effect. Also please note that batch edits need to nest
+     * correctly.</p>
+     *
+     * @return true if a batch edit is now in progress, false otherwise. Since
+     * this method starts a batch edit, that means it will always return true
+     * unless the input connection is no longer valid.
+     */
+    boolean beginBatchEdit();
+
+    /**
+     * Tell the editor that you are done with a batch edit previously
+     * initiated with {@link #beginBatchEdit}. This ends the latest
+     * batch only.
+     *
+     * <p><strong>IME authors:</strong> make sure you call this
+     * exactly once for each call to {@link #beginBatchEdit}.</p>
+     *
+     * <p><strong>Editor authors:</strong> please be careful about
+     * batch edit nesting. Updates still to be held back until the end
+     * of the last batch edit.</p>
+     *
+     * @return true if there is still a batch edit in progress after closing
+     * the latest one (in other words, if the nesting count is > 0), false
+     * otherwise or if the input connection is no longer valid.
+     */
+    boolean endBatchEdit();
+
+    /**
+     * Send a key event to the process that is currently attached
+     * through this input connection. The event will be dispatched
+     * like a normal key event, to the currently focused view; this
+     * generally is the view that is providing this InputConnection,
+     * but due to the asynchronous nature of this protocol that can
+     * not be guaranteed and the focus may have changed by the time
+     * the event is received.
+     *
+     * <p>This method can be used to send key events to the
+     * application. For example, an on-screen keyboard may use this
+     * method to simulate a hardware keyboard. There are three types
+     * of standard keyboards, numeric (12-key), predictive (20-key)
+     * and ALPHA (QWERTY). You can specify the keyboard type by
+     * specify the device id of the key event.</p>
+     *
+     * <p>You will usually want to set the flag
+     * {@link KeyEvent#FLAG_SOFT_KEYBOARD KeyEvent.FLAG_SOFT_KEYBOARD}
+     * on all key event objects you give to this API; the flag will
+     * not be set for you.</p>
+     *
+     * <p>Note that it's discouraged to send such key events in normal
+     * operation; this is mainly for use with
+     * {@link android.text.InputType#TYPE_NULL} type text fields. Use
+     * the {@link #commitText} family of methods to send text to the
+     * application instead.</p>
+     *
+     * @param event The key event.
+     * @return true on success, false if the input connection is no longer
+     * valid.
+     *
+     * @see KeyEvent
+     * @see KeyCharacterMap#NUMERIC
+     * @see KeyCharacterMap#PREDICTIVE
+     * @see KeyCharacterMap#ALPHA
+     */
+    boolean sendKeyEvent(KeyEvent event);
+
+    /**
+     * Clear the given meta key pressed states in the given input
+     * connection.
+     *
+     * <p>This can be used by the IME to clear the meta key states set
+     * by a hardware keyboard with latched meta keys, if the editor
+     * keeps track of these.</p>
+     *
+     * @param states The states to be cleared, may be one or more bits as
+     * per {@link KeyEvent#getMetaState() KeyEvent.getMetaState()}.
+     * @return true on success, false if the input connection is no longer
+     * valid.
+     */
+    boolean clearMetaKeyStates(int states);
+
+    /**
+     * Called back when the connected IME switches between fullscreen and normal modes.
+     *
+     * <p>Note: On {@link android.os.Build.VERSION_CODES#O} and later devices, input methods are no
+     * longer allowed to directly call this method at any time. To signal this event in the target
+     * application, input methods should always call
+     * {@link InputMethodService#updateFullscreenMode()} instead. This approach should work on API
+     * {@link android.os.Build.VERSION_CODES#N_MR1} and prior devices.</p>
+     *
+     * @return For editor authors, the return value will always be ignored. For IME authors, this
+     *         always returns {@code true} on {@link android.os.Build.VERSION_CODES#N_MR1} and prior
+     *         devices and {@code false} on {@link android.os.Build.VERSION_CODES#O} and later
+     *         devices.
+     * @see InputMethodManager#isFullscreenMode()
+     */
+    boolean reportFullscreenMode(boolean enabled);
+
+    /**
+     * Have the editor perform spell checking for the full content.
+     *
+     * <p>The editor can ignore this method call if it does not support spell checking.
+     *
+     * @return For editor authors, the return value will always be ignored. For IME authors, this
+     *         method returns true if the spell check request was sent (whether or not the
+     *         associated editor supports spell checking), false if the input connection is no
+     *         longer valid.
+     */
+    default boolean performSpellCheck() {
+        return false;
+    }
+
+    /**
+     * API to send private commands from an input method to its
+     * connected editor. This can be used to provide domain-specific
+     * features that are only known between certain input methods and
+     * their clients. Note that because the InputConnection protocol
+     * is asynchronous, you have no way to get a result back or know
+     * if the client understood the command; you can use the
+     * information in {@link EditorInfo} to determine if a client
+     * supports a particular command.
+     *
+     * @param action Name of the command to be performed. This <em>must</em>
+     * be a scoped name, i.e. prefixed with a package name you own, so that
+     * different developers will not create conflicting commands.
+     * @param data Any data to include with the command.
+     * @return true if the command was sent (whether or not the
+     * associated editor understood it), false if the input connection is no longer
+     * valid.
+     */
+    boolean performPrivateCommand(String action, Bundle data);
+
+    /**
+     * The editor is requested to call
+     * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)} at
+     * once, as soon as possible, regardless of cursor/anchor position changes. This flag can be
+     * used together with {@link #CURSOR_UPDATE_MONITOR}.
+     */
+    int CURSOR_UPDATE_IMMEDIATE = 1 << 0;
+
+    /**
+     * The editor is requested to call
+     * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)}
+     * whenever cursor/anchor position is changed. To disable monitoring, call
+     * {@link InputConnection#requestCursorUpdates(int)} again with this flag off.
+     * <p>
+     * This flag can be used together with {@link #CURSOR_UPDATE_IMMEDIATE}.
+     * </p>
+     */
+    int CURSOR_UPDATE_MONITOR = 1 << 1;
+
+    /**
+     * Called by the input method to ask the editor for calling back
+     * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)} to
+     * notify cursor/anchor locations.
+     *
+     * @param cursorUpdateMode {@link #CURSOR_UPDATE_IMMEDIATE} and/or
+     * {@link #CURSOR_UPDATE_MONITOR}. Pass {@code 0} to disable the effect of
+     * {@link #CURSOR_UPDATE_MONITOR}.
+     * @return {@code true} if the request is scheduled. {@code false} to indicate that when the
+     * application will not call
+     * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)}.
+     * In {@link android.os.Build.VERSION_CODES#N} and later, returns {@code false} also when the
+     * target application does not implement this method.
+     */
+    boolean requestCursorUpdates(int cursorUpdateMode);
+
+    /**
+     * Called by the {@link InputMethodManager} to enable application developers to specify a
+     * dedicated {@link Handler} on which incoming IPC method calls from input methods will be
+     * dispatched.
+     *
+     * <p>Note: This does nothing when called from input methods.</p>
+     *
+     * @return {@code null} to use the default {@link Handler}.
+     */
+    Handler getHandler();
+
+    /**
+     * Called by the system up to only once to notify that the system is about to invalidate
+     * connection between the input method and the application.
+     *
+     * <p><strong>Editor authors</strong>: You can clear all the nested batch edit right now and
+     * you no longer need to handle subsequent callbacks on this connection, including
+     * {@link #beginBatchEdit()}}.  Note that although the system tries to call this method whenever
+     * possible, there may be a chance that this method is not called in some exceptional
+     * situations.</p>
+     *
+     * <p>Note: This does nothing when called from input methods.</p>
+     */
+    void closeConnection();
+
+    /**
+     * When this flag is used, the editor will be able to request read access to the content URI
+     * contained in the {@link InputContentInfo} object.
+     *
+     * <p>Make sure that the content provider owning the Uri sets the
+     * {@link android.R.styleable#AndroidManifestProvider_grantUriPermissions
+     * grantUriPermissions} attribute in its manifest or included the
+     * {@link android.R.styleable#AndroidManifestGrantUriPermission
+     * &lt;grant-uri-permissions&gt;} tag. Otherwise {@link InputContentInfo#requestPermission()}
+     * can fail.</p>
+     *
+     * <p>Although calling this API is allowed only for the IME that is currently selected, the
+     * client is able to request a temporary read-only access even after the current IME is switched
+     * to any other IME as long as the client keeps {@link InputContentInfo} object.</p>
+     **/
+    int INPUT_CONTENT_GRANT_READ_URI_PERMISSION =
+            android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;  // 0x00000001
+
+    /**
+     * Called by the input method to commit content such as a PNG image to the editor.
+     *
+     * <p>In order to avoid a variety of compatibility issues, this focuses on a simple use case,
+     * where editors and IMEs are expected to work cooperatively as follows:</p>
+     * <ul>
+     *     <li>Editor must keep {@link EditorInfo#contentMimeTypes} equal to {@code null} if it does
+     *     not support this method at all.</li>
+     *     <li>Editor can ignore this request when the MIME type specified in
+     *     {@code inputContentInfo} does not match any of {@link EditorInfo#contentMimeTypes}.
+     *     </li>
+     *     <li>Editor can ignore the cursor position when inserting the provided content.</li>
+     *     <li>Editor can return {@code true} asynchronously, even before it starts loading the
+     *     content.</li>
+     *     <li>Editor should provide a way to delete the content inserted by this method or to
+     *     revert the effect caused by this method.</li>
+     *     <li>IME should not call this method when there is any composing text, in case calling
+     *     this method causes a focus change.</li>
+     *     <li>IME should grant a permission for the editor to read the content. See
+     *     {@link EditorInfo#packageName} about how to obtain the package name of the editor.</li>
+     * </ul>
+     *
+     * @param inputContentInfo Content to be inserted.
+     * @param flags {@link #INPUT_CONTENT_GRANT_READ_URI_PERMISSION} if the content provider
+     * allows {@link android.R.styleable#AndroidManifestProvider_grantUriPermissions
+     * grantUriPermissions} or {@code 0} if the application does not need to call
+     * {@link InputContentInfo#requestPermission()}.
+     * @param opts optional bundle data. This can be {@code null}.
+     * @return {@code true} if this request is accepted by the application, whether the request
+     * is already handled or still being handled in background, {@code false} otherwise.
+     */
+    boolean commitContent(@NonNull InputContentInfo inputContentInfo, int flags,
+            @Nullable Bundle opts);
+
+    /**
+     * Called by the input method to indicate that it consumes all input for itself, or no longer
+     * does so.
+     *
+     * <p>Editors should reflect that they are not receiving input by hiding the cursor if
+     * {@code imeConsumesInput} is {@code true}, and resume showing the cursor if it is
+     * {@code false}.
+     *
+     * @param imeConsumesInput {@code true} when the IME is consuming input and the cursor should be
+     * hidden, {@code false} when input to the editor resumes and the cursor should be shown again.
+     * @return For editor authors, the return value will always be ignored. For IME authors, this
+     *         method returns {@code true} if the request was sent (whether or not the associated
+     *         editor does something based on this request), {@code false} if the input connection
+     *         is no longer valid.
+     */
+    default boolean setImeConsumesInput(boolean imeConsumesInput) {
+        return false;
+    }
+}
diff --git a/android/view/inputmethod/InputConnectionInspector.java b/android/view/inputmethod/InputConnectionInspector.java
new file mode 100644
index 0000000..7621da7
--- /dev/null
+++ b/android/view/inputmethod/InputConnectionInspector.java
@@ -0,0 +1,293 @@
+/*
+ * 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.view.inputmethod;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+
+import java.lang.annotation.Retention;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+/**
+ * @hide
+ */
+public final class InputConnectionInspector {
+
+    @Retention(SOURCE)
+    @IntDef({MissingMethodFlags.GET_SELECTED_TEXT,
+            MissingMethodFlags.SET_COMPOSING_REGION,
+            MissingMethodFlags.COMMIT_CORRECTION,
+            MissingMethodFlags.REQUEST_CURSOR_UPDATES,
+            MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS,
+            MissingMethodFlags.GET_HANDLER,
+            MissingMethodFlags.CLOSE_CONNECTION,
+            MissingMethodFlags.COMMIT_CONTENT,
+            MissingMethodFlags.GET_SURROUNDING_TEXT
+    })
+    public @interface MissingMethodFlags {
+        /**
+         * {@link InputConnection#getSelectedText(int)} is available in
+         * {@link android.os.Build.VERSION_CODES#GINGERBREAD} and later.
+         */
+        int GET_SELECTED_TEXT = 1 << 0;
+        /**
+         * {@link InputConnection#setComposingRegion(int, int)} is available in
+         * {@link android.os.Build.VERSION_CODES#GINGERBREAD} and later.
+         */
+        int SET_COMPOSING_REGION = 1 << 1;
+        /**
+         * {@link InputConnection#commitCorrection(CorrectionInfo)} is available in
+         * {@link android.os.Build.VERSION_CODES#HONEYCOMB} and later.
+         */
+        int COMMIT_CORRECTION = 1 << 2;
+        /**
+         * {@link InputConnection#requestCursorUpdates(int)} is available in
+         * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later.
+         */
+        int REQUEST_CURSOR_UPDATES = 1 << 3;
+        /**
+         * {@link InputConnection#deleteSurroundingTextInCodePoints(int, int)}} is available in
+         * {@link android.os.Build.VERSION_CODES#N} and later.
+         */
+        int DELETE_SURROUNDING_TEXT_IN_CODE_POINTS = 1 << 4;
+        /**
+         * {@link InputConnection#deleteSurroundingTextInCodePoints(int, int)}} is available in
+         * {@link android.os.Build.VERSION_CODES#N} and later.
+         */
+        int GET_HANDLER = 1 << 5;
+        /**
+         * {@link InputConnection#closeConnection()}} is available in
+         * {@link android.os.Build.VERSION_CODES#N} and later.
+         */
+        int CLOSE_CONNECTION = 1 << 6;
+        /**
+         * {@link InputConnection#commitContent(InputContentInfo, int, Bundle)} is available in
+         * {@link android.os.Build.VERSION_CODES#N} MR-1 and later.
+         */
+        int COMMIT_CONTENT = 1 << 7;
+        /**
+         * {@link InputConnection#getSurroundingText(int, int, int)} is available in
+         * {@link android.os.Build.VERSION_CODES#S} and later.
+         */
+        int GET_SURROUNDING_TEXT = 1 << 8;
+    }
+
+    private static final Map<Class, Integer> sMissingMethodsMap = Collections.synchronizedMap(
+            new WeakHashMap<>());
+
+    @MissingMethodFlags
+    public static int getMissingMethodFlags(@Nullable final InputConnection ic) {
+        if (ic == null) {
+            return 0;
+        }
+        // Optimization for a known class.
+        if (ic instanceof BaseInputConnection) {
+            return 0;
+        }
+        // Optimization for a known class.
+        if (ic instanceof InputConnectionWrapper) {
+            return ((InputConnectionWrapper) ic).getMissingMethodFlags();
+        }
+        return getMissingMethodFlagsInternal(ic.getClass());
+    }
+
+    @MissingMethodFlags
+    public static int getMissingMethodFlagsInternal(@NonNull final Class clazz) {
+        final Integer cachedFlags = sMissingMethodsMap.get(clazz);
+        if (cachedFlags != null) {
+            return cachedFlags;
+        }
+        int flags = 0;
+        if (!hasGetSelectedText(clazz)) {
+            flags |= MissingMethodFlags.GET_SELECTED_TEXT;
+        }
+        if (!hasSetComposingRegion(clazz)) {
+            flags |= MissingMethodFlags.SET_COMPOSING_REGION;
+        }
+        if (!hasCommitCorrection(clazz)) {
+            flags |= MissingMethodFlags.COMMIT_CORRECTION;
+        }
+        if (!hasRequestCursorUpdate(clazz)) {
+            flags |= MissingMethodFlags.REQUEST_CURSOR_UPDATES;
+        }
+        if (!hasDeleteSurroundingTextInCodePoints(clazz)) {
+            flags |= MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS;
+        }
+        if (!hasGetHandler(clazz)) {
+            flags |= MissingMethodFlags.GET_HANDLER;
+        }
+        if (!hasCloseConnection(clazz)) {
+            flags |= MissingMethodFlags.CLOSE_CONNECTION;
+        }
+        if (!hasCommitContent(clazz)) {
+            flags |= MissingMethodFlags.COMMIT_CONTENT;
+        }
+        if (!hasGetSurroundingText(clazz)) {
+            flags |= MissingMethodFlags.GET_SURROUNDING_TEXT;
+        }
+        sMissingMethodsMap.put(clazz, flags);
+        return flags;
+    }
+
+    private static boolean hasGetSelectedText(@NonNull final Class clazz) {
+        try {
+            final Method method = clazz.getMethod("getSelectedText", int.class);
+            return !Modifier.isAbstract(method.getModifiers());
+        } catch (NoSuchMethodException e) {
+            return false;
+        }
+    }
+
+    private static boolean hasSetComposingRegion(@NonNull final Class clazz) {
+        try {
+            final Method method = clazz.getMethod("setComposingRegion", int.class, int.class);
+            return !Modifier.isAbstract(method.getModifiers());
+        } catch (NoSuchMethodException e) {
+            return false;
+        }
+    }
+
+    private static boolean hasCommitCorrection(@NonNull final Class clazz) {
+        try {
+            final Method method = clazz.getMethod("commitCorrection", CorrectionInfo.class);
+            return !Modifier.isAbstract(method.getModifiers());
+        } catch (NoSuchMethodException e) {
+            return false;
+        }
+    }
+
+    private static boolean hasRequestCursorUpdate(@NonNull final Class clazz) {
+        try {
+            final Method method = clazz.getMethod("requestCursorUpdates", int.class);
+            return !Modifier.isAbstract(method.getModifiers());
+        } catch (NoSuchMethodException e) {
+            return false;
+        }
+    }
+
+    private static boolean hasDeleteSurroundingTextInCodePoints(@NonNull final Class clazz) {
+        try {
+            final Method method = clazz.getMethod("deleteSurroundingTextInCodePoints", int.class,
+                    int.class);
+            return !Modifier.isAbstract(method.getModifiers());
+        } catch (NoSuchMethodException e) {
+            return false;
+        }
+    }
+
+    private static boolean hasGetHandler(@NonNull final Class clazz) {
+        try {
+            final Method method = clazz.getMethod("getHandler");
+            return !Modifier.isAbstract(method.getModifiers());
+        } catch (NoSuchMethodException e) {
+            return false;
+        }
+    }
+
+    private static boolean hasCloseConnection(@NonNull final Class clazz) {
+        try {
+            final Method method = clazz.getMethod("closeConnection");
+            return !Modifier.isAbstract(method.getModifiers());
+        } catch (NoSuchMethodException e) {
+            return false;
+        }
+    }
+
+    private static boolean hasCommitContent(@NonNull final Class clazz) {
+        try {
+            final Method method = clazz.getMethod("commitContent", InputContentInfo.class,
+                    int.class, Bundle.class);
+            return !Modifier.isAbstract(method.getModifiers());
+        } catch (NoSuchMethodException e) {
+            return false;
+        }
+    }
+
+    private static boolean hasGetSurroundingText(@NonNull final Class clazz) {
+        try {
+            final Method method = clazz.getMethod("getSurroundingText", int.class, int.class,
+                    int.class);
+            return !Modifier.isAbstract(method.getModifiers());
+        } catch (NoSuchMethodException e) {
+            return false;
+        }
+    }
+
+    public static String getMissingMethodFlagsAsString(@MissingMethodFlags final int flags) {
+        final StringBuilder sb = new StringBuilder();
+        boolean isEmpty = true;
+        if ((flags & MissingMethodFlags.GET_SELECTED_TEXT) != 0) {
+            sb.append("getSelectedText(int)");
+            isEmpty = false;
+        }
+        if ((flags & MissingMethodFlags.SET_COMPOSING_REGION) != 0) {
+            if (!isEmpty) {
+                sb.append(",");
+            }
+            sb.append("setComposingRegion(int, int)");
+            isEmpty = false;
+        }
+        if ((flags & MissingMethodFlags.COMMIT_CORRECTION) != 0) {
+            if (!isEmpty) {
+                sb.append(",");
+            }
+            sb.append("commitCorrection(CorrectionInfo)");
+            isEmpty = false;
+        }
+        if ((flags & MissingMethodFlags.REQUEST_CURSOR_UPDATES) != 0) {
+            if (!isEmpty) {
+                sb.append(",");
+            }
+            sb.append("requestCursorUpdate(int)");
+            isEmpty = false;
+        }
+        if ((flags & MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS) != 0) {
+            if (!isEmpty) {
+                sb.append(",");
+            }
+            sb.append("deleteSurroundingTextInCodePoints(int, int)");
+            isEmpty = false;
+        }
+        if ((flags & MissingMethodFlags.GET_HANDLER) != 0) {
+            if (!isEmpty) {
+                sb.append(",");
+            }
+            sb.append("getHandler()");
+        }
+        if ((flags & MissingMethodFlags.CLOSE_CONNECTION) != 0) {
+            if (!isEmpty) {
+                sb.append(",");
+            }
+            sb.append("closeConnection()");
+        }
+        if ((flags & MissingMethodFlags.COMMIT_CONTENT) != 0) {
+            if (!isEmpty) {
+                sb.append(",");
+            }
+            sb.append("commitContent(InputContentInfo, Bundle)");
+        }
+        return sb.toString();
+    }
+}
diff --git a/android/view/inputmethod/InputConnectionWrapper.java b/android/view/inputmethod/InputConnectionWrapper.java
new file mode 100644
index 0000000..b1501a4
--- /dev/null
+++ b/android/view/inputmethod/InputConnectionWrapper.java
@@ -0,0 +1,347 @@
+/*
+ * 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.view.inputmethod;
+
+import android.annotation.IntRange;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.KeyEvent;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * <p>Wrapper class for proxying calls to another InputConnection.  Subclass and have fun!
+ */
+public class InputConnectionWrapper implements InputConnection {
+    private InputConnection mTarget;
+    final boolean mMutable;
+    @InputConnectionInspector.MissingMethodFlags
+    private int mMissingMethodFlags;
+
+    /**
+     * Initializes a wrapper.
+     *
+     * <p><b>Caveat:</b> Although the system can accept {@code (InputConnection) null} in some
+     * places, you cannot emulate such a behavior by non-null {@link InputConnectionWrapper} that
+     * has {@code null} in {@code target}.</p>
+     * @param target the {@link InputConnection} to be proxied.
+     * @param mutable set {@code true} to protect this object from being reconfigured to target
+     * another {@link InputConnection}.  Note that this is ignored while the target is {@code null}.
+     */
+    public InputConnectionWrapper(InputConnection target, boolean mutable) {
+        mMutable = mutable;
+        mTarget = target;
+        mMissingMethodFlags = InputConnectionInspector.getMissingMethodFlags(target);
+    }
+
+    /**
+     * Change the target of the input connection.
+     *
+     * <p><b>Caveat:</b> Although the system can accept {@code (InputConnection) null} in some
+     * places, you cannot emulate such a behavior by non-null {@link InputConnectionWrapper} that
+     * has {@code null} in {@code target}.</p>
+     * @param target the {@link InputConnection} to be proxied.
+     * @throws SecurityException when this wrapper has non-null target and is immutable.
+     */
+    public void setTarget(InputConnection target) {
+        if (mTarget != null && !mMutable) {
+            throw new SecurityException("not mutable");
+        }
+        mTarget = target;
+        mMissingMethodFlags = InputConnectionInspector.getMissingMethodFlags(target);
+    }
+
+    /**
+     * @hide
+     */
+    @InputConnectionInspector.MissingMethodFlags
+    public int getMissingMethodFlags() {
+        return mMissingMethodFlags;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     * @throws IllegalArgumentException if {@code length} is negative.
+     */
+    @Nullable
+    @Override
+    public CharSequence getTextBeforeCursor(@IntRange(from = 0) int n, int flags) {
+        Preconditions.checkArgumentNonnegative(n);
+        return mTarget.getTextBeforeCursor(n, flags);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     * @throws IllegalArgumentException if {@code length} is negative.
+     */
+    @Nullable
+    @Override
+    public CharSequence getTextAfterCursor(@IntRange(from = 0) int n, int flags) {
+        Preconditions.checkArgumentNonnegative(n);
+        return mTarget.getTextAfterCursor(n, flags);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
+    public CharSequence getSelectedText(int flags) {
+        return mTarget.getSelectedText(flags);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     * @throws IllegalArgumentException if {@code beforeLength} or {@code afterLength} is negative.
+     */
+    @Nullable
+    @Override
+    public SurroundingText getSurroundingText(int beforeLength, int afterLength, int flags) {
+        Preconditions.checkArgumentNonnegative(beforeLength);
+        Preconditions.checkArgumentNonnegative(afterLength);
+        return mTarget.getSurroundingText(beforeLength, afterLength, flags);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
+    public int getCursorCapsMode(int reqModes) {
+        return mTarget.getCursorCapsMode(reqModes);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
+    public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
+        return mTarget.getExtractedText(request, flags);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
+    public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
+        return mTarget.deleteSurroundingTextInCodePoints(beforeLength, afterLength);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
+    public boolean deleteSurroundingText(int beforeLength, int afterLength) {
+        return mTarget.deleteSurroundingText(beforeLength, afterLength);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
+    public boolean setComposingText(CharSequence text, int newCursorPosition) {
+        return mTarget.setComposingText(text, newCursorPosition);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
+    public boolean setComposingRegion(int start, int end) {
+        return mTarget.setComposingRegion(start, end);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
+    public boolean finishComposingText() {
+        return mTarget.finishComposingText();
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
+    public boolean commitText(CharSequence text, int newCursorPosition) {
+        return mTarget.commitText(text, newCursorPosition);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
+    public boolean commitCompletion(CompletionInfo text) {
+        return mTarget.commitCompletion(text);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
+    public boolean commitCorrection(CorrectionInfo correctionInfo) {
+        return mTarget.commitCorrection(correctionInfo);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
+    public boolean setSelection(int start, int end) {
+        return mTarget.setSelection(start, end);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
+    public boolean performEditorAction(int editorAction) {
+        return mTarget.performEditorAction(editorAction);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
+    public boolean performContextMenuAction(int id) {
+        return mTarget.performContextMenuAction(id);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
+    public boolean beginBatchEdit() {
+        return mTarget.beginBatchEdit();
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
+    public boolean endBatchEdit() {
+        return mTarget.endBatchEdit();
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
+    public boolean sendKeyEvent(KeyEvent event) {
+        return mTarget.sendKeyEvent(event);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
+    public boolean clearMetaKeyStates(int states) {
+        return mTarget.clearMetaKeyStates(states);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
+    public boolean reportFullscreenMode(boolean enabled) {
+        return mTarget.reportFullscreenMode(enabled);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
+    public boolean performSpellCheck() {
+        return mTarget.performSpellCheck();
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
+    public boolean performPrivateCommand(String action, Bundle data) {
+        return mTarget.performPrivateCommand(action, data);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
+    public boolean requestCursorUpdates(int cursorUpdateMode) {
+        return mTarget.requestCursorUpdates(cursorUpdateMode);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
+    public Handler getHandler() {
+        return mTarget.getHandler();
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
+    public void closeConnection() {
+        mTarget.closeConnection();
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
+    public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
+        return mTarget.commitContent(inputContentInfo, flags, opts);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
+    public boolean setImeConsumesInput(boolean imeConsumesInput) {
+        return mTarget.setImeConsumesInput(imeConsumesInput);
+    }
+}
diff --git a/android/view/inputmethod/InputContentInfo.java b/android/view/inputmethod/InputContentInfo.java
new file mode 100644
index 0000000..5ebd9c1
--- /dev/null
+++ b/android/view/inputmethod/InputContentInfo.java
@@ -0,0 +1,293 @@
+/*
+ * 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.view.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.ClipDescription;
+import android.content.ContentProvider;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.UserHandle;
+
+import com.android.internal.inputmethod.IInputContentUriToken;
+
+import java.security.InvalidParameterException;
+
+/**
+ * A container object with which input methods can send content files to the target application.
+ */
+public final class InputContentInfo implements Parcelable {
+
+    /**
+     * The content URI that may or may not have a user ID embedded by
+     * {@link ContentProvider#maybeAddUserId(Uri, int)}.  This always preserves the exact value
+     * specified to a constructor.  In other words, if it had user ID embedded when it was passed
+     * to the constructor, it still has the same user ID no matter if it is valid or not.
+     */
+    @NonNull
+    private final Uri mContentUri;
+    /**
+     * The user ID to which {@link #mContentUri} belongs to.  If {@link #mContentUri} already
+     * embedded the user ID when it was specified then this fields has the same user ID.  Otherwise
+     * the user ID is determined based on the process ID when the constructor is called.
+     *
+     * <p>CAUTION: If you received {@link InputContentInfo} from a different process, there is no
+     * guarantee that this value is correct and valid.  Never use this for any security purpose</p>
+     */
+    @UserIdInt
+    private final int mContentUriOwnerUserId;
+    @NonNull
+    private final ClipDescription mDescription;
+    @Nullable
+    private final Uri mLinkUri;
+    @NonNull
+    private IInputContentUriToken mUriToken;
+
+    /**
+     * Constructs {@link InputContentInfo} object only with mandatory data.
+     *
+     * @param contentUri Content URI to be exported from the input method.
+     * This cannot be {@code null}.
+     * @param description A {@link ClipDescription} object that contains the metadata of
+     * {@code contentUri} such as MIME type(s). This object cannot be {@code null}. Also
+     * {@link ClipDescription#getLabel()} should be describing the content specified by
+     * {@code contentUri} for accessibility reasons.
+     */
+    public InputContentInfo(@NonNull Uri contentUri, @NonNull ClipDescription description) {
+        this(contentUri, description, null /* link Uri */);
+    }
+
+    /**
+     * Constructs {@link InputContentInfo} object with additional link URI.
+     *
+     * @param contentUri Content URI to be exported from the input method.
+     * This cannot be {@code null}.
+     * @param description A {@link ClipDescription} object that contains the metadata of
+     * {@code contentUri} such as MIME type(s). This object cannot be {@code null}. Also
+     * {@link ClipDescription#getLabel()} should be describing the content specified by
+     * {@code contentUri} for accessibility reasons.
+     * @param linkUri An optional {@code http} or {@code https} URI. The editor author may provide
+     * a way to navigate the user to the specified web page if this is not {@code null}.
+     * @throws InvalidParameterException if any invalid parameter is specified.
+     */
+    public InputContentInfo(@NonNull Uri contentUri, @NonNull ClipDescription description,
+            @Nullable Uri linkUri) {
+        validateInternal(contentUri, description, linkUri, true /* throwException */);
+        mContentUri = contentUri;
+        mContentUriOwnerUserId =
+                ContentProvider.getUserIdFromUri(mContentUri, UserHandle.myUserId());
+        mDescription = description;
+        mLinkUri = linkUri;
+    }
+
+    /**
+     * @return {@code true} if all the fields are valid.
+     * @hide
+     */
+    public boolean validate() {
+        return validateInternal(mContentUri, mDescription, mLinkUri, false /* throwException */);
+    }
+
+    /**
+     * Constructs {@link InputContentInfo} object with additional link URI.
+     *
+     * @param contentUri Content URI to be exported from the input method.
+     * This cannot be {@code null}.
+     * @param description A {@link ClipDescription} object that contains the metadata of
+     * {@code contentUri} such as MIME type(s). This object cannot be {@code null}. Also
+     * {@link ClipDescription#getLabel()} should be describing the content specified by
+     * {@code contentUri} for accessibility reasons.
+     * @param linkUri An optional {@code http} or {@code https} URI. The editor author may provide
+     * a way to navigate the user to the specified web page if this is not {@code null}.
+     * @param throwException {@code true} if this method should throw an
+     * {@link InvalidParameterException}.
+     * @throws InvalidParameterException if any invalid parameter is specified.
+     */
+    private static boolean validateInternal(@NonNull Uri contentUri,
+            @NonNull ClipDescription description, @Nullable Uri linkUri, boolean throwException) {
+        if (contentUri == null) {
+            if (throwException) {
+                throw new NullPointerException("contentUri");
+            }
+            return false;
+        }
+        if (description == null) {
+            if (throwException) {
+                throw new NullPointerException("description");
+            }
+            return false;
+        }
+        final String contentUriScheme = contentUri.getScheme();
+        if (!"content".equals(contentUriScheme)) {
+            if (throwException) {
+                throw new InvalidParameterException("contentUri must have content scheme");
+            }
+            return false;
+        }
+        if (linkUri != null) {
+            final String scheme = linkUri.getScheme();
+            if (scheme == null ||
+                    (!scheme.equalsIgnoreCase("http") && !scheme.equalsIgnoreCase("https"))) {
+                if (throwException) {
+                    throw new InvalidParameterException(
+                            "linkUri must have either http or https scheme");
+                }
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * @return Content URI with which the content can be obtained.
+     */
+    @NonNull
+    public Uri getContentUri() {
+        // Fix up the content URI when and only when the caller's user ID does not match the owner's
+        // user ID.
+        if (mContentUriOwnerUserId != UserHandle.myUserId()) {
+            return ContentProvider.maybeAddUserId(mContentUri, mContentUriOwnerUserId);
+        }
+        return mContentUri;
+    }
+
+    /**
+     * @return {@link ClipDescription} object that contains the metadata of {@code #getContentUri()}
+     * such as MIME type(s). {@link ClipDescription#getLabel()} can be used for accessibility
+     * purpose.
+     */
+    @NonNull
+    public ClipDescription getDescription() { return mDescription; }
+
+    /**
+     * @return An optional {@code http} or {@code https} URI that is related to this content.
+     */
+    @Nullable
+    public Uri getLinkUri() { return mLinkUri; }
+
+    /**
+     * Update the internal state of this object to be associated with the given token.
+     *
+     * <p>TODO(yukawa): Come up with an idea to make {@link InputContentInfo} immutable.</p>
+     *
+     * @param token special URI token obtained from the system.
+     * @hide
+     */
+    public void setUriToken(IInputContentUriToken token) {
+        if (mUriToken != null) {
+            throw new IllegalStateException("URI token is already set");
+        }
+        mUriToken = token;
+    }
+
+    /**
+     * Requests a temporary {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION read-only}
+     * access permission for the content URI associated with this object.
+     *
+     * <p>The lifecycle of the permission granted here is tied to this object instance. If the
+     * permission is not released explicitly via {@link #releasePermission()}, it will be
+     * released automatically when there are no more references to this object.</p>
+     *
+     * <p>Does nothing if the temporary permission is already granted.</p>
+     */
+    public void requestPermission() {
+        if (mUriToken == null) {
+            return;
+        }
+        try {
+            mUriToken.take();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Releases a temporary read-only access permission for content URI associated with this object.
+     *
+     * <p>Does nothing if the temporary permission is not granted.</p>
+     */
+    public void releasePermission() {
+        if (mUriToken == null) {
+            return;
+        }
+        try {
+            mUriToken.release();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Used to package this object into a {@link Parcel}.
+     *
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        Uri.writeToParcel(dest, mContentUri);
+        dest.writeInt(mContentUriOwnerUserId);
+        mDescription.writeToParcel(dest, flags);
+        Uri.writeToParcel(dest, mLinkUri);
+        if (mUriToken != null) {
+            dest.writeInt(1);
+            dest.writeStrongBinder(mUriToken.asBinder());
+        } else {
+            dest.writeInt(0);
+        }
+    }
+
+    private InputContentInfo(@NonNull Parcel source) {
+        mContentUri = Uri.CREATOR.createFromParcel(source);
+        mContentUriOwnerUserId = source.readInt();
+        mDescription = ClipDescription.CREATOR.createFromParcel(source);
+        mLinkUri = Uri.CREATOR.createFromParcel(source);
+        if (source.readInt() == 1) {
+            mUriToken = IInputContentUriToken.Stub.asInterface(source.readStrongBinder());
+        } else {
+            mUriToken = null;
+        }
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final @android.annotation.NonNull Parcelable.Creator<InputContentInfo> CREATOR
+            = new Parcelable.Creator<InputContentInfo>() {
+        @Override
+        public InputContentInfo createFromParcel(Parcel source) {
+            return new InputContentInfo(source);
+        }
+
+        @Override
+        public InputContentInfo[] newArray(int size) {
+            return new InputContentInfo[size];
+        }
+    };
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/android/view/inputmethod/InputMethod.java b/android/view/inputmethod/InputMethod.java
new file mode 100644
index 0000000..d2db0df
--- /dev/null
+++ b/android/view/inputmethod/InputMethod.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright (C) 2007-2008 The Android Open Source Project
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.view.inputmethod;
+
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.inputmethodservice.InputMethodService;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.util.Log;
+import android.view.View;
+
+import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
+import com.android.internal.view.InlineSuggestionsRequestInfo;
+
+/**
+ * The InputMethod interface represents an input method which can generate key
+ * events and text, such as digital, email addresses, CJK characters, other
+ * language characters, and etc., while handling various input events, and send
+ * the text back to the application that requests text input.  See
+ * {@link InputMethodManager} for more general information about the
+ * architecture.
+ *
+ * <p>Applications will not normally use this interface themselves, instead
+ * relying on the standard interaction provided by
+ * {@link android.widget.TextView} and {@link android.widget.EditText}.
+ * 
+ * <p>Those implementing input methods should normally do so by deriving from
+ * {@link InputMethodService} or one of its subclasses.  When implementing
+ * an input method, the service component containing it must also supply
+ * a {@link #SERVICE_META_DATA} meta-data field, referencing an XML resource
+ * providing details about the input method.  All input methods also must
+ * require that clients hold the
+ * {@link android.Manifest.permission#BIND_INPUT_METHOD} in order to interact
+ * with the service; if this is not required, the system will not use that
+ * input method, because it can not trust that it is not compromised.
+ * 
+ * <p>The InputMethod interface is actually split into two parts: the interface
+ * here is the top-level interface to the input method, providing all
+ * access to it, which only the system can access (due to the BIND_INPUT_METHOD
+ * permission requirement).  In addition its method
+ * {@link #createSession(android.view.inputmethod.InputMethod.SessionCallback)}
+ * can be called to instantate a secondary {@link InputMethodSession} interface
+ * which is what clients use to communicate with the input method.
+ */
+public interface InputMethod {
+    /** @hide **/
+    public static final String TAG = "InputMethod";
+    /**
+     * This is the interface name that a service implementing an input
+     * method should say that it supports -- that is, this is the action it
+     * uses for its intent filter.
+     * To be supported, the service must also require the
+     * {@link android.Manifest.permission#BIND_INPUT_METHOD} permission so
+     * that other applications can not abuse it.
+     */
+    @SdkConstant(SdkConstantType.SERVICE_ACTION)
+    public static final String SERVICE_INTERFACE = "android.view.InputMethod";
+    
+    /**
+     * Name under which an InputMethod service component publishes information
+     * about itself.  This meta-data must reference an XML resource containing
+     * an
+     * <code>&lt;{@link android.R.styleable#InputMethod input-method}&gt;</code>
+     * tag.
+     */
+    public static final String SERVICE_META_DATA = "android.view.im";
+    
+    public interface SessionCallback {
+        public void sessionCreated(InputMethodSession session);
+    }
+
+    /**
+     * Called first thing after an input method is created, this supplies a
+     * unique token for the session it has with the system service as well as
+     * IPC endpoint to do some other privileged operations.
+     *
+     * @param token special token for the system to identify
+     *              {@link InputMethodService}
+     * @param displayId The id of the display that current IME shown.
+     *                  Used for {{@link #updateInputMethodDisplay(int)}}
+     * @param privilegedOperations IPC endpoint to do some privileged
+     *                             operations that are allowed only to the
+     *                             current IME.
+     * @param configChanges {@link InputMethodInfo#getConfigChanges()} declared by IME.
+     * @hide
+     */
+    @MainThread
+    default void initializeInternal(IBinder token, int displayId,
+            IInputMethodPrivilegedOperations privilegedOperations, int configChanges) {
+        updateInputMethodDisplay(displayId);
+        attachToken(token);
+    }
+
+    /**
+     * Called to notify the IME that Autofill Frameworks requested an inline suggestions request.
+     *
+     * @param requestInfo information needed to create an {@link InlineSuggestionsRequest}.
+     * @param cb {@link IInlineSuggestionsRequestCallback} used to pass back the request object.
+     *
+     * @hide
+     */
+    default void onCreateInlineSuggestionsRequest(InlineSuggestionsRequestInfo requestInfo,
+            IInlineSuggestionsRequestCallback cb) {
+        try {
+            cb.onInlineSuggestionsUnsupported();
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to call onInlineSuggestionsUnsupported.", e);
+        }
+    }
+
+    /**
+     * Called first thing after an input method is created, this supplies a
+     * unique token for the session it has with the system service.  It is
+     * needed to identify itself with the service to validate its operations.
+     * This token <strong>must not</strong> be passed to applications, since
+     * it grants special priviledges that should not be given to applications.
+     *
+     * <p>The system guarantees that this method is called back between
+     * {@link InputMethodService#onCreate()} and {@link InputMethodService#onDestroy()}
+     * at most once.
+     */
+    @MainThread
+    public void attachToken(IBinder token);
+
+    /**
+     * Update context display according to given displayId.
+     *
+     * @param displayId The id of the display that need to update for context.
+     * @hide
+     */
+    @MainThread
+    default void updateInputMethodDisplay(int displayId) {
+    }
+
+    /**
+     * Bind a new application environment in to the input method, so that it
+     * can later start and stop input processing.
+     * Typically this method is called when this input method is enabled in an
+     * application for the first time.
+     * 
+     * @param binding Information about the application window that is binding
+     * to the input method.
+     * 
+     * @see InputBinding
+     * @see #unbindInput()
+     */
+    @MainThread
+    public void bindInput(InputBinding binding);
+
+    /**
+     * Unbind an application environment, called when the information previously
+     * set by {@link #bindInput} is no longer valid for this input method.
+     * 
+     * <p>
+     * Typically this method is called when the application changes to be
+     * non-foreground.
+     */
+    @MainThread
+    public void unbindInput();
+
+    /**
+     * This method is called when the application starts to receive text and it
+     * is ready for this input method to process received events and send result
+     * text back to the application.
+     * 
+     * @param inputConnection Optional specific input connection for
+     * communicating with the text box; if null, you should use the generic
+     * bound input connection.
+     * @param info Information about the text box (typically, an EditText)
+     *        that requests input.
+     * 
+     * @see EditorInfo
+     */
+    @MainThread
+    public void startInput(InputConnection inputConnection, EditorInfo info);
+
+    /**
+     * This method is called when the state of this input method needs to be
+     * reset.
+     * 
+     * <p>
+     * Typically, this method is called when the input focus is moved from one
+     * text box to another.
+     * 
+     * @param inputConnection Optional specific input connection for
+     * communicating with the text box; if null, you should use the generic
+     * bound input connection.
+     * @param attribute The attribute of the text box (typically, a EditText)
+     *        that requests input.
+     * 
+     * @see EditorInfo
+     */
+    @MainThread
+    public void restartInput(InputConnection inputConnection, EditorInfo attribute);
+
+    /**
+     * This method is called when {@code {@link #startInput(InputConnection, EditorInfo)} or
+     * {@code {@link #restartInput(InputConnection, EditorInfo)} needs to be dispatched.
+     *
+     * <p>Note: This method is hidden because the {@code startInputToken} that this method is
+     * dealing with is one of internal details, which should not be exposed to the IME developers.
+     * If you override this method, you are responsible for not breaking existing IMEs that expect
+     * {@link #startInput(InputConnection, EditorInfo)} to be still called back.</p>
+     *
+     * @param inputConnection optional specific input connection for communicating with the text
+     *                        box; if {@code null}, you should use the generic bound input
+     *                        connection
+     * @param editorInfo information about the text box (typically, an EditText) that requests input
+     * @param restarting {@code false} if this corresponds to
+     *                   {@link #startInput(InputConnection, EditorInfo)}. Otherwise this
+     *                   corresponds to {@link #restartInput(InputConnection, EditorInfo)}.
+     * @param startInputToken a token that identifies a logical session that starts with this method
+     *                        call. Some internal IPCs such as {@link
+     *                        InputMethodManager#setImeWindowStatus(IBinder, IBinder, int, int)}
+     *                        require this token to work, and you have to keep the token alive until
+     *                        the next {@link #startInput(InputConnection, EditorInfo, IBinder)} as
+     *                        long as your implementation of {@link InputMethod} relies on such
+     *                        IPCs
+     * @see #startInput(InputConnection, EditorInfo)
+     * @see #restartInput(InputConnection, EditorInfo)
+     * @see EditorInfo
+     * @hide
+     */
+    @MainThread
+    default void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
+            @NonNull EditorInfo editorInfo, boolean restarting,
+            @NonNull IBinder startInputToken) {
+        if (restarting) {
+            restartInput(inputConnection, editorInfo);
+        } else {
+            startInput(inputConnection, editorInfo);
+        }
+    }
+
+    /**
+     * Create a new {@link InputMethodSession} that can be handed to client
+     * applications for interacting with the input method.  You can later
+     * use {@link #revokeSession(InputMethodSession)} to destroy the session
+     * so that it can no longer be used by any clients.
+     * 
+     * @param callback Interface that is called with the newly created session.
+     */
+    @MainThread
+    public void createSession(SessionCallback callback);
+    
+    /**
+     * Control whether a particular input method session is active.
+     * 
+     * @param session The {@link InputMethodSession} previously provided through
+     * SessionCallback.sessionCreated() that is to be changed.
+     */
+    @MainThread
+    public void setSessionEnabled(InputMethodSession session, boolean enabled);
+    
+    /**
+     * Disable and destroy a session that was previously created with
+     * {@link #createSession(android.view.inputmethod.InputMethod.SessionCallback)}.
+     * After this call, the given session interface is no longer active and
+     * calls on it will fail.
+     * 
+     * @param session The {@link InputMethodSession} previously provided through
+     * SessionCallback.sessionCreated() that is to be revoked.
+     */
+    @MainThread
+    public void revokeSession(InputMethodSession session);
+    
+    /**
+     * Flag for {@link #showSoftInput}: this show has been explicitly
+     * requested by the user.  If not set, the system has decided it may be
+     * a good idea to show the input method based on a navigation operation
+     * in the UI.
+     */
+    public static final int SHOW_EXPLICIT = 0x00001;
+    
+    /**
+     * Flag for {@link #showSoftInput}: this show has been forced to
+     * happen by the user.  If set, the input method should remain visible
+     * until deliberated dismissed by the user in its UI.
+     */
+    public static final int SHOW_FORCED = 0x00002;
+
+    /**
+     * Request that any soft input part of the input method be shown to the user.
+     *
+     * @param flags Provides additional information about the show request.
+     * Currently may be 0 or have the bit {@link #SHOW_EXPLICIT} set.
+     * @param resultReceiver The client requesting the show may wish to
+     * be told the impact of their request, which should be supplied here.
+     * The result code should be
+     * {@link InputMethodManager#RESULT_UNCHANGED_SHOWN InputMethodManager.RESULT_UNCHANGED_SHOWN},
+     * {@link InputMethodManager#RESULT_UNCHANGED_HIDDEN InputMethodManager.RESULT_UNCHANGED_HIDDEN},
+     * {@link InputMethodManager#RESULT_SHOWN InputMethodManager.RESULT_SHOWN}, or
+     * {@link InputMethodManager#RESULT_HIDDEN InputMethodManager.RESULT_HIDDEN}.
+     * @param showInputToken an opaque {@link android.os.Binder} token to identify which API call
+     *        of {@link InputMethodManager#showSoftInput(View, int)} is associated with
+     *        this callback.
+     * @hide
+     */
+    @MainThread
+    default public void showSoftInputWithToken(int flags, ResultReceiver resultReceiver,
+            IBinder showInputToken) {
+        showSoftInput(flags, resultReceiver);
+    }
+
+    /**
+     * Request that any soft input part of the input method be shown to the user.
+     * 
+     * @param flags Provides additional information about the show request.
+     * Currently may be 0 or have the bit {@link #SHOW_EXPLICIT} set.
+     * @param resultReceiver The client requesting the show may wish to
+     * be told the impact of their request, which should be supplied here.
+     * The result code should be
+     * {@link InputMethodManager#RESULT_UNCHANGED_SHOWN InputMethodManager.RESULT_UNCHANGED_SHOWN},
+     * {@link InputMethodManager#RESULT_UNCHANGED_HIDDEN InputMethodManager.RESULT_UNCHANGED_HIDDEN},
+     * {@link InputMethodManager#RESULT_SHOWN InputMethodManager.RESULT_SHOWN}, or
+     * {@link InputMethodManager#RESULT_HIDDEN InputMethodManager.RESULT_HIDDEN}.
+     */
+    @MainThread
+    public void showSoftInput(int flags, ResultReceiver resultReceiver);
+
+    /**
+     * Request that any soft input part of the input method be hidden from the user.
+     * @param flags Provides additional information about the show request.
+     * Currently always 0.
+     * @param resultReceiver The client requesting the show may wish to
+     * be told the impact of their request, which should be supplied here.
+     * The result code should be
+     * {@link InputMethodManager#RESULT_UNCHANGED_SHOWN InputMethodManager.RESULT_UNCHANGED_SHOWN},
+     * {@link InputMethodManager#RESULT_UNCHANGED_HIDDEN InputMethodManager.RESULT_UNCHANGED_HIDDEN},
+     * {@link InputMethodManager#RESULT_SHOWN InputMethodManager.RESULT_SHOWN}, or
+     * {@link InputMethodManager#RESULT_HIDDEN InputMethodManager.RESULT_HIDDEN}.
+     * @param hideInputToken an opaque {@link android.os.Binder} token to identify which API call
+     *         of {@link InputMethodManager#hideSoftInputFromWindow(IBinder, int)}} is associated
+     *         with this callback.
+     * @hide
+     */
+    @MainThread
+    public void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver,
+            IBinder hideInputToken);
+
+    /**
+     * Request that any soft input part of the input method be hidden from the user.
+     * @param flags Provides additional information about the show request.
+     * Currently always 0.
+     * @param resultReceiver The client requesting the show may wish to
+     * be told the impact of their request, which should be supplied here.
+     * The result code should be
+     * {@link InputMethodManager#RESULT_UNCHANGED_SHOWN InputMethodManager.RESULT_UNCHANGED_SHOWN},
+     * {@link InputMethodManager#RESULT_UNCHANGED_HIDDEN
+     *        InputMethodManager.RESULT_UNCHANGED_HIDDEN},
+     * {@link InputMethodManager#RESULT_SHOWN InputMethodManager.RESULT_SHOWN}, or
+     * {@link InputMethodManager#RESULT_HIDDEN InputMethodManager.RESULT_HIDDEN}.
+     */
+    @MainThread
+    public void hideSoftInput(int flags, ResultReceiver resultReceiver);
+
+    /**
+     * Notify that the input method subtype is being changed in the same input method.
+     * @param subtype New subtype of the notified input method
+     */
+    @MainThread
+    public void changeInputMethodSubtype(InputMethodSubtype subtype);
+
+    /**
+     * Update token of the client window requesting {@link #showSoftInput(int, ResultReceiver)}
+     * @param showInputToken placeholder app window token for window requesting
+     *        {@link InputMethodManager#showSoftInput(View, int)}
+     * @hide
+     */
+    public void setCurrentShowInputToken(IBinder showInputToken);
+
+    /**
+     * Update token of the client window requesting {@link #hideSoftInput(int, ResultReceiver)}
+     * @param hideInputToken placeholder app window token for window requesting
+     *        {@link InputMethodManager#hideSoftInputFromWindow(IBinder, int)}
+     * @hide
+     */
+    public void setCurrentHideInputToken(IBinder hideInputToken);
+
+}
diff --git a/android/view/inputmethod/InputMethodInfo.java b/android/view/inputmethod/InputMethodInfo.java
new file mode 100644
index 0000000..9b463bb
--- /dev/null
+++ b/android/view/inputmethod/InputMethodInfo.java
@@ -0,0 +1,691 @@
+/*
+ * Copyright (C) 2007-2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.view.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+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.Configuration;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.inputmethodservice.InputMethodService;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Printer;
+import android.util.Slog;
+import android.util.Xml;
+import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class is used to specify meta information of an input method.
+ *
+ * <p>It should be defined in an XML resource file with an {@code <input-method>} element.
+ * For more information, see the guide to
+ * <a href="{@docRoot}guide/topics/text/creating-input-method.html">
+ * Creating an Input Method</a>.</p>
+ *
+ * @see InputMethodSubtype
+ *
+ * @attr ref android.R.styleable#InputMethod_settingsActivity
+ * @attr ref android.R.styleable#InputMethod_isDefault
+ * @attr ref android.R.styleable#InputMethod_supportsSwitchingToNextInputMethod
+ * @attr ref android.R.styleable#InputMethod_supportsInlineSuggestions
+ * @attr ref android.R.styleable#InputMethod_suppressesSpellChecker
+ * @attr ref android.R.styleable#InputMethod_showInInputMethodPicker
+ * @attr ref android.R.styleable#InputMethod_configChanges
+ */
+public final class InputMethodInfo implements Parcelable {
+    static final String TAG = "InputMethodInfo";
+
+    /**
+     * The Service that implements this input method component.
+     */
+    final ResolveInfo mService;
+
+    /**
+     * IME only supports VR mode.
+     */
+    final boolean mIsVrOnly;
+
+    /**
+     * The unique string Id to identify the input method.  This is generated
+     * from the input method component.
+     */
+    final String mId;
+
+    /**
+     * The input method setting activity's name, used by the system settings to
+     * launch the setting activity of this input method.
+     */
+    final String mSettingsActivityName;
+
+    /**
+     * The resource in the input method's .apk that holds a boolean indicating
+     * whether it should be considered the default input method for this
+     * system.  This is a resource ID instead of the final value so that it
+     * can change based on the configuration (in particular locale).
+     */
+    final int mIsDefaultResId;
+
+    /**
+     * An array-like container of the subtypes.
+     */
+    @UnsupportedAppUsage
+    private final InputMethodSubtypeArray mSubtypes;
+
+    private final boolean mIsAuxIme;
+
+    /**
+     * Caveat: mForceDefault must be false for production. This flag is only for test.
+     */
+    private final boolean mForceDefault;
+
+    /**
+     * The flag whether this IME supports ways to switch to a next input method (e.g. globe key.)
+     */
+    private final boolean mSupportsSwitchingToNextInputMethod;
+
+    /**
+     * The flag whether this IME supports inline suggestions.
+     */
+    private final boolean mInlineSuggestionsEnabled;
+
+    /**
+     * The flag whether this IME suppresses spell checker.
+     */
+    private final boolean mSuppressesSpellChecker;
+
+    /**
+     * The flag whether this IME should be shown as an option in the IME picker.
+     */
+    private final boolean mShowInInputMethodPicker;
+
+    /**
+     * The flag for configurations IME assumes the responsibility for handling in
+     * {@link InputMethodService#onConfigurationChanged(Configuration)}}.
+     */
+    private final int mHandledConfigChanges;
+
+    /**
+     * @param service the {@link ResolveInfo} corresponds in which the IME is implemented.
+     * @return a unique ID to be returned by {@link #getId()}. We have used
+     *         {@link ComponentName#flattenToShortString()} for this purpose (and it is already
+     *         unrealistic to switch to a different scheme as it is already implicitly assumed in
+     *         many places).
+     * @hide
+     */
+    public static String computeId(@NonNull ResolveInfo service) {
+        final ServiceInfo si = service.serviceInfo;
+        return new ComponentName(si.packageName, si.name).flattenToShortString();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param context The Context in which we are parsing the input method.
+     * @param service The ResolveInfo returned from the package manager about
+     * this input method's component.
+     */
+    public InputMethodInfo(Context context, ResolveInfo service)
+            throws XmlPullParserException, IOException {
+        this(context, service, null);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param context The Context in which we are parsing the input method.
+     * @param service The ResolveInfo returned from the package manager about
+     * this input method's component.
+     * @param additionalSubtypes additional subtypes being added to this InputMethodInfo
+     * @hide
+     */
+    public InputMethodInfo(Context context, ResolveInfo service,
+            List<InputMethodSubtype> additionalSubtypes)
+            throws XmlPullParserException, IOException {
+        mService = service;
+        ServiceInfo si = service.serviceInfo;
+        mId = computeId(service);
+        boolean isAuxIme = true;
+        boolean supportsSwitchingToNextInputMethod = false; // false as default
+        boolean inlineSuggestionsEnabled = false; // false as default
+        boolean suppressesSpellChecker = false; // false as default
+        boolean showInInputMethodPicker = true; // true as default
+        mForceDefault = false;
+
+        PackageManager pm = context.getPackageManager();
+        String settingsActivityComponent = null;
+        boolean isVrOnly;
+        int isDefaultResId = 0;
+
+        XmlResourceParser parser = null;
+        final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
+        try {
+            parser = si.loadXmlMetaData(pm, InputMethod.SERVICE_META_DATA);
+            if (parser == null) {
+                throw new XmlPullParserException("No "
+                        + InputMethod.SERVICE_META_DATA + " meta-data");
+            }
+
+            Resources res = pm.getResourcesForApplication(si.applicationInfo);
+
+            AttributeSet attrs = Xml.asAttributeSet(parser);
+
+            int type;
+            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+                    && type != XmlPullParser.START_TAG) {
+            }
+
+            String nodeName = parser.getName();
+            if (!"input-method".equals(nodeName)) {
+                throw new XmlPullParserException(
+                        "Meta-data does not start with input-method tag");
+            }
+
+            TypedArray sa = res.obtainAttributes(attrs,
+                    com.android.internal.R.styleable.InputMethod);
+            settingsActivityComponent = sa.getString(
+                    com.android.internal.R.styleable.InputMethod_settingsActivity);
+            isVrOnly = sa.getBoolean(com.android.internal.R.styleable.InputMethod_isVrOnly, false);
+            isDefaultResId = sa.getResourceId(
+                    com.android.internal.R.styleable.InputMethod_isDefault, 0);
+            supportsSwitchingToNextInputMethod = sa.getBoolean(
+                    com.android.internal.R.styleable.InputMethod_supportsSwitchingToNextInputMethod,
+                    false);
+            inlineSuggestionsEnabled = sa.getBoolean(
+                    com.android.internal.R.styleable.InputMethod_supportsInlineSuggestions, false);
+            suppressesSpellChecker = sa.getBoolean(
+                    com.android.internal.R.styleable.InputMethod_suppressesSpellChecker, false);
+            showInInputMethodPicker = sa.getBoolean(
+                    com.android.internal.R.styleable.InputMethod_showInInputMethodPicker, true);
+            mHandledConfigChanges = sa.getInt(
+                    com.android.internal.R.styleable.InputMethod_configChanges, 0);
+            sa.recycle();
+
+            final int depth = parser.getDepth();
+            // Parse all subtypes
+            while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+                    && type != XmlPullParser.END_DOCUMENT) {
+                if (type == XmlPullParser.START_TAG) {
+                    nodeName = parser.getName();
+                    if (!"subtype".equals(nodeName)) {
+                        throw new XmlPullParserException(
+                                "Meta-data in input-method does not start with subtype tag");
+                    }
+                    final TypedArray a = res.obtainAttributes(
+                            attrs, com.android.internal.R.styleable.InputMethod_Subtype);
+                    final InputMethodSubtype subtype = new InputMethodSubtypeBuilder()
+                            .setSubtypeNameResId(a.getResourceId(com.android.internal.R.styleable
+                                    .InputMethod_Subtype_label, 0))
+                            .setSubtypeIconResId(a.getResourceId(com.android.internal.R.styleable
+                                    .InputMethod_Subtype_icon, 0))
+                            .setLanguageTag(a.getString(com.android.internal.R.styleable
+                                    .InputMethod_Subtype_languageTag))
+                            .setSubtypeLocale(a.getString(com.android.internal.R.styleable
+                                    .InputMethod_Subtype_imeSubtypeLocale))
+                            .setSubtypeMode(a.getString(com.android.internal.R.styleable
+                                    .InputMethod_Subtype_imeSubtypeMode))
+                            .setSubtypeExtraValue(a.getString(com.android.internal.R.styleable
+                                    .InputMethod_Subtype_imeSubtypeExtraValue))
+                            .setIsAuxiliary(a.getBoolean(com.android.internal.R.styleable
+                                    .InputMethod_Subtype_isAuxiliary, false))
+                            .setOverridesImplicitlyEnabledSubtype(a.getBoolean(
+                                    com.android.internal.R.styleable
+                                    .InputMethod_Subtype_overridesImplicitlyEnabledSubtype, false))
+                            .setSubtypeId(a.getInt(com.android.internal.R.styleable
+                                    .InputMethod_Subtype_subtypeId, 0 /* use Arrays.hashCode */))
+                            .setIsAsciiCapable(a.getBoolean(com.android.internal.R.styleable
+                                    .InputMethod_Subtype_isAsciiCapable, false)).build();
+                    if (!subtype.isAuxiliary()) {
+                        isAuxIme = false;
+                    }
+                    subtypes.add(subtype);
+                }
+            }
+        } catch (NameNotFoundException | IndexOutOfBoundsException | NumberFormatException e) {
+            throw new XmlPullParserException(
+                    "Unable to create context for: " + si.packageName);
+        } finally {
+            if (parser != null) parser.close();
+        }
+
+        if (subtypes.size() == 0) {
+            isAuxIme = false;
+        }
+
+        if (additionalSubtypes != null) {
+            final int N = additionalSubtypes.size();
+            for (int i = 0; i < N; ++i) {
+                final InputMethodSubtype subtype = additionalSubtypes.get(i);
+                if (!subtypes.contains(subtype)) {
+                    subtypes.add(subtype);
+                } else {
+                    Slog.w(TAG, "Duplicated subtype definition found: "
+                            + subtype.getLocale() + ", " + subtype.getMode());
+                }
+            }
+        }
+        mSubtypes = new InputMethodSubtypeArray(subtypes);
+        mSettingsActivityName = settingsActivityComponent;
+        mIsDefaultResId = isDefaultResId;
+        mIsAuxIme = isAuxIme;
+        mSupportsSwitchingToNextInputMethod = supportsSwitchingToNextInputMethod;
+        mInlineSuggestionsEnabled = inlineSuggestionsEnabled;
+        mSuppressesSpellChecker = suppressesSpellChecker;
+        mShowInInputMethodPicker = showInInputMethodPicker;
+        mIsVrOnly = isVrOnly;
+    }
+
+    InputMethodInfo(Parcel source) {
+        mId = source.readString();
+        mSettingsActivityName = source.readString();
+        mIsDefaultResId = source.readInt();
+        mIsAuxIme = source.readInt() == 1;
+        mSupportsSwitchingToNextInputMethod = source.readInt() == 1;
+        mInlineSuggestionsEnabled = source.readInt() == 1;
+        mSuppressesSpellChecker = source.readBoolean();
+        mShowInInputMethodPicker = source.readBoolean();
+        mIsVrOnly = source.readBoolean();
+        mService = ResolveInfo.CREATOR.createFromParcel(source);
+        mSubtypes = new InputMethodSubtypeArray(source);
+        mHandledConfigChanges = source.readInt();
+        mForceDefault = false;
+    }
+
+    /**
+     * Temporary API for creating a built-in input method for test.
+     */
+    public InputMethodInfo(String packageName, String className,
+            CharSequence label, String settingsActivity) {
+        this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */,
+                settingsActivity, null /* subtypes */, 0 /* isDefaultResId */,
+                false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */,
+                false /* inlineSuggestionsEnabled */, false /* isVrOnly */,
+                0 /* handledConfigChanges */);
+    }
+
+    /**
+     * Temporary API for creating a built-in input method for test.
+     * @hide
+     */
+    @TestApi
+    public InputMethodInfo(@NonNull String packageName, @NonNull String className,
+            @NonNull CharSequence label, @NonNull String settingsActivity,
+            int handledConfigChanges) {
+        this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */,
+                settingsActivity, null /* subtypes */, 0 /* isDefaultResId */,
+                false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */,
+                false /* inlineSuggestionsEnabled */, false /* isVrOnly */, handledConfigChanges);
+    }
+
+    /**
+     * Temporary API for creating a built-in input method for test.
+     * @hide
+     */
+    public InputMethodInfo(ResolveInfo ri, boolean isAuxIme,
+            String settingsActivity, List<InputMethodSubtype> subtypes, int isDefaultResId,
+            boolean forceDefault) {
+        this(ri, isAuxIme, settingsActivity, subtypes, isDefaultResId, forceDefault,
+                true /* supportsSwitchingToNextInputMethod */, false /* inlineSuggestionsEnabled */,
+                false /* isVrOnly */, 0 /* handledconfigChanges */);
+    }
+
+    /**
+     * Temporary API for creating a built-in input method for test.
+     * @hide
+     */
+    public InputMethodInfo(ResolveInfo ri, boolean isAuxIme, String settingsActivity,
+            List<InputMethodSubtype> subtypes, int isDefaultResId, boolean forceDefault,
+            boolean supportsSwitchingToNextInputMethod, boolean isVrOnly) {
+        this(ri, isAuxIme, settingsActivity, subtypes, isDefaultResId, forceDefault,
+                supportsSwitchingToNextInputMethod, false /* inlineSuggestionsEnabled */, isVrOnly,
+                0 /* handledConfigChanges */);
+    }
+
+    /**
+     * Temporary API for creating a built-in input method for test.
+     * @hide
+     */
+    public InputMethodInfo(ResolveInfo ri, boolean isAuxIme, String settingsActivity,
+            List<InputMethodSubtype> subtypes, int isDefaultResId, boolean forceDefault,
+            boolean supportsSwitchingToNextInputMethod, boolean inlineSuggestionsEnabled,
+            boolean isVrOnly, int handledConfigChanges) {
+        final ServiceInfo si = ri.serviceInfo;
+        mService = ri;
+        mId = new ComponentName(si.packageName, si.name).flattenToShortString();
+        mSettingsActivityName = settingsActivity;
+        mIsDefaultResId = isDefaultResId;
+        mIsAuxIme = isAuxIme;
+        mSubtypes = new InputMethodSubtypeArray(subtypes);
+        mForceDefault = forceDefault;
+        mSupportsSwitchingToNextInputMethod = supportsSwitchingToNextInputMethod;
+        mInlineSuggestionsEnabled = inlineSuggestionsEnabled;
+        mSuppressesSpellChecker = false;
+        mShowInInputMethodPicker = true;
+        mIsVrOnly = isVrOnly;
+        mHandledConfigChanges = handledConfigChanges;
+    }
+
+    private static ResolveInfo buildFakeResolveInfo(String packageName, String className,
+            CharSequence label) {
+        ResolveInfo ri = new ResolveInfo();
+        ServiceInfo si = new ServiceInfo();
+        ApplicationInfo ai = new ApplicationInfo();
+        ai.packageName = packageName;
+        ai.enabled = true;
+        si.applicationInfo = ai;
+        si.enabled = true;
+        si.packageName = packageName;
+        si.name = className;
+        si.exported = true;
+        si.nonLocalizedLabel = label;
+        ri.serviceInfo = si;
+        return ri;
+    }
+
+    /**
+     * Return a unique ID for this input method.  The ID is generated from
+     * the package and class name implementing the method.
+     */
+    public String getId() {
+        return mId;
+    }
+
+    /**
+     * Return the .apk package that implements this input method.
+     */
+    public String getPackageName() {
+        return mService.serviceInfo.packageName;
+    }
+
+    /**
+     * Return the class name of the service component that implements
+     * this input method.
+     */
+    public String getServiceName() {
+        return mService.serviceInfo.name;
+    }
+
+    /**
+     * Return the raw information about the Service implementing this
+     * input method.  Do not modify the returned object.
+     */
+    public ServiceInfo getServiceInfo() {
+        return mService.serviceInfo;
+    }
+
+    /**
+     * Return the component of the service that implements this input
+     * method.
+     */
+    public ComponentName getComponent() {
+        return new ComponentName(mService.serviceInfo.packageName,
+                mService.serviceInfo.name);
+    }
+
+    /**
+     * Load the user-displayed label for this input method.
+     *
+     * @param pm Supply a PackageManager used to load the input method's
+     * resources.
+     */
+    public CharSequence loadLabel(PackageManager pm) {
+        return mService.loadLabel(pm);
+    }
+
+    /**
+     * Load the user-displayed icon for this input method.
+     *
+     * @param pm Supply a PackageManager used to load the input method's
+     * resources.
+     */
+    public Drawable loadIcon(PackageManager pm) {
+        return mService.loadIcon(pm);
+    }
+
+    /**
+     * Return the class name of an activity that provides a settings UI for
+     * the input method.  You can launch this activity be starting it with
+     * an {@link android.content.Intent} whose action is MAIN and with an
+     * explicit {@link android.content.ComponentName}
+     * composed of {@link #getPackageName} and the class name returned here.
+     *
+     * <p>A null will be returned if there is no settings activity associated
+     * with the input method.</p>
+     */
+    public String getSettingsActivity() {
+        return mSettingsActivityName;
+    }
+
+    /**
+     * Returns true if IME supports VR mode only.
+     * @hide
+     */
+    public boolean isVrOnly() {
+        return mIsVrOnly;
+    }
+
+    /**
+     * Return the count of the subtypes of Input Method.
+     */
+    public int getSubtypeCount() {
+        return mSubtypes.getCount();
+    }
+
+    /**
+     * Return the Input Method's subtype at the specified index.
+     *
+     * @param index the index of the subtype to return.
+     */
+    public InputMethodSubtype getSubtypeAt(int index) {
+        return mSubtypes.get(index);
+    }
+
+    /**
+     * Return the resource identifier of a resource inside of this input
+     * method's .apk that determines whether it should be considered a
+     * default input method for the system.
+     */
+    public int getIsDefaultResourceId() {
+        return mIsDefaultResId;
+    }
+
+    /**
+     * Return whether or not this ime is a default ime or not.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public boolean isDefault(Context context) {
+        if (mForceDefault) {
+            return true;
+        }
+        try {
+            if (getIsDefaultResourceId() == 0) {
+                return false;
+            }
+            final Resources res = context.createPackageContext(getPackageName(), 0).getResources();
+            return res.getBoolean(getIsDefaultResourceId());
+        } catch (NameNotFoundException | NotFoundException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Returns the bit mask of kinds of configuration changes that this IME
+     * can handle itself (without being restarted by the system).
+     *
+     * @attr ref android.R.styleable#InputMethod_configChanges
+     */
+    @ActivityInfo.Config
+    public int getConfigChanges() {
+        return mHandledConfigChanges;
+    }
+
+    public void dump(Printer pw, String prefix) {
+        pw.println(prefix + "mId=" + mId
+                + " mSettingsActivityName=" + mSettingsActivityName
+                + " mIsVrOnly=" + mIsVrOnly
+                + " mSupportsSwitchingToNextInputMethod=" + mSupportsSwitchingToNextInputMethod
+                + " mInlineSuggestionsEnabled=" + mInlineSuggestionsEnabled
+                + " mSuppressesSpellChecker=" + mSuppressesSpellChecker
+                + " mShowInInputMethodPicker=" + mShowInInputMethodPicker);
+        pw.println(prefix + "mIsDefaultResId=0x"
+                + Integer.toHexString(mIsDefaultResId));
+        pw.println(prefix + "Service:");
+        mService.dump(pw, prefix + "  ");
+    }
+
+    @Override
+    public String toString() {
+        return "InputMethodInfo{" + mId
+                + ", settings: "
+                + mSettingsActivityName + "}";
+    }
+
+    /**
+     * Used to test whether the given parameter object is an
+     * {@link InputMethodInfo} and its Id is the same to this one.
+     *
+     * @return true if the given parameter object is an
+     *         {@link InputMethodInfo} and its Id is the same to this one.
+     */
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (o == this) return true;
+        if (o == null) return false;
+
+        if (!(o instanceof InputMethodInfo)) return false;
+
+        InputMethodInfo obj = (InputMethodInfo) o;
+        return mId.equals(obj.mId);
+    }
+
+    @Override
+    public int hashCode() {
+        return mId.hashCode();
+    }
+
+    /**
+     * @hide
+     * @return {@code true} if the IME is a trusted system component (e.g. pre-installed)
+     */
+    public boolean isSystem() {
+        return (mService.serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+    }
+
+    /**
+     * @hide
+     */
+    public boolean isAuxiliaryIme() {
+        return mIsAuxIme;
+    }
+
+    /**
+     * @return true if this input method supports ways to switch to a next input method.
+     * @hide
+     */
+    public boolean supportsSwitchingToNextInputMethod() {
+        return mSupportsSwitchingToNextInputMethod;
+    }
+
+    /**
+     * @return true if this input method supports inline suggestions.
+     * @hide
+     */
+    public boolean isInlineSuggestionsEnabled() {
+        return mInlineSuggestionsEnabled;
+    }
+
+    /**
+     * Return {@code true} if this input method suppresses spell checker.
+     */
+    public boolean suppressesSpellChecker() {
+        return mSuppressesSpellChecker;
+    }
+
+    /**
+     * Returns {@code true} if this input method should be shown in menus for selecting an Input
+     * Method, such as the system Input Method Picker. This is {@code false} if the IME is intended
+     * to be accessed programmatically.
+     */
+    public boolean shouldShowInInputMethodPicker() {
+        return mShowInInputMethodPicker;
+    }
+
+    /**
+     * Used to package this object into a {@link Parcel}.
+     *
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mId);
+        dest.writeString(mSettingsActivityName);
+        dest.writeInt(mIsDefaultResId);
+        dest.writeInt(mIsAuxIme ? 1 : 0);
+        dest.writeInt(mSupportsSwitchingToNextInputMethod ? 1 : 0);
+        dest.writeInt(mInlineSuggestionsEnabled ? 1 : 0);
+        dest.writeBoolean(mSuppressesSpellChecker);
+        dest.writeBoolean(mShowInInputMethodPicker);
+        dest.writeBoolean(mIsVrOnly);
+        mService.writeToParcel(dest, flags);
+        mSubtypes.writeToParcel(dest);
+        dest.writeInt(mHandledConfigChanges);
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final @android.annotation.NonNull Parcelable.Creator<InputMethodInfo> CREATOR
+            = new Parcelable.Creator<InputMethodInfo>() {
+        @Override
+        public InputMethodInfo createFromParcel(Parcel source) {
+            return new InputMethodInfo(source);
+        }
+
+        @Override
+        public InputMethodInfo[] newArray(int size) {
+            return new InputMethodInfo[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/android/view/inputmethod/InputMethodManager.java b/android/view/inputmethod/InputMethodManager.java
new file mode 100644
index 0000000..42d77cd
--- /dev/null
+++ b/android/view/inputmethod/InputMethodManager.java
@@ -0,0 +1,3244 @@
+/*
+ * Copyright (C) 2007-2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.view.inputmethod;
+
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
+import static android.util.imetracing.ImeTracing.PROTO_ARG;
+import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.DISPLAY_ID;
+import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.EDITOR_INFO;
+import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_INSETS_SOURCE_CONSUMER;
+import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INPUT_CONNECTION;
+import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INPUT_CONNECTION_CALL;
+import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INPUT_METHOD_MANAGER;
+import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.VIEW_ROOT_IMPL;
+import static android.view.inputmethod.InputMethodManagerProto.ACTIVE;
+import static android.view.inputmethod.InputMethodManagerProto.CUR_ID;
+import static android.view.inputmethod.InputMethodManagerProto.FULLSCREEN_MODE;
+import static android.view.inputmethod.InputMethodManagerProto.SERVED_CONNECTING;
+
+import static com.android.internal.inputmethod.StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION;
+import static com.android.internal.inputmethod.StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION;
+
+import android.annotation.DisplayContext;
+import android.annotation.DrawableRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresFeature;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.annotation.UserIdInt;
+import android.app.ActivityThread;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Rect;
+import android.inputmethodservice.InputMethodService;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.style.SuggestionSpan;
+import android.util.Log;
+import android.util.Pools.Pool;
+import android.util.Pools.SimplePool;
+import android.util.PrintWriterPrinter;
+import android.util.Printer;
+import android.util.SparseArray;
+import android.util.imetracing.ImeTracing;
+import android.util.proto.ProtoOutputStream;
+import android.view.Display;
+import android.view.ImeFocusController;
+import android.view.ImeInsetsSourceConsumer;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventSender;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
+import android.view.autofill.AutofillManager;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.internal.inputmethod.InputMethodPrivilegedOperationsRegistry;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
+import com.android.internal.inputmethod.StartInputFlags;
+import com.android.internal.inputmethod.StartInputReason;
+import com.android.internal.inputmethod.UnbindReason;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.view.IInputConnectionWrapper;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethodClient;
+import com.android.internal.view.IInputMethodManager;
+import com.android.internal.view.IInputMethodSession;
+import com.android.internal.view.InputBindResult;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Central system API to the overall input method framework (IMF) architecture,
+ * which arbitrates interaction between applications and the current input method.
+ *
+ * <p>Topics covered here:
+ * <ol>
+ * <li><a href="#ArchitectureOverview">Architecture Overview</a>
+ * <li><a href="#Applications">Applications</a>
+ * <li><a href="#InputMethods">Input Methods</a>
+ * <li><a href="#Security">Security</a>
+ * </ol>
+ *
+ * <a name="ArchitectureOverview"></a>
+ * <h3>Architecture Overview</h3>
+ *
+ * <p>There are three primary parties involved in the input method
+ * framework (IMF) architecture:</p>
+ *
+ * <ul>
+ * <li> The <strong>input method manager</strong> as expressed by this class
+ * is the central point of the system that manages interaction between all
+ * other parts.  It is expressed as the client-side API here which exists
+ * in each application context and communicates with a global system service
+ * that manages the interaction across all processes.
+ * <li> An <strong>input method (IME)</strong> implements a particular
+ * interaction model allowing the user to generate text.  The system binds
+ * to the current input method that is in use, causing it to be created and run,
+ * and tells it when to hide and show its UI.  Only one IME is running at a time.
+ * <li> Multiple <strong>client applications</strong> arbitrate with the input
+ * method manager for input focus and control over the state of the IME.  Only
+ * one such client is ever active (working with the IME) at a time.
+ * </ul>
+ *
+ *
+ * <a name="Applications"></a>
+ * <h3>Applications</h3>
+ *
+ * <p>In most cases, applications that are using the standard
+ * {@link android.widget.TextView} or its subclasses will have little they need
+ * to do to work well with soft input methods.  The main things you need to
+ * be aware of are:</p>
+ *
+ * <ul>
+ * <li> Properly set the {@link android.R.attr#inputType} in your editable
+ * text views, so that the input method will have enough context to help the
+ * user in entering text into them.
+ * <li> Deal well with losing screen space when the input method is
+ * displayed.  Ideally an application should handle its window being resized
+ * smaller, but it can rely on the system performing panning of the window
+ * if needed.  You should set the {@link android.R.attr#windowSoftInputMode}
+ * attribute on your activity or the corresponding values on windows you
+ * create to help the system determine whether to pan or resize (it will
+ * try to determine this automatically but may get it wrong).
+ * <li> You can also control the preferred soft input state (open, closed, etc)
+ * for your window using the same {@link android.R.attr#windowSoftInputMode}
+ * attribute.
+ * </ul>
+ *
+ * <p>More finer-grained control is available through the APIs here to directly
+ * interact with the IMF and its IME -- either showing or hiding the input
+ * area, letting the user pick an input method, etc.</p>
+ *
+ * <p>For the rare people amongst us writing their own text editors, you
+ * will need to implement {@link android.view.View#onCreateInputConnection}
+ * to return a new instance of your own {@link InputConnection} interface
+ * allowing the IME to interact with your editor.</p>
+ *
+ *
+ * <a name="InputMethods"></a>
+ * <h3>Input Methods</h3>
+ *
+ * <p>An input method (IME) is implemented
+ * as a {@link android.app.Service}, typically deriving from
+ * {@link android.inputmethodservice.InputMethodService}.  It must provide
+ * the core {@link InputMethod} interface, though this is normally handled by
+ * {@link android.inputmethodservice.InputMethodService} and implementors will
+ * only need to deal with the higher-level API there.</p>
+ *
+ * See the {@link android.inputmethodservice.InputMethodService} class for
+ * more information on implementing IMEs.
+ *
+ *
+ * <a name="Security"></a>
+ * <h3>Security</h3>
+ *
+ * <p>There are a lot of security issues associated with input methods,
+ * since they essentially have freedom to completely drive the UI and monitor
+ * everything the user enters.  The Android input method framework also allows
+ * arbitrary third party IMEs, so care must be taken to restrict their
+ * selection and interactions.</p>
+ *
+ * <p>Here are some key points about the security architecture behind the
+ * IMF:</p>
+ *
+ * <ul>
+ * <li> <p>Only the system is allowed to directly access an IME's
+ * {@link InputMethod} interface, via the
+ * {@link android.Manifest.permission#BIND_INPUT_METHOD} permission.  This is
+ * enforced in the system by not binding to an input method service that does
+ * not require this permission, so the system can guarantee no other untrusted
+ * clients are accessing the current input method outside of its control.</p>
+ *
+ * <li> <p>There may be many client processes of the IMF, but only one may
+ * be active at a time.  The inactive clients can not interact with key
+ * parts of the IMF through the mechanisms described below.</p>
+ *
+ * <li> <p>Clients of an input method are only given access to its
+ * {@link InputMethodSession} interface.  One instance of this interface is
+ * created for each client, and only calls from the session associated with
+ * the active client will be processed by the current IME.  This is enforced
+ * by {@link android.inputmethodservice.AbstractInputMethodService} for normal
+ * IMEs, but must be explicitly handled by an IME that is customizing the
+ * raw {@link InputMethodSession} implementation.</p>
+ *
+ * <li> <p>Only the active client's {@link InputConnection} will accept
+ * operations.  The IMF tells each client process whether it is active, and
+ * the framework enforces that in inactive processes calls on to the current
+ * InputConnection will be ignored.  This ensures that the current IME can
+ * only deliver events and text edits to the UI that the user sees as
+ * being in focus.</p>
+ *
+ * <li> <p>An IME can never interact with an {@link InputConnection} while
+ * the screen is off.  This is enforced by making all clients inactive while
+ * the screen is off, and prevents bad IMEs from driving the UI when the user
+ * can not be aware of its behavior.</p>
+ *
+ * <li> <p>A client application can ask that the system let the user pick a
+ * new IME, but can not programmatically switch to one itself.  This avoids
+ * malicious applications from switching the user to their own IME, which
+ * remains running when the user navigates away to another application.  An
+ * IME, on the other hand, <em>is</em> allowed to programmatically switch
+ * the system to another IME, since it already has full control of user
+ * input.</p>
+ *
+ * <li> <p>The user must explicitly enable a new IME in settings before
+ * they can switch to it, to confirm with the system that they know about it
+ * and want to make it available for use.</p>
+ * </ul>
+ */
+@SystemService(Context.INPUT_METHOD_SERVICE)
+@RequiresFeature(PackageManager.FEATURE_INPUT_METHODS)
+public final class InputMethodManager {
+    static final boolean DEBUG = false;
+    static final String TAG = "InputMethodManager";
+
+    static final String PENDING_EVENT_COUNTER = "aq:imm";
+
+    private static final int NOT_A_SUBTYPE_ID = -1;
+
+    /**
+     * A constant that represents Voice IME.
+     *
+     * @see InputMethodSubtype#getMode()
+     */
+    private static final String SUBTYPE_MODE_VOICE = "voice";
+
+    /**
+     * Ensures that {@link #sInstance} becomes non-{@code null} for application that have directly
+     * or indirectly relied on {@link #sInstance} via reflection or something like that.
+     *
+     * <p>Here are scenarios we know and there could be more scenarios we are not
+     * aware of right know.</p>
+     *
+     * <ul>
+     *     <li>Apps that directly access {@link #sInstance} via reflection, which is currently
+     *     allowed because of {@link UnsupportedAppUsage} annotation.  Currently
+     *     {@link android.view.WindowManagerGlobal#getWindowSession()} is likely to guarantee that
+     *     {@link #sInstance} is not {@code null} when such an app is accessing it, but removing
+     *     that code from {@link android.view.WindowManagerGlobal#getWindowSession()} can reveal
+     *     untested code paths in their apps, which probably happen in an early startup time of that
+     *     app.</li>
+     *     <li>Apps that directly access {@link #peekInstance()} via reflection, which is currently
+     *     allowed because of {@link UnsupportedAppUsage} annotation.  Currently
+     *     {@link android.view.WindowManagerGlobal#getWindowSession()} is likely to guarantee that
+     *     {@link #peekInstance()} returns non-{@code null} object when such an app is calling
+     *     {@link #peekInstance()}, but removing that code from
+     *     {@link android.view.WindowManagerGlobal#getWindowSession()} can reveal untested code
+     *     paths in their apps, which probably happen in an early startup time of that app. The good
+     *     news is that unlike {@link #sInstance}'s case we can at least work around this scenario
+     *     by changing the semantics of {@link #peekInstance()}, which is currently defined as
+     *     "retrieve the global {@link InputMethodManager} instance, if it exists" to something that
+     *     always returns non-{@code null} {@link InputMethodManager}.  However, introducing such an
+     *     workaround can also trigger different compatibility issues if {@link #peekInstance()} was
+     *     called before {@link android.view.WindowManagerGlobal#getWindowSession()} and it expected
+     *     {@link #peekInstance()} to return {@code null} as written in the JavaDoc.</li>
+     * </ul>
+     *
+     * <p>Since this is purely a compatibility hack, this method must be used only from
+     * {@link android.view.WindowManagerGlobal#getWindowSession()} and {@link #getInstance()}.</p>
+     *
+     * <p>TODO(Bug 116157766): Remove this method once we clean up {@link UnsupportedAppUsage}.</p>
+     * @hide
+     */
+    public static void ensureDefaultInstanceForDefaultDisplayIfNecessary() {
+        forContextInternal(Display.DEFAULT_DISPLAY, Looper.getMainLooper());
+    }
+
+    private static final Object sLock = new Object();
+
+    /**
+     * @deprecated This cannot be compatible with multi-display. Please do not use this.
+     */
+    @Deprecated
+    @GuardedBy("sLock")
+    @UnsupportedAppUsage
+    static InputMethodManager sInstance;
+
+    /**
+     * Global map between display to {@link InputMethodManager}.
+     *
+     * <p>Currently this map works like a so-called leaky singleton.  Once an instance is registered
+     * for the associated display ID, that instance will never be garbage collected.</p>
+     *
+     * <p>TODO(Bug 116699479): Implement instance clean up mechanism.</p>
+     */
+    @GuardedBy("sLock")
+    private static final SparseArray<InputMethodManager> sInstanceMap = new SparseArray<>();
+
+    /**
+     * Timeout in milliseconds for delivering a key to an IME.
+     */
+    static final long INPUT_METHOD_NOT_RESPONDING_TIMEOUT = 2500;
+
+    /** @hide */
+    public static final int DISPATCH_IN_PROGRESS = -1;
+
+    /** @hide */
+    public static final int DISPATCH_NOT_HANDLED = 0;
+
+    /** @hide */
+    public static final int DISPATCH_HANDLED = 1;
+
+    /** @hide */
+    public static final int SHOW_IM_PICKER_MODE_AUTO = 0;
+    /** @hide */
+    public static final int SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES = 1;
+    /** @hide */
+    public static final int SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES = 2;
+
+    @UnsupportedAppUsage
+    final IInputMethodManager mService;
+    final Looper mMainLooper;
+
+    // For scheduling work on the main thread.  This also serves as our
+    // global lock.
+    // Remark on @UnsupportedAppUsage: there were context leaks on old versions
+    // of android (b/37043700), so developers used this field to perform manual clean up.
+    // Leaks were fixed, hacks were backported to AppCompatActivity,
+    // so an access to the field is closed.
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    final H mH;
+
+    // Our generic input connection if the current target does not have its own.
+    final IInputContext mIInputContext;
+
+    private final int mDisplayId;
+
+    /**
+     * True if this input method client is active, initially false.
+     */
+    boolean mActive = false;
+
+    /**
+     * {@code true} if next {@link ImeFocusController#onPostWindowFocus} needs to
+     * restart input.
+     */
+    private boolean mRestartOnNextWindowFocus = true;
+
+    /**
+     * As reported by IME through InputConnection.
+     */
+    boolean mFullscreenMode;
+
+    // -----------------------------------------------------------
+
+    /**
+     * This is the root view of the overall window that currently has input
+     * method focus.
+     */
+    @GuardedBy("mH")
+    ViewRootImpl mCurRootView;
+    /**
+     * This is set when we are in the process of connecting, to determine
+     * when we have actually finished.
+     */
+    boolean mServedConnecting;
+    /**
+     * This is non-null when we have connected the served view; it holds
+     * the attributes that were last retrieved from the served view and given
+     * to the input connection.
+     */
+    EditorInfo mCurrentTextBoxAttribute;
+    /**
+     * The InputConnection that was last retrieved from the served view.
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    IInputConnectionWrapper mServedInputConnectionWrapper;
+    /**
+     * The completions that were last provided by the served view.
+     */
+    CompletionInfo[] mCompletions;
+
+    // Cursor position on the screen.
+    @UnsupportedAppUsage
+    Rect mTmpCursorRect = new Rect();
+    @UnsupportedAppUsage
+    Rect mCursorRect = new Rect();
+    int mCursorSelStart;
+    int mCursorSelEnd;
+    int mCursorCandStart;
+    int mCursorCandEnd;
+
+    /**
+     * The instance that has previously been sent to the input method.
+     */
+    private CursorAnchorInfo mCursorAnchorInfo = null;
+
+    /**
+     * As reported by {@link InputBindResult}. This value is determined by
+     * {@link com.android.internal.R.styleable#InputMethod_suppressesSpellChecking}.
+     */
+    @GuardedBy("mH")
+    private boolean mIsInputMethodSuppressingSpellChecker = false;
+
+    // -----------------------------------------------------------
+
+    /**
+     * Sequence number of this binding, as returned by the server.
+     */
+    int mBindSequence = -1;
+    /**
+     * ID of the method we are bound to.
+     */
+    @UnsupportedAppUsage
+    String mCurId;
+
+    /**
+     * Kept for {@link UnsupportedAppUsage}.  Not officially maintained.
+     *
+     * @deprecated New code should use {@link #mCurrentInputMethodSession}.
+     */
+    @Deprecated
+    @GuardedBy("mH")
+    @Nullable
+    @UnsupportedAppUsage
+    IInputMethodSession mCurMethod;
+
+    /**
+     * Encapsulates IPCs to the currently connected InputMethodService.
+     */
+    @Nullable
+    @GuardedBy("mH")
+    private InputMethodSessionWrapper mCurrentInputMethodSession = null;
+
+    InputChannel mCurChannel;
+    ImeInputEventSender mCurSender;
+
+    private static final int REQUEST_UPDATE_CURSOR_ANCHOR_INFO_NONE = 0x0;
+
+    /**
+     * The monitor mode for {@link #updateCursorAnchorInfo(View, CursorAnchorInfo)}.
+     */
+    private int mRequestUpdateCursorAnchorInfoMonitorMode = REQUEST_UPDATE_CURSOR_ANCHOR_INFO_NONE;
+
+    /**
+     * Applies the IME visibility and listens for other state changes.
+     */
+    private ImeInsetsSourceConsumer mImeInsetsConsumer;
+
+    final Pool<PendingEvent> mPendingEventPool = new SimplePool<>(20);
+    final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20);
+
+    final DelegateImpl mDelegate = new DelegateImpl();
+
+    // -----------------------------------------------------------
+
+    static final int MSG_DUMP = 1;
+    static final int MSG_BIND = 2;
+    static final int MSG_UNBIND = 3;
+    static final int MSG_SET_ACTIVE = 4;
+    static final int MSG_SEND_INPUT_EVENT = 5;
+    static final int MSG_TIMEOUT_INPUT_EVENT = 6;
+    static final int MSG_FLUSH_INPUT_EVENT = 7;
+    static final int MSG_REPORT_FULLSCREEN_MODE = 10;
+
+    private static boolean isAutofillUIShowing(View servedView) {
+        AutofillManager afm = servedView.getContext().getSystemService(AutofillManager.class);
+        return afm != null && afm.isAutofillUiShowing();
+    }
+
+    /**
+     * Returns fallback {@link InputMethodManager} if the called one is not likely to be compatible
+     * with the given {@code view}.
+     *
+     * @param view {@link View} to be checked.
+     * @return {@code null} when it is unnecessary (or impossible) to use fallback
+     *         {@link InputMethodManager} to which IME API calls need to be re-dispatched.
+     *          Non-{@code null} {@link InputMethodManager} if this method believes it'd be safer to
+     *          re-dispatch IME APIs calls on it.
+     */
+    @Nullable
+    private InputMethodManager getFallbackInputMethodManagerIfNecessary(@Nullable View view) {
+        if (view == null) {
+            return null;
+        }
+        // As evidenced in Bug 118341760, view.getViewRootImpl().getDisplayId() is supposed to be
+        // more reliable to determine with which display the given view is interacting than
+        // view.getContext().getDisplayId() / view.getContext().getSystemService(), which can be
+        // easily messed up by app developers (or library authors) by creating inconsistent
+        // ContextWrapper objects that re-dispatch those methods to other Context such as
+        // ApplicationContext.
+        final ViewRootImpl viewRootImpl = view.getViewRootImpl();
+        if (viewRootImpl == null) {
+            return null;
+        }
+        final int viewRootDisplayId = viewRootImpl.getDisplayId();
+        if (viewRootDisplayId == mDisplayId) {
+            // Expected case.  Good to go.
+            return null;
+        }
+        final InputMethodManager fallbackImm =
+                viewRootImpl.mContext.getSystemService(InputMethodManager.class);
+        if (fallbackImm == null) {
+            Log.v(TAG, "b/117267690: Failed to get non-null fallback IMM. view=" + view);
+            return null;
+        }
+        if (fallbackImm.mDisplayId != viewRootDisplayId) {
+            Log.v(TAG, "b/117267690: Failed to get fallback IMM with expected displayId="
+                    + viewRootDisplayId + " actual IMM#displayId=" + fallbackImm.mDisplayId
+                    + " view=" + view);
+            return null;
+        }
+        Log.v(TAG, "b/117267690: Display ID mismatch found."
+                + " ViewRootImpl displayId=" + viewRootDisplayId
+                + " InputMethodManager displayId=" + mDisplayId
+                + ". Use the right InputMethodManager instance to avoid performance overhead.",
+                new Throwable());
+        return fallbackImm;
+    }
+
+    private static boolean canStartInput(View servedView) {
+        // We can start input ether the servedView has window focus
+        // or the activity is showing autofill ui.
+        return servedView.hasWindowFocus() || isAutofillUIShowing(servedView);
+    }
+
+    /**
+     * Reports whether the IME is currently perceptible or not, according to the leash applied by
+     * {@link android.view.WindowInsetsController}.
+     * @hide
+     */
+    public void reportPerceptible(IBinder windowToken, boolean perceptible) {
+        try {
+            mService.reportPerceptibleAsync(windowToken, perceptible);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private final class DelegateImpl implements
+            ImeFocusController.InputMethodManagerDelegate {
+        /**
+         * Used by {@link ImeFocusController} to start input connection.
+         */
+        @Override
+        public boolean startInput(@StartInputReason int startInputReason, View focusedView,
+                @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
+                int windowFlags) {
+            final View servedView;
+            ImeTracing.getInstance().triggerClientDump(
+                    "InputMethodManager.DelegateImpl#startInput", InputMethodManager.this,
+                    null /* icProto */);
+            synchronized (mH) {
+                mCurrentTextBoxAttribute = null;
+                mCompletions = null;
+                mServedConnecting = true;
+                servedView = getServedViewLocked();
+            }
+            return startInputInner(startInputReason,
+                    focusedView != null ? focusedView.getWindowToken() : null, startInputFlags,
+                    softInputMode, windowFlags);
+        }
+
+        /**
+         * Used by {@link ImeFocusController} to finish input connection.
+         */
+        @Override
+        public void finishInput() {
+            ImeTracing.getInstance().triggerClientDump(
+                    "InputMethodManager.DelegateImpl#finishInput", InputMethodManager.this,
+                    null /* icProto */);
+            synchronized (mH) {
+                finishInputLocked();
+            }
+        }
+
+        /**
+         * Used by {@link ImeFocusController} to finish input connection and callback
+         * {@link InputMethodService#onFinishInput()}.
+         *
+         * This method is especially for when ImeFocusController received device screen-off event to
+         * ensure the entire finish input connection and the connection lifecycle callback to
+         * IME can be done for security concern.
+         */
+        @Override
+        public void finishInputAndReportToIme() {
+            synchronized (mH) {
+                finishInputLocked();
+                if (mCurrentInputMethodSession != null) {
+                    mCurrentInputMethodSession.finishInput();
+                }
+            }
+        }
+
+        /**
+         * Used by {@link ImeFocusController} to hide current input method editor.
+         */
+        @Override
+        public void closeCurrentIme() {
+            closeCurrentInput();
+        }
+
+        /**
+         * For {@link ImeFocusController} to start input asynchronously when focus gain.
+         */
+        @Override
+        public void startInputAsyncOnWindowFocusGain(View focusedView,
+                @SoftInputModeFlags int softInputMode, int windowFlags, boolean forceNewFocus) {
+            int startInputFlags = getStartInputFlags(focusedView, 0);
+            startInputFlags |= StartInputFlags.WINDOW_GAINED_FOCUS;
+
+            ImeTracing.getInstance().triggerClientDump(
+                    "InputMethodManager.DelegateImpl#startInputAsyncOnWindowFocusGain",
+                    InputMethodManager.this, null /* icProto */);
+
+            final ImeFocusController controller = getFocusController();
+            if (controller == null) {
+                return;
+            }
+            if (controller.checkFocus(forceNewFocus, false)) {
+                // We need to restart input on the current focus view.  This
+                // should be done in conjunction with telling the system service
+                // about the window gaining focus, to help make the transition
+                // smooth.
+                if (startInput(StartInputReason.WINDOW_FOCUS_GAIN,
+                        focusedView, startInputFlags, softInputMode, windowFlags)) {
+                    return;
+                }
+            }
+
+            synchronized (mH) {
+                // For some reason we didn't do a startInput + windowFocusGain, so
+                // we'll just do a window focus gain and call it a day.
+                try {
+                    View servedView = controller.getServedView();
+                    boolean nextFocusHasConnection = servedView != null && servedView == focusedView
+                            && hasActiveConnection(focusedView);
+                    if (DEBUG) {
+                        Log.v(TAG, "Reporting focus gain, without startInput"
+                                + ", nextFocusIsServedView=" + nextFocusHasConnection);
+                    }
+
+                    final int startInputReason = nextFocusHasConnection
+                            ? WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION
+                            : WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION;
+                    // ignore the result
+                    mService.startInputOrWindowGainedFocus(
+                            startInputReason, mClient,
+                            focusedView.getWindowToken(), startInputFlags, softInputMode,
+                            windowFlags,
+                            null,
+                            null,
+                            0 /* missingMethodFlags */,
+                            mCurRootView.mContext.getApplicationInfo().targetSdkVersion);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+
+        /**
+         * Used by {@link ImeFocusController} to finish current composing text.
+         */
+        @Override
+        public void finishComposingText() {
+            if (mServedInputConnectionWrapper != null) {
+                mServedInputConnectionWrapper.finishComposingText();
+            }
+        }
+
+        /**
+         * Used for {@link ImeFocusController} to set the current focused root view.
+         */
+        @Override
+        public void setCurrentRootView(ViewRootImpl rootView) {
+            synchronized (mH) {
+                mCurRootView = rootView;
+            }
+        }
+
+        /**
+         * Used for {@link ImeFocusController} to return if the root view from the
+         * controller is this {@link InputMethodManager} currently focused.
+         * TODO: Address event-order problem when get current root view in multi-threads.
+         */
+        @Override
+        public boolean isCurrentRootView(ViewRootImpl rootView) {
+            synchronized (mH) {
+                return mCurRootView == rootView;
+            }
+        }
+
+        /**
+         * For {@link ImeFocusController#checkFocus} if needed to force check new focus.
+         */
+        @Override
+        public boolean isRestartOnNextWindowFocus(boolean reset) {
+            final boolean result = mRestartOnNextWindowFocus;
+            if (reset) {
+                mRestartOnNextWindowFocus = false;
+            }
+            return result;
+        }
+
+        /**
+         * Checks whether the active input connection (if any) is for the given view.
+         *
+         * TODO(b/182259171): Clean-up hasActiveConnection to simplify the logic.
+         *
+         * Note that this method is only intended for restarting input after focus gain
+         * (e.g. b/160391516), DO NOT leverage this method to do another check.
+         */
+        @Override
+        public boolean hasActiveConnection(View view) {
+            synchronized (mH) {
+                if (!hasServedByInputMethodLocked(view)
+                        || mCurrentInputMethodSession == null) {
+                    return false;
+                }
+
+                return mServedInputConnectionWrapper != null
+                        && mServedInputConnectionWrapper.isActive()
+                        && mServedInputConnectionWrapper.getServedView() == view;
+            }
+        }
+    }
+
+    /** @hide */
+    public DelegateImpl getDelegate() {
+        return mDelegate;
+    }
+
+    /**
+     * Checks whether the active input connection (if any) is for the given view.
+     *
+     * @hide
+     * @see ImeFocusController#getImmDelegate()#hasActiveInputConnection(View)
+     */
+    @TestApi
+    public boolean hasActiveInputConnection(@Nullable View view) {
+        return mDelegate.hasActiveConnection(view);
+    }
+
+    private View getServedViewLocked() {
+        return mCurRootView != null ? mCurRootView.getImeFocusController().getServedView() : null;
+    }
+
+    private View getNextServedViewLocked() {
+        return mCurRootView != null ? mCurRootView.getImeFocusController().getNextServedView()
+                : null;
+    }
+
+    private void setServedViewLocked(View view) {
+        if (mCurRootView != null) {
+            mCurRootView.getImeFocusController().setServedView(view);
+        }
+    }
+
+    private void setNextServedViewLocked(View view) {
+        if (mCurRootView != null) {
+            mCurRootView.getImeFocusController().setNextServedView(view);
+        }
+    }
+
+    private ImeFocusController getFocusController() {
+        synchronized (mH) {
+            if (mCurRootView != null) {
+                return mCurRootView.getImeFocusController();
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Returns {@code true} when the given view has been served by Input Method.
+     */
+    private boolean hasServedByInputMethodLocked(View view) {
+        final View servedView = getServedViewLocked();
+        return (servedView == view
+                || (servedView != null && servedView.checkInputConnectionProxy(view)));
+    }
+
+    class H extends Handler {
+        H(Looper looper) {
+            super(looper, null, true);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_DUMP: {
+                    SomeArgs args = (SomeArgs)msg.obj;
+                    try {
+                        doDump((FileDescriptor)args.arg1,
+                                (PrintWriter)args.arg2, (String[])args.arg3);
+                    } catch (RuntimeException e) {
+                        ((PrintWriter)args.arg2).println("Exception: " + e);
+                    }
+                    synchronized (args.arg4) {
+                        ((CountDownLatch)args.arg4).countDown();
+                    }
+                    args.recycle();
+                    return;
+                }
+                case MSG_BIND: {
+                    final InputBindResult res = (InputBindResult)msg.obj;
+                    if (DEBUG) {
+                        Log.i(TAG, "handleMessage: MSG_BIND " + res.sequence + "," + res.id);
+                    }
+                    synchronized (mH) {
+                        if (mBindSequence < 0 || mBindSequence != res.sequence) {
+                            Log.w(TAG, "Ignoring onBind: cur seq=" + mBindSequence
+                                    + ", given seq=" + res.sequence);
+                            if (res.channel != null && res.channel != mCurChannel) {
+                                res.channel.dispose();
+                            }
+                            return;
+                        }
+
+                        mRequestUpdateCursorAnchorInfoMonitorMode =
+                                REQUEST_UPDATE_CURSOR_ANCHOR_INFO_NONE;
+
+                        setInputChannelLocked(res.channel);
+                        mCurMethod = res.method; // for @UnsupportedAppUsage
+                        mCurrentInputMethodSession =
+                                InputMethodSessionWrapper.createOrNull(res.method);
+                        mCurId = res.id;
+                        mBindSequence = res.sequence;
+                        mIsInputMethodSuppressingSpellChecker =
+                                res.isInputMethodSuppressingSpellChecker;
+                    }
+                    startInputInner(StartInputReason.BOUND_TO_IMMS, null, 0, 0, 0);
+                    return;
+                }
+                case MSG_UNBIND: {
+                    final int sequence = msg.arg1;
+                    @UnbindReason
+                    final int reason = msg.arg2;
+                    if (DEBUG) {
+                        Log.i(TAG, "handleMessage: MSG_UNBIND " + sequence +
+                                " reason=" + InputMethodDebug.unbindReasonToString(reason));
+                    }
+                    final boolean startInput;
+                    synchronized (mH) {
+                        if (mBindSequence != sequence) {
+                            return;
+                        }
+                        clearBindingLocked();
+                        // If we were actively using the last input method, then
+                        // we would like to re-connect to the next input method.
+                        final View servedView = getServedViewLocked();
+                        if (servedView != null && servedView.isFocused()) {
+                            mServedConnecting = true;
+                        }
+                        startInput = mActive;
+                    }
+                    if (startInput) {
+                        startInputInner(
+                                StartInputReason.UNBOUND_FROM_IMMS, null, 0, 0, 0);
+                    }
+                    return;
+                }
+                case MSG_SET_ACTIVE: {
+                    final boolean active = msg.arg1 != 0;
+                    final boolean fullscreen = msg.arg2 != 0;
+                    final boolean reportToImeController = msg.obj != null && (boolean) msg.obj;
+                    if (DEBUG) {
+                        Log.i(TAG, "handleMessage: MSG_SET_ACTIVE " + active + ", was " + mActive);
+                    }
+                    synchronized (mH) {
+                        mActive = active;
+                        mFullscreenMode = fullscreen;
+
+                        // Report active state to ImeFocusController to handle IME input
+                        // connection lifecycle callback when it allowed.
+                        final ImeFocusController controller = getFocusController();
+                        final View rootView = mCurRootView != null ? mCurRootView.getView() : null;
+                        if (controller != null && rootView != null && reportToImeController) {
+                            rootView.post(() -> controller.onInteractiveChanged(active));
+                            return;
+                        }
+
+                        if (!active) {
+                            // Some other client has starting using the IME, so note
+                            // that this happened and make sure our own editor's
+                            // state is reset.
+                            mRestartOnNextWindowFocus = true;
+                            try {
+                                // Note that finishComposingText() is allowed to run
+                                // even when we are not active.
+                                mIInputContext.finishComposingText();
+                            } catch (RemoteException e) {
+                            }
+                        }
+                        // Check focus again in case that "onWindowFocus" is called before
+                        // handling this message.
+                        final View servedView = getServedViewLocked();
+                        if (servedView != null && canStartInput(servedView)) {
+                            if (mCurRootView != null && mCurRootView.getImeFocusController()
+                                    .checkFocus(mRestartOnNextWindowFocus, false)) {
+                                final int reason = active ? StartInputReason.ACTIVATED_BY_IMMS
+                                        : StartInputReason.DEACTIVATED_BY_IMMS;
+                                mDelegate.startInput(reason, null, 0, 0, 0);
+                            }
+                        }
+                    }
+                    return;
+                }
+                case MSG_SEND_INPUT_EVENT: {
+                    sendInputEventAndReportResultOnMainLooper((PendingEvent)msg.obj);
+                    return;
+                }
+                case MSG_TIMEOUT_INPUT_EVENT: {
+                    finishedInputEvent(msg.arg1, false, true);
+                    return;
+                }
+                case MSG_FLUSH_INPUT_EVENT: {
+                    finishedInputEvent(msg.arg1, false, false);
+                    return;
+                }
+                case MSG_REPORT_FULLSCREEN_MODE: {
+                    final boolean fullscreen = msg.arg1 != 0;
+                    InputConnection ic = null;
+                    synchronized (mH) {
+                        mFullscreenMode = fullscreen;
+                        if (mServedInputConnectionWrapper != null) {
+                            ic = mServedInputConnectionWrapper.getInputConnection();
+                        }
+                    }
+                    if (ic != null) {
+                        ic.reportFullscreenMode(fullscreen);
+                    }
+                    return;
+                }
+            }
+        }
+    }
+
+    final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() {
+        @Override
+        protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+            // No need to check for dump permission, since we only give this
+            // interface to the system.
+            CountDownLatch latch = new CountDownLatch(1);
+            SomeArgs sargs = SomeArgs.obtain();
+            sargs.arg1 = fd;
+            sargs.arg2 = fout;
+            sargs.arg3 = args;
+            sargs.arg4 = latch;
+            mH.sendMessage(mH.obtainMessage(MSG_DUMP, sargs));
+            try {
+                if (!latch.await(5, TimeUnit.SECONDS)) {
+                    fout.println("Timeout waiting for dump");
+                }
+            } catch (InterruptedException e) {
+                fout.println("Interrupted waiting for dump");
+            }
+        }
+
+        @Override
+        public void onBindMethod(InputBindResult res) {
+            mH.obtainMessage(MSG_BIND, res).sendToTarget();
+        }
+
+        @Override
+        public void onUnbindMethod(int sequence, @UnbindReason int unbindReason) {
+            mH.obtainMessage(MSG_UNBIND, sequence, unbindReason).sendToTarget();
+        }
+
+        @Override
+        public void setActive(boolean active, boolean fullscreen, boolean reportToImeController) {
+            mH.obtainMessage(MSG_SET_ACTIVE, active ? 1 : 0, fullscreen ? 1 : 0,
+                    reportToImeController).sendToTarget();
+        }
+
+        @Override
+        public void scheduleStartInputIfNecessary(boolean fullscreen) {
+            // TODO(b/149859205): See if we can optimize this by having a fused dedicated operation.
+            mH.obtainMessage(MSG_SET_ACTIVE, 0 /* active */, fullscreen ? 1 : 0).sendToTarget();
+            mH.obtainMessage(MSG_SET_ACTIVE, 1 /* active */, fullscreen ? 1 : 0).sendToTarget();
+        }
+
+        @Override
+        public void reportFullscreenMode(boolean fullscreen) {
+            mH.obtainMessage(MSG_REPORT_FULLSCREEN_MODE, fullscreen ? 1 : 0, 0)
+                    .sendToTarget();
+        }
+
+        @Override
+        public void setImeTraceEnabled(boolean enabled) {
+            ImeTracing.getInstance().setEnabled(enabled);
+        }
+
+        @Override
+        public void throwExceptionFromSystem(String message) {
+            throw new RuntimeException(message);
+        }
+    };
+
+    final InputConnection mDummyInputConnection = new BaseInputConnection(this, false);
+
+    /**
+     * For layoutlib to clean up static objects inside {@link InputMethodManager}.
+     */
+    static void tearDownEditMode() {
+        if (!isInEditMode()) {
+            throw new UnsupportedOperationException(
+                    "This method must be called only from layoutlib");
+        }
+        synchronized (sLock) {
+            sInstance = null;
+        }
+    }
+
+    /**
+     * For layoutlib to override this method to return {@code true}.
+     *
+     * @return {@code true} if the process is running for developer tools
+     * @see View#isInEditMode()
+     */
+    private static boolean isInEditMode() {
+        return false;
+    }
+
+    @NonNull
+    private static InputMethodManager createInstance(int displayId, Looper looper) {
+        return isInEditMode() ? createStubInstance(displayId, looper)
+                : createRealInstance(displayId, looper);
+    }
+
+    @NonNull
+    private static InputMethodManager createRealInstance(int displayId, Looper looper) {
+        final IInputMethodManager service;
+        try {
+            service = IInputMethodManager.Stub.asInterface(
+                    ServiceManager.getServiceOrThrow(Context.INPUT_METHOD_SERVICE));
+        } catch (ServiceNotFoundException e) {
+            throw new IllegalStateException(e);
+        }
+        final InputMethodManager imm = new InputMethodManager(service, displayId, looper);
+        // InputMethodManagerService#addClient() relies on Binder.getCalling{Pid, Uid}() to
+        // associate PID/UID with each IME client. This means:
+        //  A. if this method call will be handled as an IPC, there is no problem.
+        //  B. if this method call will be handled as an in-proc method call, we need to
+        //     ensure that Binder.getCalling{Pid, Uid}() return Process.my{Pid, Uid}()
+        // Either ways we can always call Binder.{clear, restore}CallingIdentity() because
+        // 1) doing so has no effect for A and 2) doing so is sufficient for B.
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            service.addClient(imm.mClient, imm.mIInputContext, displayId);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+        return imm;
+    }
+
+    @NonNull
+    private static InputMethodManager createStubInstance(int displayId, Looper looper) {
+        // If InputMethodManager is running for layoutlib, stub out IPCs into IMMS.
+        final Class<IInputMethodManager> c = IInputMethodManager.class;
+        final IInputMethodManager stubInterface =
+                (IInputMethodManager) Proxy.newProxyInstance(c.getClassLoader(),
+                        new Class[]{c}, (proxy, method, args) -> {
+                            final Class<?> returnType = method.getReturnType();
+                            if (returnType == boolean.class) {
+                                return false;
+                            } else if (returnType == int.class) {
+                                return 0;
+                            } else if (returnType == long.class) {
+                                return 0L;
+                            } else if (returnType == short.class) {
+                                return 0;
+                            } else if (returnType == char.class) {
+                                return 0;
+                            } else if (returnType == byte.class) {
+                                return 0;
+                            } else if (returnType == float.class) {
+                                return 0f;
+                            } else if (returnType == double.class) {
+                                return 0.0;
+                            } else {
+                                return null;
+                            }
+                        });
+        return new InputMethodManager(stubInterface, displayId, looper);
+    }
+
+    private InputMethodManager(IInputMethodManager service, int displayId, Looper looper) {
+        mService = service;
+        mMainLooper = looper;
+        mH = new H(looper);
+        mDisplayId = displayId;
+        mIInputContext = new IInputConnectionWrapper(looper, mDummyInputConnection, this, null);
+    }
+
+    /**
+     * Retrieve an instance for the given {@link Context}, creating it if it doesn't already exist.
+     *
+     * @param context {@link Context} for which IME APIs need to work
+     * @return {@link InputMethodManager} instance
+     * @hide
+     */
+    @NonNull
+    public static InputMethodManager forContext(@DisplayContext Context context) {
+        final int displayId = context.getDisplayId();
+        // For better backward compatibility, we always use Looper.getMainLooper() for the default
+        // display case.
+        final Looper looper = displayId == Display.DEFAULT_DISPLAY
+                ? Looper.getMainLooper() : context.getMainLooper();
+        return forContextInternal(displayId, looper);
+    }
+
+    @NonNull
+    private static InputMethodManager forContextInternal(int displayId, Looper looper) {
+        final boolean isDefaultDisplay = displayId == Display.DEFAULT_DISPLAY;
+        synchronized (sLock) {
+            InputMethodManager instance = sInstanceMap.get(displayId);
+            if (instance != null) {
+                return instance;
+            }
+            instance = createInstance(displayId, looper);
+            // For backward compatibility, store the instance also to sInstance for default display.
+            if (sInstance == null && isDefaultDisplay) {
+                sInstance = instance;
+            }
+            sInstanceMap.put(displayId, instance);
+            return instance;
+        }
+    }
+
+    /**
+     * Deprecated. Do not use.
+     *
+     * @return global {@link InputMethodManager} instance
+     * @deprecated Use {@link Context#getSystemService(Class)} instead. This method cannot fully
+     *             support multi-display scenario.
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public static InputMethodManager getInstance() {
+        Log.w(TAG, "InputMethodManager.getInstance() is deprecated because it cannot be"
+                        + " compatible with multi-display."
+                        + " Use context.getSystemService(InputMethodManager.class) instead.",
+                new Throwable());
+        ensureDefaultInstanceForDefaultDisplayIfNecessary();
+        return peekInstance();
+    }
+
+    /**
+     * Deprecated. Do not use.
+     *
+     * @return {@link #sInstance}
+     * @deprecated Use {@link Context#getSystemService(Class)} instead. This method cannot fully
+     *             support multi-display scenario.
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public static InputMethodManager peekInstance() {
+        Log.w(TAG, "InputMethodManager.peekInstance() is deprecated because it cannot be"
+                        + " compatible with multi-display."
+                        + " Use context.getSystemService(InputMethodManager.class) instead.",
+                new Throwable());
+        synchronized (sLock) {
+            return sInstance;
+        }
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public IInputMethodClient getClient() {
+        return mClient;
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public IInputContext getInputContext() {
+        return mIInputContext;
+    }
+
+    /**
+     * Returns the list of installed input methods.
+     *
+     * <p>On multi user environment, this API returns a result for the calling process user.</p>
+     *
+     * @return {@link List} of {@link InputMethodInfo}.
+     */
+    public List<InputMethodInfo> getInputMethodList() {
+        try {
+            // We intentionally do not use UserHandle.getCallingUserId() here because for system
+            // services InputMethodManagerInternal.getInputMethodListAsUser() should be used
+            // instead.
+            return mService.getInputMethodList(UserHandle.myUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the list of installed input methods for the specified user.
+     *
+     * @param userId user ID to query
+     * @return {@link List} of {@link InputMethodInfo}.
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
+    @NonNull
+    public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
+        try {
+            return mService.getInputMethodList(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the list of enabled input methods.
+     *
+     * <p>On multi user environment, this API returns a result for the calling process user.</p>
+     *
+     * @return {@link List} of {@link InputMethodInfo}.
+     */
+    public List<InputMethodInfo> getEnabledInputMethodList() {
+        try {
+            // We intentionally do not use UserHandle.getCallingUserId() here because for system
+            // services InputMethodManagerInternal.getEnabledInputMethodListAsUser() should be used
+            // instead.
+            return mService.getEnabledInputMethodList(UserHandle.myUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the list of enabled input methods for the specified user.
+     *
+     * @param userId user ID to query
+     * @return {@link List} of {@link InputMethodInfo}.
+     * @hide
+     */
+    @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
+    public List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId) {
+        try {
+            return mService.getEnabledInputMethodList(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns a list of enabled input method subtypes for the specified input method info.
+     *
+     * <p>On multi user environment, this API returns a result for the calling process user.</p>
+     *
+     * @param imi An input method info whose subtypes list will be returned.
+     * @param allowsImplicitlySelectedSubtypes A boolean flag to allow to return the implicitly
+     * selected subtypes. If an input method info doesn't have enabled subtypes, the framework
+     * will implicitly enable subtypes according to the current system language.
+     */
+    public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi,
+            boolean allowsImplicitlySelectedSubtypes) {
+        try {
+            return mService.getEnabledInputMethodSubtypeList(
+                    imi == null ? null : imi.getId(),
+                    allowsImplicitlySelectedSubtypes);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @deprecated Use {@link InputMethodService#showStatusIcon(int)} instead. This method was
+     * intended for IME developers who should be accessing APIs through the service. APIs in this
+     * class are intended for app developers interacting with the IME.
+     */
+    @Deprecated
+    public void showStatusIcon(IBinder imeToken, String packageName, @DrawableRes int iconId) {
+        InputMethodPrivilegedOperationsRegistry.get(
+                imeToken).updateStatusIconAsync(packageName, iconId);
+    }
+
+    /**
+     * @deprecated Use {@link InputMethodService#hideStatusIcon()} instead. This method was
+     * intended for IME developers who should be accessing APIs through the service. APIs in
+     * this class are intended for app developers interacting with the IME.
+     */
+    @Deprecated
+    public void hideStatusIcon(IBinder imeToken) {
+        InputMethodPrivilegedOperationsRegistry.get(imeToken).updateStatusIconAsync(null, 0);
+    }
+
+    /**
+     * This hidden API is deprecated in {@link android.os.Build.VERSION_CODES#Q}. Does nothing.
+     *
+     * @param spans will be ignored.
+     *
+     * @deprecated Do not use.
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
+        Log.w(TAG, "registerSuggestionSpansForNotification() is deprecated.  Does nothing.");
+    }
+
+    /**
+     * This hidden API is deprecated in {@link android.os.Build.VERSION_CODES#Q}. Does nothing.
+     *
+     * @deprecated Do not use.
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public void notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
+        Log.w(TAG, "notifySuggestionPicked() is deprecated.  Does nothing.");
+    }
+
+    /**
+     * Allows you to discover whether the attached input method is running
+     * in fullscreen mode.  Return true if it is fullscreen, entirely covering
+     * your UI, else returns false.
+     */
+    public boolean isFullscreenMode() {
+        synchronized (mH) {
+            return mFullscreenMode;
+        }
+    }
+
+    /**
+     * Return true if the given view is the currently active view for the
+     * input method.
+     */
+    public boolean isActive(View view) {
+        // Re-dispatch if there is a context mismatch.
+        final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
+        if (fallbackImm != null) {
+            return fallbackImm.isActive(view);
+        }
+
+        checkFocus();
+        synchronized (mH) {
+            return hasServedByInputMethodLocked(view) && mCurrentTextBoxAttribute != null;
+        }
+    }
+
+    /**
+     * Return true if any view is currently active in the input method.
+     */
+    public boolean isActive() {
+        checkFocus();
+        synchronized (mH) {
+            return getServedViewLocked() != null && mCurrentTextBoxAttribute != null;
+        }
+    }
+
+    /**
+     * Return {@code true} if the currently served view is accepting full text edits.
+     * If {@code false}, it has no input connection, so it can only handle raw key events.
+     */
+    public boolean isAcceptingText() {
+        checkFocus();
+        synchronized (mH) {
+            return mServedInputConnectionWrapper != null
+                    && mServedInputConnectionWrapper.getInputConnection() != null;
+        }
+    }
+
+    /**
+     * Return {@code true} if the input method is suppressing system spell checker.
+     */
+    public boolean isInputMethodSuppressingSpellChecker() {
+        synchronized (mH) {
+            return mIsInputMethodSuppressingSpellChecker;
+        }
+    }
+
+    /**
+     * Reset all of the state associated with being bound to an input method.
+     */
+    void clearBindingLocked() {
+        if (DEBUG) Log.v(TAG, "Clearing binding!");
+        clearConnectionLocked();
+        setInputChannelLocked(null);
+        mBindSequence = -1;
+        mCurId = null;
+        mCurMethod = null; // for @UnsupportedAppUsage
+        mCurrentInputMethodSession = null;
+    }
+
+    void setInputChannelLocked(InputChannel channel) {
+        if (mCurChannel == channel) {
+            return;
+        }
+        if (mCurChannel != null && channel != null
+                && mCurChannel.getToken() == channel.getToken()) {
+            // channel is a dupe of 'mCurChannel', because they have the same token, and represent
+            // the same connection. Ignore the incoming channel and keep using 'mCurChannel' to
+            // avoid confusing the InputEventReceiver.
+            return;
+        }
+        if (mCurSender != null) {
+            flushPendingEventsLocked();
+            mCurSender.dispose();
+            mCurSender = null;
+        }
+        if (mCurChannel != null) {
+            mCurChannel.dispose();
+        }
+        mCurChannel = channel;
+    }
+
+    /**
+     * Reset all of the state associated with a served view being connected
+     * to an input method
+     */
+    void clearConnectionLocked() {
+        mCurrentTextBoxAttribute = null;
+        if (mServedInputConnectionWrapper != null) {
+            mServedInputConnectionWrapper.deactivate();
+            mServedInputConnectionWrapper = null;
+        }
+    }
+
+    /**
+     * Disconnect any existing input connection, clearing the served view.
+     */
+    @UnsupportedAppUsage
+    void finishInputLocked() {
+        mIsInputMethodSuppressingSpellChecker = false;
+        setNextServedViewLocked(null);
+        if (getServedViewLocked() != null) {
+            if (DEBUG) {
+                Log.v(TAG, "FINISH INPUT: mServedView="
+                        + dumpViewInfo(getServedViewLocked()));
+            }
+            setServedViewLocked(null);
+            mCompletions = null;
+            mServedConnecting = false;
+            clearConnectionLocked();
+        }
+    }
+
+    public void displayCompletions(View view, CompletionInfo[] completions) {
+        // Re-dispatch if there is a context mismatch.
+        final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
+        if (fallbackImm != null) {
+            fallbackImm.displayCompletions(view, completions);
+            return;
+        }
+
+        checkFocus();
+        synchronized (mH) {
+            if (!hasServedByInputMethodLocked(view)) {
+                return;
+            }
+
+            mCompletions = completions;
+            if (mCurrentInputMethodSession != null) {
+                mCurrentInputMethodSession.displayCompletions(mCompletions);
+            }
+        }
+    }
+
+    public void updateExtractedText(View view, int token, ExtractedText text) {
+        // Re-dispatch if there is a context mismatch.
+        final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
+        if (fallbackImm != null) {
+            fallbackImm.updateExtractedText(view, token, text);
+            return;
+        }
+
+        checkFocus();
+        synchronized (mH) {
+            if (!hasServedByInputMethodLocked(view)) {
+                return;
+            }
+
+            if (mCurrentInputMethodSession != null) {
+                mCurrentInputMethodSession.updateExtractedText(token, text);
+            }
+        }
+    }
+
+    /**
+     * Flag for {@link #showSoftInput} to indicate that this is an implicit
+     * request to show the input window, not as the result of a direct request
+     * by the user.  The window may not be shown in this case.
+     */
+    public static final int SHOW_IMPLICIT = 0x0001;
+
+    /**
+     * Flag for {@link #showSoftInput} to indicate that the user has forced
+     * the input method open (such as by long-pressing menu) so it should
+     * not be closed until they explicitly do so.
+     */
+    public static final int SHOW_FORCED = 0x0002;
+
+    /**
+     * Synonym for {@link #showSoftInput(View, int, ResultReceiver)} without
+     * a result receiver: explicitly request that the current input method's
+     * soft input area be shown to the user, if needed.
+     *
+     * @param view The currently focused view, which would like to receive
+     * soft keyboard input.
+     * @param flags Provides additional operating flags.  Currently may be
+     * 0 or have the {@link #SHOW_IMPLICIT} bit set.
+     */
+    public boolean showSoftInput(View view, int flags) {
+        // Re-dispatch if there is a context mismatch.
+        final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
+        if (fallbackImm != null) {
+            return fallbackImm.showSoftInput(view, flags);
+        }
+
+        return showSoftInput(view, flags, null);
+    }
+
+    /**
+     * Flag for the {@link ResultReceiver} result code from
+     * {@link #showSoftInput(View, int, ResultReceiver)} and
+     * {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)}: the
+     * state of the soft input window was unchanged and remains shown.
+     */
+    public static final int RESULT_UNCHANGED_SHOWN = 0;
+
+    /**
+     * Flag for the {@link ResultReceiver} result code from
+     * {@link #showSoftInput(View, int, ResultReceiver)} and
+     * {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)}: the
+     * state of the soft input window was unchanged and remains hidden.
+     */
+    public static final int RESULT_UNCHANGED_HIDDEN = 1;
+
+    /**
+     * Flag for the {@link ResultReceiver} result code from
+     * {@link #showSoftInput(View, int, ResultReceiver)} and
+     * {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)}: the
+     * state of the soft input window changed from hidden to shown.
+     */
+    public static final int RESULT_SHOWN = 2;
+
+    /**
+     * Flag for the {@link ResultReceiver} result code from
+     * {@link #showSoftInput(View, int, ResultReceiver)} and
+     * {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)}: the
+     * state of the soft input window changed from shown to hidden.
+     */
+    public static final int RESULT_HIDDEN = 3;
+
+    /**
+     * Explicitly request that the current input method's soft input area be
+     * shown to the user, if needed.  Call this if the user interacts with
+     * your view in such a way that they have expressed they would like to
+     * start performing input into it.
+     *
+     * <p><strong>Caveat:</strong> {@link ResultReceiver} instance passed to
+     * this method can be a long-lived object, because it may not be
+     * garbage-collected until all the corresponding {@link ResultReceiver}
+     * objects transferred to different processes get garbage-collected.
+     * Follow the general patterns to avoid memory leaks in Android.
+     * Consider to use {@link java.lang.ref.WeakReference} so that application
+     * logic objects such as {@link android.app.Activity} and {@link Context}
+     * can be garbage collected regardless of the lifetime of
+     * {@link ResultReceiver}.
+     *
+     * @param view The currently focused view, which would like to receive
+     * soft keyboard input.
+     * @param flags Provides additional operating flags.  Currently may be
+     * 0 or have the {@link #SHOW_IMPLICIT} bit set.
+     * @param resultReceiver If non-null, this will be called by the IME when
+     * it has processed your request to tell you what it has done.  The result
+     * code you receive may be either {@link #RESULT_UNCHANGED_SHOWN},
+     * {@link #RESULT_UNCHANGED_HIDDEN}, {@link #RESULT_SHOWN}, or
+     * {@link #RESULT_HIDDEN}.
+     */
+    public boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) {
+        return showSoftInput(view, flags, resultReceiver, SoftInputShowHideReason.SHOW_SOFT_INPUT);
+    }
+
+    private boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {
+        ImeTracing.getInstance().triggerClientDump("InputMethodManager#showSoftInput", this,
+                null /* icProto */);
+        // Re-dispatch if there is a context mismatch.
+        final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
+        if (fallbackImm != null) {
+            return fallbackImm.showSoftInput(view, flags, resultReceiver);
+        }
+
+        checkFocus();
+        synchronized (mH) {
+            if (!hasServedByInputMethodLocked(view)) {
+                Log.w(TAG, "Ignoring showSoftInput() as view=" + view + " is not served.");
+                return false;
+            }
+
+            try {
+                Log.d(TAG, "showSoftInput() view=" + view + " flags=" + flags + " reason="
+                        + InputMethodDebug.softInputDisplayReasonToString(reason));
+                return mService.showSoftInput(
+                        mClient,
+                        view.getWindowToken(),
+                        flags,
+                        resultReceiver,
+                        reason);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * This method is still kept for a while until android.support.v7.widget.SearchView ver. 26.0
+     * is publicly released because previous implementations of that class had relied on this method
+     * via reflection.
+     *
+     * @deprecated This is a hidden API. You should never use this.
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768499)
+    public void showSoftInputUnchecked(int flags, ResultReceiver resultReceiver) {
+        synchronized (mH) {
+            try {
+                Log.w(TAG, "showSoftInputUnchecked() is a hidden method, which will be"
+                        + " removed soon. If you are using android.support.v7.widget.SearchView,"
+                        + " please update to version 26.0 or newer version.");
+                if (mCurRootView == null || mCurRootView.getView() == null) {
+                    Log.w(TAG, "No current root view, ignoring showSoftInputUnchecked()");
+                    return;
+                }
+                mService.showSoftInput(
+                        mClient,
+                        mCurRootView.getView().getWindowToken(),
+                        flags,
+                        resultReceiver,
+                        SoftInputShowHideReason.SHOW_SOFT_INPUT);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Flag for {@link #hideSoftInputFromWindow} and {@link InputMethodService#requestHideSelf(int)}
+     * to indicate that the soft input window should only be hidden if it was not explicitly shown
+     * by the user.
+     */
+    public static final int HIDE_IMPLICIT_ONLY = 0x0001;
+
+    /**
+     * Flag for {@link #hideSoftInputFromWindow} and {@link InputMethodService#requestShowSelf(int)}
+     * to indicate that the soft input window should normally be hidden, unless it was originally
+     * shown with {@link #SHOW_FORCED}.
+     */
+    public static final int HIDE_NOT_ALWAYS = 0x0002;
+
+    /**
+     * Synonym for {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)}
+     * without a result: request to hide the soft input window from the
+     * context of the window that is currently accepting input.
+     *
+     * @param windowToken The token of the window that is making the request,
+     * as returned by {@link View#getWindowToken() View.getWindowToken()}.
+     * @param flags Provides additional operating flags.  Currently may be
+     * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set.
+     */
+    public boolean hideSoftInputFromWindow(IBinder windowToken, int flags) {
+        return hideSoftInputFromWindow(windowToken, flags, null);
+    }
+
+    /**
+     * Request to hide the soft input window from the context of the window
+     * that is currently accepting input.  This should be called as a result
+     * of the user doing some actually than fairly explicitly requests to
+     * have the input window hidden.
+     *
+     * <p><strong>Caveat:</strong> {@link ResultReceiver} instance passed to
+     * this method can be a long-lived object, because it may not be
+     * garbage-collected until all the corresponding {@link ResultReceiver}
+     * objects transferred to different processes get garbage-collected.
+     * Follow the general patterns to avoid memory leaks in Android.
+     * Consider to use {@link java.lang.ref.WeakReference} so that application
+     * logic objects such as {@link android.app.Activity} and {@link Context}
+     * can be garbage collected regardless of the lifetime of
+     * {@link ResultReceiver}.
+     *
+     * @param windowToken The token of the window that is making the request,
+     * as returned by {@link View#getWindowToken() View.getWindowToken()}.
+     * @param flags Provides additional operating flags.  Currently may be
+     * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set.
+     * @param resultReceiver If non-null, this will be called by the IME when
+     * it has processed your request to tell you what it has done.  The result
+     * code you receive may be either {@link #RESULT_UNCHANGED_SHOWN},
+     * {@link #RESULT_UNCHANGED_HIDDEN}, {@link #RESULT_SHOWN}, or
+     * {@link #RESULT_HIDDEN}.
+     */
+    public boolean hideSoftInputFromWindow(IBinder windowToken, int flags,
+            ResultReceiver resultReceiver) {
+        return hideSoftInputFromWindow(windowToken, flags, resultReceiver,
+                SoftInputShowHideReason.HIDE_SOFT_INPUT);
+    }
+
+    private boolean hideSoftInputFromWindow(IBinder windowToken, int flags,
+            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+        ImeTracing.getInstance().triggerClientDump("InputMethodManager#hideSoftInputFromWindow",
+                this, null /* icProto */);
+        checkFocus();
+        synchronized (mH) {
+            final View servedView = getServedViewLocked();
+            if (servedView == null || servedView.getWindowToken() != windowToken) {
+                return false;
+            }
+
+            try {
+                return mService.hideSoftInput(mClient, windowToken, flags, resultReceiver, reason);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * This method toggles the input method window display.
+     * If the input window is already displayed, it gets hidden.
+     * If not the input window will be displayed.
+     * @param windowToken The token of the window that is making the request,
+     * as returned by {@link View#getWindowToken() View.getWindowToken()}.
+     * @param showFlags Provides additional operating flags.  May be
+     * 0 or have the {@link #SHOW_IMPLICIT},
+     * {@link #SHOW_FORCED} bit set.
+     * @param hideFlags Provides additional operating flags.  May be
+     * 0 or have the {@link #HIDE_IMPLICIT_ONLY},
+     * {@link #HIDE_NOT_ALWAYS} bit set.
+     *
+     * @deprecated Use {@link #showSoftInput(View, int)} or
+     * {@link #hideSoftInputFromWindow(IBinder, int)} explicitly instead.
+     * In particular during focus changes, the current visibility of the IME is not
+     * well defined. Starting in {@link Build.VERSION_CODES#S Android S}, this only
+     * has an effect if the calling app is the current IME focus.
+     */
+    @Deprecated
+    public void toggleSoftInputFromWindow(IBinder windowToken, int showFlags, int hideFlags) {
+        ImeTracing.getInstance().triggerClientDump(
+                "InputMethodManager#toggleSoftInputFromWindow", InputMethodManager.this,
+                null /* icProto */);
+        synchronized (mH) {
+            final View servedView = getServedViewLocked();
+            if (servedView == null || servedView.getWindowToken() != windowToken) {
+                return;
+            }
+            toggleSoftInput(showFlags, hideFlags);
+        }
+    }
+
+    /**
+     * This method toggles the input method window display.
+     *
+     * If the input window is already displayed, it gets hidden.
+     * If not the input window will be displayed.
+     * @param showFlags Provides additional operating flags.  May be
+     * 0 or have the {@link #SHOW_IMPLICIT},
+     * {@link #SHOW_FORCED} bit set.
+     * @param hideFlags Provides additional operating flags.  May be
+     * 0 or have the {@link #HIDE_IMPLICIT_ONLY},
+     * {@link #HIDE_NOT_ALWAYS} bit set.
+     *
+     * @deprecated Use {@link #showSoftInput(View, int)} or
+     * {@link #hideSoftInputFromWindow(IBinder, int)} explicitly instead.
+     * In particular during focus changes, the current visibility of the IME is not
+     * well defined. Starting in {@link Build.VERSION_CODES#S Android S}, this only
+     * has an effect if the calling app is the current IME focus.
+     */
+    @Deprecated
+    public void toggleSoftInput(int showFlags, int hideFlags) {
+        ImeTracing.getInstance().triggerClientDump(
+                "InputMethodManager#toggleSoftInput", InputMethodManager.this,
+                null /* icProto */);
+        synchronized (mH) {
+            final View view = getServedViewLocked();
+            if (mImeInsetsConsumer != null && view != null) {
+                if (mImeInsetsConsumer.isRequestedVisible()) {
+                    hideSoftInputFromWindow(view.getWindowToken(), hideFlags, null,
+                            SoftInputShowHideReason.HIDE_TOGGLE_SOFT_INPUT);
+                } else {
+                    showSoftInput(view, showFlags, null,
+                            SoftInputShowHideReason.SHOW_TOGGLE_SOFT_INPUT);
+                }
+            }
+        }
+    }
+
+    /**
+     * If the input method is currently connected to the given view,
+     * restart it with its new contents.  You should call this when the text
+     * within your view changes outside of the normal input method or key
+     * input flow, such as when an application calls TextView.setText().
+     *
+     * @param view The view whose text has changed.
+     */
+    public void restartInput(View view) {
+        // Re-dispatch if there is a context mismatch.
+        final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
+        if (fallbackImm != null) {
+            fallbackImm.restartInput(view);
+            return;
+        }
+
+        checkFocus();
+        synchronized (mH) {
+            if (!hasServedByInputMethodLocked(view)) {
+                return;
+            }
+
+            mServedConnecting = true;
+        }
+
+        startInputInner(StartInputReason.APP_CALLED_RESTART_INPUT_API, null, 0, 0, 0);
+    }
+
+    /**
+     * Called when {@link DelegateImpl#startInput}, {@link #restartInput(View)},
+     * {@link #MSG_BIND} or {@link #MSG_UNBIND}.
+     * Note that this method should *NOT* be called inside of {@code mH} lock to prevent start input
+     * background thread may blocked by other methods which already inside {@code mH} lock.
+     */
+    boolean startInputInner(@StartInputReason int startInputReason,
+            @Nullable IBinder windowGainingFocus, @StartInputFlags int startInputFlags,
+            @SoftInputModeFlags int softInputMode, int windowFlags) {
+        final View view;
+        synchronized (mH) {
+            view = getServedViewLocked();
+
+            // Make sure we have a window token for the served view.
+            if (DEBUG) {
+                Log.v(TAG, "Starting input: view=" + dumpViewInfo(view) +
+                        " reason=" + InputMethodDebug.startInputReasonToString(startInputReason));
+            }
+            if (view == null) {
+                if (DEBUG) Log.v(TAG, "ABORT input: no served view!");
+                return false;
+            }
+        }
+
+        if (windowGainingFocus == null) {
+            windowGainingFocus = view.getWindowToken();
+            if (windowGainingFocus == null) {
+                Log.e(TAG, "ABORT input: ServedView must be attached to a Window");
+                return false;
+            }
+            startInputFlags = getStartInputFlags(view, startInputFlags);
+            softInputMode = view.getViewRootImpl().mWindowAttributes.softInputMode;
+            windowFlags = view.getViewRootImpl().mWindowAttributes.flags;
+        }
+
+        // Now we need to get an input connection from the served view.
+        // This is complicated in a couple ways: we can't be holding our lock
+        // when calling out to the view, and we need to make sure we call into
+        // the view on the same thread that is driving its view hierarchy.
+        Handler vh = view.getHandler();
+        if (vh == null) {
+            // If the view doesn't have a handler, something has changed out
+            // from under us, so just close the current input.
+            // If we don't close the current input, the current input method can remain on the
+            // screen without a connection.
+            if (DEBUG) Log.v(TAG, "ABORT input: no handler for view! Close current input.");
+            closeCurrentInput();
+            return false;
+        }
+        if (vh.getLooper() != Looper.myLooper()) {
+            // The view is running on a different thread than our own, so
+            // we need to reschedule our work for over there.
+            if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread");
+            vh.post(() -> mDelegate.startInput(startInputReason, null, 0, 0, 0));
+            return false;
+        }
+
+        // Okay we are now ready to call into the served view and have it
+        // do its stuff.
+        // Life is good: let's hook everything up!
+        EditorInfo tba = new EditorInfo();
+        // Note: Use Context#getOpPackageName() rather than Context#getPackageName() so that the
+        // system can verify the consistency between the uid of this process and package name passed
+        // from here. See comment of Context#getOpPackageName() for details.
+        tba.packageName = view.getContext().getOpPackageName();
+        tba.autofillId = view.getAutofillId();
+        tba.fieldId = view.getId();
+        InputConnection ic = view.onCreateInputConnection(tba);
+        if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic);
+
+        final Handler icHandler;
+        InputBindResult res = null;
+        synchronized (mH) {
+            // Now that we are locked again, validate that our state hasn't
+            // changed.
+            final View servedView = getServedViewLocked();
+            if (servedView != view || !mServedConnecting) {
+                // Something else happened, so abort.
+                if (DEBUG) Log.v(TAG,
+                        "Starting input: finished by someone else. view=" + dumpViewInfo(view)
+                        + " servedView=" + dumpViewInfo(servedView)
+                        + " mServedConnecting=" + mServedConnecting);
+                return false;
+            }
+
+            // If we already have a text box, then this view is already
+            // connected so we want to restart it.
+            if (mCurrentTextBoxAttribute == null) {
+                startInputFlags |= StartInputFlags.INITIAL_CONNECTION;
+            }
+
+            // Hook 'em up and let 'er rip.
+            mCurrentTextBoxAttribute = tba;
+
+            mServedConnecting = false;
+            if (mServedInputConnectionWrapper != null) {
+                mServedInputConnectionWrapper.deactivate();
+                mServedInputConnectionWrapper = null;
+            }
+            IInputConnectionWrapper servedContext;
+            final int missingMethodFlags;
+            if (ic != null) {
+                mCursorSelStart = tba.initialSelStart;
+                mCursorSelEnd = tba.initialSelEnd;
+                mCursorCandStart = -1;
+                mCursorCandEnd = -1;
+                mCursorRect.setEmpty();
+                mCursorAnchorInfo = null;
+                missingMethodFlags = InputConnectionInspector.getMissingMethodFlags(ic);
+                if ((missingMethodFlags & InputConnectionInspector.MissingMethodFlags.GET_HANDLER)
+                        != 0) {
+                    // InputConnection#getHandler() is not implemented.
+                    icHandler = null;
+                } else {
+                    icHandler = ic.getHandler();
+                }
+                servedContext = new IInputConnectionWrapper(
+                        icHandler != null ? icHandler.getLooper() : vh.getLooper(), ic, this, view);
+            } else {
+                servedContext = null;
+                missingMethodFlags = 0;
+                icHandler = null;
+            }
+            mServedInputConnectionWrapper = servedContext;
+
+            if (DEBUG) {
+                Log.v(TAG, "START INPUT: view=" + dumpViewInfo(view) + " ic="
+                        + ic + " tba=" + tba + " startInputFlags="
+                        + InputMethodDebug.startInputFlagsToString(startInputFlags));
+            }
+            try {
+                res = mService.startInputOrWindowGainedFocus(
+                        startInputReason, mClient, windowGainingFocus, startInputFlags,
+                        softInputMode, windowFlags, tba, servedContext, missingMethodFlags,
+                        view.getContext().getApplicationInfo().targetSdkVersion);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+            if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
+            if (res == null) {
+                Log.wtf(TAG, "startInputOrWindowGainedFocus must not return"
+                        + " null. startInputReason="
+                        + InputMethodDebug.startInputReasonToString(startInputReason)
+                        + " editorInfo=" + tba
+                        + " startInputFlags="
+                        + InputMethodDebug.startInputFlagsToString(startInputFlags));
+                return false;
+            }
+            mIsInputMethodSuppressingSpellChecker = res.isInputMethodSuppressingSpellChecker;
+            if (res.id != null) {
+                setInputChannelLocked(res.channel);
+                mBindSequence = res.sequence;
+                mCurMethod = res.method; // for @UnsupportedAppUsage
+                mCurrentInputMethodSession = InputMethodSessionWrapper.createOrNull(res.method);
+                mCurId = res.id;
+            } else if (res.channel != null && res.channel != mCurChannel) {
+                res.channel.dispose();
+            }
+            switch (res.result) {
+                case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW:
+                    mRestartOnNextWindowFocus = true;
+                    break;
+            }
+            if (mCurrentInputMethodSession != null && mCompletions != null) {
+                mCurrentInputMethodSession.displayCompletions(mCompletions);
+            }
+        }
+
+        // Notify the app that the InputConnection is initialized and ready for use.
+        if (ic != null && res != null && res.method != null) {
+            if (DEBUG) {
+                Log.v(TAG, "Calling View.onInputConnectionOpened: view= " + view
+                        + ", ic=" + ic + ", tba=" + tba + ", handler=" + icHandler);
+            }
+            view.onInputConnectionOpenedInternal(ic, tba, icHandler);
+        }
+
+        return true;
+    }
+
+    /**
+     * An empty method only to avoid crashes of apps that call this method via reflection and do not
+     * handle {@link NoSuchMethodException} in a graceful manner.
+     *
+     * @deprecated This is an empty method.  No framework method must call this method.
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage(trackingBug = 37122102, maxTargetSdk = Build.VERSION_CODES.Q,
+            publicAlternatives = "{@code androidx.activity.ComponentActivity}")
+    public void windowDismissed(IBinder appWindowToken) {
+        // Intentionally empty.
+        //
+        // It seems that some applications call this method via reflection to null clear the
+        // following fields that used to exist in InputMethodManager:
+        //  * InputMethodManager#mCurRootView
+        //  * InputMethodManager#mServedView
+        //  * InputMethodManager#mNextServedView
+        // so that these objects can be garbage-collected when an Activity gets dismissed.
+        //
+        // It is indeed true that older versions of InputMethodManager had issues that prevented
+        // these fields from being null-cleared when it should have been, but the understanding of
+        // the engineering team is that all known issues have already been fixed as of Android 10.
+        //
+        // For older devices, developers can work around the object leaks by using
+        // androidx.activity.ComponentActivity.
+        // See https://issuetracker.google.com/u/1/issues/37122102 for details.
+        //
+        // If you believe InputMethodManager is leaking objects in API 24 or any later version,
+        // please file a bug at https://issuetracker.google.com/issues/new?component=192705.
+    }
+
+    private int getStartInputFlags(View focusedView, int startInputFlags) {
+        startInputFlags |= StartInputFlags.VIEW_HAS_FOCUS;
+        if (focusedView.onCheckIsTextEditor()) {
+            startInputFlags |= StartInputFlags.IS_TEXT_EDITOR;
+        }
+        return startInputFlags;
+    }
+
+    /**
+     * Check the next served view from {@link ImeFocusController} if needs to start input.
+     * Note that this method should *NOT* be called inside of {@code mH} lock to prevent start input
+     * background thread may blocked by other methods which already inside {@code mH} lock.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void checkFocus() {
+        final ImeFocusController controller = getFocusController();
+        if (controller != null) {
+            controller.checkFocus(false /* forceNewFocus */, true /* startInput */);
+        }
+    }
+
+    @UnsupportedAppUsage
+    void closeCurrentInput() {
+        synchronized (mH) {
+            if (mCurRootView == null || mCurRootView.getView() == null) {
+                Log.w(TAG, "No current root view, ignoring closeCurrentInput()");
+                return;
+            }
+            try {
+                mService.hideSoftInput(
+                        mClient,
+                        mCurRootView.getView().getWindowToken(),
+                        HIDE_NOT_ALWAYS,
+                        null,
+                        SoftInputShowHideReason.HIDE_SOFT_INPUT);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Register for IME state callbacks and applying visibility in
+     * {@link android.view.ImeInsetsSourceConsumer}.
+     * @hide
+     */
+    public void registerImeConsumer(@NonNull ImeInsetsSourceConsumer imeInsetsConsumer) {
+        if (imeInsetsConsumer == null) {
+            throw new IllegalStateException("ImeInsetsSourceConsumer cannot be null.");
+        }
+
+        synchronized (mH) {
+            mImeInsetsConsumer = imeInsetsConsumer;
+        }
+    }
+
+    /**
+     * Unregister for IME state callbacks and applying visibility in
+     * {@link android.view.ImeInsetsSourceConsumer}.
+     * @hide
+     */
+    public void unregisterImeConsumer(@NonNull ImeInsetsSourceConsumer imeInsetsConsumer) {
+        if (imeInsetsConsumer == null) {
+            throw new IllegalStateException("ImeInsetsSourceConsumer cannot be null.");
+        }
+
+        synchronized (mH) {
+            if (mImeInsetsConsumer == imeInsetsConsumer) {
+                mImeInsetsConsumer = null;
+            }
+        }
+    }
+
+    /**
+     * Call showSoftInput with currently focused view.
+     *
+     * @param windowToken the window from which this request originates. If this doesn't match the
+     *                    currently served view, the request is ignored and returns {@code false}.
+     *
+     * @return {@code true} if IME can (eventually) be shown, {@code false} otherwise.
+     * @hide
+     */
+    public boolean requestImeShow(IBinder windowToken) {
+        synchronized (mH) {
+            final View servedView = getServedViewLocked();
+            if (servedView == null || servedView.getWindowToken() != windowToken) {
+                return false;
+            }
+            showSoftInput(servedView, 0 /* flags */, null /* resultReceiver */,
+                    SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API);
+            return true;
+        }
+    }
+
+    /**
+     * Notify IME directly that it is no longer visible.
+     *
+     * @param windowToken the window from which this request originates. If this doesn't match the
+     *                    currently served view, the request is ignored.
+     * @hide
+     */
+    public void notifyImeHidden(IBinder windowToken) {
+        ImeTracing.getInstance().triggerClientDump("InputMethodManager#notifyImeHidden", this,
+                null /* icProto */);
+        synchronized (mH) {
+            if (mCurrentInputMethodSession != null && mCurRootView != null
+                    && mCurRootView.getWindowToken() == windowToken) {
+                mCurrentInputMethodSession.notifyImeHidden();
+            }
+        }
+    }
+
+    /**
+     * Notify IME directly to remove surface as it is no longer visible.
+     * @param windowToken The client window token that requests the IME to remove its surface.
+     * @hide
+     */
+    public void removeImeSurface(IBinder windowToken) {
+        synchronized (mH) {
+            try {
+                mService.removeImeSurfaceFromWindowAsync(windowToken);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Report the current selection range.
+     *
+     * <p><strong>Editor authors</strong>, you need to call this method whenever
+     * the cursor moves in your editor. Remember that in addition to doing this, your
+     * editor needs to always supply current cursor values in
+     * {@link EditorInfo#initialSelStart} and {@link EditorInfo#initialSelEnd} every
+     * time {@link android.view.View#onCreateInputConnection(EditorInfo)} is
+     * called, which happens whenever the keyboard shows up or the focus changes
+     * to a text field, among other cases.</p>
+     */
+    public void updateSelection(View view, int selStart, int selEnd,
+            int candidatesStart, int candidatesEnd) {
+        // Re-dispatch if there is a context mismatch.
+        final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
+        if (fallbackImm != null) {
+            fallbackImm.updateSelection(view, selStart, selEnd, candidatesStart, candidatesEnd);
+            return;
+        }
+
+        checkFocus();
+        synchronized (mH) {
+            if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
+                    || mCurrentInputMethodSession == null) {
+                return;
+            }
+
+            if (mCursorSelStart != selStart || mCursorSelEnd != selEnd
+                    || mCursorCandStart != candidatesStart
+                    || mCursorCandEnd != candidatesEnd) {
+                if (DEBUG) Log.d(TAG, "updateSelection");
+
+                if (DEBUG) {
+                    Log.v(TAG, "SELECTION CHANGE: " + mCurrentInputMethodSession);
+                }
+                final int oldSelStart = mCursorSelStart;
+                final int oldSelEnd = mCursorSelEnd;
+                // Update internal values before sending updateSelection to the IME, because
+                // if it changes the text within its onUpdateSelection handler in a way that
+                // does not move the cursor we don't want to call it again with the same values.
+                mCursorSelStart = selStart;
+                mCursorSelEnd = selEnd;
+                mCursorCandStart = candidatesStart;
+                mCursorCandEnd = candidatesEnd;
+                mCurrentInputMethodSession.updateSelection(
+                        oldSelStart, oldSelEnd, selStart, selEnd, candidatesStart, candidatesEnd);
+            }
+        }
+    }
+
+    /**
+     * Notify the event when the user tapped or clicked the text view.
+     *
+     * @param view {@link View} which is being clicked.
+     * @see InputMethodService#onViewClicked(boolean)
+     * @deprecated The semantics of this method can never be defined well for composite {@link View}
+     *             that works as a giant "Canvas", which can host its own UI hierarchy and sub focus
+     *             state. {@link android.webkit.WebView} is a good example. Application / IME
+     *             developers should not rely on this method.
+     */
+    @Deprecated
+    public void viewClicked(View view) {
+        // Re-dispatch if there is a context mismatch.
+        final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
+        if (fallbackImm != null) {
+            fallbackImm.viewClicked(view);
+            return;
+        }
+
+        final View servedView;
+        final View nextServedView;
+        synchronized (mH) {
+            servedView = getServedViewLocked();
+            nextServedView = getNextServedViewLocked();
+        }
+        final boolean focusChanged = servedView != nextServedView;
+        checkFocus();
+        synchronized (mH) {
+            if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
+                    || mCurrentInputMethodSession == null) {
+                return;
+            }
+            if (DEBUG) Log.v(TAG, "onViewClicked: " + focusChanged);
+            mCurrentInputMethodSession.viewClicked(focusChanged);
+        }
+    }
+
+    /**
+     * Return true if the current input method wants to watch the location
+     * of the input editor's cursor in its window.
+     *
+     * @deprecated Use {@link InputConnection#requestCursorUpdates(int)} instead.
+     */
+    @Deprecated
+    public boolean isWatchingCursor(View view) {
+        return false;
+    }
+
+    /**
+     * Return true if the current input method wants to be notified when cursor/anchor location
+     * is changed.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public boolean isCursorAnchorInfoEnabled() {
+        synchronized (mH) {
+            final boolean isImmediate = (mRequestUpdateCursorAnchorInfoMonitorMode &
+                    InputConnection.CURSOR_UPDATE_IMMEDIATE) != 0;
+            final boolean isMonitoring = (mRequestUpdateCursorAnchorInfoMonitorMode &
+                    InputConnection.CURSOR_UPDATE_MONITOR) != 0;
+            return isImmediate || isMonitoring;
+        }
+    }
+
+    /**
+     * Set the requested mode for {@link #updateCursorAnchorInfo(View, CursorAnchorInfo)}.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public void setUpdateCursorAnchorInfoMode(int flags) {
+        synchronized (mH) {
+            mRequestUpdateCursorAnchorInfoMonitorMode = flags;
+        }
+    }
+
+    /**
+     * Report the current cursor location in its window.
+     *
+     * @deprecated Use {@link #updateCursorAnchorInfo(View, CursorAnchorInfo)} instead.
+     */
+    @Deprecated
+    public void updateCursor(View view, int left, int top, int right, int bottom) {
+        // Re-dispatch if there is a context mismatch.
+        final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
+        if (fallbackImm != null) {
+            fallbackImm.updateCursor(view, left, top, right, bottom);
+            return;
+        }
+
+        checkFocus();
+        synchronized (mH) {
+            if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
+                    || mCurrentInputMethodSession == null) {
+                return;
+            }
+
+            mTmpCursorRect.set(left, top, right, bottom);
+            if (!mCursorRect.equals(mTmpCursorRect)) {
+                if (DEBUG) Log.d(TAG, "updateCursor: " + mCurrentInputMethodSession);
+
+                mCurrentInputMethodSession.updateCursor(mTmpCursorRect);
+                mCursorRect.set(mTmpCursorRect);
+            }
+        }
+    }
+
+    /**
+     * Report positional change of the text insertion point and/or characters in the composition
+     * string.
+     */
+    public void updateCursorAnchorInfo(View view, final CursorAnchorInfo cursorAnchorInfo) {
+        if (view == null || cursorAnchorInfo == null) {
+            return;
+        }
+        // Re-dispatch if there is a context mismatch.
+        final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
+        if (fallbackImm != null) {
+            fallbackImm.updateCursorAnchorInfo(view, cursorAnchorInfo);
+            return;
+        }
+
+        checkFocus();
+        synchronized (mH) {
+            if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
+                    || mCurrentInputMethodSession == null) {
+                return;
+            }
+            // If immediate bit is set, we will call updateCursorAnchorInfo() even when the data has
+            // not been changed from the previous call.
+            final boolean isImmediate = (mRequestUpdateCursorAnchorInfoMonitorMode &
+                    InputConnection.CURSOR_UPDATE_IMMEDIATE) != 0;
+            if (!isImmediate && Objects.equals(mCursorAnchorInfo, cursorAnchorInfo)) {
+                // TODO: Consider always emitting this message once we have addressed redundant
+                // calls of this method from android.widget.Editor.
+                if (DEBUG) {
+                    Log.w(TAG, "Ignoring redundant updateCursorAnchorInfo: info="
+                            + cursorAnchorInfo);
+                }
+                return;
+            }
+            if (DEBUG) Log.v(TAG, "updateCursorAnchorInfo: " + cursorAnchorInfo);
+            mCurrentInputMethodSession.updateCursorAnchorInfo(cursorAnchorInfo);
+            mCursorAnchorInfo = cursorAnchorInfo;
+            // Clear immediate bit (if any).
+            mRequestUpdateCursorAnchorInfoMonitorMode &= ~InputConnection.CURSOR_UPDATE_IMMEDIATE;
+        }
+    }
+
+    /**
+     * Call {@link InputMethodSession#appPrivateCommand(String, Bundle)
+     * InputMethodSession.appPrivateCommand()} on the current Input Method.
+     * @param view Optional View that is sending the command, or null if
+     * you want to send the command regardless of the view that is attached
+     * to the input method.
+     * @param action Name of the command to be performed.  This <em>must</em>
+     * be a scoped name, i.e. prefixed with a package name you own, so that
+     * different developers will not create conflicting commands.
+     * @param data Any data to include with the command.
+     */
+    public void sendAppPrivateCommand(View view, String action, Bundle data) {
+        // Re-dispatch if there is a context mismatch.
+        final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
+        if (fallbackImm != null) {
+            fallbackImm.sendAppPrivateCommand(view, action, data);
+            return;
+        }
+
+        checkFocus();
+        synchronized (mH) {
+            if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
+                    || mCurrentInputMethodSession == null) {
+                return;
+            }
+            if (DEBUG) Log.v(TAG, "APP PRIVATE COMMAND " + action + ": " + data);
+            mCurrentInputMethodSession.appPrivateCommand(action, data);
+        }
+    }
+
+    /**
+     * Force switch to a new input method component. This can only be called
+     * from an application or a service which has a token of the currently active input method.
+     *
+     * <p>On Android {@link Build.VERSION_CODES#Q} and later devices, the undocumented behavior that
+     * token can be {@code null} when the caller has
+     * {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} is deprecated. Instead, update
+     * {@link android.provider.Settings.Secure#DEFAULT_INPUT_METHOD} and
+     * {@link android.provider.Settings.Secure#SELECTED_INPUT_METHOD_SUBTYPE} directly.</p>
+     *
+     * @param token Supplies the identifying token given to an input method
+     * when it was started, which allows it to perform this operation on
+     * itself.
+     * @param id The unique identifier for the new input method to be switched to.
+     * @deprecated Use {@link InputMethodService#switchInputMethod(String)}
+     * instead. This method was intended for IME developers who should be accessing APIs through
+     * the service. APIs in this class are intended for app developers interacting with the IME.
+     */
+    @Deprecated
+    public void setInputMethod(IBinder token, String id) {
+        if (token == null) {
+            // There are still some system components that rely on this undocumented behavior
+            // regarding null IME token with WRITE_SECURE_SETTINGS.  Provide a fallback logic as a
+            // temporary remedy.
+            if (id == null) {
+                return;
+            }
+            if (Process.myUid() == Process.SYSTEM_UID) {
+                Log.w(TAG, "System process should not be calling setInputMethod() because almost "
+                        + "always it is a bug under multi-user / multi-profile environment. "
+                        + "Consider interacting with InputMethodManagerService directly via "
+                        + "LocalServices.");
+                return;
+            }
+            final Context fallbackContext = ActivityThread.currentApplication();
+            if (fallbackContext == null) {
+                return;
+            }
+            if (fallbackContext.checkSelfPermission(WRITE_SECURE_SETTINGS)
+                    != PackageManager.PERMISSION_GRANTED) {
+                return;
+            }
+            final List<InputMethodInfo> imis = getEnabledInputMethodList();
+            final int numImis = imis.size();
+            boolean found = false;
+            for (int i = 0; i < numImis; ++i) {
+                final InputMethodInfo imi = imis.get(i);
+                if (id.equals(imi.getId())) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                Log.e(TAG, "Ignoring setInputMethod(null, " + id + ") because the specified "
+                        + "id not found in enabled IMEs.");
+                return;
+            }
+            Log.w(TAG, "The undocumented behavior that setInputMethod() accepts null token "
+                    + "when the caller has WRITE_SECURE_SETTINGS is deprecated. This behavior may "
+                    + "be completely removed in a future version.  Update secure settings directly "
+                    + "instead.");
+            final ContentResolver resolver = fallbackContext.getContentResolver();
+            Settings.Secure.putInt(resolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
+                    NOT_A_SUBTYPE_ID);
+            Settings.Secure.putString(resolver, Settings.Secure.DEFAULT_INPUT_METHOD, id);
+            return;
+        }
+        InputMethodPrivilegedOperationsRegistry.get(token).setInputMethod(id);
+    }
+
+    /**
+     * Force switch to a new input method and subtype. This can only be called
+     * from an application or a service which has a token of the currently active input method.
+     *
+     * <p>On Android {@link Build.VERSION_CODES#Q} and later devices, {@code token} cannot be
+     * {@code null} even with {@link android.Manifest.permission#WRITE_SECURE_SETTINGS}. Instead,
+     * update {@link android.provider.Settings.Secure#DEFAULT_INPUT_METHOD} and
+     * {@link android.provider.Settings.Secure#SELECTED_INPUT_METHOD_SUBTYPE} directly.</p>
+     *
+     * @param token Supplies the identifying token given to an input method
+     * when it was started, which allows it to perform this operation on
+     * itself.
+     * @param id The unique identifier for the new input method to be switched to.
+     * @param subtype The new subtype of the new input method to be switched to.
+     * @deprecated Use
+     * {@link InputMethodService#switchInputMethod(String, InputMethodSubtype)}
+     * instead. This method was intended for IME developers who should be accessing APIs through
+     * the service. APIs in this class are intended for app developers interacting with the IME.
+     */
+    @Deprecated
+    public void setInputMethodAndSubtype(@NonNull IBinder token, String id,
+            InputMethodSubtype subtype) {
+        if (token == null) {
+            Log.e(TAG, "setInputMethodAndSubtype() does not accept null token on Android Q "
+                    + "and later.");
+            return;
+        }
+        InputMethodPrivilegedOperationsRegistry.get(token).setInputMethodAndSubtype(id, subtype);
+    }
+
+    /**
+     * Close/hide the input method's soft input area, so the user no longer
+     * sees it or can interact with it.  This can only be called
+     * from the currently active input method, as validated by the given token.
+     *
+     * @param token Supplies the identifying token given to an input method
+     * when it was started, which allows it to perform this operation on
+     * itself.
+     * @param flags Provides additional operating flags.  Currently may be
+     * 0 or have the {@link #HIDE_IMPLICIT_ONLY},
+     * {@link #HIDE_NOT_ALWAYS} bit set.
+     * @deprecated Use {@link InputMethodService#requestHideSelf(int)} instead. This method was
+     * intended for IME developers who should be accessing APIs through the service. APIs in this
+     * class are intended for app developers interacting with the IME.
+     */
+    @Deprecated
+    public void hideSoftInputFromInputMethod(IBinder token, int flags) {
+        InputMethodPrivilegedOperationsRegistry.get(token).hideMySoftInput(flags);
+    }
+
+    /**
+     * Show the input method's soft input area, so the user
+     * sees the input method window and can interact with it.
+     * This can only be called from the currently active input method,
+     * as validated by the given token.
+     *
+     * @param token Supplies the identifying token given to an input method
+     * when it was started, which allows it to perform this operation on
+     * itself.
+     * @param flags Provides additional operating flags.  Currently may be
+     * 0 or have the {@link #SHOW_IMPLICIT} or
+     * {@link #SHOW_FORCED} bit set.
+     * @deprecated Use {@link InputMethodService#requestShowSelf(int)} instead. This method was
+     * intended for IME developers who should be accessing APIs through the service. APIs in this
+     * class are intended for app developers interacting with the IME.
+     */
+    @Deprecated
+    public void showSoftInputFromInputMethod(IBinder token, int flags) {
+        InputMethodPrivilegedOperationsRegistry.get(token).showMySoftInput(flags);
+    }
+
+    /**
+     * Dispatches an input event to the IME.
+     *
+     * Returns {@link #DISPATCH_HANDLED} if the event was handled.
+     * Returns {@link #DISPATCH_NOT_HANDLED} if the event was not handled.
+     * Returns {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the
+     * callback will be invoked later.
+     *
+     * @hide
+     */
+    public int dispatchInputEvent(InputEvent event, Object token,
+            FinishedInputEventCallback callback, Handler handler) {
+        synchronized (mH) {
+            if (mCurrentInputMethodSession != null) {
+                if (event instanceof KeyEvent) {
+                    KeyEvent keyEvent = (KeyEvent)event;
+                    if (keyEvent.getAction() == KeyEvent.ACTION_DOWN
+                            && keyEvent.getKeyCode() == KeyEvent.KEYCODE_SYM
+                            && keyEvent.getRepeatCount() == 0) {
+                        showInputMethodPickerLocked();
+                        return DISPATCH_HANDLED;
+                    }
+                }
+
+                if (DEBUG) {
+                    Log.v(TAG, "DISPATCH INPUT EVENT: " + mCurrentInputMethodSession);
+                }
+
+                PendingEvent p = obtainPendingEventLocked(
+                        event, token, mCurId, callback, handler);
+                if (mMainLooper.isCurrentThread()) {
+                    // Already running on the IMM thread so we can send the event immediately.
+                    return sendInputEventOnMainLooperLocked(p);
+                }
+
+                // Post the event to the IMM thread.
+                Message msg = mH.obtainMessage(MSG_SEND_INPUT_EVENT, p);
+                msg.setAsynchronous(true);
+                mH.sendMessage(msg);
+                return DISPATCH_IN_PROGRESS;
+            }
+        }
+        return DISPATCH_NOT_HANDLED;
+    }
+
+    /**
+     * Provides the default implementation of {@link InputConnection#sendKeyEvent(KeyEvent)}, which
+     * is expected to dispatch an keyboard event sent from the IME to an appropriate event target
+     * depending on the given {@link View} and the current focus state.
+     *
+     * <p>CAUTION: This method is provided only for the situation where
+     * {@link InputConnection#sendKeyEvent(KeyEvent)} needs to be implemented without relying on
+     * {@link BaseInputConnection}. Do not use this API for anything else.</p>
+     *
+     * @param targetView the default target view. If {@code null} is specified, then this method
+     * tries to find a good event target based on the current focus state.
+     * @param event the key event to be dispatched.
+     */
+    public void dispatchKeyEventFromInputMethod(@Nullable View targetView,
+            @NonNull KeyEvent event) {
+        // Re-dispatch if there is a context mismatch.
+        final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(targetView);
+        if (fallbackImm != null) {
+            fallbackImm.dispatchKeyEventFromInputMethod(targetView, event);
+            return;
+        }
+
+        synchronized (mH) {
+            ViewRootImpl viewRootImpl = targetView != null ? targetView.getViewRootImpl() : null;
+            if (viewRootImpl == null) {
+                final View servedView = getServedViewLocked();
+                if (servedView != null) {
+                    viewRootImpl = servedView.getViewRootImpl();
+                }
+            }
+            if (viewRootImpl != null) {
+                viewRootImpl.dispatchKeyFromIme(event);
+            }
+        }
+    }
+
+    // Must be called on the main looper
+    void sendInputEventAndReportResultOnMainLooper(PendingEvent p) {
+        final boolean handled;
+        synchronized (mH) {
+            int result = sendInputEventOnMainLooperLocked(p);
+            if (result == DISPATCH_IN_PROGRESS) {
+                return;
+            }
+
+            handled = (result == DISPATCH_HANDLED);
+        }
+
+        invokeFinishedInputEventCallback(p, handled);
+    }
+
+    // Must be called on the main looper
+    int sendInputEventOnMainLooperLocked(PendingEvent p) {
+        if (mCurChannel != null) {
+            if (mCurSender == null) {
+                mCurSender = new ImeInputEventSender(mCurChannel, mH.getLooper());
+            }
+
+            final InputEvent event = p.mEvent;
+            final int seq = event.getSequenceNumber();
+            if (mCurSender.sendInputEvent(seq, event)) {
+                mPendingEvents.put(seq, p);
+                Trace.traceCounter(Trace.TRACE_TAG_INPUT, PENDING_EVENT_COUNTER,
+                        mPendingEvents.size());
+
+                Message msg = mH.obtainMessage(MSG_TIMEOUT_INPUT_EVENT, seq, 0, p);
+                msg.setAsynchronous(true);
+                mH.sendMessageDelayed(msg, INPUT_METHOD_NOT_RESPONDING_TIMEOUT);
+                return DISPATCH_IN_PROGRESS;
+            }
+
+            Log.w(TAG, "Unable to send input event to IME: " + mCurId + " dropping: " + event);
+        }
+        return DISPATCH_NOT_HANDLED;
+    }
+
+    void finishedInputEvent(int seq, boolean handled, boolean timeout) {
+        final PendingEvent p;
+        synchronized (mH) {
+            int index = mPendingEvents.indexOfKey(seq);
+            if (index < 0) {
+                return; // spurious, event already finished or timed out
+            }
+
+            p = mPendingEvents.valueAt(index);
+            mPendingEvents.removeAt(index);
+            Trace.traceCounter(Trace.TRACE_TAG_INPUT, PENDING_EVENT_COUNTER, mPendingEvents.size());
+
+            if (timeout) {
+                Log.w(TAG, "Timeout waiting for IME to handle input event after "
+                        + INPUT_METHOD_NOT_RESPONDING_TIMEOUT + " ms: " + p.mInputMethodId);
+            } else {
+                mH.removeMessages(MSG_TIMEOUT_INPUT_EVENT, p);
+            }
+        }
+
+        invokeFinishedInputEventCallback(p, handled);
+    }
+
+    // Assumes the event has already been removed from the queue.
+    void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) {
+        p.mHandled = handled;
+        if (p.mHandler.getLooper().isCurrentThread()) {
+            // Already running on the callback handler thread so we can send the
+            // callback immediately.
+            p.run();
+        } else {
+            // Post the event to the callback handler thread.
+            // In this case, the callback will be responsible for recycling the event.
+            Message msg = Message.obtain(p.mHandler, p);
+            msg.setAsynchronous(true);
+            msg.sendToTarget();
+        }
+    }
+
+    private void flushPendingEventsLocked() {
+        mH.removeMessages(MSG_FLUSH_INPUT_EVENT);
+
+        final int count = mPendingEvents.size();
+        for (int i = 0; i < count; i++) {
+            int seq = mPendingEvents.keyAt(i);
+            Message msg = mH.obtainMessage(MSG_FLUSH_INPUT_EVENT, seq, 0);
+            msg.setAsynchronous(true);
+            msg.sendToTarget();
+        }
+    }
+
+    private PendingEvent obtainPendingEventLocked(InputEvent event, Object token,
+            String inputMethodId, FinishedInputEventCallback callback, Handler handler) {
+        PendingEvent p = mPendingEventPool.acquire();
+        if (p == null) {
+            p = new PendingEvent();
+        }
+        p.mEvent = event;
+        p.mToken = token;
+        p.mInputMethodId = inputMethodId;
+        p.mCallback = callback;
+        p.mHandler = handler;
+        return p;
+    }
+
+    private void recyclePendingEventLocked(PendingEvent p) {
+        p.recycle();
+        mPendingEventPool.release(p);
+    }
+
+    /**
+     * Show IME picker popup window.
+     *
+     * <p>Requires the {@link PackageManager#FEATURE_INPUT_METHODS} feature which can be detected
+     * using {@link PackageManager#hasSystemFeature(String)}.
+     */
+    public void showInputMethodPicker() {
+        synchronized (mH) {
+            showInputMethodPickerLocked();
+        }
+    }
+
+    /**
+     * Shows the input method chooser dialog from system.
+     *
+     * @param showAuxiliarySubtypes Set true to show auxiliary input methods.
+     * @param displayId The ID of the display where the chooser dialog should be shown.
+     * @hide
+     */
+    @RequiresPermission(WRITE_SECURE_SETTINGS)
+    public void showInputMethodPickerFromSystem(boolean showAuxiliarySubtypes, int displayId) {
+        final int mode = showAuxiliarySubtypes
+                ? SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES
+                : SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES;
+        try {
+            mService.showInputMethodPickerFromSystem(mClient, mode, displayId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private void showInputMethodPickerLocked() {
+        try {
+            mService.showInputMethodPickerFromClient(mClient, SHOW_IM_PICKER_MODE_AUTO);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * A test API for CTS to make sure that {@link #showInputMethodPicker()} works as expected.
+     *
+     * <p>When customizing the implementation of {@link #showInputMethodPicker()} API, make sure
+     * that this test API returns when and only while and only while
+     * {@link #showInputMethodPicker()} is showing UI. Otherwise your OS implementation may not
+     * pass CTS.</p>
+     *
+     * @return {@code true} while and only while {@link #showInputMethodPicker()} is showing UI.
+     * @hide
+     */
+    @TestApi
+    public boolean isInputMethodPickerShown() {
+        try {
+            return mService.isInputMethodPickerShownForTest();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Show the settings for enabling subtypes of the specified input method.
+     *
+     * @param imiId An input method, whose subtypes settings will be shown. If imiId is null,
+     * subtypes of all input methods will be shown.
+     */
+    public void showInputMethodAndSubtypeEnabler(String imiId) {
+        try {
+            mService.showInputMethodAndSubtypeEnablerFromClient(mClient, imiId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the current input method subtype. This subtype is one of the subtypes in
+     * the current input method. This method returns null when the current input method doesn't
+     * have any input method subtype.
+     */
+    public InputMethodSubtype getCurrentInputMethodSubtype() {
+        try {
+            return mService.getCurrentInputMethodSubtype();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Switch to a new input method subtype of the current input method.
+     * @param subtype A new input method subtype to switch.
+     * @return true if the current subtype was successfully switched. When the specified subtype is
+     * null, this method returns false.
+     * @deprecated If the calling process is an IME, use
+     *             {@link InputMethodService#switchInputMethod(String, InputMethodSubtype)}, which
+     *             does not require any permission as long as the caller is the current IME.
+     *             If the calling process is some privileged app that already has
+     *             {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission, just
+     *             directly update {@link Settings.Secure#SELECTED_INPUT_METHOD_SUBTYPE}.
+     */
+    @Deprecated
+    @RequiresPermission(WRITE_SECURE_SETTINGS)
+    public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
+        if (Process.myUid() == Process.SYSTEM_UID) {
+            Log.w(TAG, "System process should not call setCurrentInputMethodSubtype() because "
+                    + "almost always it is a bug under multi-user / multi-profile environment. "
+                    + "Consider directly interacting with InputMethodManagerService "
+                    + "via LocalServices.");
+            return false;
+        }
+        if (subtype == null) {
+            // See the JavaDoc. This is how this method has worked.
+            return false;
+        }
+        final Context fallbackContext = ActivityThread.currentApplication();
+        if (fallbackContext == null) {
+            return false;
+        }
+        if (fallbackContext.checkSelfPermission(WRITE_SECURE_SETTINGS)
+                != PackageManager.PERMISSION_GRANTED) {
+            return false;
+        }
+        final ContentResolver contentResolver = fallbackContext.getContentResolver();
+        final String imeId = Settings.Secure.getString(contentResolver,
+                Settings.Secure.DEFAULT_INPUT_METHOD);
+        if (ComponentName.unflattenFromString(imeId) == null) {
+            // Null or invalid IME ID format.
+            return false;
+        }
+        final List<InputMethodSubtype> enabledSubtypes;
+        try {
+            enabledSubtypes = mService.getEnabledInputMethodSubtypeList(imeId, true);
+        } catch (RemoteException e) {
+            return false;
+        }
+        final int numSubtypes = enabledSubtypes.size();
+        for (int i = 0; i < numSubtypes; ++i) {
+            final InputMethodSubtype enabledSubtype = enabledSubtypes.get(i);
+            if (enabledSubtype.equals(subtype)) {
+                Settings.Secure.putInt(contentResolver,
+                        Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, enabledSubtype.hashCode());
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Notify that a user took some action with this input method.
+     *
+     * @deprecated Just kept to avoid possible app compat issue.
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage(trackingBug = 114740982, maxTargetSdk = Build.VERSION_CODES.P)
+    public void notifyUserAction() {
+        Log.w(TAG, "notifyUserAction() is a hidden method, which is now just a stub method"
+                + " that does nothing.  Leave comments in b.android.com/114740982 if your "
+                + " application still depends on the previous behavior of this method.");
+    }
+
+    /**
+     * Returns a map of all shortcut input method info and their subtypes.
+     */
+    public Map<InputMethodInfo, List<InputMethodSubtype>> getShortcutInputMethodsAndSubtypes() {
+        final List<InputMethodInfo> enabledImes = getEnabledInputMethodList();
+
+        // Ensure we check system IMEs first.
+        enabledImes.sort(Comparator.comparingInt(imi -> imi.isSystem() ? 0 : 1));
+
+        final int numEnabledImes = enabledImes.size();
+        for (int imiIndex = 0; imiIndex < numEnabledImes; ++imiIndex) {
+            final InputMethodInfo imi = enabledImes.get(imiIndex);
+            final List<InputMethodSubtype> subtypes = getEnabledInputMethodSubtypeList(
+                    imi, true);
+            final int subtypeCount = subtypes.size();
+            for (int subtypeIndex = 0; subtypeIndex < subtypeCount; ++subtypeIndex) {
+                final InputMethodSubtype subtype = imi.getSubtypeAt(subtypeIndex);
+                if (SUBTYPE_MODE_VOICE.equals(subtype.getMode())) {
+                    return Collections.singletonMap(imi, Collections.singletonList(subtype));
+                }
+            }
+        }
+        return Collections.emptyMap();
+    }
+
+    /**
+     * This is kept due to {@link android.compat.annotation.UnsupportedAppUsage}.
+     *
+     * <p>TODO(Bug 113914148): Check if we can remove this.  We have accidentally exposed
+     * WindowManagerInternal#getInputMethodWindowVisibleHeight to app developers and some of them
+     * started relying on it.</p>
+     *
+     * @return Something that is not well-defined.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public int getInputMethodWindowVisibleHeight() {
+        try {
+            return mService.getInputMethodWindowVisibleHeight();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Force switch to the last used input method and subtype. If the last input method didn't have
+     * any subtypes, the framework will simply switch to the last input method with no subtype
+     * specified.
+     * @param imeToken Supplies the identifying token given to an input method when it was started,
+     * which allows it to perform this operation on itself.
+     * @return true if the current input method and subtype was successfully switched to the last
+     * used input method and subtype.
+     * @deprecated Use {@link InputMethodService#switchToPreviousInputMethod()} instead. This method
+     * was intended for IME developers who should be accessing APIs through the service. APIs in
+     * this class are intended for app developers interacting with the IME.
+     */
+    @Deprecated
+    public boolean switchToLastInputMethod(IBinder imeToken) {
+        return InputMethodPrivilegedOperationsRegistry.get(imeToken).switchToPreviousInputMethod();
+    }
+
+    /**
+     * Force switch to the next input method and subtype. If there is no IME enabled except
+     * current IME and subtype, do nothing.
+     * @param imeToken Supplies the identifying token given to an input method when it was started,
+     * which allows it to perform this operation on itself.
+     * @param onlyCurrentIme if true, the framework will find the next subtype which
+     * belongs to the current IME
+     * @return true if the current input method and subtype was successfully switched to the next
+     * input method and subtype.
+     * @deprecated Use {@link InputMethodService#switchToNextInputMethod(boolean)} instead. This
+     * method was intended for IME developers who should be accessing APIs through the service.
+     * APIs in this class are intended for app developers interacting with the IME.
+     */
+    @Deprecated
+    public boolean switchToNextInputMethod(IBinder imeToken, boolean onlyCurrentIme) {
+        return InputMethodPrivilegedOperationsRegistry.get(imeToken)
+                .switchToNextInputMethod(onlyCurrentIme);
+    }
+
+    /**
+     * Returns true if the current IME needs to offer the users ways to switch to a next input
+     * method (e.g. a globe key.).
+     * When an IME sets supportsSwitchingToNextInputMethod and this method returns true,
+     * the IME has to offer ways to to invoke {@link #switchToNextInputMethod} accordingly.
+     * <p> Note that the system determines the most appropriate next input method
+     * and subtype in order to provide the consistent user experience in switching
+     * between IMEs and subtypes.
+     * @param imeToken Supplies the identifying token given to an input method when it was started,
+     * which allows it to perform this operation on itself.
+     * @deprecated Use {@link InputMethodService#shouldOfferSwitchingToNextInputMethod()}
+     * instead. This method was intended for IME developers who should be accessing APIs through
+     * the service. APIs in this class are intended for app developers interacting with the IME.
+     */
+    @Deprecated
+    public boolean shouldOfferSwitchingToNextInputMethod(IBinder imeToken) {
+        return InputMethodPrivilegedOperationsRegistry.get(imeToken)
+                .shouldOfferSwitchingToNextInputMethod();
+    }
+
+    /**
+     * Set additional input method subtypes. Only a process which shares the same uid with the IME
+     * can add additional input method subtypes to the IME.
+     * Please note that a subtype's status is stored in the system.
+     * For example, enabled subtypes are remembered by the framework even after they are removed
+     * by using this method. If you re-add the same subtypes again,
+     * they will just get enabled. If you want to avoid such conflicts, for instance, you may
+     * want to create a "different" new subtype even with the same locale and mode,
+     * by changing its extra value. The different subtype won't get affected by the stored past
+     * status. (You may want to take a look at {@link InputMethodSubtype#hashCode()} to refer
+     * to the current implementation.)
+     *
+     * <p>NOTE: If the same subtype exists in both the manifest XML file and additional subtypes
+     * specified by {@code subtypes}, those multiple instances are automatically merged into one
+     * instance.</p>
+     *
+     * <p>CAVEAT: In API Level 23 and prior, the system may do nothing if an empty
+     * {@link InputMethodSubtype} is specified in {@code subtypes}, which prevents you from removing
+     * the last one entry of additional subtypes. If your IME statically defines one or more
+     * subtypes in the manifest XML file, you may be able to work around this limitation by
+     * specifying one of those statically defined subtypes in {@code subtypes}.</p>
+     *
+     * @param imiId Id of InputMethodInfo which additional input method subtypes will be added to.
+     * @param subtypes subtypes will be added as additional subtypes of the current input method.
+     * @deprecated For IMEs that have already implemented features like customizable/downloadable
+     *             keyboard layouts/languages, please start migration to other approaches. One idea
+     *             would be exposing only one unified {@link InputMethodSubtype} then implement
+     *             IME's own language switching mechanism within that unified subtype. The support
+     *             of "Additional Subtype" may be completely dropped in a future version of Android.
+     */
+    @Deprecated
+    public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
+        try {
+            mService.setAdditionalInputMethodSubtypes(imiId, subtypes);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    public InputMethodSubtype getLastInputMethodSubtype() {
+        try {
+            return mService.getLastInputMethodSubtype();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * <p>This is used for CTS test only. Do not use this method outside of CTS package.<p/>
+     * @return the ID of this display which this {@link InputMethodManager} resides
+     * @hide
+     */
+    @TestApi
+    public int getDisplayId() {
+        return mDisplayId;
+    }
+
+    void doDump(FileDescriptor fd, PrintWriter fout, String[] args) {
+        if (processDump(fd, args)) {
+            return;
+        }
+
+        final Printer p = new PrintWriterPrinter(fout);
+        p.println("Input method client state for " + this + ":");
+
+        p.println("  mService=" + mService);
+        p.println("  mMainLooper=" + mMainLooper);
+        p.println("  mIInputContext=" + mIInputContext);
+        p.println("  mActive=" + mActive
+                + " mRestartOnNextWindowFocus=" + mRestartOnNextWindowFocus
+                + " mBindSequence=" + mBindSequence
+                + " mCurId=" + mCurId);
+        p.println("  mFullscreenMode=" + mFullscreenMode);
+        if (mCurrentInputMethodSession != null) {
+            p.println("  mCurMethod=" + mCurrentInputMethodSession);
+        } else {
+            p.println("  mCurMethod= null");
+        }
+        p.println("  mCurRootView=" + mCurRootView);
+        p.println("  mServedView=" + getServedViewLocked());
+        p.println("  mNextServedView=" + getNextServedViewLocked());
+        p.println("  mServedConnecting=" + mServedConnecting);
+        if (mCurrentTextBoxAttribute != null) {
+            p.println("  mCurrentTextBoxAttribute:");
+            mCurrentTextBoxAttribute.dump(p, "    ");
+        } else {
+            p.println("  mCurrentTextBoxAttribute: null");
+        }
+        p.println("  mServedInputConnectionWrapper=" + mServedInputConnectionWrapper);
+        p.println("  mCompletions=" + Arrays.toString(mCompletions));
+        p.println("  mCursorRect=" + mCursorRect);
+        p.println("  mCursorSelStart=" + mCursorSelStart
+                + " mCursorSelEnd=" + mCursorSelEnd
+                + " mCursorCandStart=" + mCursorCandStart
+                + " mCursorCandEnd=" + mCursorCandEnd);
+    }
+
+    /**
+     * Callback that is invoked when an input event that was dispatched to
+     * the IME has been finished.
+     * @hide
+     */
+    public interface FinishedInputEventCallback {
+        public void onFinishedInputEvent(Object token, boolean handled);
+    }
+
+    private final class ImeInputEventSender extends InputEventSender {
+        public ImeInputEventSender(InputChannel inputChannel, Looper looper) {
+            super(inputChannel, looper);
+        }
+
+        @Override
+        public void onInputEventFinished(int seq, boolean handled) {
+            finishedInputEvent(seq, handled, false);
+        }
+    }
+
+    private final class PendingEvent implements Runnable {
+        public InputEvent mEvent;
+        public Object mToken;
+        public String mInputMethodId;
+        public FinishedInputEventCallback mCallback;
+        public Handler mHandler;
+        public boolean mHandled;
+
+        public void recycle() {
+            mEvent = null;
+            mToken = null;
+            mInputMethodId = null;
+            mCallback = null;
+            mHandler = null;
+            mHandled = false;
+        }
+
+        @Override
+        public void run() {
+            mCallback.onFinishedInputEvent(mToken, mHandled);
+
+            synchronized (mH) {
+                recyclePendingEventLocked(this);
+            }
+        }
+    }
+
+    private static String dumpViewInfo(@Nullable final View view) {
+        if (view == null) {
+            return "null";
+        }
+        final StringBuilder sb = new StringBuilder();
+        sb.append(view);
+        sb.append(",focus=" + view.hasFocus());
+        sb.append(",windowFocus=" + view.hasWindowFocus());
+        sb.append(",autofillUiShowing=" + isAutofillUIShowing(view));
+        sb.append(",window=" + view.getWindowToken());
+        sb.append(",displayId=" + view.getContext().getDisplayId());
+        sb.append(",temporaryDetach=" + view.isTemporarilyDetached());
+        sb.append(",hasImeFocus=" + view.hasImeFocus());
+
+        return sb.toString();
+    }
+
+    /**
+     * Checks the args to see if a proto-based ime dump was requested and writes the client side
+     * ime dump to the given {@link FileDescriptor}.
+     *
+     * @return {@code true} if a proto-based ime dump was requested.
+     */
+    private boolean processDump(final FileDescriptor fd, final String[] args) {
+        if (args == null) {
+            return false;
+        }
+
+        for (String arg : args) {
+            if (arg.equals(PROTO_ARG)) {
+                final ProtoOutputStream proto = new ProtoOutputStream(fd);
+                dumpDebug(proto, null /* icProto */);
+                proto.flush();
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Write the proto dump of various client side components to the provided
+     * {@link ProtoOutputStream}.
+     *
+     * @param proto The proto stream to which the dumps are written.
+     * @param icProto {@link InputConnection} call data in proto format.
+     * @hide
+     */
+    @GuardedBy("mH")
+    public void dumpDebug(ProtoOutputStream proto, ProtoOutputStream icProto) {
+        if (mCurrentInputMethodSession == null) {
+            return;
+        }
+
+        proto.write(DISPLAY_ID, mDisplayId);
+        final long token = proto.start(INPUT_METHOD_MANAGER);
+        synchronized (mH) {
+            proto.write(CUR_ID, mCurId);
+            proto.write(FULLSCREEN_MODE, mFullscreenMode);
+            proto.write(ACTIVE, mActive);
+            proto.write(SERVED_CONNECTING, mServedConnecting);
+            proto.end(token);
+            if (mCurRootView != null) {
+                mCurRootView.dumpDebug(proto, VIEW_ROOT_IMPL);
+            }
+            if (mCurrentTextBoxAttribute != null) {
+                mCurrentTextBoxAttribute.dumpDebug(proto, EDITOR_INFO);
+            }
+            if (mImeInsetsConsumer != null) {
+                mImeInsetsConsumer.dumpDebug(proto, IME_INSETS_SOURCE_CONSUMER);
+            }
+            if (mServedInputConnectionWrapper != null) {
+                mServedInputConnectionWrapper.dumpDebug(proto, INPUT_CONNECTION);
+            }
+            if (icProto != null) {
+                proto.write(INPUT_CONNECTION_CALL, icProto.getBytes());
+            }
+        }
+    }
+}
diff --git a/android/view/inputmethod/InputMethodManager_Accessor.java b/android/view/inputmethod/InputMethodManager_Accessor.java
new file mode 100644
index 0000000..85dc5d3
--- /dev/null
+++ b/android/view/inputmethod/InputMethodManager_Accessor.java
@@ -0,0 +1,27 @@
+/*
+ * 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.view.inputmethod;
+
+/**
+ * Class allowing access to package-protected methods/fields.
+ */
+public class InputMethodManager_Accessor {
+
+    public static void tearDownEditMode() {
+        InputMethodManager.tearDownEditMode();
+    }
+}
diff --git a/android/view/inputmethod/InputMethodManager_Delegate.java b/android/view/inputmethod/InputMethodManager_Delegate.java
new file mode 100644
index 0000000..5dd25b4
--- /dev/null
+++ b/android/view/inputmethod/InputMethodManager_Delegate.java
@@ -0,0 +1,37 @@
+/*
+ * 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.view.inputmethod;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link InputMethodManager}
+ *
+ * Through the layoutlib_create tool, the original  methods of InputMethodManager have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class InputMethodManager_Delegate {
+
+    // ---- Overridden methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static boolean isInEditMode() {
+        return true;
+    }
+}
diff --git a/android/view/inputmethod/InputMethodSession.java b/android/view/inputmethod/InputMethodSession.java
new file mode 100644
index 0000000..52c1cd4
--- /dev/null
+++ b/android/view/inputmethod/InputMethodSession.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2007-2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.view.inputmethod;
+
+import android.graphics.Rect;
+import android.inputmethodservice.InputMethodService;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+/**
+ * The InputMethodSession interface provides the per-client functionality
+ * of {@link InputMethod} that is safe to expose to applications.
+ *
+ * <p>Applications will not normally use this interface themselves, instead
+ * relying on the standard interaction provided by
+ * {@link android.widget.TextView} and {@link android.widget.EditText}.
+ */
+public interface InputMethodSession {
+
+    public interface EventCallback {
+        void finishedEvent(int seq, boolean handled);
+    }
+
+    /**
+     * This method is called when the application would like to stop
+     * receiving text input.
+     */
+    public void finishInput();
+
+    /**
+     * This method is called when the selection or cursor in the current
+     * target input field has changed.
+     *
+     * @param oldSelStart The previous text offset of the cursor selection
+     * start position.
+     * @param oldSelEnd The previous text offset of the cursor selection
+     * end position.
+     * @param newSelStart The new text offset of the cursor selection
+     * start position.
+     * @param newSelEnd The new text offset of the cursor selection
+     * end position.
+     * @param candidatesStart The text offset of the current candidate
+     * text start position.
+     * @param candidatesEnd The text offset of the current candidate
+     * text end position.
+     */
+    public void updateSelection(int oldSelStart, int oldSelEnd,
+            int newSelStart, int newSelEnd,
+            int candidatesStart, int candidatesEnd);
+
+    /**
+     * This method is called when the user tapped a text view.
+     * IMEs can't rely on this method being called because this was not part of the original IME
+     * protocol, so applications with custom text editing written before this method appeared will
+     * not call to inform the IME of this interaction.
+     * @param focusChanged true if the user changed the focused view by this click.
+     */
+    public void viewClicked(boolean focusChanged);
+
+    /**
+     * This method is called when cursor location of the target input field
+     * has changed within its window.  This is not normally called, but will
+     * only be reported if requested by the input method.
+     *
+     * @param newCursor The rectangle of the cursor currently being shown in
+     * the input field's window coordinates.
+     */
+    public void updateCursor(Rect newCursor);
+
+    /**
+     * Called by a text editor that performs auto completion, to tell the
+     * input method about the completions it has available.  This can be used
+     * by the input method to display them to the user to select the text to
+     * be inserted.
+     *
+     * @param completions Array of text completions that are available, starting with
+     * the best.  If this array is null, any existing completions will be
+     * removed.
+     */
+    public void displayCompletions(CompletionInfo[] completions);
+
+    /**
+     * Called by a text editor to report its new extracted text when its
+     * contents change.  This will only be called if the input method
+     * calls {@link InputConnection#getExtractedText(ExtractedTextRequest, int)
+     * InputConnection.getExtractedText()} with the option to report updates.
+     *
+     * @param token The input method supplied token for identifying its request.
+     * @param text The new extracted text.
+     */
+    public void updateExtractedText(int token, ExtractedText text);
+
+    /**
+     * This method is called when a key is pressed.  When done with the event,
+     * the implementation must call back on <var>callback</var> with its
+     * result.
+     *
+     * <p>
+     * If the input method wants to handle this event, return true, otherwise
+     * return false and the caller (i.e. the application) will handle the event.
+     *
+     * @param event The key event.
+     *
+     * @return Whether the input method wants to handle this event.
+     *
+     * @see android.view.KeyEvent
+     */
+    public void dispatchKeyEvent(int seq, KeyEvent event, EventCallback callback);
+
+    /**
+     * This method is called when there is a track ball event.
+     *
+     * <p>
+     * If the input method wants to handle this event, return true, otherwise
+     * return false and the caller (i.e. the application) will handle the event.
+     *
+     * @param event The motion event.
+     *
+     * @return Whether the input method wants to handle this event.
+     *
+     * @see android.view.MotionEvent
+     */
+    public void dispatchTrackballEvent(int seq, MotionEvent event, EventCallback callback);
+
+    /**
+     * This method is called when there is a generic motion event.
+     *
+     * <p>
+     * If the input method wants to handle this event, return true, otherwise
+     * return false and the caller (i.e. the application) will handle the event.
+     *
+     * @param event The motion event.
+     *
+     * @return Whether the input method wants to handle this event.
+     *
+     * @see android.view.MotionEvent
+     */
+    public void dispatchGenericMotionEvent(int seq, MotionEvent event, EventCallback callback);
+
+    /**
+     * Process a private command sent from the application to the input method.
+     * This can be used to provide domain-specific features that are
+     * only known between certain input methods and their clients.
+     *
+     * @param action Name of the command to be performed.  This <em>must</em>
+     * be a scoped name, i.e. prefixed with a package name you own, so that
+     * different developers will not create conflicting commands.
+     * @param data Any data to include with the command.
+     */
+    public void appPrivateCommand(String action, Bundle data);
+
+    /**
+     * Toggle the soft input window.
+     * Applications can toggle the state of the soft input window.
+     * @param showFlags Provides additional operating flags.  May be
+     * 0 or have the {@link InputMethodManager#SHOW_IMPLICIT},
+     * {@link InputMethodManager#SHOW_FORCED} bit set.
+     * @param hideFlags Provides additional operating flags.  May be
+     * 0 or have the {@link  InputMethodManager#HIDE_IMPLICIT_ONLY},
+     * {@link  InputMethodManager#HIDE_NOT_ALWAYS} bit set.
+     *
+     * @deprecated Starting in {@link Build.VERSION_CODES#S} the system no longer invokes this
+     * method, instead it explicitly shows or hides the IME. An {@code InputMethodService}
+     * wishing to toggle its own visibility should instead invoke {@link
+     * InputMethodService#requestShowSelf} or {@link InputMethodService#requestHideSelf}
+     */
+    @Deprecated
+    public void toggleSoftInput(int showFlags, int hideFlags);
+
+    /**
+     * This method is called when the cursor and/or the character position relevant to text input
+     * is changed on the screen.  This is not called by default.  It will only be reported if
+     * requested by the input method.
+     *
+     * @param cursorAnchorInfo Positional information relevant to text input, such as text
+     * insertion point and composition string.
+     */
+    public void updateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo);
+
+    /**
+     * Notifies {@link android.inputmethodservice.InputMethodService} that IME has been
+     * hidden from user.
+     * @hide
+     */
+    public void notifyImeHidden();
+
+    /**
+     * Notify IME directly to remove surface as it is no longer visible.
+     * @hide
+     */
+    public void removeImeSurface();
+}
diff --git a/android/view/inputmethod/InputMethodSessionWrapper.java b/android/view/inputmethod/InputMethodSessionWrapper.java
new file mode 100644
index 0000000..ef1814b
--- /dev/null
+++ b/android/view/inputmethod/InputMethodSessionWrapper.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.view.IInputMethodSession;
+
+/**
+ * This class wrap the {@link IInputMethodSession} object from {@link InputMethodManager}.
+ * Using current {@link IInputMethodSession} object to communicate with
+ * {@link android.inputmethodservice.InputMethodService}.
+ */
+final class InputMethodSessionWrapper {
+
+    private static final String TAG = "InputMethodSessionWrapper";
+
+    /**
+     * The actual instance of the method to make calls on it.
+     */
+    @NonNull
+    private final IInputMethodSession mSession;
+
+    private InputMethodSessionWrapper(@NonNull IInputMethodSession inputMethodSession) {
+        mSession = inputMethodSession;
+    }
+
+    /**
+     * Create a {@link InputMethodSessionWrapper} instance if applicability.
+     *
+     * @param inputMethodSession {@link IInputMethodSession} object to be wrapped.
+     * @return an instance of {@link InputMethodSessionWrapper} if {@code inputMethodSession} is not
+     *         {@code null}. {@code null} otherwise.
+     */
+    @Nullable
+    public static InputMethodSessionWrapper createOrNull(
+            @NonNull IInputMethodSession inputMethodSession) {
+        return inputMethodSession != null ? new InputMethodSessionWrapper(inputMethodSession)
+                : null;
+    }
+
+    @AnyThread
+    void finishInput() {
+        try {
+            mSession.finishInput();
+        } catch (RemoteException e) {
+            Log.w(TAG, "IME died", e);
+        }
+    }
+
+    @AnyThread
+    void updateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo) {
+        try {
+            mSession.updateCursorAnchorInfo(cursorAnchorInfo);
+        } catch (RemoteException e) {
+            Log.w(TAG, "IME died", e);
+        }
+    }
+
+    @AnyThread
+    void displayCompletions(CompletionInfo[] completions) {
+        try {
+            mSession.displayCompletions(completions);
+        } catch (RemoteException e) {
+            Log.w(TAG, "IME died", e);
+        }
+    }
+
+    @AnyThread
+    void updateExtractedText(int token, ExtractedText text) {
+        try {
+            mSession.updateExtractedText(token, text);
+        } catch (RemoteException e) {
+            Log.w(TAG, "IME died", e);
+        }
+    }
+
+    @AnyThread
+    void appPrivateCommand(String action, Bundle data) {
+        try {
+            mSession.appPrivateCommand(action, data);
+        } catch (RemoteException e) {
+            Log.w(TAG, "IME died", e);
+        }
+    }
+
+    @AnyThread
+    void notifyImeHidden() {
+        try {
+            mSession.notifyImeHidden();
+        } catch (RemoteException e) {
+            Log.w(TAG, "IME died", e);
+        }
+    }
+
+    @AnyThread
+    void viewClicked(boolean focusChanged) {
+        try {
+            mSession.viewClicked(focusChanged);
+        } catch (RemoteException e) {
+            Log.w(TAG, "IME died", e);
+        }
+    }
+
+    @AnyThread
+    void updateCursor(Rect newCursor) {
+        try {
+            mSession.updateCursor(newCursor);
+        } catch (RemoteException e) {
+            Log.w(TAG, "IME died", e);
+        }
+    }
+
+    @AnyThread
+    void updateSelection(int oldSelStart, int oldSelEnd, int selStart, int selEnd,
+            int candidatesStart, int candidatesEnd) {
+        try {
+            mSession.updateSelection(
+                    oldSelStart, oldSelEnd, selStart, selEnd, candidatesStart, candidatesEnd);
+        } catch (RemoteException e) {
+            Log.w(TAG, "IME died", e);
+        }
+    }
+
+    /**
+     * @return {@link IInputMethodSession#toString()} as a debug string.
+     */
+    @AnyThread
+    @NonNull
+    @Override
+    public String toString() {
+        return mSession.toString();
+    }
+}
diff --git a/android/view/inputmethod/InputMethodSubtype.java b/android/view/inputmethod/InputMethodSubtype.java
new file mode 100644
index 0000000..9c63d56
--- /dev/null
+++ b/android/view/inputmethod/InputMethodSubtype.java
@@ -0,0 +1,697 @@
+/*
+ * 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.view.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.res.Configuration;
+import android.icu.text.DisplayContext;
+import android.icu.text.LocaleDisplayNames;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.inputmethod.SubtypeLocaleUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IllegalFormatException;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * This class is used to specify meta information of a subtype contained in an input method editor
+ * (IME). Subtype can describe locale (e.g. en_US, fr_FR...) and mode (e.g. voice, keyboard...),
+ * and is used for IME switch and settings. The input method subtype allows the system to bring up
+ * the specified subtype of the designated IME directly.
+ *
+ * <p>It should be defined in an XML resource file of the input method with the
+ * <code>&lt;subtype&gt;</code> element, which resides within an {@code <input-method>} element.
+ * For more information, see the guide to
+ * <a href="{@docRoot}guide/topics/text/creating-input-method.html">
+ * Creating an Input Method</a>.</p>
+ *
+ * @see InputMethodInfo
+ *
+ * @attr ref android.R.styleable#InputMethod_Subtype_label
+ * @attr ref android.R.styleable#InputMethod_Subtype_icon
+ * @attr ref android.R.styleable#InputMethod_Subtype_languageTag
+ * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeLocale
+ * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeMode
+ * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeExtraValue
+ * @attr ref android.R.styleable#InputMethod_Subtype_isAuxiliary
+ * @attr ref android.R.styleable#InputMethod_Subtype_overridesImplicitlyEnabledSubtype
+ * @attr ref android.R.styleable#InputMethod_Subtype_subtypeId
+ * @attr ref android.R.styleable#InputMethod_Subtype_isAsciiCapable
+ */
+public final class InputMethodSubtype implements Parcelable {
+    private static final String TAG = InputMethodSubtype.class.getSimpleName();
+    private static final String LANGUAGE_TAG_NONE = "";
+    private static final String EXTRA_VALUE_PAIR_SEPARATOR = ",";
+    private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "=";
+    // TODO: remove this
+    private static final String EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME =
+            "UntranslatableReplacementStringInSubtypeName";
+    /** {@hide} */
+    public static final int SUBTYPE_ID_NONE = 0;
+
+    private final boolean mIsAuxiliary;
+    private final boolean mOverridesImplicitlyEnabledSubtype;
+    private final boolean mIsAsciiCapable;
+    private final int mSubtypeHashCode;
+    private final int mSubtypeIconResId;
+    private final int mSubtypeNameResId;
+    private final int mSubtypeId;
+    private final String mSubtypeLocale;
+    private final String mSubtypeLanguageTag;
+    private final String mSubtypeMode;
+    private final String mSubtypeExtraValue;
+    private final Object mLock = new Object();
+    private volatile Locale mCachedLocaleObj;
+    private volatile HashMap<String, String> mExtraValueHashMapCache;
+
+    /**
+     * InputMethodSubtypeBuilder is a builder class of InputMethodSubtype.
+     * This class is designed to be used with
+     * {@link android.view.inputmethod.InputMethodManager#setAdditionalInputMethodSubtypes}.
+     * The developer needs to be aware of what each parameter means.
+     */
+    public static class InputMethodSubtypeBuilder {
+        /**
+         * @param isAuxiliary should true when this subtype is auxiliary, false otherwise.
+         * An auxiliary subtype has the following differences with a regular subtype:
+         * - An auxiliary subtype cannot be chosen as the default IME in Settings.
+         * - The framework will never switch to this subtype through
+         *   {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}.
+         * Note that the subtype will still be available in the IME switcher.
+         * The intent is to allow for IMEs to specify they are meant to be invoked temporarily
+         * in a one-shot way, and to return to the previous IME once finished (e.g. voice input).
+         */
+        public InputMethodSubtypeBuilder setIsAuxiliary(boolean isAuxiliary) {
+            mIsAuxiliary = isAuxiliary;
+            return this;
+        }
+        private boolean mIsAuxiliary = false;
+
+        /**
+         * @param overridesImplicitlyEnabledSubtype should be true if this subtype should be
+         * enabled by default if no other subtypes in the IME are enabled explicitly. Note that a
+         * subtype with this parameter set will not be shown in the list of subtypes in each IME's
+         * subtype enabler. A canonical use of this would be for an IME to supply an "automatic"
+         * subtype that adapts to the current system language.
+         */
+        public InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(
+                boolean overridesImplicitlyEnabledSubtype) {
+            mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype;
+            return this;
+        }
+        private boolean mOverridesImplicitlyEnabledSubtype = false;
+
+        /**
+         * @param isAsciiCapable should be true if this subtype is ASCII capable. If the subtype
+         * is ASCII capable, it should guarantee that the user can input ASCII characters with
+         * this subtype. This is important because many password fields only allow
+         * ASCII-characters.
+         */
+        public InputMethodSubtypeBuilder setIsAsciiCapable(boolean isAsciiCapable) {
+            mIsAsciiCapable = isAsciiCapable;
+            return this;
+        }
+        private boolean mIsAsciiCapable = false;
+
+        /**
+         * @param subtypeIconResId is a resource ID of the subtype icon drawable.
+         */
+        public InputMethodSubtypeBuilder setSubtypeIconResId(int subtypeIconResId) {
+            mSubtypeIconResId = subtypeIconResId;
+            return this;
+        }
+        private int mSubtypeIconResId = 0;
+
+        /**
+         * @param subtypeNameResId is the resource ID of the subtype name string.
+         * The string resource may have exactly one %s in it. If present,
+         * the %s part will be replaced with the locale's display name by
+         * the formatter. Please refer to {@link #getDisplayName} for details.
+         */
+        public InputMethodSubtypeBuilder setSubtypeNameResId(int subtypeNameResId) {
+            mSubtypeNameResId = subtypeNameResId;
+            return this;
+        }
+        private int mSubtypeNameResId = 0;
+
+        /**
+         * @param subtypeId is the unique ID for this subtype. The input method framework keeps
+         * track of enabled subtypes by ID. When the IME package gets upgraded, enabled IDs will
+         * stay enabled even if other attributes are different. If the ID is unspecified or 0,
+         * Arrays.hashCode(new Object[] {locale, mode, extraValue,
+         * isAuxiliary, overridesImplicitlyEnabledSubtype, isAsciiCapable}) will be used instead.
+         */
+        public InputMethodSubtypeBuilder setSubtypeId(int subtypeId) {
+            mSubtypeId = subtypeId;
+            return this;
+        }
+        private int mSubtypeId = SUBTYPE_ID_NONE;
+
+        /**
+         * @param subtypeLocale is the locale supported by this subtype.
+         */
+        public InputMethodSubtypeBuilder setSubtypeLocale(String subtypeLocale) {
+            mSubtypeLocale = subtypeLocale == null ? "" : subtypeLocale;
+            return this;
+        }
+        private String mSubtypeLocale = "";
+
+        /**
+         * @param languageTag is the BCP-47 Language Tag supported by this subtype.
+         */
+        public InputMethodSubtypeBuilder setLanguageTag(String languageTag) {
+            mSubtypeLanguageTag = languageTag == null ? LANGUAGE_TAG_NONE : languageTag;
+            return this;
+        }
+        private String mSubtypeLanguageTag = LANGUAGE_TAG_NONE;
+
+        /**
+         * @param subtypeMode is the mode supported by this subtype.
+         */
+        public InputMethodSubtypeBuilder setSubtypeMode(String subtypeMode) {
+            mSubtypeMode = subtypeMode == null ? "" : subtypeMode;
+            return this;
+        }
+        private String mSubtypeMode = "";
+        /**
+         * @param subtypeExtraValue is the extra value of the subtype. This string is free-form,
+         * but the API supplies tools to deal with a key-value comma-separated list; see
+         * {@link #containsExtraValueKey} and {@link #getExtraValueOf}.
+         */
+        public InputMethodSubtypeBuilder setSubtypeExtraValue(String subtypeExtraValue) {
+            mSubtypeExtraValue = subtypeExtraValue == null ? "" : subtypeExtraValue;
+            return this;
+        }
+        private String mSubtypeExtraValue = "";
+
+        /**
+         * @return InputMethodSubtype using parameters in this InputMethodSubtypeBuilder.
+         */
+        public InputMethodSubtype build() {
+            return new InputMethodSubtype(this);
+        }
+     }
+
+     private static InputMethodSubtypeBuilder getBuilder(int nameId, int iconId, String locale,
+             String mode, String extraValue, boolean isAuxiliary,
+             boolean overridesImplicitlyEnabledSubtype, int id, boolean isAsciiCapable) {
+         final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder();
+         builder.mSubtypeNameResId = nameId;
+         builder.mSubtypeIconResId = iconId;
+         builder.mSubtypeLocale = locale;
+         builder.mSubtypeMode = mode;
+         builder.mSubtypeExtraValue = extraValue;
+         builder.mIsAuxiliary = isAuxiliary;
+         builder.mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype;
+         builder.mSubtypeId = id;
+         builder.mIsAsciiCapable = isAsciiCapable;
+         return builder;
+     }
+
+    /**
+     * Constructor with no subtype ID specified.
+     * @deprecated use {@link InputMethodSubtypeBuilder} instead.
+     * Arguments for this constructor have the same meanings as
+     * {@link InputMethodSubtype#InputMethodSubtype(int, int, String, String, String, boolean,
+     * boolean, int)} except "id".
+     */
+    @Deprecated
+    public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue,
+            boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype) {
+        this(nameId, iconId, locale, mode, extraValue, isAuxiliary,
+                overridesImplicitlyEnabledSubtype, 0);
+    }
+
+    /**
+     * Constructor.
+     * @deprecated use {@link InputMethodSubtypeBuilder} instead.
+     * "isAsciiCapable" is "false" in this constructor.
+     * @param nameId Resource ID of the subtype name string. The string resource may have exactly
+     * one %s in it. If there is, the %s part will be replaced with the locale's display name by
+     * the formatter. Please refer to {@link #getDisplayName} for details.
+     * @param iconId Resource ID of the subtype icon drawable.
+     * @param locale The locale supported by the subtype
+     * @param mode The mode supported by the subtype
+     * @param extraValue The extra value of the subtype. This string is free-form, but the API
+     * supplies tools to deal with a key-value comma-separated list; see
+     * {@link #containsExtraValueKey} and {@link #getExtraValueOf}.
+     * @param isAuxiliary true when this subtype is auxiliary, false otherwise. An auxiliary
+     * subtype will not be shown in the list of enabled IMEs for choosing the current IME in
+     * the Settings even when this subtype is enabled. Please note that this subtype will still
+     * be shown in the list of IMEs in the IME switcher to allow the user to tentatively switch
+     * to this subtype while an IME is shown. The framework will never switch the current IME to
+     * this subtype by {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}.
+     * The intent of having this flag is to allow for IMEs that are invoked in a one-shot way as
+     * auxiliary input mode, and return to the previous IME once it is finished (e.g. voice input).
+     * @param overridesImplicitlyEnabledSubtype true when this subtype should be enabled by default
+     * if no other subtypes in the IME are enabled explicitly. Note that a subtype with this
+     * parameter being true will not be shown in the list of subtypes in each IME's subtype enabler.
+     * Having an "automatic" subtype is an example use of this flag.
+     * @param id The unique ID for the subtype. The input method framework keeps track of enabled
+     * subtypes by ID. When the IME package gets upgraded, enabled IDs will stay enabled even if
+     * other attributes are different. If the ID is unspecified or 0,
+     * Arrays.hashCode(new Object[] {locale, mode, extraValue,
+     * isAuxiliary, overridesImplicitlyEnabledSubtype, isAsciiCapable}) will be used instead.
+     */
+    @Deprecated
+    public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue,
+            boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, int id) {
+        this(getBuilder(nameId, iconId, locale, mode, extraValue, isAuxiliary,
+                overridesImplicitlyEnabledSubtype, id, false));
+    }
+
+    /**
+     * Constructor.
+     * @param builder Builder for InputMethodSubtype
+     */
+    private InputMethodSubtype(InputMethodSubtypeBuilder builder) {
+        mSubtypeNameResId = builder.mSubtypeNameResId;
+        mSubtypeIconResId = builder.mSubtypeIconResId;
+        mSubtypeLocale = builder.mSubtypeLocale;
+        mSubtypeLanguageTag = builder.mSubtypeLanguageTag;
+        mSubtypeMode = builder.mSubtypeMode;
+        mSubtypeExtraValue = builder.mSubtypeExtraValue;
+        mIsAuxiliary = builder.mIsAuxiliary;
+        mOverridesImplicitlyEnabledSubtype = builder.mOverridesImplicitlyEnabledSubtype;
+        mSubtypeId = builder.mSubtypeId;
+        mIsAsciiCapable = builder.mIsAsciiCapable;
+        // If hashCode() of this subtype is 0 and you want to specify it as an id of this subtype,
+        // just specify 0 as this subtype's id. Then, this subtype's id is treated as 0.
+        if (mSubtypeId != SUBTYPE_ID_NONE) {
+            mSubtypeHashCode = mSubtypeId;
+        } else {
+            mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeMode, mSubtypeExtraValue,
+                    mIsAuxiliary, mOverridesImplicitlyEnabledSubtype, mIsAsciiCapable);
+        }
+    }
+
+    InputMethodSubtype(Parcel source) {
+        String s;
+        mSubtypeNameResId = source.readInt();
+        mSubtypeIconResId = source.readInt();
+        s = source.readString();
+        mSubtypeLocale = s != null ? s : "";
+        s = source.readString();
+        mSubtypeLanguageTag = s != null ? s : LANGUAGE_TAG_NONE;
+        s = source.readString();
+        mSubtypeMode = s != null ? s : "";
+        s = source.readString();
+        mSubtypeExtraValue = s != null ? s : "";
+        mIsAuxiliary = (source.readInt() == 1);
+        mOverridesImplicitlyEnabledSubtype = (source.readInt() == 1);
+        mSubtypeHashCode = source.readInt();
+        mSubtypeId = source.readInt();
+        mIsAsciiCapable = (source.readInt() == 1);
+    }
+
+    /**
+     * @return Resource ID of the subtype name string.
+     */
+    public int getNameResId() {
+        return mSubtypeNameResId;
+    }
+
+    /**
+     * @return Resource ID of the subtype icon drawable.
+     */
+    public int getIconResId() {
+        return mSubtypeIconResId;
+    }
+
+    /**
+     * @return The locale of the subtype. This method returns the "locale" string parameter passed
+     * to the constructor.
+     *
+     * @deprecated Use {@link #getLanguageTag()} instead.
+     */
+    @Deprecated
+    @NonNull
+    public String getLocale() {
+        return mSubtypeLocale;
+    }
+
+    /**
+     * @return the BCP-47 Language Tag of the subtype.  Returns an empty string when no Language Tag
+     * is specified.
+     *
+     * @see Locale#forLanguageTag(String)
+     */
+    @NonNull
+    public String getLanguageTag() {
+        return mSubtypeLanguageTag;
+    }
+
+    /**
+     * @return {@link Locale} constructed from {@link #getLanguageTag()}. If the Language Tag is not
+     * specified, then try to construct from {@link #getLocale()}
+     *
+     * <p>TODO: Consider to make this a public API, or move this to support lib.</p>
+     * @hide
+     */
+    @Nullable
+    public Locale getLocaleObject() {
+        if (mCachedLocaleObj != null) {
+            return mCachedLocaleObj;
+        }
+        synchronized (mLock) {
+            if (mCachedLocaleObj != null) {
+                return mCachedLocaleObj;
+            }
+            if (!TextUtils.isEmpty(mSubtypeLanguageTag)) {
+                mCachedLocaleObj = Locale.forLanguageTag(mSubtypeLanguageTag);
+            } else {
+                mCachedLocaleObj = SubtypeLocaleUtils.constructLocaleFromString(mSubtypeLocale);
+            }
+            return mCachedLocaleObj;
+        }
+    }
+
+    /**
+     * @return The mode of the subtype.
+     */
+    public String getMode() {
+        return mSubtypeMode;
+    }
+
+    /**
+     * @return The extra value of the subtype.
+     */
+    public String getExtraValue() {
+        return mSubtypeExtraValue;
+    }
+
+    /**
+     * @return true if this subtype is auxiliary, false otherwise. An auxiliary subtype will not be
+     * shown in the list of enabled IMEs for choosing the current IME in the Settings even when this
+     * subtype is enabled. Please note that this subtype will still be shown in the list of IMEs in
+     * the IME switcher to allow the user to tentatively switch to this subtype while an IME is
+     * shown. The framework will never switch the current IME to this subtype by
+     * {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}.
+     * The intent of having this flag is to allow for IMEs that are invoked in a one-shot way as
+     * auxiliary input mode, and return to the previous IME once it is finished (e.g. voice input).
+     */
+    public boolean isAuxiliary() {
+        return mIsAuxiliary;
+    }
+
+    /**
+     * @return true when this subtype will be enabled by default if no other subtypes in the IME
+     * are enabled explicitly, false otherwise. Note that a subtype with this method returning true
+     * will not be shown in the list of subtypes in each IME's subtype enabler. Having an
+     * "automatic" subtype is an example use of this flag.
+     */
+    public boolean overridesImplicitlyEnabledSubtype() {
+        return mOverridesImplicitlyEnabledSubtype;
+    }
+
+    /**
+     * @return true if this subtype is Ascii capable, false otherwise. If the subtype is ASCII
+     * capable, it should guarantee that the user can input ASCII characters with this subtype.
+     * This is important because many password fields only allow ASCII-characters.
+     */
+    public boolean isAsciiCapable() {
+        return mIsAsciiCapable;
+    }
+
+    /**
+     * Returns a display name for this subtype.
+     *
+     * <p>If {@code subtypeNameResId} is specified (!= 0) text generated from that resource will
+     * be returned. The localized string resource of the label should be capitalized for inclusion
+     * in UI lists. The string resource may contain at most one {@code %s}. If present, the
+     * {@code %s} will be replaced with the display name of the subtype locale in the user's locale.
+     *
+     * <p>If {@code subtypeNameResId} is not specified (== 0) the framework returns the display name
+     * of the subtype locale, as capitalized for use in UI lists, in the user's locale.
+     *
+     * @param context {@link Context} will be used for getting {@link Locale} and
+     * {@link android.content.pm.PackageManager}.
+     * @param packageName The package name of the input method.
+     * @param appInfo The {@link ApplicationInfo} of the input method.
+     * @return a display name for this subtype.
+     */
+    @NonNull
+    public CharSequence getDisplayName(
+            Context context, String packageName, ApplicationInfo appInfo) {
+        if (mSubtypeNameResId == 0) {
+            return getLocaleDisplayName(getLocaleFromContext(context), getLocaleObject(),
+                    DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU);
+        }
+
+        final CharSequence subtypeName = context.getPackageManager().getText(
+                packageName, mSubtypeNameResId, appInfo);
+        if (TextUtils.isEmpty(subtypeName)) {
+            return "";
+        }
+        final String subtypeNameString = subtypeName.toString();
+        String replacementString;
+        if (containsExtraValueKey(EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)) {
+            replacementString = getExtraValueOf(
+                    EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME);
+        } else {
+            final DisplayContext displayContext;
+            if (TextUtils.equals(subtypeNameString, "%s")) {
+                displayContext = DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU;
+            } else if (subtypeNameString.startsWith("%s")) {
+                displayContext = DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE;
+            } else {
+                displayContext = DisplayContext.CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE;
+            }
+            replacementString = getLocaleDisplayName(getLocaleFromContext(context),
+                    getLocaleObject(), displayContext);
+        }
+        if (replacementString == null) {
+            replacementString = "";
+        }
+        try {
+            return String.format(subtypeNameString, replacementString);
+        } catch (IllegalFormatException e) {
+            Slog.w(TAG, "Found illegal format in subtype name("+ subtypeName + "): " + e);
+            return "";
+        }
+    }
+
+    @Nullable
+    private static Locale getLocaleFromContext(@Nullable final Context context) {
+        if (context == null) {
+            return null;
+        }
+        if (context.getResources() == null) {
+            return null;
+        }
+        final Configuration configuration = context.getResources().getConfiguration();
+        if (configuration == null) {
+            return null;
+        }
+        return configuration.getLocales().get(0);
+    }
+
+    /**
+     * @param displayLocale {@link Locale} to be used to display {@code localeToDisplay}
+     * @param localeToDisplay {@link Locale} to be displayed in {@code displayLocale}
+     * @param displayContext context parameter to be used to display {@code localeToDisplay} in
+     * {@code displayLocale}
+     * @return Returns the name of the {@code localeToDisplay} in the user's current locale.
+     */
+    @NonNull
+    private static String getLocaleDisplayName(
+            @Nullable Locale displayLocale, @Nullable Locale localeToDisplay,
+            final DisplayContext displayContext) {
+        if (localeToDisplay == null) {
+            return "";
+        }
+        final Locale nonNullDisplayLocale =
+                displayLocale != null ? displayLocale : Locale.getDefault();
+        return LocaleDisplayNames
+                .getInstance(nonNullDisplayLocale, displayContext)
+                .localeDisplayName(localeToDisplay);
+    }
+
+    private HashMap<String, String> getExtraValueHashMap() {
+        synchronized (this) {
+            HashMap<String, String> extraValueMap = mExtraValueHashMapCache;
+            if (extraValueMap != null) {
+                return extraValueMap;
+            }
+            extraValueMap = new HashMap<>();
+            final String[] pairs = mSubtypeExtraValue.split(EXTRA_VALUE_PAIR_SEPARATOR);
+            for (int i = 0; i < pairs.length; ++i) {
+                final String[] pair = pairs[i].split(EXTRA_VALUE_KEY_VALUE_SEPARATOR);
+                if (pair.length == 1) {
+                    extraValueMap.put(pair[0], null);
+                } else if (pair.length > 1) {
+                    if (pair.length > 2) {
+                        Slog.w(TAG, "ExtraValue has two or more '='s");
+                    }
+                    extraValueMap.put(pair[0], pair[1]);
+                }
+            }
+            mExtraValueHashMapCache = extraValueMap;
+            return extraValueMap;
+        }
+    }
+
+    /**
+     * The string of ExtraValue in subtype should be defined as follows:
+     * example: key0,key1=value1,key2,key3,key4=value4
+     * @param key The key of extra value
+     * @return The subtype contains specified the extra value
+     */
+    public boolean containsExtraValueKey(String key) {
+        return getExtraValueHashMap().containsKey(key);
+    }
+
+    /**
+     * The string of ExtraValue in subtype should be defined as follows:
+     * example: key0,key1=value1,key2,key3,key4=value4
+     * @param key The key of extra value
+     * @return The value of the specified key
+     */
+    public String getExtraValueOf(String key) {
+        return getExtraValueHashMap().get(key);
+    }
+
+    @Override
+    public int hashCode() {
+        return mSubtypeHashCode;
+    }
+
+    /**
+     * @hide
+     * @return {@code true} if a valid subtype ID exists.
+     */
+    public final boolean hasSubtypeId() {
+        return mSubtypeId != SUBTYPE_ID_NONE;
+    }
+
+    /**
+     * @hide
+     * @return subtype ID. {@code 0} means that not subtype ID is specified.
+     */
+    public final int getSubtypeId() {
+        return mSubtypeId;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (o instanceof InputMethodSubtype) {
+            InputMethodSubtype subtype = (InputMethodSubtype) o;
+            if (subtype.mSubtypeId != 0 || mSubtypeId != 0) {
+                return (subtype.hashCode() == hashCode());
+            }
+            return (subtype.hashCode() == hashCode())
+                    && (subtype.getLocale().equals(getLocale()))
+                    && (subtype.getLanguageTag().equals(getLanguageTag()))
+                    && (subtype.getMode().equals(getMode()))
+                    && (subtype.getExtraValue().equals(getExtraValue()))
+                    && (subtype.isAuxiliary() == isAuxiliary())
+                    && (subtype.overridesImplicitlyEnabledSubtype()
+                            == overridesImplicitlyEnabledSubtype())
+                    && (subtype.isAsciiCapable() == isAsciiCapable());
+        }
+        return false;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int parcelableFlags) {
+        dest.writeInt(mSubtypeNameResId);
+        dest.writeInt(mSubtypeIconResId);
+        dest.writeString(mSubtypeLocale);
+        dest.writeString(mSubtypeLanguageTag);
+        dest.writeString(mSubtypeMode);
+        dest.writeString(mSubtypeExtraValue);
+        dest.writeInt(mIsAuxiliary ? 1 : 0);
+        dest.writeInt(mOverridesImplicitlyEnabledSubtype ? 1 : 0);
+        dest.writeInt(mSubtypeHashCode);
+        dest.writeInt(mSubtypeId);
+        dest.writeInt(mIsAsciiCapable ? 1 : 0);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<InputMethodSubtype> CREATOR
+            = new Parcelable.Creator<InputMethodSubtype>() {
+        @Override
+        public InputMethodSubtype createFromParcel(Parcel source) {
+            return new InputMethodSubtype(source);
+        }
+
+        @Override
+        public InputMethodSubtype[] newArray(int size) {
+            return new InputMethodSubtype[size];
+        }
+    };
+
+    private static int hashCodeInternal(String locale, String mode, String extraValue,
+            boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype,
+            boolean isAsciiCapable) {
+        // CAVEAT: Must revisit how to compute needsToCalculateCompatibleHashCode when a new
+        // attribute is added in order to avoid enabled subtypes being unexpectedly disabled.
+        final boolean needsToCalculateCompatibleHashCode = !isAsciiCapable;
+        if (needsToCalculateCompatibleHashCode) {
+            return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary,
+                    overridesImplicitlyEnabledSubtype});
+        }
+        return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary,
+                overridesImplicitlyEnabledSubtype, isAsciiCapable});
+    }
+
+    /**
+     * Sort the list of InputMethodSubtype
+     * @param context Context will be used for getting localized strings from IME
+     * @param flags Flags for the sort order
+     * @param imi InputMethodInfo of which subtypes are subject to be sorted
+     * @param subtypeList List of InputMethodSubtype which will be sorted
+     * @return Sorted list of subtypes
+     * @hide
+     */
+    public static List<InputMethodSubtype> sort(Context context, int flags, InputMethodInfo imi,
+            List<InputMethodSubtype> subtypeList) {
+        if (imi == null) return subtypeList;
+        final HashSet<InputMethodSubtype> inputSubtypesSet = new HashSet<InputMethodSubtype>(
+                subtypeList);
+        final ArrayList<InputMethodSubtype> sortedList = new ArrayList<InputMethodSubtype>();
+        int N = imi.getSubtypeCount();
+        for (int i = 0; i < N; ++i) {
+            InputMethodSubtype subtype = imi.getSubtypeAt(i);
+            if (inputSubtypesSet.contains(subtype)) {
+                sortedList.add(subtype);
+                inputSubtypesSet.remove(subtype);
+            }
+        }
+        // If subtypes in inputSubtypesSet remain, that means these subtypes are not
+        // contained in imi, so the remaining subtypes will be appended.
+        for (InputMethodSubtype subtype: inputSubtypesSet) {
+            sortedList.add(subtype);
+        }
+        return sortedList;
+    }
+}
\ No newline at end of file
diff --git a/android/view/inputmethod/InputMethodSubtypeArray.java b/android/view/inputmethod/InputMethodSubtypeArray.java
new file mode 100644
index 0000000..50e95c8
--- /dev/null
+++ b/android/view/inputmethod/InputMethodSubtypeArray.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2007-2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.view.inputmethod;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.util.Slog;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.List;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * An array-like container that stores multiple instances of {@link InputMethodSubtype}.
+ *
+ * <p>This container is designed to reduce the risk of {@link TransactionTooLargeException}
+ * when one or more instancess of {@link InputMethodInfo} are transferred through IPC.
+ * Basically this class does following three tasks.</p>
+ * <ul>
+ * <li>Applying compression for the marshalled data</li>
+ * <li>Lazily unmarshalling objects</li>
+ * <li>Caching the marshalled data when appropriate</li>
+ * </ul>
+ *
+ * @hide
+ */
+public class InputMethodSubtypeArray {
+    private final static String TAG = "InputMethodSubtypeArray";
+
+    /**
+     * Create a new instance of {@link InputMethodSubtypeArray} from an existing list of
+     * {@link InputMethodSubtype}.
+     *
+     * @param subtypes A list of {@link InputMethodSubtype} from which
+     * {@link InputMethodSubtypeArray} will be created.
+     */
+    @UnsupportedAppUsage
+    public InputMethodSubtypeArray(final List<InputMethodSubtype> subtypes) {
+        if (subtypes == null) {
+            mCount = 0;
+            return;
+        }
+        mCount = subtypes.size();
+        mInstance = subtypes.toArray(new InputMethodSubtype[mCount]);
+    }
+
+    /**
+     * Unmarshall an instance of {@link InputMethodSubtypeArray} from a given {@link Parcel}
+     * object.
+     *
+     * @param source A {@link Parcel} object from which {@link InputMethodSubtypeArray} will be
+     * unmarshalled.
+     */
+    public InputMethodSubtypeArray(final Parcel source) {
+        mCount = source.readInt();
+        if (mCount > 0) {
+            mDecompressedSize = source.readInt();
+            mCompressedData = source.createByteArray();
+        }
+    }
+
+    /**
+     * Marshall the instance into a given {@link Parcel} object.
+     *
+     * <p>This methods may take a bit additional time to compress data lazily when called
+     * first time.</p>
+     *
+     * @param source A {@link Parcel} object to which {@link InputMethodSubtypeArray} will be
+     * marshalled.
+     */
+    public void writeToParcel(final Parcel dest) {
+        if (mCount == 0) {
+            dest.writeInt(mCount);
+            return;
+        }
+
+        byte[] compressedData = mCompressedData;
+        int decompressedSize = mDecompressedSize;
+        if (compressedData == null && decompressedSize == 0) {
+            synchronized (mLockObject) {
+                compressedData = mCompressedData;
+                decompressedSize = mDecompressedSize;
+                if (compressedData == null && decompressedSize == 0) {
+                    final byte[] decompressedData = marshall(mInstance);
+                    compressedData = compress(decompressedData);
+                    if (compressedData == null) {
+                        decompressedSize = -1;
+                        Slog.i(TAG, "Failed to compress data.");
+                    } else {
+                        decompressedSize = decompressedData.length;
+                    }
+                    mDecompressedSize = decompressedSize;
+                    mCompressedData = compressedData;
+                }
+            }
+        }
+
+        if (compressedData != null && decompressedSize > 0) {
+            dest.writeInt(mCount);
+            dest.writeInt(decompressedSize);
+            dest.writeByteArray(compressedData);
+        } else {
+            Slog.i(TAG, "Unexpected state. Behaving as an empty array.");
+            dest.writeInt(0);
+        }
+    }
+
+    /**
+     * Return {@link InputMethodSubtype} specified with the given index.
+     *
+     * <p>This methods may take a bit additional time to decompress data lazily when called
+     * first time.</p>
+     *
+     * @param index The index of {@link InputMethodSubtype}.
+     */
+    public InputMethodSubtype get(final int index) {
+        if (index < 0 || mCount <= index) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        InputMethodSubtype[] instance = mInstance;
+        if (instance == null) {
+            synchronized (mLockObject) {
+                instance = mInstance;
+                if (instance == null) {
+                    final byte[] decompressedData =
+                          decompress(mCompressedData, mDecompressedSize);
+                    // Clear the compressed data until {@link #getMarshalled()} is called.
+                    mCompressedData = null;
+                    mDecompressedSize = 0;
+                    if (decompressedData != null) {
+                        instance = unmarshall(decompressedData);
+                    } else {
+                        Slog.e(TAG, "Failed to decompress data. Returns null as fallback.");
+                        instance = new InputMethodSubtype[mCount];
+                    }
+                    mInstance = instance;
+                }
+            }
+        }
+        return instance[index];
+    }
+
+    /**
+     * Return the number of {@link InputMethodSubtype} objects.
+     */
+    public int getCount() {
+        return mCount;
+    }
+
+    private final Object mLockObject = new Object();
+    private final int mCount;
+
+    private volatile InputMethodSubtype[] mInstance;
+    private volatile byte[] mCompressedData;
+    private volatile int mDecompressedSize;
+
+    private static byte[] marshall(final InputMethodSubtype[] array) {
+        Parcel parcel = null;
+        try {
+            parcel = Parcel.obtain();
+            parcel.writeTypedArray(array, 0);
+            return parcel.marshall();
+        } finally {
+            if (parcel != null) {
+                parcel.recycle();
+                parcel = null;
+            }
+        }
+    }
+
+    private static InputMethodSubtype[] unmarshall(final byte[] data) {
+        Parcel parcel = null;
+        try {
+            parcel = Parcel.obtain();
+            parcel.unmarshall(data, 0, data.length);
+            parcel.setDataPosition(0);
+            return parcel.createTypedArray(InputMethodSubtype.CREATOR);
+        } finally {
+            if (parcel != null) {
+                parcel.recycle();
+                parcel = null;
+            }
+        }
+    }
+
+    private static byte[] compress(final byte[] data) {
+        try (final ByteArrayOutputStream resultStream = new ByteArrayOutputStream();
+                final GZIPOutputStream zipper = new GZIPOutputStream(resultStream)) {
+            zipper.write(data);
+            zipper.finish();
+            return resultStream.toByteArray();
+        } catch(Exception e) {
+            Slog.e(TAG, "Failed to compress the data.", e);
+            return null;
+        }
+    }
+
+    private static byte[] decompress(final byte[] data, final int expectedSize) {
+        try (final ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
+                final GZIPInputStream unzipper = new GZIPInputStream(inputStream)) {
+            final byte [] result = new byte[expectedSize];
+            int totalReadBytes = 0;
+            while (totalReadBytes < result.length) {
+                final int restBytes = result.length - totalReadBytes;
+                final int readBytes = unzipper.read(result, totalReadBytes, restBytes);
+                if (readBytes < 0) {
+                    break;
+                }
+                totalReadBytes += readBytes;
+            }
+            if (expectedSize != totalReadBytes) {
+                return null;
+            }
+            return result;
+        } catch(Exception e) {
+            Slog.e(TAG, "Failed to decompress the data.", e);
+            return null;
+        }
+    }
+}
diff --git a/android/view/inputmethod/SparseRectFArray.java b/android/view/inputmethod/SparseRectFArray.java
new file mode 100644
index 0000000..07d0b06
--- /dev/null
+++ b/android/view/inputmethod/SparseRectFArray.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.view.inputmethod;
+
+import android.annotation.Nullable;
+import android.graphics.RectF;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+
+/**
+ * An implementation of SparseArray specialized for {@link android.graphics.RectF}.
+ * <p>
+ * As this is a sparse array, it represents an array of {@link RectF} most of which are null. This
+ * class could be in some other packages like android.graphics or android.util but currently
+ * belong to android.view.inputmethod because this class is hidden and used only in input method
+ * framework.
+ * </p>
+ * @hide
+ */
+public final class SparseRectFArray implements Parcelable {
+    /**
+     * The keys, in ascending order, of those {@link RectF} that are not null. For example,
+     * {@code [null, null, null, Rect1, null, Rect2]} would be represented by {@code [3,5]}.
+     * @see #mCoordinates
+     */
+    private final int[] mKeys;
+
+    /**
+     * Stores coordinates of the rectangles, in the order of
+     * {@code rects[mKeys[0]].left}, {@code rects[mKeys[0]].top},
+     * {@code rects[mKeys[0]].right}, {@code rects[mKeys[0]].bottom},
+     * {@code rects[mKeys[1]].left}, {@code rects[mKeys[1]].top},
+     * {@code rects[mKeys[1]].right}, {@code rects[mKeys[1]].bottom},
+     * {@code rects[mKeys[2]].left}, {@code rects[mKeys[2]].top}, ....
+     */
+    private final float[] mCoordinates;
+
+    /**
+     * Stores visibility information.
+     */
+    private final int[] mFlagsArray;
+
+    public SparseRectFArray(final Parcel source) {
+        mKeys = source.createIntArray();
+        mCoordinates = source.createFloatArray();
+        mFlagsArray = source.createIntArray();
+    }
+
+    /**
+     * Used to package this object into a {@link Parcel}.
+     *
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeIntArray(mKeys);
+        dest.writeFloatArray(mCoordinates);
+        dest.writeIntArray(mFlagsArray);
+    }
+
+    @Override
+    public int hashCode() {
+        // TODO: Improve the hash function.
+        if (mKeys == null || mKeys.length == 0) {
+            return 0;
+        }
+        int hash = mKeys.length;
+        // For performance reasons, only the first rectangle is used for the hash code now.
+        for (int i = 0; i < 4; i++) {
+            hash *= 31;
+            hash += mCoordinates[i];
+        }
+        hash *= 31;
+        hash += mFlagsArray[0];
+        return hash;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object obj){
+        if (obj == null) {
+            return false;
+        }
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof SparseRectFArray)) {
+            return false;
+        }
+        final SparseRectFArray that = (SparseRectFArray) obj;
+
+        return Arrays.equals(mKeys, that.mKeys) && Arrays.equals(mCoordinates, that.mCoordinates)
+                && Arrays.equals(mFlagsArray, that.mFlagsArray);
+    }
+
+    @Override
+    public String toString() {
+        if (mKeys == null || mCoordinates == null || mFlagsArray == null) {
+            return "SparseRectFArray{}";
+        }
+        final StringBuilder sb = new StringBuilder();
+        sb.append("SparseRectFArray{");
+        for (int i = 0; i < mKeys.length; i++) {
+            if (i != 0) {
+                sb.append(", ");
+            }
+            final int baseIndex = i * 4;
+            sb.append(mKeys[i]);
+            sb.append(":[");
+            sb.append(mCoordinates[baseIndex + 0]);
+            sb.append(",");
+            sb.append(mCoordinates[baseIndex + 1]);
+            sb.append("],[");
+            sb.append(mCoordinates[baseIndex + 2]);
+            sb.append(",");
+            sb.append(mCoordinates[baseIndex + 3]);
+            sb.append("]:flagsArray=");
+            sb.append(mFlagsArray[i]);
+        }
+        sb.append("}");
+        return sb.toString();
+    }
+
+    /**
+     * Builder for {@link SparseRectFArray}. This class is not designed to be thread-safe.
+     * @hide
+     */
+    public static final class SparseRectFArrayBuilder {
+        /**
+         * Throws {@link IllegalArgumentException} to make sure that this class is correctly used.
+         * @param key key to be checked.
+         */
+        private void checkIndex(final int key) {
+            if (mCount == 0) {
+                return;
+            }
+            if (mKeys[mCount - 1] >= key) {
+                throw new IllegalArgumentException("key must be greater than all existing keys.");
+            }
+        }
+
+        /**
+         * Extends the internal array if necessary.
+         */
+        private void ensureBufferSize() {
+            if (mKeys == null) {
+                mKeys = new int[INITIAL_SIZE];
+            }
+            if (mCoordinates == null) {
+                mCoordinates = new float[INITIAL_SIZE * 4];
+            }
+            if (mFlagsArray == null) {
+                mFlagsArray = new int[INITIAL_SIZE];
+            }
+            final int requiredIndexArraySize = mCount + 1;
+            if (mKeys.length <= requiredIndexArraySize) {
+                final int[] newArray = new int[requiredIndexArraySize * 2];
+                System.arraycopy(mKeys, 0, newArray, 0, mCount);
+                mKeys = newArray;
+            }
+            final int requiredCoordinatesArraySize = (mCount + 1) * 4;
+            if (mCoordinates.length <= requiredCoordinatesArraySize) {
+                final float[] newArray = new float[requiredCoordinatesArraySize * 2];
+                System.arraycopy(mCoordinates, 0, newArray, 0, mCount * 4);
+                mCoordinates = newArray;
+            }
+            final int requiredFlagsArraySize = requiredIndexArraySize;
+            if (mFlagsArray.length <= requiredFlagsArraySize) {
+                final int[] newArray = new int[requiredFlagsArraySize * 2];
+                System.arraycopy(mFlagsArray, 0, newArray, 0, mCount);
+                mFlagsArray = newArray;
+            }
+        }
+
+        /**
+         * Puts the rectangle with an integer key.
+         * @param key the key to be associated with the rectangle. It must be greater than all
+         * existing keys that have been previously specified.
+         * @param left left of the rectangle.
+         * @param top top of the rectangle.
+         * @param right right of the rectangle.
+         * @param bottom bottom of the rectangle.
+         * @param flags an arbitrary integer value to be associated with this rectangle.
+         * @return the receiver object itself for chaining method calls.
+         * @throws IllegalArgumentException If the index is not greater than all of existing keys.
+         */
+        public SparseRectFArrayBuilder append(final int key,
+                final float left, final float top, final float right, final float bottom,
+                final int flags) {
+            checkIndex(key);
+            ensureBufferSize();
+            final int baseCoordinatesIndex = mCount * 4;
+            mCoordinates[baseCoordinatesIndex + 0] = left;
+            mCoordinates[baseCoordinatesIndex + 1] = top;
+            mCoordinates[baseCoordinatesIndex + 2] = right;
+            mCoordinates[baseCoordinatesIndex + 3] = bottom;
+            final int flagsIndex = mCount;
+            mFlagsArray[flagsIndex] = flags;
+            mKeys[mCount] = key;
+            ++mCount;
+            return this;
+        }
+        private int mCount = 0;
+        private int[] mKeys = null;
+        private float[] mCoordinates = null;
+        private int[] mFlagsArray = null;
+        private static int INITIAL_SIZE = 16;
+
+        public boolean isEmpty() {
+            return mCount <= 0;
+        }
+
+        /**
+         * @return {@link SparseRectFArray} using parameters in this {@link SparseRectFArray}.
+         */
+        public SparseRectFArray build() {
+            return new SparseRectFArray(this);
+        }
+
+        public void reset() {
+            if (mCount == 0) {
+                mKeys = null;
+                mCoordinates = null;
+                mFlagsArray = null;
+            }
+            mCount = 0;
+        }
+    }
+
+    private SparseRectFArray(final SparseRectFArrayBuilder builder) {
+        if (builder.mCount == 0) {
+            mKeys = null;
+            mCoordinates = null;
+            mFlagsArray = null;
+        } else {
+            mKeys = new int[builder.mCount];
+            mCoordinates = new float[builder.mCount * 4];
+            mFlagsArray = new int[builder.mCount];
+            System.arraycopy(builder.mKeys, 0, mKeys, 0, builder.mCount);
+            System.arraycopy(builder.mCoordinates, 0, mCoordinates, 0, builder.mCount * 4);
+            System.arraycopy(builder.mFlagsArray, 0, mFlagsArray, 0, builder.mCount);
+        }
+    }
+
+    public RectF get(final int index) {
+        if (mKeys == null) {
+            return null;
+        }
+        if (index < 0) {
+            return null;
+        }
+        final int arrayIndex = Arrays.binarySearch(mKeys, index);
+        if (arrayIndex < 0) {
+            return null;
+        }
+        final int baseCoordIndex = arrayIndex * 4;
+        return new RectF(mCoordinates[baseCoordIndex],
+                mCoordinates[baseCoordIndex + 1],
+                mCoordinates[baseCoordIndex + 2],
+                mCoordinates[baseCoordIndex + 3]);
+    }
+
+    public int getFlags(final int index, final int valueIfKeyNotFound) {
+        if (mKeys == null) {
+            return valueIfKeyNotFound;
+        }
+        if (index < 0) {
+            return valueIfKeyNotFound;
+        }
+        final int arrayIndex = Arrays.binarySearch(mKeys, index);
+        if (arrayIndex < 0) {
+            return valueIfKeyNotFound;
+        }
+        return mFlagsArray[arrayIndex];
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final @android.annotation.NonNull Parcelable.Creator<SparseRectFArray> CREATOR =
+            new Parcelable.Creator<SparseRectFArray>() {
+                @Override
+                public SparseRectFArray createFromParcel(Parcel source) {
+                    return new SparseRectFArray(source);
+                }
+                @Override
+                public SparseRectFArray[] newArray(int size) {
+                    return new SparseRectFArray[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
+
diff --git a/android/view/inputmethod/SurroundingText.java b/android/view/inputmethod/SurroundingText.java
new file mode 100644
index 0000000..c85a18a
--- /dev/null
+++ b/android/view/inputmethod/SurroundingText.java
@@ -0,0 +1,184 @@
+/*
+ * 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.view.inputmethod;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Information about the surrounding text around the cursor for use by an input method.
+ *
+ * <p>This contains information about the text and the selection relative to the text. </p>
+ */
+public final class SurroundingText implements Parcelable {
+    /**
+     * The surrounding text around the cursor.
+     */
+    @NonNull
+    private final CharSequence mText;
+
+    /**
+     * The text offset of the start of the selection in the surrounding text.
+     *
+     * <p>This needs to be the position relative to the {@link #mText} instead of the real position
+     * in the editor.</p>
+     */
+    @IntRange(from = 0)
+    private final int mSelectionStart;
+
+    /**
+     * The text offset of the end of the selection in the surrounding text.
+     *
+     * <p>This needs to be the position relative to the {@link #mText} instead of the real position
+     * in the editor.</p>
+     */
+    @IntRange(from = 0)
+    private final int mSelectionEnd;
+
+    /**
+     * The text offset between the start of the editor's text and the start of the surrounding text.
+     *
+     * <p>-1 indicates the offset information is unknown.</p>
+     */
+    @IntRange(from = -1)
+    private final int mOffset;
+
+    /**
+     * Constructor.
+     *
+     * @param text The surrounding text.
+     * @param selectionStart The text offset of the start of the selection in the surrounding text.
+     *                       Reversed selection is allowed.
+     * @param selectionEnd The text offset of the end of the selection in the surrounding text.
+     *                     Reversed selection is allowed.
+     * @param offset The text offset between the start of the editor's text and the start of the
+     *               surrounding text. -1 indicates the offset is unknown.
+     */
+    public SurroundingText(@NonNull final CharSequence text,
+            @IntRange(from = 0) int selectionStart, @IntRange(from = 0) int selectionEnd,
+            @IntRange(from = -1) int offset) {
+        mText = copyWithParcelableSpans(text);
+        mSelectionStart = selectionStart;
+        mSelectionEnd = selectionEnd;
+        mOffset = offset;
+    }
+
+    /**
+     * Returns the surrounding text around the cursor.
+     */
+    @NonNull
+    public CharSequence getText() {
+        return mText;
+    }
+
+    /**
+     * Returns the text offset of the start of the selection in the surrounding text.
+     *
+     * <p>A selection is the current range of the text that is selected by the user, or the current
+     * position of the cursor. A cursor is a selection where the start and end are at the same
+     * offset.<p>
+     */
+    @IntRange(from = 0)
+    public int getSelectionStart() {
+        return mSelectionStart;
+    }
+
+    /**
+     * Returns the text offset of the end of the selection in the surrounding text.
+     *
+     * <p>A selection is the current range of the text that is selected by the user, or the current
+     * position of the cursor. A cursor is a selection where the start and end are at the same
+     * offset.<p>
+     */
+    @IntRange(from = 0)
+    public int getSelectionEnd() {
+        return mSelectionEnd;
+    }
+
+    /**
+     * Returns text offset between the start of the editor's text and the start of the surrounding
+     * text.
+     *
+     * <p>-1 indicates the offset information is unknown.</p>
+     */
+    @IntRange(from = -1)
+    public int getOffset() {
+        return mOffset;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        TextUtils.writeToParcel(mText, out, flags);
+        out.writeInt(mSelectionStart);
+        out.writeInt(mSelectionEnd);
+        out.writeInt(mOffset);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<SurroundingText> CREATOR =
+            new Parcelable.Creator<SurroundingText>() {
+                @NonNull
+                public SurroundingText createFromParcel(Parcel in) {
+                    final CharSequence text =
+                            TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+                    final int selectionHead = in.readInt();
+                    final int selectionEnd = in.readInt();
+                    final int offset = in.readInt();
+                    return new SurroundingText(
+                            text == null ? "" : text,  selectionHead, selectionEnd, offset);
+                }
+
+                @NonNull
+                public SurroundingText[] newArray(int size) {
+                    return new SurroundingText[size];
+                }
+            };
+
+    /**
+     * Create a copy of the given {@link CharSequence} object, with completely copy
+     * {@link ParcelableSpan} instances.
+     *
+     * @param source the original {@link CharSequence} to be copied.
+     * @return the copied {@link CharSequence}. {@code null} if {@code source} is {@code null}.
+     */
+    @Nullable
+    private static CharSequence copyWithParcelableSpans(@Nullable CharSequence source) {
+        if (source == null) {
+            return null;
+        }
+        Parcel parcel = null;
+        try {
+            parcel = Parcel.obtain();
+            TextUtils.writeToParcel(source, parcel, /* parcelableFlags= */ 0);
+            parcel.setDataPosition(0);
+            return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+        } finally {
+            if (parcel != null) {
+                parcel.recycle();
+            }
+        }
+    }
+}
diff --git a/android/view/inspector/InspectableProperty.java b/android/view/inspector/InspectableProperty.java
new file mode 100644
index 0000000..03f1ec5
--- /dev/null
+++ b/android/view/inspector/InspectableProperty.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inspector;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.TestApi;
+import android.content.res.Resources;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a getter of a property on an inspectable node.
+ *
+ * This annotation is inherited by default. If a child class doesn't add it to a getter, but a
+ * parent class does, the property will be inspected, even if the child overrides the definition
+ * of the getter. If a child class defines a property of the same name of a property on the parent
+ * but on a different getter, the inspector will use the child's getter when inspecting instances
+ * of the child, and the parent's otherwise.
+ *
+ * @see InspectionCompanion#mapProperties(PropertyMapper)
+ * @see InspectionCompanion#readProperties(Object, PropertyReader)
+ * @hide
+ */
+@Target({METHOD, FIELD})
+@Retention(SOURCE)
+@TestApi
+public @interface InspectableProperty {
+    /**
+     * The name of the property.
+     *
+     * If left empty (the default), the property name will be inferred from the name of the getter
+     * method.
+     *
+     * @return The name of the property.
+     */
+    String name() default "";
+
+    /**
+     * If the property is inflated from XML, the resource ID of its XML attribute.
+     *
+     * If left as {ID_NULL}, and {@link #hasAttributeId()} is true, the attribute ID will be
+     * inferred from {@link #name()}.
+     *
+     * @return The attribute ID of the property or {@link Resources#ID_NULL}
+     */
+    int attributeId() default Resources.ID_NULL;
+
+    /**
+     * If this property has an attribute ID.
+     *
+     * Set to false if the annotated property does not have an attribute ID, that is, it is not
+     * inflated from an XML attribute. This will prevent the automatic inference of the attribute
+     * ID if {@link #attributeId()} is set to {@link Resources#ID_NULL}.
+     *
+     * @return Whether to infer an attribute ID if not supplied
+     */
+    boolean hasAttributeId() default true;
+
+    /**
+     * Specify how to interpret a value type packed into a primitive integer.
+     *
+     * @return A {@link ValueType}
+     */
+    ValueType valueType() default ValueType.INFERRED;
+
+    /**
+     * For enumerations packed into primitive {int} properties, map the values to string names.
+     *
+     * Note that {@link #enumMapping()} cannot be used simultaneously with {@link #flagMapping()}.
+     *
+     * @return An array of {@link EnumEntry}, empty if not applicable
+     * @see android.annotation.IntDef
+     */
+    EnumEntry[] enumMapping() default {};
+
+    /**
+     * For flags packed into primitive {int} properties, model the string names of the flags.
+     *
+     * Note that {@link #flagMapping()} cannot be used simultaneously with {@link #enumMapping()}.
+     *
+     * @return An array of {@link FlagEntry}, empty if not applicable
+     * @see android.annotation.IntDef
+     * @see IntFlagMapping
+     */
+    FlagEntry[] flagMapping() default {};
+
+
+    /**
+     * One entry in an enumeration packed into a primitive {int}.
+     *
+     * @see IntEnumMapping
+     * @hide
+     */
+    @Target({TYPE})
+    @Retention(SOURCE)
+    @TestApi
+    @interface EnumEntry {
+        /**
+         * The string name of this enumeration value.
+         *
+         * @return A string name
+         */
+        String name();
+
+        /**
+         * The integer value of this enumeration value.
+         *
+         * @return An integer value
+         */
+        int value();
+    }
+
+    /**
+     * One flag value of many that may be packed into a primitive {int}.
+     *
+     * @see IntFlagMapping
+     * @hide
+     */
+    @Target({TYPE})
+    @Retention(SOURCE)
+    @TestApi
+    @interface FlagEntry {
+        /**
+         * The string name of this flag.
+         *
+         * @return A string name
+         */
+        String name();
+
+        /**
+         * A target value that the property's value must equal after masking.
+         *
+         * If a mask is not supplied (i.e., {@link #mask()} is 0), the target will be reused as the
+         * mask. This handles the common case where no flags mutually exclude each other.
+         *
+         * @return The target value to compare against
+         */
+        int target();
+
+        /**
+         * A mask that the property will be bitwise anded with before comparing to the target.
+         *
+         * If set to 0 (the default), the value of {@link #target()} will be used as a mask. Zero
+         * was chosen as the default since bitwise and with zero is always zero.
+         *
+         * @return A mask, or 0 to use the target as a mask
+         */
+        int mask() default 0;
+    }
+
+    /**
+     * The type of value packed into a primitive {int}.
+     *
+     * @hide
+     */
+    @TestApi
+    enum ValueType {
+        /**
+         * No special handling, property is considered to be a numeric value.
+         *
+         * @hide
+         */
+        @TestApi
+        NONE,
+
+        /**
+         * The default the annotation processor infers the value type from context.
+         *
+         * @hide
+         */
+        @TestApi
+        INFERRED,
+
+        /**
+         * Value packs an enumeration.
+         *
+         * This is inferred if {@link #enumMapping()} is specified.
+         *
+         * @see EnumEntry
+         * @hide
+         */
+        @TestApi
+        INT_ENUM,
+
+        /**
+         * Value packs flags, of which many may be enabled at once.
+         *
+         * This is inferred if {@link #flagMapping()} is specified.
+         *
+         * @see FlagEntry
+         * @hide
+         */
+        @TestApi
+        INT_FLAG,
+
+        /**
+         * Value packs color information.
+         *
+         * This is inferred from {@link android.annotation.ColorInt}, or
+         * {@link android.annotation.ColorLong} on the getter method.
+         *
+         * @see android.graphics.Color
+         * @hide
+         */
+        @TestApi
+        COLOR,
+
+        /**
+         * Value packs gravity information.
+         *
+         * This type is not inferred, and is non-trivial to represent using {@link FlagEntry}.
+         *
+         * @see android.view.Gravity
+         * @hide
+         */
+        @TestApi
+        GRAVITY,
+
+        /**
+         * Value is a resource ID
+         *
+         * This type is inferred from the presence of a resource ID annotation such as
+         * {@link android.annotation.AnyRes}.
+         *
+         * @hide
+         */
+        @TestApi
+        RESOURCE_ID
+    }
+}
diff --git a/android/view/inspector/InspectionCompanion.java b/android/view/inspector/InspectionCompanion.java
new file mode 100644
index 0000000..a633a58
--- /dev/null
+++ b/android/view/inspector/InspectionCompanion.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inspector;
+
+import android.annotation.NonNull;
+
+/**
+ * An interface for companion objects used to inspect views.
+ *
+ * Inspection companions only need to handle the properties and node name of the specific class
+ * they are defined for, not anything from a parent class. At runtime, the inspector instantiates
+ * one instance of each inspection companion, and handles visiting them in the correct inheritance
+ * order for each type it inspects.
+ *
+ * Properties are read from the top of the type tree to the bottom, so that classes that override
+ * a property in their parent class can overwrite it in the reader. In general, properties will
+ * cleanly inherit through their getters, and the inspector runtime will read the properties of a
+ * parent class via the parent's inspection companion, and the child companion will only read
+ * properties added or changed since the parent was defined.
+ *
+ * @param <T> The type of inspectable this is the companion to
+ */
+public interface InspectionCompanion<T> {
+    /**
+     * Map the string names of the properties this companion knows about to integer IDs.
+     *
+     * Each companion is responsible for storing the integer IDs of all its properties. This is the
+     * only method that is allowed to modify the stored IDs.
+     *
+     * Calling {@link #readProperties(T, PropertyReader)} before calling this results in
+     * undefined behavior.
+     *
+     * @param propertyMapper A {@link PropertyMapper} maps string names to IDs.
+     */
+    void mapProperties(@NonNull PropertyMapper propertyMapper);
+
+    /**
+     * Read the values of an instance of this companion's type into a {@link PropertyReader}.
+     *
+     * This method needs to return the property IDs stored by
+     * {@link #mapProperties(PropertyMapper)}. Implementations should track if their properties
+     * have been mapped and throw a {@link UninitializedPropertyMapException} if this method is
+     * called before {mapProperties}.
+     *
+     * @param inspectable A object of type {T} to read the properties of.
+     * @param propertyReader An object which receives the property IDs and values.
+     */
+    void readProperties(@NonNull T inspectable, @NonNull PropertyReader propertyReader);
+
+    /**
+     * Thrown by {@link #readProperties(Object, PropertyReader)} if called before
+     * {@link #mapProperties(PropertyMapper)}.
+     */
+    class UninitializedPropertyMapException extends RuntimeException {
+        public UninitializedPropertyMapException() {
+            super("Unable to read properties of an inspectable before mapping their IDs.");
+        }
+    }
+}
diff --git a/android/view/inspector/InspectionCompanionProvider.java b/android/view/inspector/InspectionCompanionProvider.java
new file mode 100644
index 0000000..c08f49c
--- /dev/null
+++ b/android/view/inspector/InspectionCompanionProvider.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 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.view.inspector;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * An interface for services that can provide inspection companions for a class.
+ */
+public interface InspectionCompanionProvider {
+    /**
+     * Provide an {@link InspectionCompanion} for the supplied class.
+     *
+     * Implementing classes must not cache companion instances, and should instantiate a new one
+     * for each request.
+     *
+     * @param cls A {@link Class} representing the inspectable type
+     * @param <T> The type to find the companion for
+     * @return The inspection companion for the supplied type
+     */
+    @Nullable
+    <T> InspectionCompanion<T> provide(@NonNull Class<T> cls);
+}
diff --git a/android/view/inspector/IntFlagMapping.java b/android/view/inspector/IntFlagMapping.java
new file mode 100644
index 0000000..f501329
--- /dev/null
+++ b/android/view/inspector/IntFlagMapping.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inspector;
+
+import android.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Maps the values of an {@code int} property to sets of string for properties that encode flags.
+ *
+ * An {@link InspectionCompanion} may provide an instance of this class to a {@link PropertyMapper}
+ * for flag values packed into primitive {@code int} properties.
+ *
+ * Each flag has a mask and a target value, for non-exclusive flags, the target can also be used as
+ * the mask. A given integer value is compared against each flag to find what flags are active for
+ * it by bitwise anding it with the mask and comparing the result against the target, that is,
+ * {@code (value & mask) == target}.
+ *
+ * @see PropertyMapper#mapIntFlag(String, int, java.util.function.IntFunction)
+ */
+public final class IntFlagMapping {
+    private final List<Flag> mFlags = new ArrayList<>();
+
+    /**
+     * Get a set of the names of enabled flags for a given property value.
+     *
+     * @param value The value of the property
+     * @return The names of the enabled flags, empty if no flags enabled
+     */
+    @NonNull
+    public Set<String> get(int value) {
+        final Set<String> enabledFlagNames = new HashSet<>(mFlags.size());
+
+        for (Flag flag : mFlags) {
+            if (flag.isEnabledFor(value)) {
+                enabledFlagNames.add(flag.mName);
+            }
+        }
+
+        return Collections.unmodifiableSet(enabledFlagNames);
+    }
+
+    /**
+     * Add a mutually exclusive flag to the map.
+     *
+     * @param mask The bit mask to compare to and with a value
+     * @param target The target value to compare the masked value with
+     * @param name The name of the flag to include if enabled
+     */
+    public void add(int mask, int target, @NonNull String name) {
+        mFlags.add(new Flag(mask, target, name));
+    }
+
+    /**
+     * Inner class that holds the name, mask, and target value of a flag
+     */
+    private static final class Flag {
+        @NonNull private final String mName;
+        private final int mTarget;
+        private final int mMask;
+
+        private Flag(int mask, int target, @NonNull String name) {
+            mTarget = target;
+            mMask = mask;
+            mName = Objects.requireNonNull(name);
+        }
+
+        /**
+         * Compare the supplied property value against the mask and target.
+         *
+         * @param value The value to check
+         * @return True if this flag is enabled
+         */
+        private boolean isEnabledFor(int value) {
+            return (value & mMask) == mTarget;
+        }
+    }
+}
diff --git a/android/view/inspector/PropertyMapper.java b/android/view/inspector/PropertyMapper.java
new file mode 100644
index 0000000..cbc690e
--- /dev/null
+++ b/android/view/inspector/PropertyMapper.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inspector;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+
+import java.util.Set;
+import java.util.function.IntFunction;
+
+/**
+ * An interface for mapping the string names of inspectable properties to integer identifiers.
+ *
+ * This interface is consumed by {@link InspectionCompanion#mapProperties(PropertyMapper)}.
+ *
+ * Mapping properties to IDs enables quick comparisons against shadow copies of inspectable
+ * objects without performing a large number of string comparisons.
+ *
+ * @see InspectionCompanion#mapProperties(PropertyMapper)
+ */
+public interface PropertyMapper {
+    /**
+     * Map a string name to an integer ID for a primitive boolean property.
+     *
+     * @param name The name of the property
+     * @param attributeId If the property is from an XML attribute, the resource ID of the property
+     * @return An integer ID for the property
+     * @throws PropertyConflictException If the property name is already mapped as another type.
+     */
+    int mapBoolean(@NonNull String name, @AttrRes int attributeId);
+
+    /**
+     * Map a string name to an integer ID for a primitive byte property.
+     *
+     * @param name The name of the property
+     * @param attributeId If the property is from an XML attribute, the resource ID of the property
+     * @return An integer ID for the property
+     * @throws PropertyConflictException If the property name is already mapped as another type.
+     */
+    int mapByte(@NonNull String name, @AttrRes int attributeId);
+
+    /**
+     * Map a string name to an integer ID for a primitive char property.
+     *
+     * @param name The name of the property
+     * @param attributeId If the property is from an XML attribute, the resource ID of the property
+     * @return An integer ID for the property
+     * @throws PropertyConflictException If the property name is already mapped as another type.
+     */
+    int mapChar(@NonNull String name, @AttrRes int attributeId);
+
+    /**
+     * Map a string name to an integer ID for a primitive double property.
+     *
+     * @param name The name of the property
+     * @param attributeId If the property is from an XML attribute, the resource ID of the property
+     * @return An integer ID for the property
+     * @throws PropertyConflictException If the property name is already mapped as another type.
+     */
+    int mapDouble(@NonNull String name, @AttrRes int attributeId);
+
+    /**
+     * Map a string name to an integer ID for a primitive float property.
+     *
+     * @param name The name of the property
+     * @param attributeId If the property is from an XML attribute, the resource ID of the property
+     * @return An integer ID for the property
+     * @throws PropertyConflictException If the property name is already mapped as another type.
+     */
+    int mapFloat(@NonNull String name, @AttrRes int attributeId);
+
+    /**
+     * Map a string name to an integer ID for a primitive int property.
+     *
+     * @param name The name of the property
+     * @param attributeId If the property is from an XML attribute, the resource ID of the property
+     * @return An integer ID for the property
+     * @throws PropertyConflictException If the property name is already mapped as another type.
+     */
+    int mapInt(@NonNull String name, @AttrRes int attributeId);
+
+    /**
+     * Map a string name to an integer ID for a primitive long property.
+     *
+     * @param name The name of the property
+     * @param attributeId If the property is from an XML attribute, the resource ID of the property
+     * @return An integer ID for the property
+     * @throws PropertyConflictException If the property name is already mapped as another type.
+     */
+    int mapLong(@NonNull String name, @AttrRes int attributeId);
+
+    /**
+     * Map a string name to an integer ID for a primitive short property.
+     *
+     * @param name The name of the property
+     * @param attributeId If the property is from an XML attribute, the resource ID of the property
+     * @return An integer ID for the property
+     * @throws PropertyConflictException If the property name is already mapped as another type.
+     */
+    int mapShort(@NonNull String name, @AttrRes int attributeId);
+
+    /**
+     * Map a string name to an integer ID for an object property.
+     *
+     * @param name The name of the property
+     * @param attributeId If the property is from an XML attribute, the resource ID of the property
+     * @return An integer ID for the property
+     * @throws PropertyConflictException If the property name is already mapped as another type.
+     */
+    int mapObject(@NonNull String name, @AttrRes int attributeId);
+
+    /**
+     * Map a string name to an integer ID for a color property.
+     *
+     * @param name The name of the property
+     * @param attributeId If the property is from an XML attribute, the resource ID of the property
+     * @return An integer ID for the property
+     * @throws PropertyConflictException If the property name is already mapped as another type.
+     * @see android.graphics.Color
+     */
+    int mapColor(@NonNull String name, @AttrRes int attributeId);
+
+    /**
+     * Map a string name to an integer ID for a gravity property.
+     *
+     * @param name The name of the property
+     * @param attributeId If the property is from an XML attribute, the resource ID of the property
+     * @return An integer ID for the property
+     * @throws PropertyConflictException If the property name is already mapped as another type.
+     * @see android.view.Gravity
+     */
+    int mapGravity(@NonNull String name, @AttrRes int attributeId);
+
+    /**
+     * Map a string name to an integer ID for an enumeration packed into an int property.
+     *
+     * @param name The name of the property
+     * @param attributeId If the property is from an XML attribute, the resource ID of the property
+     * @param mapping A mapping from int to String
+     * @return An integer ID for the property
+     * @throws PropertyConflictException If the property name is already mapped as another type.
+     */
+    int mapIntEnum(
+            @NonNull String name,
+            @AttrRes int attributeId,
+            @NonNull IntFunction<String> mapping);
+
+    /**
+     * Map a string name to an integer ID for an attribute that contains resource IDs.
+     *
+     * @param name The name of the property
+     * @param attributeId If the property is from an XML attribute, the resource ID of the property
+     * @return An integer ID for the property
+     * @throws PropertyConflictException If the property name is already mapped as another type.
+     */
+    int mapResourceId(@NonNull String name, @AttrRes int attributeId);
+
+    /**
+     * Map a string name to an integer ID for a flag set packed into an int property.
+     *
+     * @param name The name of the property
+     * @param attributeId If the property is from an XML attribute, the resource ID of the property
+     * @param mapping A mapping from int to a set of strings
+     * @return An integer ID for the property
+     * @throws PropertyConflictException If the property name is already mapped as another type.
+     */
+    int mapIntFlag(
+            @NonNull String name,
+            @AttrRes int attributeId,
+            @NonNull IntFunction<Set<String>> mapping);
+    /**
+     * Thrown from a map method if a property name is already mapped as different type.
+     */
+    class PropertyConflictException extends RuntimeException {
+        public PropertyConflictException(
+                @NonNull String name,
+                @NonNull String newPropertyType,
+                @NonNull String existingPropertyType) {
+            super(String.format(
+                    "Attempted to map property \"%s\" as type %s, but it is already mapped as %s.",
+                    name,
+                    newPropertyType,
+                    existingPropertyType
+            ));
+        }
+    }
+}
diff --git a/android/view/inspector/PropertyReader.java b/android/view/inspector/PropertyReader.java
new file mode 100644
index 0000000..5be0e3f
--- /dev/null
+++ b/android/view/inspector/PropertyReader.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inspector;
+
+import android.annotation.AnyRes;
+import android.annotation.ColorInt;
+import android.annotation.ColorLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Color;
+
+/**
+ * An interface for reading the properties of an inspectable object.
+ *
+ * {@code PropertyReader} is defined as an interface that will be called by
+ * {@link InspectionCompanion#readProperties(Object, PropertyReader)}. This approach allows a
+ * client inspector to read the values of primitive properties without the overhead of
+ * instantiating a class to hold the property values for each inspection pass. If an inspectable
+ * remains unchanged between reading passes, it should be possible for a {@code PropertyReader} to
+ * avoid new allocations for subsequent reading passes.
+ *
+ * It has separate methods for all primitive types to avoid autoboxing overhead if a concrete
+ * implementation is able to work with primitives. Implementations should be prepared to accept
+ * {null} as the value of {@link PropertyReader#readObject(int, Object)}.
+ *
+ * @see InspectionCompanion#readProperties(Object, PropertyReader)
+ */
+public interface PropertyReader {
+    /**
+     * Read a primitive boolean property.
+     *
+     * @param id Identifier of the property from a {@link PropertyMapper}
+     * @param value Value of the property
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as a {@code boolean}
+     */
+    void readBoolean(int id, boolean value);
+
+    /**
+     * Read a primitive byte property.
+     *
+     * @param id Identifier of the property from a {@link PropertyMapper}
+     * @param value Value of the property
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as a {@code byte}
+     */
+    void readByte(int id, byte value);
+
+    /**
+     * Read a primitive character property.
+     *
+     * @param id Identifier of the property from a {@link PropertyMapper}
+     * @param value Value of the property
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as a {@code char}
+     */
+    void readChar(int id, char value);
+
+    /**
+     * Read a read a primitive double property.
+     *
+     * @param id Identifier of the property from a {@link PropertyMapper}
+     * @param value Value of the property
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as a {@code double}
+     */
+    void readDouble(int id, double value);
+
+    /**
+     * Read a primitive float property.
+     *
+     * @param id Identifier of the property from a {@link PropertyMapper}
+     * @param value Value of the property
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as a {@code float}
+     */
+    void readFloat(int id, float value);
+
+    /**
+     * Read a primitive integer property.
+     *
+     * @param id Identifier of the property from a {@link PropertyMapper}
+     * @param value Value of the property
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as an {@code int}
+     */
+    void readInt(int id, int value);
+
+    /**
+     * Read a primitive long property.
+     *
+     * @param id Identifier of the property from a {@link PropertyMapper}
+     * @param value Value of the property
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as a {@code long}
+     */
+    void readLong(int id, long value);
+
+    /**
+     * Read a primitive short property.
+     *
+     * @param id Identifier of the property from a {@link PropertyMapper}
+     * @param value Value of the property
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as a {@code short}
+     */
+    void readShort(int id, short value);
+
+    /**
+     * Read any object as a property.
+     *
+     * If value is null, the property is marked as empty.
+     *
+     * @param id Identifier of the property from a {@link PropertyMapper}
+     * @param value Value of the property
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as an object
+     */
+    void readObject(int id, @Nullable Object value);
+
+    /**
+     * Read a color packed into a {@link ColorInt} as a property.
+     *
+     * @param id Identifier of the property from a {@link PropertyMapper}
+     * @param value Value of the property
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as a color
+     */
+    void readColor(int id, @ColorInt int value);
+
+    /**
+     * Read a color packed into a {@code ColorLong} as a property.
+     *
+     * @param id Identifier of the property from a {@link PropertyMapper}
+     * @param value Value of the property packed as a {@code ColorLong}. See the
+     *              {@link Color} class for details of the packing.
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as a color
+     */
+    void readColor(int id, @ColorLong long value);
+
+    /**
+     * Read a {@link Color} object as a property.
+     *
+     * @param id Identifier of the property from a {@link PropertyMapper}
+     * @param value Value of the property
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as a color
+     */
+    void readColor(int id, @Nullable Color value);
+
+    /**
+     * Read {@link android.view.Gravity} packed into an primitive {@code int}.
+     *
+     * @param id Identifier of the property from a {@link PropertyMapper}
+     * @param value Value of the property
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as a gravity property
+     */
+    void readGravity(int id, int value);
+
+    /**
+     * Read an enumeration packed into a primitive {@code int}.
+     *
+     * @param id Identifier of the property from a {@link PropertyMapper}
+     * @param value Value of the property
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as an object
+     */
+    void readIntEnum(int id, int value);
+
+    /**
+     * Read a flag packed into a primitive {@code int}.
+     *
+     * @param id Identifier of the property from a {@link PropertyMapper}
+     * @param value Value of the property
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as an object
+     */
+    void readIntFlag(int id, int value);
+
+    /**
+     * Read an integer that contains a resource ID.
+     *
+     * @param id Identifier of the property from a {@link PropertyMapper}
+     * @param value Value of the property
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as a resource ID.
+     */
+    void readResourceId(int id, @AnyRes int value);
+
+    /**
+     * Thrown if a client calls a typed read method for a property of a different type.
+     */
+    class PropertyTypeMismatchException extends RuntimeException {
+        public PropertyTypeMismatchException(
+                int id,
+                @NonNull String expectedPropertyType,
+                @NonNull String actualPropertyType,
+                @Nullable String propertyName) {
+            super(formatMessage(id, expectedPropertyType, actualPropertyType, propertyName));
+        }
+
+        public PropertyTypeMismatchException(
+                int id,
+                @NonNull String expectedPropertyType,
+                @NonNull String actualPropertyType) {
+            super(formatMessage(id, expectedPropertyType, actualPropertyType, null));
+        }
+
+        private static @NonNull String formatMessage(
+                int id,
+                @NonNull String expectedPropertyType,
+                @NonNull String actualPropertyType,
+                @Nullable String propertyName) {
+
+            if (propertyName == null) {
+                return String.format(
+                        "Attempted to read property with ID 0x%08X as type %s, "
+                            + "but the ID is of type %s.",
+                        id,
+                        expectedPropertyType,
+                        actualPropertyType
+                );
+            } else {
+                return String.format(
+                        "Attempted to read property \"%s\" with ID 0x%08X as type %s, "
+                            + "but the ID is of type %s.",
+                        propertyName,
+                        id,
+                        expectedPropertyType,
+                        actualPropertyType
+                );
+            }
+        }
+    }
+}
diff --git a/android/view/inspector/StaticInspectionCompanionProvider.java b/android/view/inspector/StaticInspectionCompanionProvider.java
new file mode 100644
index 0000000..903fc13
--- /dev/null
+++ b/android/view/inspector/StaticInspectionCompanionProvider.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 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.view.inspector;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * An inspection companion provider that finds companions as inner classes or generated code.
+ */
+public class StaticInspectionCompanionProvider implements InspectionCompanionProvider {
+    /**
+     * The suffix used for the generated classes and inner classes
+     */
+    private static final String COMPANION_SUFFIX = "$InspectionCompanion";
+
+    @Override
+    @Nullable
+    @SuppressWarnings("unchecked")
+    public <T> InspectionCompanion<T> provide(@NonNull Class<T> cls) {
+        final String companionName = cls.getName() + COMPANION_SUFFIX;
+
+        try {
+            final Class<InspectionCompanion<T>> companionClass =
+                    (Class<InspectionCompanion<T>>) cls.getClassLoader().loadClass(companionName);
+
+            if (InspectionCompanion.class.isAssignableFrom(companionClass)) {
+                return companionClass.newInstance();
+            } else {
+                return null;
+            }
+        } catch (ClassNotFoundException e) {
+            return null;
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        } catch (InstantiationException e) {
+            Throwable cause = e.getCause();
+            if (cause instanceof RuntimeException) throw (RuntimeException) cause;
+            if (cause instanceof Error) throw (Error) cause;
+            throw new RuntimeException(cause);
+        }
+    }
+}
diff --git a/android/view/inspector/WindowInspector.java b/android/view/inspector/WindowInspector.java
new file mode 100644
index 0000000..69d004e
--- /dev/null
+++ b/android/view/inspector/WindowInspector.java
@@ -0,0 +1,40 @@
+/*
+ * 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.view.inspector;
+
+import android.annotation.NonNull;
+import android.view.View;
+import android.view.WindowManagerGlobal;
+
+import java.util.List;
+
+/**
+ * Provides access to window inspection information.
+ */
+public final class WindowInspector {
+    private WindowInspector() {
+        // Non-instantiable.
+    }
+
+    /**
+     * @return the list of all window views attached to the current process
+     */
+    @NonNull
+    public static List<View> getGlobalWindowViews() {
+        return WindowManagerGlobal.getInstance().getWindowViews();
+    }
+}
diff --git a/android/view/math/Math3DHelper.java b/android/view/math/Math3DHelper.java
new file mode 100644
index 0000000..35a1ee1
--- /dev/null
+++ b/android/view/math/Math3DHelper.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.math;
+
+public class Math3DHelper {
+
+    private Math3DHelper() { }
+
+    public final static int min(int x1, int x2, int x3) {
+        return (x1 > x2) ? ((x2 > x3) ? x3 : x2) : ((x1 > x3) ? x3 : x1);
+    }
+
+    public final static int max(int x1, int x2, int x3) {
+        return (x1 < x2) ? ((x2 < x3) ? x3 : x2) : ((x1 < x3) ? x3 : x1);
+    }
+
+    /**
+     * @return Rect bound of flattened (ignoring z). LTRB
+     * @param dimension - 2D or 3D
+     */
+    public static float[] flatBound(float[] poly, int dimension) {
+        int polySize = poly.length/dimension;
+        float left = poly[0];
+        float right = poly[0];
+        float top = poly[1];
+        float bottom = poly[1];
+
+        for (int i = 0; i < polySize; i++) {
+            float x = poly[i * dimension + 0];
+            float y = poly[i * dimension + 1];
+
+            if (left > x) {
+                left = x;
+            } else if (right < x) {
+                right = x;
+            }
+
+            if (top > y) {
+                top = y;
+            } else if (bottom < y) {
+                bottom = y;
+            }
+        }
+        return new float[]{left, top, right, bottom};
+    }
+
+    /**
+     * Translate the polygon to x and y
+     * @param dimension in what dimension is polygon represented (supports 2 or 3D).
+     */
+    public static void translate(float[] poly, float translateX, float translateY, int dimension) {
+        int polySize = poly.length/dimension;
+
+        for (int i = 0; i < polySize; i++) {
+            poly[i * dimension + 0] += translateX;
+            poly[i * dimension + 1] += translateY;
+        }
+    }
+
+}
+
diff --git a/android/view/shadow/AmbientShadowConfig.java b/android/view/shadow/AmbientShadowConfig.java
new file mode 100644
index 0000000..97efd86
--- /dev/null
+++ b/android/view/shadow/AmbientShadowConfig.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.shadow;
+
+/**
+ * Model for ambient shadow rendering. Assumes light sources from centroid of the object.
+ */
+class AmbientShadowConfig {
+
+    private final float mEdgeScale;
+    private final float mShadowBoundRatio;
+    private final float mShadowStrength;
+
+    private final float[] mPolygon;
+    private float[] mLightSourcePosition;
+
+    private AmbientShadowConfig(Builder builder) {
+        mEdgeScale = builder.mEdgeScale;
+        mShadowBoundRatio = builder.mShadowBoundRatio;
+        mShadowStrength = builder.mShadowStrength;
+        mPolygon = builder.mPolygon;
+        mLightSourcePosition = builder.mLightSourcePosition;
+    }
+
+    /**
+     * Returns scales intensity of the edge of the shadow (opacity) [0-100]
+     */
+    public float getEdgeScale() {
+        return mEdgeScale;
+    }
+
+    /**
+     * Returns scales the area (in xy) of the shadow [0-1]
+     */
+    public float getShadowBoundRatio() {
+        return mShadowBoundRatio;
+    }
+
+    /**
+     * Returns scales the intensity of the entire shadow (opacity) [0-1]
+     */
+    public float getShadowStrength() {
+        return mShadowStrength;
+    }
+
+    /**
+     * Returns opaque polygon to cast shadow
+     */
+    public float[] getPolygon() {
+        return mPolygon;
+    }
+
+    /**
+     * Returns 2D position of the light source
+     */
+    public float[] getLightSourcePosition() {
+        return mLightSourcePosition;
+    }
+
+    public static class Builder {
+
+        private float mEdgeScale;
+        private float mShadowBoundRatio;
+        private float mShadowStrength;
+
+        private float[] mPolygon;
+        private float[] mLightSourcePosition;
+
+        public Builder setEdgeScale(float edgeScale) {
+            mEdgeScale = edgeScale;
+            return this;
+        }
+
+        public Builder setShadowBoundRatio(float shadowBoundRatio) {
+            mShadowBoundRatio = shadowBoundRatio;
+            return this;
+        }
+
+        public Builder setShadowStrength(float shadowStrength) {
+            mShadowStrength = shadowStrength;
+            return this;
+        }
+
+        public Builder setPolygon(float[] polygon) {
+            mPolygon = polygon;
+            return this;
+        }
+
+        public Builder setLightSourcePosition(float x, float y) {
+            mLightSourcePosition = new float[] { x, y };
+            return this;
+        }
+
+        public AmbientShadowConfig build() {
+            return new AmbientShadowConfig(this);
+        }
+    }
+}
diff --git a/android/view/shadow/AmbientShadowTriangulator.java b/android/view/shadow/AmbientShadowTriangulator.java
new file mode 100644
index 0000000..d768fa3
--- /dev/null
+++ b/android/view/shadow/AmbientShadowTriangulator.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.shadow;
+
+import com.android.ide.common.rendering.api.ILayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+
+/**
+ * Generates ambient shadow bitmap
+ */
+class AmbientShadowTriangulator {
+
+    private final AmbientShadowConfig mShadowConfig;
+    private final AmbientShadowVertexCalculator mCalculator;
+
+    private boolean mValid;
+
+    public AmbientShadowTriangulator(AmbientShadowConfig shadowConfig) {
+        mShadowConfig = shadowConfig;
+
+        mCalculator = new AmbientShadowVertexCalculator(mShadowConfig);
+    }
+
+    /**
+     * Populate vertices and fill the triangle buffers.
+     */
+    public void triangulate() {
+        try {
+            mCalculator.generateVertex();
+            mValid = true;
+        } catch (IndexOutOfBoundsException|ArithmeticException mathError) {
+            Bridge.getLog().warning(ILayoutLog.TAG_INFO,  "Arithmetic error while drawing " +
+                            "ambient shadow",
+                    null, mathError);
+        } catch (Exception ex) {
+            Bridge.getLog().warning(ILayoutLog.TAG_INFO,  "Error while drawing shadow",
+                    null, ex);
+        }
+    }
+
+    public boolean isValid() {
+        return mValid;
+    }
+
+    public float[] getVertices() { return mCalculator.getVertex(); }
+
+    public int[] getIndices() { return mCalculator.getIndex(); }
+
+    public float[] getColors() { return mCalculator.getColor(); }
+}
diff --git a/android/view/shadow/AmbientShadowVertexCalculator.java b/android/view/shadow/AmbientShadowVertexCalculator.java
new file mode 100644
index 0000000..5928f35
--- /dev/null
+++ b/android/view/shadow/AmbientShadowVertexCalculator.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.shadow;
+
+import android.view.math.Math3DHelper;
+
+/**
+ * Generates vertices, colours, and indices required for ambient shadow. Ambient shadows are
+ * assumed to be raycasted from the centroid of the polygon, and reaches upto a ratio based on
+ * the polygon's z-height.
+ */
+class AmbientShadowVertexCalculator {
+
+    private final float[] mVertex;
+    private final float[] mColor;
+    private final int[] mIndex;
+    private final AmbientShadowConfig mConfig;
+
+    public AmbientShadowVertexCalculator(AmbientShadowConfig config) {
+        mConfig = config;
+
+        int size = mConfig.getPolygon().length / 3;
+
+        mVertex = new float[size * 2 * 2];
+        mColor = new float[size * 2 * 4];
+        mIndex = new int[(size * 3 - 2) * 3];
+    }
+
+    /**
+     * Generates ambient shadow triangulation using configuration provided
+     */
+    public void generateVertex() {
+        float[] polygon = mConfig.getPolygon();
+        float cx = mConfig.getLightSourcePosition()[0];
+        float cy = mConfig.getLightSourcePosition()[1];
+
+        int polygonLength = polygon.length/3;
+
+        float opacity = .8f * (0.5f / (mConfig.getEdgeScale() / 10f));
+
+        int trShift = 0;
+        for (int i = 0; i < polygonLength; ++i, trShift += 6) {
+            int shift = i * 4;
+            int colorShift = i * 8;
+            int idxShift = i * 2;
+
+            float px = polygon[3 * i + 0];
+            float py = polygon[3 * i + 1];
+            mVertex[shift + 0] = px;
+            mVertex[shift + 1] = py;
+
+            // TODO: I do not really understand this but this matches the previous behavior.
+            // The weird bit is that for outlines with low elevation the ambient shadow is
+            // entirely drawn underneath the shadow caster. This is most probably incorrect
+            float h = polygon[3 * i + 2] * mConfig.getShadowBoundRatio();
+
+            mVertex[shift + 2] = cx + h * (px - cx);
+            mVertex[shift + 3] = cy + h * (py - cy);
+
+            mColor[colorShift + 3] = opacity;
+
+            mIndex[trShift + 0] = idxShift + 0;
+            mIndex[trShift + 1] = idxShift + 1;
+            mIndex[trShift + 2] = idxShift + 2;
+
+            mIndex[trShift + 3] = idxShift + 1;
+            mIndex[trShift + 4] = idxShift + 2;
+            mIndex[trShift + 5] = idxShift + 3;
+        }
+        // cycle back to the front
+        mIndex[trShift - 1] = 1;
+        mIndex[trShift - 2] = 0;
+        mIndex[trShift - 4] = 0;
+
+        // Filling the shadow right under the outline. Can we skip that?
+        for (int i = 1; i < polygonLength - 1; ++i, trShift += 3) {
+            mIndex[trShift + 0] = 0;
+            mIndex[trShift + 1] = 2 * i;
+            mIndex[trShift + 2] = 2 * (i+1);
+        }
+    }
+
+    public int[] getIndex() {
+        return mIndex;
+    }
+
+    /**
+     * @return list of vertices in 2d in format : {x1, y1, x2, y2 ...}
+     */
+    public float[] getVertex() {
+        return mVertex;
+    }
+
+    public float[] getColor() {
+        return mColor;
+    }
+}
diff --git a/android/view/shadow/HighQualityShadowPainter.java b/android/view/shadow/HighQualityShadowPainter.java
new file mode 100644
index 0000000..7b8873c
--- /dev/null
+++ b/android/view/shadow/HighQualityShadowPainter.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.shadow;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.util.DisplayMetrics;
+import android.view.ViewGroup;
+import android.view.math.Math3DHelper;
+
+import static android.view.shadow.ShadowConstants.MIN_ALPHA;
+import static android.view.shadow.ShadowConstants.SCALE_DOWN;
+
+public class HighQualityShadowPainter {
+    private static final float sRoundedGap = (float) (1.0 - Math.sqrt(2.0) / 2.0);
+
+    private HighQualityShadowPainter() { }
+
+    /**
+     * Draws simple Rect shadow
+     */
+    public static void paintRectShadow(ViewGroup parent, Outline outline, float elevation,
+            Canvas canvas, float alpha, float densityDpi) {
+
+        if (!validate(elevation, densityDpi)) {
+            return;
+        }
+
+        int width = parent.getWidth() / SCALE_DOWN;
+        int height = parent.getHeight() / SCALE_DOWN;
+
+        Rect rectOriginal = new Rect();
+        Rect rectScaled = new Rect();
+        if (!outline.getRect(rectScaled) || alpha < MIN_ALPHA) {
+            // If alpha below MIN_ALPHA it's invisible (based on manual test). Save some perf.
+            return;
+        }
+
+        outline.getRect(rectOriginal);
+
+        rectScaled.left /= SCALE_DOWN;
+        rectScaled.right /= SCALE_DOWN;
+        rectScaled.top /= SCALE_DOWN;
+        rectScaled.bottom /= SCALE_DOWN;
+        float radius = outline.getRadius() / SCALE_DOWN;
+
+        if (radius > rectScaled.width() || radius > rectScaled.height()) {
+            // Rounded edge generation fails if radius is bigger than drawing box.
+            return;
+        }
+
+        // ensure alpha doesn't go over 1
+        alpha = (alpha > 1.0f) ? 1.0f : alpha;
+        boolean isOpaque = outline.getAlpha() * alpha == 1.0f;
+        float[] poly = getPoly(rectScaled, elevation / SCALE_DOWN, radius);
+
+        AmbientShadowConfig ambientConfig = new AmbientShadowConfig.Builder()
+                .setPolygon(poly)
+                .setLightSourcePosition(
+                        (rectScaled.left + rectScaled.right) / 2.0f,
+                        (rectScaled.top + rectScaled.bottom) / 2.0f)
+                .setEdgeScale(ShadowConstants.AMBIENT_SHADOW_EDGE_SCALE)
+                .setShadowBoundRatio(ShadowConstants.AMBIENT_SHADOW_SHADOW_BOUND)
+                .setShadowStrength(ShadowConstants.AMBIENT_SHADOW_STRENGTH * alpha)
+                .build();
+
+        AmbientShadowTriangulator ambientTriangulator = new AmbientShadowTriangulator(ambientConfig);
+        ambientTriangulator.triangulate();
+
+        SpotShadowTriangulator spotTriangulator = null;
+        float lightZHeightPx = ShadowConstants.SPOT_SHADOW_LIGHT_Z_HEIGHT_DP * (densityDpi / DisplayMetrics.DENSITY_DEFAULT);
+        if (lightZHeightPx - elevation / SCALE_DOWN >= ShadowConstants.SPOT_SHADOW_LIGHT_Z_EPSILON) {
+
+            float lightX = (rectScaled.left + rectScaled.right) / 2;
+            float lightY = rectScaled.top;
+            // Light shouldn't be bigger than the object by too much.
+            int dynamicLightRadius = Math.min(rectScaled.width(), rectScaled.height());
+
+            SpotShadowConfig spotConfig = new SpotShadowConfig.Builder()
+                    .setLightCoord(lightX, lightY, lightZHeightPx)
+                    .setLightRadius(dynamicLightRadius)
+                    .setShadowStrength(ShadowConstants.SPOT_SHADOW_STRENGTH * alpha)
+                    .setPolygon(poly, poly.length / ShadowConstants.COORDINATE_SIZE)
+                    .build();
+
+            spotTriangulator = new SpotShadowTriangulator(spotConfig);
+            spotTriangulator.triangulate();
+        }
+
+        int translateX = 0;
+        int translateY = 0;
+        int imgW = 0;
+        int imgH = 0;
+
+        if (ambientTriangulator.isValid()) {
+            float[] shadowBounds = Math3DHelper.flatBound(ambientTriangulator.getVertices(), 2);
+            // Move the shadow to the left top corner to occupy the least possible bitmap
+
+            translateX = -(int) Math.floor(shadowBounds[0]);
+            translateY = -(int) Math.floor(shadowBounds[1]);
+
+            // create bitmap of the least possible size that covers the entire shadow
+            imgW = (int) Math.ceil(shadowBounds[2] + translateX);
+            imgH = (int) Math.ceil(shadowBounds[3] + translateY);
+        }
+
+        if (spotTriangulator != null && spotTriangulator.validate()) {
+
+            // Bit of a hack to re-adjust spot shadow to fit correctly within parent canvas.
+            // Problem is that outline passed is not a final position, which throws off our
+            // whereas our shadow rendering algorithm, which requires pre-set range for
+            // optimization purposes.
+            float[] shadowBounds = Math3DHelper.flatBound(spotTriangulator.getStrips()[0], 3);
+
+            if ((shadowBounds[2] - shadowBounds[0]) > width ||
+                    (shadowBounds[3] - shadowBounds[1]) > height) {
+                // Spot shadow to be casted is larger than the parent canvas,
+                // We'll let ambient shadow do the trick and skip spot shadow here.
+                spotTriangulator = null;
+            }
+
+            translateX = Math.max(-(int) Math.floor(shadowBounds[0]), translateX);
+            translateY = Math.max(-(int) Math.floor(shadowBounds[1]), translateY);
+
+            // create bitmap of the least possible size that covers the entire shadow
+            imgW = Math.max((int) Math.ceil(shadowBounds[2] + translateX), imgW);
+            imgH = Math.max((int) Math.ceil(shadowBounds[3] + translateY), imgH);
+        }
+
+        TriangleBuffer renderer = new TriangleBuffer();
+        renderer.setSize(imgW, imgH, 0);
+
+        if (ambientTriangulator.isValid()) {
+
+            Math3DHelper.translate(ambientTriangulator.getVertices(), translateX, translateY, 2);
+            renderer.drawTriangles(ambientTriangulator.getIndices(), ambientTriangulator.getVertices(),
+                    ambientTriangulator.getColors(), ambientConfig.getShadowStrength());
+        }
+
+        if (spotTriangulator != null && spotTriangulator.validate()) {
+            float[][] strips = spotTriangulator.getStrips();
+            for (int i = 0; i < strips.length; ++i) {
+                Math3DHelper.translate(strips[i], translateX, translateY, 3);
+                renderer.drawTriangles(strips[i], ShadowConstants.SPOT_SHADOW_STRENGTH * alpha);
+            }
+        }
+
+        Bitmap img = renderer.createImage();
+
+        drawScaled(canvas, img, translateX, translateY, rectOriginal, radius, isOpaque);
+    }
+
+    /**
+     * High quality shadow does not work well with object that is too high in elevation. Check if
+     * the object elevation is reasonable and returns true if shadow will work well. False other
+     * wise.
+     */
+    private static boolean validate(float elevation, float densityDpi) {
+        float scaledElevationPx = elevation / SCALE_DOWN;
+        float scaledSpotLightHeightPx = ShadowConstants.SPOT_SHADOW_LIGHT_Z_HEIGHT_DP *
+                (densityDpi / DisplayMetrics.DENSITY_DEFAULT);
+        if (scaledElevationPx > scaledSpotLightHeightPx) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Draw the bitmap scaled up.
+     * @param translateX - offset in x axis by which the bitmap is shifted.
+     * @param translateY - offset in y axis by which the bitmap is shifted.
+     * @param shadowCaster - unscaled outline of shadow caster
+     * @param radius
+     */
+    private static void drawScaled(Canvas canvas, Bitmap bitmap, int translateX, int translateY,
+            Rect shadowCaster, float radius, boolean isOpaque) {
+        int unscaledTranslateX = translateX * SCALE_DOWN;
+        int unscaledTranslateY = translateY * SCALE_DOWN;
+
+        // To the canvas
+        Rect dest = new Rect(
+                -unscaledTranslateX,
+                -unscaledTranslateY,
+                (bitmap.getWidth() * SCALE_DOWN) - unscaledTranslateX,
+                (bitmap.getHeight() * SCALE_DOWN) - unscaledTranslateY);
+        Rect destSrc = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
+        // We can skip drawing the shadows behind the caster if either
+        // 1) radius is 0, the shadow caster is rectangle and we can have a perfect cut
+        // 2) shadow caster is opaque and even if remove shadow only partially it won't affect
+        // the visual quality, otherwise we will observe shadow part through the translucent caster
+        // This can be improved by:
+        // TODO: do not draw the shadow behind the caster at all during the tesselation phase
+        if (radius > 0 && !isOpaque) {
+            // Rounded edge.
+            int save = canvas.save();
+            canvas.drawBitmap(bitmap, destSrc, dest, null);
+            canvas.restoreToCount(save);
+            return;
+        }
+
+        /**
+         * ----------------------------------
+         * |                                |
+         * |              top               |
+         * |                                |
+         * ----------------------------------
+         * |      |                 |       |
+         * | left |  shadow caster  | right |
+         * |      |                 |       |
+         * ----------------------------------
+         * |                                |
+         * |            bottom              |
+         * |                                |
+         * ----------------------------------
+         *
+         * dest == top + left + shadow caster + right + bottom
+         * Visually, canvas.drawBitmap(bitmap, destSrc, dest, paint) would achieve the same result.
+         */
+        int gap = (int) Math.ceil(radius * SCALE_DOWN * sRoundedGap);
+        shadowCaster.bottom -= gap;
+        shadowCaster.top += gap;
+        shadowCaster.left += gap;
+        shadowCaster.right -= gap;
+        Rect left = new Rect(dest.left, shadowCaster.top, shadowCaster.left,
+                shadowCaster.bottom);
+        int leftScaled = left.width() / SCALE_DOWN + destSrc.left;
+
+        Rect top = new Rect(dest.left, dest.top, dest.right, shadowCaster.top);
+        int topScaled = top.height() / SCALE_DOWN + destSrc.top;
+
+        Rect right = new Rect(shadowCaster.right, shadowCaster.top, dest.right,
+                shadowCaster.bottom);
+        int rightScaled = (shadowCaster.right - dest.left) / SCALE_DOWN + destSrc.left;
+
+        Rect bottom = new Rect(dest.left, shadowCaster.bottom, dest.right, dest.bottom);
+        int bottomScaled = (shadowCaster.bottom - dest.top) / SCALE_DOWN + destSrc.top;
+
+        // calculate parts of the middle ground that can be ignored.
+        Rect leftSrc = new Rect(destSrc.left, topScaled, leftScaled, bottomScaled);
+        Rect topSrc = new Rect(destSrc.left, destSrc.top, destSrc.right, topScaled);
+        Rect rightSrc = new Rect(rightScaled, topScaled, destSrc.right, bottomScaled);
+        Rect bottomSrc = new Rect(destSrc.left, bottomScaled, destSrc.right, destSrc.bottom);
+
+        int save = canvas.save();
+        Paint paint = new Paint();
+        canvas.drawBitmap(bitmap, leftSrc, left, paint);
+        canvas.drawBitmap(bitmap, topSrc, top, paint);
+        canvas.drawBitmap(bitmap, rightSrc, right, paint);
+        canvas.drawBitmap(bitmap, bottomSrc, bottom, paint);
+        canvas.restoreToCount(save);
+    }
+
+    private static float[] getPoly(Rect rect, float elevation, float radius) {
+        if (radius <= 0) {
+            float[] poly = new float[ShadowConstants.RECT_VERTICES_SIZE * ShadowConstants.COORDINATE_SIZE];
+
+            poly[0] = poly[9] = rect.left;
+            poly[1] = poly[4] = rect.top;
+            poly[3] = poly[6] = rect.right;
+            poly[7] = poly[10] = rect.bottom;
+            poly[2] = poly[5] = poly[8] = poly[11] = elevation;
+
+            return poly;
+        }
+
+        return buildRoundedEdges(rect, elevation, radius);
+    }
+
+    private static float[] buildRoundedEdges(
+            Rect rect, float elevation, float radius) {
+
+        float[] roundedEdgeVertices = new float[(ShadowConstants.SPLICE_ROUNDED_EDGE + 1) * 4 * 3];
+        int index = 0;
+        // 1.0 LT. From theta 0 to pi/2 in K division.
+        for (int i = 0; i <= ShadowConstants.SPLICE_ROUNDED_EDGE; i++) {
+            double theta = (Math.PI / 2.0d) * ((double) i / ShadowConstants.SPLICE_ROUNDED_EDGE);
+            float x = (float) (rect.left + (radius - radius * Math.cos(theta)));
+            float y = (float) (rect.top + (radius - radius * Math.sin(theta)));
+            roundedEdgeVertices[index++] = x;
+            roundedEdgeVertices[index++] = y;
+            roundedEdgeVertices[index++] = elevation;
+        }
+
+        // 2.0 RT
+        for (int i = ShadowConstants.SPLICE_ROUNDED_EDGE; i >= 0; i--) {
+            double theta = (Math.PI / 2.0d) * ((double) i / ShadowConstants.SPLICE_ROUNDED_EDGE);
+            float x = (float) (rect.right - (radius - radius * Math.cos(theta)));
+            float y = (float) (rect.top + (radius - radius * Math.sin(theta)));
+            roundedEdgeVertices[index++] = x;
+            roundedEdgeVertices[index++] = y;
+            roundedEdgeVertices[index++] = elevation;
+        }
+
+        // 3.0 RB
+        for (int i = 0; i <= ShadowConstants.SPLICE_ROUNDED_EDGE; i++) {
+            double theta = (Math.PI / 2.0d) * ((double) i / ShadowConstants.SPLICE_ROUNDED_EDGE);
+            float x = (float) (rect.right - (radius - radius * Math.cos(theta)));
+            float y = (float) (rect.bottom - (radius - radius * Math.sin(theta)));
+            roundedEdgeVertices[index++] = x;
+            roundedEdgeVertices[index++] = y;
+            roundedEdgeVertices[index++] = elevation;
+        }
+
+        // 4.0 LB
+        for (int i = ShadowConstants.SPLICE_ROUNDED_EDGE; i >= 0; i--) {
+            double theta = (Math.PI / 2.0d) * ((double) i / ShadowConstants.SPLICE_ROUNDED_EDGE);
+            float x = (float) (rect.left + (radius - radius * Math.cos(theta)));
+            float y = (float) (rect.bottom - (radius - radius * Math.sin(theta)));
+            roundedEdgeVertices[index++] = x;
+            roundedEdgeVertices[index++] = y;
+            roundedEdgeVertices[index++] = elevation;
+        }
+
+        return roundedEdgeVertices;
+    }
+}
diff --git a/android/view/shadow/ShadowConstants.java b/android/view/shadow/ShadowConstants.java
new file mode 100644
index 0000000..049a549
--- /dev/null
+++ b/android/view/shadow/ShadowConstants.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.shadow;
+
+/**
+ * Constant values for shadow related configuration
+ */
+class ShadowConstants {
+
+    /**
+     * This is used as a factor by which to scale down the shadow bitmap. If we have world
+     * Width x Height, shadow bitmap will be Width/SCALE_DOWN x Height/SCALE_DOWN and during
+     * canvas draw the shadow will be scaled up, resulting faster perf (due to smaller bitmap) but
+     * blurrier (lower quality) shadow.
+     */
+    public static final int SCALE_DOWN = 5;
+
+    public static final float MIN_ALPHA = 0.2f;
+
+    public static final int SPOT_SHADOW_LIGHT_Z_HEIGHT_DP = 50 / SCALE_DOWN;
+    public static final int SPOT_SHADOW_LIGHT_Z_EPSILON = 10 / SCALE_DOWN;
+    public static final float SPOT_SHADOW_STRENGTH = 0.3f;
+
+    public static final float AMBIENT_SHADOW_EDGE_SCALE = 60f;
+    public static final float AMBIENT_SHADOW_SHADOW_BOUND = 0.02f * SCALE_DOWN;
+    public static final float AMBIENT_SHADOW_STRENGTH = 1.0f;
+
+    public static final int COORDINATE_SIZE = 3;
+    public static final int RECT_VERTICES_SIZE = 4;
+
+    public static final int SPLICE_ROUNDED_EDGE = 5;
+}
diff --git a/android/view/shadow/SpotShadowConfig.java b/android/view/shadow/SpotShadowConfig.java
new file mode 100644
index 0000000..7b4fa4b
--- /dev/null
+++ b/android/view/shadow/SpotShadowConfig.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.shadow;
+
+
+/**
+ * Model for spot shadow rendering. Assumes single light, single object.
+ */
+class SpotShadowConfig {
+
+    // No need to be final but making it immutable for now.
+    private final int mLightRadius;
+
+
+    // No need to be final but making it immutable for now.
+    private final float[] mPoly;
+    private final int mPolyLength;
+
+    private float[] mLightCoord;
+
+    private final float mShadowStrength;
+
+    private SpotShadowConfig(SpotShadowConfig.Builder builder) {
+        mLightRadius = builder.mLightRadius;
+        mPoly = builder.mPoly;
+        mPolyLength = builder.mPolyLength;
+
+        mLightCoord = new float[3];
+        mLightCoord[0] = builder.mLightX;
+        mLightCoord[1] = builder.mLightY;
+        mLightCoord[2] = builder.mLightHeight;
+        mShadowStrength = builder.mShadowStrength;
+    }
+
+    /**
+     * @return size of the light source radius (light source is always generated as a circular shape)
+     */
+    public int getLightRadius() {
+        return mLightRadius;
+    }
+
+    /**
+     * @return object that casts shadow. xyz coordinates.
+     */
+    public float[] getPoly() {
+        return mPoly;
+    }
+
+    /**
+     * @return # of vertices in the object {@link #getPoly()} that casts shadow.
+     */
+    public int getPolyLength() {
+        return mPolyLength;
+    }
+
+    /**
+     * Update the light source coord.
+     * @param x - horizontal coordinate
+     * @param y - vertical coordinate
+     */
+    public void setLightCoord(float x, float y) {
+        mLightCoord[0] = x;
+        mLightCoord[1] = y;
+    }
+
+    /**
+     * @return shadow intensity from 0 to 1
+     */
+    public float getShadowStrength() {
+        return mShadowStrength;
+    }
+
+    public float[] getLightCoord() {
+        return mLightCoord;
+    }
+
+    public static class Builder {
+
+        // No need to be final but making it immutable for now.
+        private int mLightRadius;
+
+        // No need to be final but making it immutable for now.
+        private float[] mPoly;
+        private int mPolyLength;
+
+        private float mLightX;
+        private float mLightY;
+        private float mLightHeight;
+
+        private float mShadowStrength;
+
+        /**
+         * @param shadowStrength from 0 to 1
+         */
+        public Builder setShadowStrength(float shadowStrength) {
+            this.mShadowStrength = shadowStrength;
+            return this;
+        }
+
+        public Builder setLightRadius(int mLightRadius) {
+            this.mLightRadius = mLightRadius;
+            return this;
+        }
+
+        public Builder setPolygon(float[] poly, int polyLength) {
+            this.mPoly = poly;
+            this.mPolyLength = polyLength;
+            return this;
+        }
+
+        public Builder setLightCoord(float lightX, float lightY, float lightHeight) {
+            this.mLightX = lightX;
+            this.mLightY = lightY;
+            this.mLightHeight = lightHeight;
+            return this;
+        }
+
+        public SpotShadowConfig build() {
+            return new SpotShadowConfig(this);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/android/view/shadow/SpotShadowTriangulator.java b/android/view/shadow/SpotShadowTriangulator.java
new file mode 100644
index 0000000..d666153
--- /dev/null
+++ b/android/view/shadow/SpotShadowTriangulator.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.shadow;
+
+import com.android.ide.common.rendering.api.ILayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+
+/**
+ * Generate spot shadow bitmap.
+ */
+class SpotShadowTriangulator {
+
+    private final SpotShadowConfig mShadowConfig;
+    private float[][] mStrips;
+
+    public SpotShadowTriangulator(SpotShadowConfig config) {
+        mShadowConfig = config;
+    }
+
+    /**
+     * Populate the shadow bitmap.
+     */
+    public void triangulate() {
+        try {
+            float[] lightSources =
+                    SpotShadowVertexCalculator.calculateLight(mShadowConfig.getLightRadius(),
+                            mShadowConfig.getLightCoord()[0],
+                            mShadowConfig.getLightCoord()[1], mShadowConfig.getLightCoord()[2]);
+
+
+            mStrips = new float[2][];
+            int[] sizes = SpotShadowVertexCalculator.getStripSizes(mShadowConfig.getPolyLength());
+            for (int i = 0; i < sizes.length; ++i) {
+                mStrips[i] = new float[3 * sizes[i]];
+            }
+
+            SpotShadowVertexCalculator.calculateShadow(lightSources,
+                    mShadowConfig.getPoly(),
+                    mShadowConfig.getPolyLength(),
+                    mShadowConfig.getShadowStrength(),
+                    mStrips);
+        } catch (IndexOutOfBoundsException|ArithmeticException mathError) {
+            Bridge.getLog().warning(ILayoutLog.TAG_INFO,  "Arithmetic error while drawing " +
+                            "spot shadow",
+                    null, mathError);
+        } catch (Exception ex) {
+            Bridge.getLog().warning(ILayoutLog.TAG_INFO,  "Error while drawing shadow",
+                    null, ex);
+        }
+    }
+    /**
+     * @return true if generated shadow poly is valid. False otherwise.
+     */
+    public boolean validate() {
+        return mStrips != null && mStrips[0].length >= 9;
+    }
+
+    public float[][] getStrips() {
+        return mStrips;
+    }
+}
diff --git a/android/view/shadow/SpotShadowVertexCalculator.java b/android/view/shadow/SpotShadowVertexCalculator.java
new file mode 100644
index 0000000..fc02d18
--- /dev/null
+++ b/android/view/shadow/SpotShadowVertexCalculator.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.shadow;
+
+import android.graphics.Rect;
+import android.view.math.Math3DHelper;
+
+/**
+ * Generates the vertices required for spot shadow and all other shadow-related rendering.
+ */
+class SpotShadowVertexCalculator {
+
+    private SpotShadowVertexCalculator() { }
+
+    /**
+     * Create evenly distributed circular light source points from x and y (on flat z plane).
+     * This is useful for ray tracing the shadow points later. Format : (x1,y1,z1,x2,y2,z2 ...)
+     *
+     * @param radius - radius of the light source
+     * @param x - center X of the light source
+     * @param y - center Y of the light source
+     * @param height - how high (z depth) the light should be
+     * @return float points (x,y,z) of light source points.
+     */
+    public static float[] calculateLight(float radius, float x, float y, float height) {
+        float[] ret = new float[4 * 3];
+        // bottom
+        ret[0] = x;
+        ret[1] = y + radius;
+        ret[2] = height;
+        // left
+        ret[3] = x - radius;
+        ret[4] = y;
+        ret[5] = height;
+        // top
+        ret[6] = x;
+        ret[7] = y - radius;
+        ret[8] = height;
+        // right
+        ret[9] = x + radius;
+        ret[10] = y;
+        ret[11] = height;
+
+        return ret;
+    }
+
+    /**
+     * @param polyLength - length of the outline polygon
+     * @return size required for shadow vertices mData array based on # of vertices in the
+     * outline polygon
+     */
+    public static int[] getStripSizes(int polyLength){
+        return new int[] { ((polyLength + 4) / 8) * 16 + 2, 4};
+    }
+
+    /**
+     * Generate shadow vertices based on params. Format : (x1,y1,z1,x2,y2,z2 ...)
+     * Precondition : Light poly must be evenly distributed on a flat surface
+     * Precondition : Poly vertices must be a convex
+     * Precondition : Light height must be higher than any poly vertices
+     *
+     * @param lightPoly - Vertices of a light source.
+     * @param poly - Vertices of opaque object casting shadow
+     * @param polyLength - Size of the vertices
+     * @param strength - Strength of the shadow overall [0-1]
+     * @param retstrips - Arrays of triplets, each triplet represents a point, thus every array to
+     * be filled in format : {x1, y1, z1, x2, y2, z2, ...},
+     * every 3 consecutive triplets constitute a triangle to fill, namely [t1, t2, t3], [t2, t3,
+     * t4], ... If at some point [t(n-1), tn, t(n+1)] is no longer a desired a triangle and
+     * there are more triangles to draw one can start a new array, hence retstrips is a 2D array.
+     */
+    public static void calculateShadow(
+            float[] lightPoly,
+            float[] poly,
+            int polyLength,
+            float strength,
+            float[][] retstrips) {
+        float[] outerStrip = retstrips[0];
+
+        // We would like to unify the cases where we have roundrects and rectangles
+        int roundedEdgeSegments = ((polyLength == 4) ? 0 : ShadowConstants.SPLICE_ROUNDED_EDGE);
+        int sideLength = (roundedEdgeSegments / 2 + 1) * 2;
+        float[] umbra = new float[4 * 2 * sideLength];
+        int idx = (roundedEdgeSegments + 1) / 2;
+        int uShift = 0;
+        // If we have even number of segments in rounded corner (0 included), the central point of
+        // rounded corner contributes to the hull twice, from 2 different light sources, thus
+        // rollBack in that case, otherwise every point contributes only once
+        int rollBack = (((polyLength % 8) == 0) ? 0 : 1);
+        // Calculate umbra - a hull of all projections
+        for (int s = 0; s < 4; ++s) { // 4 sides
+            float lx = lightPoly[s * 3 + 0];
+            float ly = lightPoly[s * 3 + 1];
+            float lz = lightPoly[s * 3 + 2];
+            for (int i = 0; i < sideLength; ++i, uShift += 2, ++idx) {
+                int shift = (idx % polyLength) * 3;
+
+                float t = lz / (lz - poly[shift + 2]);
+
+                umbra[uShift + 0] = lx - t * (lx - poly[shift + 0]);
+                umbra[uShift + 1] = ly - t * (ly - poly[shift + 1]);
+            }
+
+            idx -= rollBack;
+        }
+
+        idx = roundedEdgeSegments;
+        // An array that wil contain top, right, bottom, left coordinate of penumbra
+        float[] penumbraRect = new float[4];
+        // Calculate penumbra
+        for (int s = 0; s < 4; ++s, idx += (roundedEdgeSegments + 1)) { // 4 sides
+            int sp = (s + 2) % 4;
+
+            float lz = lightPoly[sp * 3 + 2];
+
+            int shift = (idx % polyLength) * 3;
+
+            float t = lz / (lz - poly[shift + 2]);
+
+            // We are interested in just one coordinate: x or y, depending on the light source
+            int c = (s + 1) % 2;
+            penumbraRect[s] =
+                    lightPoly[sp * 3 + c] - t * (lightPoly[sp * 3 + c] - poly[shift + c]);
+        }
+        if (penumbraRect[0] > penumbraRect[2]) {
+            float tmp = (penumbraRect[0] + penumbraRect[2]) / 2.0f;
+            penumbraRect[0] = penumbraRect[2] = tmp;
+        }
+        if (penumbraRect[3] > penumbraRect[1]) {
+            float tmp = (penumbraRect[1] + penumbraRect[3]) / 2.0f;
+            penumbraRect[1] = penumbraRect[3] = tmp;
+        }
+
+        // Now just connect umbra points (at least 8 of them) with the closest points from
+        // penumbra (only 4 of them) to form triangles to fill the entire space between umbra and
+        // penumbra
+        idx = sideLength * 4 - sideLength / 2;
+        int rsShift = 0;
+        for (int s = 0; s < 4; ++s) {
+            int xidx = (((s + 3) % 4) / 2) * 2 + 1;
+            int yidx = (s / 2) * 2;
+            float penumbraX = penumbraRect[xidx];
+            float penumbraY = penumbraRect[yidx];
+            for (int i = 0; i < sideLength; ++i, rsShift += 6, ++idx) {
+                int shift = (idx % (sideLength * 4)) * 2;
+
+                outerStrip[rsShift + 0] = umbra[shift + 0];
+                outerStrip[rsShift + 1] = umbra[shift + 1];
+                outerStrip[rsShift + 3] = penumbraX;
+                outerStrip[rsShift + 4] = penumbraY;
+                outerStrip[rsShift + 5] = strength;
+            }
+        }
+        // Connect with the beginning
+        outerStrip[rsShift + 0] = outerStrip[0];
+        outerStrip[rsShift + 1] = outerStrip[1];
+        // outerStrip[rsShift + 2] = 0;
+        outerStrip[rsShift + 3] = outerStrip[3];
+        outerStrip[rsShift + 4] = outerStrip[4];
+        outerStrip[rsShift + 5] = strength;
+
+        float[] innerStrip = retstrips[1];
+        // Covering penumbra rectangle
+        // left, top
+        innerStrip[0] = penumbraRect[3];
+        innerStrip[1] = penumbraRect[0];
+        innerStrip[2] = strength;
+        // right, top
+        innerStrip[3] = penumbraRect[1];
+        innerStrip[4] = penumbraRect[0];
+        innerStrip[5] = strength;
+        // left, bottom
+        innerStrip[6] = penumbraRect[3];
+        innerStrip[7] = penumbraRect[2];
+        innerStrip[8] = strength;
+        // right, bottom
+        innerStrip[9] = penumbraRect[1];
+        innerStrip[10] = penumbraRect[2];
+        innerStrip[11] = strength;
+    }
+}
\ No newline at end of file
diff --git a/android/view/shadow/TriangleBuffer.java b/android/view/shadow/TriangleBuffer.java
new file mode 100644
index 0000000..3c01171
--- /dev/null
+++ b/android/view/shadow/TriangleBuffer.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.shadow;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.view.math.Math3DHelper;
+
+import java.util.Arrays;
+
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+
+/**
+ * 2D Triangle buffer element that colours using z value. (z scale set).
+ */
+class TriangleBuffer {
+    int mWidth;
+    int mHeight;
+    int mImgWidth;
+    int mImgHeight;
+    int mBorder;
+    Bitmap mBitmap;
+    int mData[];
+
+    public void setSize(int width, int height, int border) {
+        if (mWidth == width && mHeight == height) {
+            return;
+        }
+        mWidth = width-2*border;
+        mHeight = height-2*border;
+        mBorder = border;
+        mImgWidth = width;
+        mImgHeight = height;
+
+        mBitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
+        mData = new int[width * height];
+    }
+
+    public void drawTriangles(int[] index, float[] vert, float[] color,float scale) {
+        int indexSize = index.length / 3;
+        for (int i = 0; i < indexSize; i++) {
+            int vIndex = index[i * 3 + 0];
+            float vx = vert[vIndex * 2 + 0];
+            float vy = vert[vIndex * 2 + 1];
+            float c =  scale*color[vIndex * 4 + 3];
+            float fx3 = vx, fy3 = vy, fz3 = c;
+
+            vIndex = index[i * 3 + 1];
+            vx = vert[vIndex * 2 + 0];
+            vy = vert[vIndex * 2 + 1];
+            c =  scale*color[vIndex * 4 + 3];
+            float fx2 = vx, fy2 = vy, fz2 = c;
+
+            vIndex = index[i * 3 + 2];
+            vx = vert[vIndex * 2 + 0];
+            vy = vert[vIndex * 2 + 1];
+            c =  scale*color[vIndex * 4 + 3];
+            float fx1 = vx, fy1 = vy, fz1 = c;
+
+            triangleZBuffMin(mData, mImgWidth, mImgHeight, fx1, fy1, fz1, fx2, fy2,
+                    fz2, fx3, fy3, fz3);
+        }
+    }
+
+    public void drawTriangles(float[] strip,float scale) {
+        for (int i = 0; i < strip.length-8; i+=3) {
+            float fx3 = strip[i], fy3 = strip[i+1], fz3 = scale* strip[i+2];
+            float fx2 = strip[i+3], fy2 = strip[i+4], fz2 = scale* strip[i+5];
+            float fx1 = strip[i+6], fy1 = strip[i+7], fz1 = scale* strip[i+8];
+
+            if (fx1*(fy2-fy3)+fx2*(fy3-fy1)+fx3*(fy1-fy2) ==0) {
+                continue;
+            }
+            triangleZBuffMin(mData, mImgWidth, mImgHeight, fx3, fy3, fz3, fx2, fy2,
+                    fz2, fx1, fy1, fz1);
+        }
+    }
+
+    public Bitmap createImage() {
+        mBitmap.setPixels(mData, 0, mWidth, 0, 0, mWidth, mHeight);
+        return mBitmap;
+    }
+
+    private static void triangleZBuffMin(int[] buff, int w, int h, float fx3,
+            float fy3, float fz3, float fx2, float fy2, float fz2, float fx1,
+            float fy1, float fz1) {
+        if (((fx1 - fx2) * (fy3 - fy2) - (fy1 - fy2) * (fx3 - fx2)) < 0) {
+            float tmpx = fx1;
+            float tmpy = fy1;
+            float tmpz = fz1;
+            fx1 = fx2;
+            fy1 = fy2;
+            fz1 = fz2;
+            fx2 = tmpx;
+            fy2 = tmpy;
+            fz2 = tmpz;
+        }
+        // using maxmima
+        // solve([x1*dx+y1*dy+zoff=z1,x2*dx+y2*dy+zoff=z2,x3*dx+y3*dy+zoff=z3],[dx,dy,zoff]);
+        double d = (fx1 * (fy3 - fy2) - fx2 * fy3 + fx3 * fy2 + (fx2 - fx3) * fy1);
+        if (d == 0) {
+            return;
+        }
+        float dx = (float) (-(fy1 * (fz3 - fz2) - fy2 * fz3 + fy3 * fz2 + (fy2 - fy3)
+                * fz1) / d);
+        float dy = (float) ((fx1 * (fz3 - fz2) - fx2 * fz3 + fx3 * fz2 + (fx2 - fx3)
+                * fz1) / d);
+        float zoff = (float) ((fx1 * (fy3 * fz2 - fy2 * fz3) + fy1
+                * (fx2 * fz3 - fx3 * fz2) + (fx3 * fy2 - fx2 * fy3) * fz1) / d);
+
+        // 28.4 fixed-point coordinates
+        int y1 = (int) (16.0f * fy1 + .5f);
+        int y2 = (int) (16.0f * fy2 + .5f);
+        int y3 = (int) (16.0f * fy3 + .5f);
+
+        int x1 = (int) (16.0f * fx1 + .5f);
+        int x2 = (int) (16.0f * fx2 + .5f);
+        int x3 = (int) (16.0f * fx3 + .5f);
+
+        int dx12 = x1 - x2;
+        int dx23 = x2 - x3;
+        int dx31 = x3 - x1;
+
+        int dy12 = y1 - y2;
+        int dy23 = y2 - y3;
+        int dy31 = y3 - y1;
+
+        int fdx12 = dx12 << 4;
+        int fdx23 = dx23 << 4;
+        int fdx31 = dx31 << 4;
+
+        int fdy12 = dy12 << 4;
+        int fdy23 = dy23 << 4;
+        int fdy31 = dy31 << 4;
+
+        int minx = (Math3DHelper.min(x1, x2, x3) + 0xF) >> 4;
+        int maxx = (Math3DHelper.max(x1, x2, x3) + 0xF) >> 4;
+        int miny = (Math3DHelper.min(y1, y2, y3) + 0xF) >> 4;
+        int maxy = (Math3DHelper.max(y1, y2, y3) + 0xF) >> 4;
+
+        if (miny < 0) {
+            miny = 0;
+        }
+        if (minx < 0) {
+            minx = 0;
+        }
+        if (maxx > w) {
+            maxx = w;
+        }
+        if (maxy > h) {
+            maxy = h;
+        }
+        int off = miny * w;
+
+        int c1 = dy12 * x1 - dx12 * y1;
+        int c2 = dy23 * x2 - dx23 * y2;
+        int c3 = dy31 * x3 - dx31 * y3;
+
+        if (dy12 < 0 || (dy12 == 0 && dx12 > 0)) {
+            c1++;
+        }
+        if (dy23 < 0 || (dy23 == 0 && dx23 > 0)) {
+            c2++;
+        }
+        if (dy31 < 0 || (dy31 == 0 && dx31 > 0)) {
+            c3++;
+        }
+        int cy1 = c1 + dx12 * (miny << 4) - dy12 * (minx << 4);
+        int cy2 = c2 + dx23 * (miny << 4) - dy23 * (minx << 4);
+        int cy3 = c3 + dx31 * (miny << 4) - dy31 * (minx << 4);
+
+        for (int y = miny; y < maxy; y++) {
+            int cx1 = cy1;
+            int cx2 = cy2;
+            int cx3 = cy3;
+            float p = zoff + dy * y;
+
+            int startx = start(cx1, fdy12, minx, minx, maxx);
+            startx = start(cx2, fdy23, minx, startx, maxx);
+            startx = start(cx3, fdy31, minx, startx, maxx);
+
+            cx1 -= (startx - minx) * fdy12;
+            cx2 -= (startx - minx) * fdy23;
+            cx3 -= (startx - minx) * fdy31;
+
+            int endx = end(cx1, fdy12, startx, minx, maxx);
+            endx = end(cx2, fdy23, startx, minx, endx);
+            endx = end(cx3, fdy31, startx, minx, endx);
+
+            for (int x = startx; x < endx; x++) {
+                int point = x + off;
+                float zval = p + dx * x;
+                // Simple alpha-blending
+                int prev = (buff[point] >> 24) & 0xFF;
+                int res = (int) (zval * (255 - prev )) + prev;
+                buff[point] = res << 24;
+            }
+            cy1 += fdx12;
+            cy2 += fdx23;
+            cy3 += fdx31;
+            off += w;
+        }
+    }
+
+    /**
+     * Returns the minimum value of x in the range [minx, maxx]: y0 - dy * (x - x0) > 0
+     * If no value satisfies the expression, maxx is returned
+     *
+     * @param y0 - value in x0
+     * @param dy - delta y
+     * @param x0 - some position, for which value is known (y0)
+     * @param minx - start of the range
+     * @param maxx - end of the range
+     * @return minimum x
+     */
+    private static int start(int y0, int dy, int x0, int minx, int maxx) {
+        if (y0 > 0) {
+            return minx;
+        }
+        if (dy >= 0) {
+            return maxx;
+        }
+        return max(x0 + y0 / dy + 1, minx);
+    }
+
+    /**
+     * Returns the minimum value of x in range [minx, maxx]: y0 - dy * (x - x0) <= 0
+     * If no value satisfies the expression maxx is returned
+     *
+     * @param y0 - value in x0
+     * @param dy - delta y
+     * @param x0 - some position, for which value is known (y0)
+     * @param minx - start of the range
+     * @param maxx - end of the range
+     * @return minimum x
+     */
+    private static int end(int y0, int dy, int x0, int minx, int maxx) {
+        if (y0 <= 0) {
+            return minx;
+        }
+        if (dy <= 0) {
+            return maxx;
+        }
+        return min(x0 + (y0 - 1) / dy + 1, maxx);
+    }
+
+    public void clear() {
+        Arrays.fill(mData, 0);
+    }
+}
\ No newline at end of file
diff --git a/android/view/textclassifier/ConversationAction.java b/android/view/textclassifier/ConversationAction.java
new file mode 100644
index 0000000..bf0409d
--- /dev/null
+++ b/android/view/textclassifier/ConversationAction.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.textclassifier;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringDef;
+import android.app.RemoteAction;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.util.Objects;
+
+/** Represents the action suggested by a {@link TextClassifier} on a given conversation. */
+public final class ConversationAction implements Parcelable {
+
+    /** @hide */
+    @Retention(SOURCE)
+    @StringDef(
+            value = {
+                    TYPE_VIEW_CALENDAR,
+                    TYPE_VIEW_MAP,
+                    TYPE_TRACK_FLIGHT,
+                    TYPE_OPEN_URL,
+                    TYPE_SEND_SMS,
+                    TYPE_CALL_PHONE,
+                    TYPE_SEND_EMAIL,
+                    TYPE_TEXT_REPLY,
+                    TYPE_CREATE_REMINDER,
+                    TYPE_SHARE_LOCATION
+            },
+            prefix = "TYPE_")
+    public @interface ActionType {}
+
+    /**
+     * Indicates an action to view a calendar at a specified time.
+     */
+    public static final String TYPE_VIEW_CALENDAR = "view_calendar";
+    /**
+     * Indicates an action to view the map at a specified location.
+     */
+    public static final String TYPE_VIEW_MAP = "view_map";
+    /**
+     * Indicates an action to track a flight.
+     */
+    public static final String TYPE_TRACK_FLIGHT = "track_flight";
+    /**
+     * Indicates an action to open an URL.
+     */
+    public static final String TYPE_OPEN_URL = "open_url";
+    /**
+     * Indicates an action to send a SMS.
+     */
+    public static final String TYPE_SEND_SMS = "send_sms";
+    /**
+     * Indicates an action to call a phone number.
+     */
+    public static final String TYPE_CALL_PHONE = "call_phone";
+    /**
+     * Indicates an action to send an email.
+     */
+    public static final String TYPE_SEND_EMAIL = "send_email";
+    /**
+     * Indicates an action to reply with a text message.
+     */
+    public static final String TYPE_TEXT_REPLY = "text_reply";
+    /**
+     * Indicates an action to create a reminder.
+     */
+    public static final String TYPE_CREATE_REMINDER = "create_reminder";
+    /**
+     * Indicates an action to reply with a location.
+     */
+    public static final String TYPE_SHARE_LOCATION = "share_location";
+
+    // TODO: Make this public API
+    /** @hide **/
+    public static final String TYPE_ADD_CONTACT = "add_contact";
+
+    // TODO: Make this public API
+    /** @hide **/
+    public static final String TYPE_COPY = "copy";
+
+    public static final @NonNull Creator<ConversationAction> CREATOR =
+            new Creator<ConversationAction>() {
+                @Override
+                public ConversationAction createFromParcel(Parcel in) {
+                    return new ConversationAction(in);
+                }
+
+                @Override
+                public ConversationAction[] newArray(int size) {
+                    return new ConversationAction[size];
+                }
+            };
+
+    @NonNull
+    @ActionType
+    private final String mType;
+    @NonNull
+    private final CharSequence mTextReply;
+    @Nullable
+    private final RemoteAction mAction;
+
+    @FloatRange(from = 0, to = 1)
+    private final float mScore;
+
+    @NonNull
+    private final Bundle mExtras;
+
+    private ConversationAction(
+            @NonNull String type,
+            @Nullable RemoteAction action,
+            @Nullable CharSequence textReply,
+            float score,
+            @NonNull Bundle extras) {
+        mType = Objects.requireNonNull(type);
+        mAction = action;
+        mTextReply = textReply;
+        mScore = score;
+        mExtras = Objects.requireNonNull(extras);
+    }
+
+    private ConversationAction(Parcel in) {
+        mType = in.readString();
+        mAction = in.readParcelable(null);
+        mTextReply = in.readCharSequence();
+        mScore = in.readFloat();
+        mExtras = in.readBundle();
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeString(mType);
+        parcel.writeParcelable(mAction, flags);
+        parcel.writeCharSequence(mTextReply);
+        parcel.writeFloat(mScore);
+        parcel.writeBundle(mExtras);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Returns the type of this action, for example, {@link #TYPE_VIEW_CALENDAR}. */
+    @NonNull
+    @ActionType
+    public String getType() {
+        return mType;
+    }
+
+    /**
+     * Returns a RemoteAction object, which contains the icon, label and a PendingIntent, for
+     * the specified action type.
+     */
+    @Nullable
+    public RemoteAction getAction() {
+        return mAction;
+    }
+
+    /**
+     * Returns the confidence score for the specified action. The value ranges from 0 (low
+     * confidence) to 1 (high confidence).
+     */
+    @FloatRange(from = 0, to = 1)
+    public float getConfidenceScore() {
+        return mScore;
+    }
+
+    /**
+     * Returns the text reply that could be sent as a reply to the given conversation.
+     * <p>
+     * This is only available when the type of the action is {@link #TYPE_TEXT_REPLY}.
+     */
+    @Nullable
+    public CharSequence getTextReply() {
+        return mTextReply;
+    }
+
+    /**
+     * Returns the extended data related to this conversation action.
+     *
+     * <p><b>NOTE: </b>Do not modify this bundle.
+     */
+    @NonNull
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    /** @hide */
+    public Builder toBuilder() {
+        return new Builder(mType)
+            .setTextReply(mTextReply)
+            .setAction(mAction)
+            .setConfidenceScore(mScore)
+            .setExtras(mExtras);
+    }
+
+    /** Builder class to construct {@link ConversationAction}. */
+    public static final class Builder {
+        @Nullable
+        @ActionType
+        private String mType;
+        @Nullable
+        private RemoteAction mAction;
+        @Nullable
+        private CharSequence mTextReply;
+        private float mScore;
+        @Nullable
+        private Bundle mExtras;
+
+        public Builder(@NonNull @ActionType String actionType) {
+            mType = Objects.requireNonNull(actionType);
+        }
+
+        /**
+         * Sets an action that may be performed on the given conversation.
+         */
+        @NonNull
+        public Builder setAction(@Nullable RemoteAction action) {
+            mAction = action;
+            return this;
+        }
+
+        /**
+         * Sets a text reply that may be performed on the given conversation.
+         */
+        @NonNull
+        public Builder setTextReply(@Nullable CharSequence textReply) {
+            mTextReply = textReply;
+            return this;
+        }
+
+        /** Sets the confident score. */
+        @NonNull
+        public Builder setConfidenceScore(@FloatRange(from = 0, to = 1) float score) {
+            mScore = score;
+            return this;
+        }
+
+        /**
+         * Sets the extended data for the conversation action object.
+         */
+        @NonNull
+        public Builder setExtras(@Nullable Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /** Builds the {@link ConversationAction} object. */
+        @NonNull
+        public ConversationAction build() {
+            return new ConversationAction(
+                    mType,
+                    mAction,
+                    mTextReply,
+                    mScore,
+                    mExtras == null ? Bundle.EMPTY : mExtras);
+        }
+    }
+}
diff --git a/android/view/textclassifier/ConversationActions.java b/android/view/textclassifier/ConversationActions.java
new file mode 100644
index 0000000..6ad5cb9
--- /dev/null
+++ b/android/view/textclassifier/ConversationActions.java
@@ -0,0 +1,523 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.textclassifier;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringDef;
+import android.app.Person;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.SpannedString;
+
+import java.lang.annotation.Retention;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Represents a list of actions suggested by a {@link TextClassifier} on a given conversation.
+ *
+ * @see TextClassifier#suggestConversationActions(Request)
+ */
+public final class ConversationActions implements Parcelable {
+
+    public static final @android.annotation.NonNull Creator<ConversationActions> CREATOR =
+            new Creator<ConversationActions>() {
+                @Override
+                public ConversationActions createFromParcel(Parcel in) {
+                    return new ConversationActions(in);
+                }
+
+                @Override
+                public ConversationActions[] newArray(int size) {
+                    return new ConversationActions[size];
+                }
+            };
+
+    private final List<ConversationAction> mConversationActions;
+    private final String mId;
+
+    /** Constructs a {@link ConversationActions} object. */
+    public ConversationActions(
+            @NonNull List<ConversationAction> conversationActions, @Nullable String id) {
+        mConversationActions =
+                Collections.unmodifiableList(Objects.requireNonNull(conversationActions));
+        mId = id;
+    }
+
+    private ConversationActions(Parcel in) {
+        mConversationActions =
+                Collections.unmodifiableList(in.createTypedArrayList(ConversationAction.CREATOR));
+        mId = in.readString();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeTypedList(mConversationActions);
+        parcel.writeString(mId);
+    }
+
+    /**
+     * Returns an immutable list of {@link ConversationAction} objects, which are ordered from high
+     * confidence to low confidence.
+     */
+    @NonNull
+    public List<ConversationAction> getConversationActions() {
+        return mConversationActions;
+    }
+
+    /**
+     * Returns the id, if one exists, for this object.
+     */
+    @Nullable
+    public String getId() {
+        return mId;
+    }
+
+    /** Represents a message in the conversation. */
+    public static final class Message implements Parcelable {
+        /**
+         * Represents the local user.
+         *
+         * @see Builder#Builder(Person)
+         */
+        @NonNull
+        public static final Person PERSON_USER_SELF =
+                new Person.Builder()
+                        .setKey("text-classifier-conversation-actions-user-self")
+                        .build();
+
+        /**
+         * Represents the remote user.
+         * <p>
+         * If possible, you are suggested to create a {@link Person} object that can identify
+         * the remote user better, so that the underlying model could differentiate between
+         * different remote users.
+         *
+         * @see Builder#Builder(Person)
+         */
+        @NonNull
+        public static final Person PERSON_USER_OTHERS =
+                new Person.Builder()
+                        .setKey("text-classifier-conversation-actions-user-others")
+                        .build();
+
+        @Nullable
+        private final Person mAuthor;
+        @Nullable
+        private final ZonedDateTime mReferenceTime;
+        @Nullable
+        private final CharSequence mText;
+        @NonNull
+        private final Bundle mExtras;
+
+        private Message(
+                @Nullable Person author,
+                @Nullable ZonedDateTime referenceTime,
+                @Nullable CharSequence text,
+                @NonNull Bundle bundle) {
+            mAuthor = author;
+            mReferenceTime = referenceTime;
+            mText = text;
+            mExtras = Objects.requireNonNull(bundle);
+        }
+
+        private Message(Parcel in) {
+            mAuthor = in.readParcelable(null);
+            mReferenceTime =
+                    in.readInt() == 0
+                            ? null
+                            : ZonedDateTime.parse(
+                                    in.readString(), DateTimeFormatter.ISO_ZONED_DATE_TIME);
+            mText = in.readCharSequence();
+            mExtras = in.readBundle();
+        }
+
+        @Override
+        public void writeToParcel(Parcel parcel, int flags) {
+            parcel.writeParcelable(mAuthor, flags);
+            parcel.writeInt(mReferenceTime != null ? 1 : 0);
+            if (mReferenceTime != null) {
+                parcel.writeString(mReferenceTime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
+            }
+            parcel.writeCharSequence(mText);
+            parcel.writeBundle(mExtras);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        public static final @android.annotation.NonNull Creator<Message> CREATOR =
+                new Creator<Message>() {
+                    @Override
+                    public Message createFromParcel(Parcel in) {
+                        return new Message(in);
+                    }
+
+                    @Override
+                    public Message[] newArray(int size) {
+                        return new Message[size];
+                    }
+                };
+
+        /** Returns the person that composed the message. */
+        @NonNull
+        public Person getAuthor() {
+            return mAuthor;
+        }
+
+        /**
+         * Returns the reference time of the message, for example it could be the compose or send
+         * time of this message.
+         */
+        @Nullable
+        public ZonedDateTime getReferenceTime() {
+            return mReferenceTime;
+        }
+
+        /** Returns the text of the message. */
+        @Nullable
+        public CharSequence getText() {
+            return mText;
+        }
+
+        /**
+         * Returns the extended data related to this conversation action.
+         *
+         * <p><b>NOTE: </b>Do not modify this bundle.
+         */
+        @NonNull
+        public Bundle getExtras() {
+            return mExtras;
+        }
+
+        /** Builder class to construct a {@link Message} */
+        public static final class Builder {
+            @Nullable
+            private Person mAuthor;
+            @Nullable
+            private ZonedDateTime mReferenceTime;
+            @Nullable
+            private CharSequence mText;
+            @Nullable
+            private Bundle mExtras;
+
+            /**
+             * Constructs a builder.
+             *
+             * @param author the person that composed the message, use {@link #PERSON_USER_SELF}
+             *               to represent the local user. If it is not possible to identify the
+             *               remote user that the local user is conversing with, use
+             *               {@link #PERSON_USER_OTHERS} to represent a remote user.
+             */
+            public Builder(@NonNull Person author) {
+                mAuthor = Objects.requireNonNull(author);
+            }
+
+            /** Sets the text of this message. */
+            @NonNull
+            public Builder setText(@Nullable CharSequence text) {
+                mText = text;
+                return this;
+            }
+
+            /**
+             * Sets the reference time of this message, for example it could be the compose or send
+             * time of this message.
+             */
+            @NonNull
+            public Builder setReferenceTime(@Nullable ZonedDateTime referenceTime) {
+                mReferenceTime = referenceTime;
+                return this;
+            }
+
+            /** Sets a set of extended data to the message. */
+            @NonNull
+            public Builder setExtras(@Nullable Bundle bundle) {
+                this.mExtras = bundle;
+                return this;
+            }
+
+            /** Builds the {@link Message} object. */
+            @NonNull
+            public Message build() {
+                return new Message(
+                        mAuthor,
+                        mReferenceTime,
+                        mText == null ? null : new SpannedString(mText),
+                        mExtras == null ? Bundle.EMPTY : mExtras);
+            }
+        }
+    }
+
+    /**
+     * A request object for generating conversation action suggestions.
+     *
+     * @see TextClassifier#suggestConversationActions(Request)
+     */
+    public static final class Request implements Parcelable {
+
+        /** @hide */
+        @Retention(SOURCE)
+        @StringDef(
+                value = {
+                        HINT_FOR_NOTIFICATION,
+                        HINT_FOR_IN_APP,
+                },
+                prefix = "HINT_")
+        public @interface Hint {}
+
+        /**
+         * To indicate the generated actions will be used within the app.
+         */
+        public static final String HINT_FOR_IN_APP = "in_app";
+        /**
+         * To indicate the generated actions will be used for notification.
+         */
+        public static final String HINT_FOR_NOTIFICATION = "notification";
+
+        @NonNull
+        private final List<Message> mConversation;
+        @NonNull
+        private final TextClassifier.EntityConfig mTypeConfig;
+        private final int mMaxSuggestions;
+        @NonNull
+        @Hint
+        private final List<String> mHints;
+        @NonNull
+        private Bundle mExtras;
+        @Nullable private SystemTextClassifierMetadata mSystemTcMetadata;
+
+        private Request(
+                @NonNull List<Message> conversation,
+                @NonNull TextClassifier.EntityConfig typeConfig,
+                int maxSuggestions,
+                @Nullable @Hint List<String> hints,
+                @NonNull Bundle extras) {
+            mConversation = Objects.requireNonNull(conversation);
+            mTypeConfig = Objects.requireNonNull(typeConfig);
+            mMaxSuggestions = maxSuggestions;
+            mHints = hints;
+            mExtras = extras;
+        }
+
+        private static Request readFromParcel(Parcel in) {
+            List<Message> conversation = new ArrayList<>();
+            in.readParcelableList(conversation, null);
+            TextClassifier.EntityConfig typeConfig = in.readParcelable(null);
+            int maxSuggestions = in.readInt();
+            List<String> hints = new ArrayList<>();
+            in.readStringList(hints);
+            Bundle extras = in.readBundle();
+            SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null);
+
+            Request request = new Request(
+                    conversation,
+                    typeConfig,
+                    maxSuggestions,
+                    hints,
+                    extras);
+            request.setSystemTextClassifierMetadata(systemTcMetadata);
+            return request;
+        }
+
+        @Override
+        public void writeToParcel(Parcel parcel, int flags) {
+            parcel.writeParcelableList(mConversation, flags);
+            parcel.writeParcelable(mTypeConfig, flags);
+            parcel.writeInt(mMaxSuggestions);
+            parcel.writeStringList(mHints);
+            parcel.writeBundle(mExtras);
+            parcel.writeParcelable(mSystemTcMetadata, flags);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        public static final @android.annotation.NonNull Creator<Request> CREATOR =
+                new Creator<Request>() {
+                    @Override
+                    public Request createFromParcel(Parcel in) {
+                        return readFromParcel(in);
+                    }
+
+                    @Override
+                    public Request[] newArray(int size) {
+                        return new Request[size];
+                    }
+                };
+
+        /** Returns the type config. */
+        @NonNull
+        public TextClassifier.EntityConfig getTypeConfig() {
+            return mTypeConfig;
+        }
+
+        /** Returns an immutable list of messages that make up the conversation. */
+        @NonNull
+        public List<Message> getConversation() {
+            return mConversation;
+        }
+
+        /**
+         * Return the maximal number of suggestions the caller wants, value -1 means no restriction
+         * and this is the default.
+         */
+        @IntRange(from = -1)
+        public int getMaxSuggestions() {
+            return mMaxSuggestions;
+        }
+
+        /** Returns an immutable list of hints */
+        @NonNull
+        @Hint
+        public List<String> getHints() {
+            return mHints;
+        }
+
+        /**
+         * Returns the name of the package that sent this request.
+         * This returns {@code null} if no calling package name is set.
+         */
+        @Nullable
+        public String getCallingPackageName() {
+            return mSystemTcMetadata != null ? mSystemTcMetadata.getCallingPackageName() : null;
+        }
+
+        /**
+         * Sets the information about the {@link SystemTextClassifier} that sent this request.
+         *
+         * @hide
+         */
+        void setSystemTextClassifierMetadata(@Nullable SystemTextClassifierMetadata systemTcData) {
+            mSystemTcMetadata = systemTcData;
+        }
+
+        /**
+         * Returns the information about the {@link SystemTextClassifier} that sent this request.
+         *
+         * @hide
+         */
+        @Nullable
+        public SystemTextClassifierMetadata getSystemTextClassifierMetadata() {
+            return mSystemTcMetadata;
+        }
+
+        /**
+         * Returns the extended data related to this request.
+         *
+         * <p><b>NOTE: </b>Do not modify this bundle.
+         */
+        @NonNull
+        public Bundle getExtras() {
+            return mExtras;
+        }
+
+        /** Builder object to construct the {@link Request} object. */
+        public static final class Builder {
+            @NonNull
+            private List<Message> mConversation;
+            @Nullable
+            private TextClassifier.EntityConfig mTypeConfig;
+            private int mMaxSuggestions = -1;
+            @Nullable
+            @Hint
+            private List<String> mHints;
+            @Nullable
+            private Bundle mExtras;
+
+            /**
+             * Constructs a builder.
+             *
+             * @param conversation the conversation that the text classifier is going to generate
+             *     actions for.
+             */
+            public Builder(@NonNull List<Message> conversation) {
+                mConversation = Objects.requireNonNull(conversation);
+            }
+
+            /**
+             * Sets the hints to help text classifier to generate actions. It could be used to help
+             * text classifier to infer what types of actions the caller may be interested in.
+             */
+            @NonNull
+            public Builder setHints(@Nullable @Hint List<String> hints) {
+                mHints = hints;
+                return this;
+            }
+
+            /** Sets the type config. */
+            @NonNull
+            public Builder setTypeConfig(@Nullable TextClassifier.EntityConfig typeConfig) {
+                mTypeConfig = typeConfig;
+                return this;
+            }
+
+            /**
+             * Sets the maximum number of suggestions you want. Value -1 means no restriction and
+             * this is the default.
+             */
+            @NonNull
+            public Builder setMaxSuggestions(@IntRange(from = -1) int maxSuggestions) {
+                if (maxSuggestions < -1) {
+                    throw new IllegalArgumentException("maxSuggestions has to be greater than or "
+                            + "equal to -1.");
+                }
+                mMaxSuggestions = maxSuggestions;
+                return this;
+            }
+
+            /** Sets a set of extended data to the request. */
+            @NonNull
+            public Builder setExtras(@Nullable Bundle bundle) {
+                mExtras = bundle;
+                return this;
+            }
+
+            /** Builds the {@link Request} object. */
+            @NonNull
+            public Request build() {
+                return new Request(
+                        Collections.unmodifiableList(mConversation),
+                        mTypeConfig == null
+                                ? new TextClassifier.EntityConfig.Builder().build()
+                                : mTypeConfig,
+                        mMaxSuggestions,
+                        mHints == null
+                                ? Collections.emptyList()
+                                : Collections.unmodifiableList(mHints),
+                        mExtras == null ? Bundle.EMPTY : mExtras);
+            }
+        }
+    }
+}
diff --git a/android/view/textclassifier/EntityConfidence.java b/android/view/textclassifier/EntityConfidence.java
new file mode 100644
index 0000000..b4313b7
--- /dev/null
+++ b/android/view/textclassifier/EntityConfidence.java
@@ -0,0 +1,146 @@
+/*
+ * 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.view.textclassifier;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArrayMap;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Helper object for setting and getting entity scores for classified text.
+ *
+ * @hide
+ */
+final class EntityConfidence implements Parcelable {
+
+    private final ArrayMap<String, Float> mEntityConfidence = new ArrayMap<>();
+    private final ArrayList<String> mSortedEntities = new ArrayList<>();
+
+    EntityConfidence() {}
+
+    EntityConfidence(@NonNull EntityConfidence source) {
+        Objects.requireNonNull(source);
+        mEntityConfidence.putAll(source.mEntityConfidence);
+        mSortedEntities.addAll(source.mSortedEntities);
+    }
+
+    /**
+     * Constructs an EntityConfidence from a map of entity to confidence.
+     *
+     * Map entries that have 0 confidence are removed, and values greater than 1 are clamped to 1.
+     *
+     * @param source a map from entity to a confidence value in the range 0 (low confidence) to
+     *               1 (high confidence).
+     */
+    EntityConfidence(@NonNull Map<String, Float> source) {
+        Objects.requireNonNull(source);
+
+        // Prune non-existent entities and clamp to 1.
+        mEntityConfidence.ensureCapacity(source.size());
+        for (Map.Entry<String, Float> it : source.entrySet()) {
+            if (it.getValue() <= 0) continue;
+            mEntityConfidence.put(it.getKey(), Math.min(1, it.getValue()));
+        }
+        resetSortedEntitiesFromMap();
+    }
+
+    /**
+     * Returns an immutable list of entities found in the classified text ordered from
+     * high confidence to low confidence.
+     */
+    @NonNull
+    public List<String> getEntities() {
+        return Collections.unmodifiableList(mSortedEntities);
+    }
+
+    /**
+     * Returns the confidence score for the specified entity. The value ranges from
+     * 0 (low confidence) to 1 (high confidence). 0 indicates that the entity was not found for the
+     * classified text.
+     */
+    @FloatRange(from = 0.0, to = 1.0)
+    public float getConfidenceScore(String entity) {
+        if (mEntityConfidence.containsKey(entity)) {
+            return mEntityConfidence.get(entity);
+        }
+        return 0;
+    }
+
+    public Map<String, Float> toMap() {
+        return new ArrayMap(mEntityConfidence);
+    }
+
+    @Override
+    public String toString() {
+        return mEntityConfidence.toString();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mEntityConfidence.size());
+        for (Map.Entry<String, Float> entry : mEntityConfidence.entrySet()) {
+            dest.writeString(entry.getKey());
+            dest.writeFloat(entry.getValue());
+        }
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<EntityConfidence> CREATOR =
+            new Parcelable.Creator<EntityConfidence>() {
+                @Override
+                public EntityConfidence createFromParcel(Parcel in) {
+                    return new EntityConfidence(in);
+                }
+
+                @Override
+                public EntityConfidence[] newArray(int size) {
+                    return new EntityConfidence[size];
+                }
+            };
+
+    private EntityConfidence(Parcel in) {
+        final int numEntities = in.readInt();
+        mEntityConfidence.ensureCapacity(numEntities);
+        for (int i = 0; i < numEntities; ++i) {
+            mEntityConfidence.put(in.readString(), in.readFloat());
+        }
+        resetSortedEntitiesFromMap();
+    }
+
+    private void resetSortedEntitiesFromMap() {
+        mSortedEntities.clear();
+        mSortedEntities.ensureCapacity(mEntityConfidence.size());
+        mSortedEntities.addAll(mEntityConfidence.keySet());
+        mSortedEntities.sort((e1, e2) -> {
+            float score1 = mEntityConfidence.get(e1);
+            float score2 = mEntityConfidence.get(e2);
+            return Float.compare(score2, score1);
+        });
+    }
+}
diff --git a/android/view/textclassifier/ExtrasUtils.java b/android/view/textclassifier/ExtrasUtils.java
new file mode 100644
index 0000000..9e2b642
--- /dev/null
+++ b/android/view/textclassifier/ExtrasUtils.java
@@ -0,0 +1,139 @@
+/*
+ * 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.view.textclassifier;
+
+import android.annotation.Nullable;
+import android.app.RemoteAction;
+import android.content.Intent;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+
+/**
+ * Utility class for inserting and retrieving data in TextClassifier request/response extras.
+ * @hide
+ */
+// TODO: Make this a TestApi for CTS testing.
+public final class ExtrasUtils {
+
+    // Keys for response objects.
+    private static final String ACTION_INTENT = "action-intent";
+    private static final String ACTIONS_INTENTS = "actions-intents";
+    private static final String FOREIGN_LANGUAGE = "foreign-language";
+    private static final String ENTITY_TYPE = "entity-type";
+    private static final String SCORE = "score";
+    private static final String MODEL_NAME = "model-name";
+
+    private ExtrasUtils() {
+    }
+
+    /**
+     * Returns foreign language detection information contained in the TextClassification object.
+     * responses.
+     */
+    @Nullable
+    public static Bundle getForeignLanguageExtra(@Nullable TextClassification classification) {
+        if (classification == null) {
+            return null;
+        }
+        return classification.getExtras().getBundle(FOREIGN_LANGUAGE);
+    }
+
+    /**
+     * Returns {@code actionIntent} information contained in a TextClassifier response object.
+     */
+    @Nullable
+    public static Intent getActionIntent(Bundle container) {
+        return container.getParcelable(ACTION_INTENT);
+    }
+
+    /**
+     * Returns {@code actionIntents} information contained in the TextClassification object.
+     */
+    @Nullable
+    public static ArrayList<Intent> getActionsIntents(@Nullable TextClassification classification) {
+        if (classification == null) {
+            return null;
+        }
+        return classification.getExtras().getParcelableArrayList(ACTIONS_INTENTS);
+    }
+
+    /**
+     * Returns the first action found in the {@code classification} object with an intent
+     * action string, {@code intentAction}.
+     */
+    @Nullable
+    private static RemoteAction findAction(
+            @Nullable TextClassification classification, @Nullable String intentAction) {
+        if (classification == null || intentAction == null) {
+            return null;
+        }
+        final ArrayList<Intent> actionIntents = getActionsIntents(classification);
+        if (actionIntents != null) {
+            final int size = actionIntents.size();
+            for (int i = 0; i < size; i++) {
+                final Intent intent = actionIntents.get(i);
+                if (intent != null && intentAction.equals(intent.getAction())) {
+                    return classification.getActions().get(i);
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the first "translate" action found in the {@code classification} object.
+     */
+    @Nullable
+    public static RemoteAction findTranslateAction(@Nullable TextClassification classification) {
+        return findAction(classification, Intent.ACTION_TRANSLATE);
+    }
+
+    /**
+     * Returns the entity type contained in the {@code extra}.
+     */
+    @Nullable
+    public static String getEntityType(@Nullable Bundle extra) {
+        if (extra == null) {
+            return null;
+        }
+        return extra.getString(ENTITY_TYPE);
+    }
+
+    /**
+     * Returns the score contained in the {@code extra}.
+     */
+    @Nullable
+    public static float getScore(Bundle extra) {
+        final int defaultValue = -1;
+        if (extra == null) {
+            return defaultValue;
+        }
+        return extra.getFloat(SCORE, defaultValue);
+    }
+
+    /**
+     * Returns the model name contained in the {@code extra}.
+     */
+    @Nullable
+    public static String getModelName(@Nullable Bundle extra) {
+        if (extra == null) {
+            return null;
+        }
+        return extra.getString(MODEL_NAME);
+    }
+}
\ No newline at end of file
diff --git a/android/view/textclassifier/Log.java b/android/view/textclassifier/Log.java
new file mode 100644
index 0000000..98ee09c
--- /dev/null
+++ b/android/view/textclassifier/Log.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+/**
+ * Logging for android.view.textclassifier package.
+ * <p>
+ * To enable full log:
+ * 1. adb shell setprop log.tag.androidtc VERBOSE
+ * 2. adb shell stop && adb shell start
+ *
+ * @hide
+ */
+public final class Log {
+
+    /**
+     * true: Enables full logging.
+     * false: Limits logging to debug level.
+     */
+    static final boolean ENABLE_FULL_LOGGING =
+            android.util.Log.isLoggable(TextClassifier.LOG_TAG, android.util.Log.VERBOSE);
+
+    private Log() {
+    }
+
+    public static void v(String tag, String msg) {
+        if (ENABLE_FULL_LOGGING) {
+            android.util.Log.v(tag, msg);
+        }
+    }
+
+    public static void d(String tag, String msg) {
+        android.util.Log.d(tag, msg);
+    }
+
+    public static void w(String tag, String msg) {
+        android.util.Log.w(tag, msg);
+    }
+
+    public static void e(String tag, String msg, Throwable tr) {
+        if (ENABLE_FULL_LOGGING) {
+            android.util.Log.e(tag, msg, tr);
+        } else {
+            final String trString = (tr != null) ? tr.getClass().getSimpleName() : "??";
+            android.util.Log.d(tag, String.format("%s (%s)", msg, trString));
+        }
+    }
+}
diff --git a/android/view/textclassifier/SelectionEvent.java b/android/view/textclassifier/SelectionEvent.java
new file mode 100644
index 0000000..858825b
--- /dev/null
+++ b/android/view/textclassifier/SelectionEvent.java
@@ -0,0 +1,704 @@
+/*
+ * 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.view.textclassifier;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.textclassifier.TextClassifier.EntityType;
+import android.view.textclassifier.TextClassifier.WidgetType;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * A selection event.
+ * Specify index parameters as word token indices.
+ */
+public final class SelectionEvent implements Parcelable {
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({ACTION_OVERTYPE, ACTION_COPY, ACTION_PASTE, ACTION_CUT,
+            ACTION_SHARE, ACTION_SMART_SHARE, ACTION_DRAG, ACTION_ABANDON,
+            ACTION_OTHER, ACTION_SELECT_ALL, ACTION_RESET})
+    // NOTE: ActionType values should not be lower than 100 to avoid colliding with the other
+    // EventTypes declared below.
+    public @interface ActionType {
+        /*
+         * Terminal event types range: [100,200).
+         * Non-terminal event types range: [200,300).
+         */
+    }
+
+    /** User typed over the selection. */
+    public static final int ACTION_OVERTYPE = 100;
+    /** User copied the selection. */
+    public static final int ACTION_COPY = 101;
+    /** User pasted over the selection. */
+    public static final int ACTION_PASTE = 102;
+    /** User cut the selection. */
+    public static final int ACTION_CUT = 103;
+    /** User shared the selection. */
+    public static final int ACTION_SHARE = 104;
+    /** User clicked the textAssist menu item. */
+    public static final int ACTION_SMART_SHARE = 105;
+    /** User dragged+dropped the selection. */
+    public static final int ACTION_DRAG = 106;
+    /** User abandoned the selection. */
+    public static final int ACTION_ABANDON = 107;
+    /** User performed an action on the selection. */
+    public static final int ACTION_OTHER = 108;
+
+    // Non-terminal actions.
+    /** User activated Select All */
+    public static final int ACTION_SELECT_ALL = 200;
+    /** User reset the smart selection. */
+    public static final int ACTION_RESET = 201;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({ACTION_OVERTYPE, ACTION_COPY, ACTION_PASTE, ACTION_CUT,
+            ACTION_SHARE, ACTION_SMART_SHARE, ACTION_DRAG, ACTION_ABANDON,
+            ACTION_OTHER, ACTION_SELECT_ALL, ACTION_RESET,
+            EVENT_SELECTION_STARTED, EVENT_SELECTION_MODIFIED,
+            EVENT_SMART_SELECTION_SINGLE, EVENT_SMART_SELECTION_MULTI,
+            EVENT_AUTO_SELECTION})
+    // NOTE: EventTypes declared here must be less than 100 to avoid colliding with the
+    // ActionTypes declared above.
+    public @interface EventType {
+        /*
+         * Range: 1 -> 99.
+         */
+    }
+
+    /** User started a new selection. */
+    public static final int EVENT_SELECTION_STARTED = 1;
+    /** User modified an existing selection. */
+    public static final int EVENT_SELECTION_MODIFIED = 2;
+    /** Smart selection triggered for a single token (word). */
+    public static final int EVENT_SMART_SELECTION_SINGLE = 3;
+    /** Smart selection triggered spanning multiple tokens (words). */
+    public static final int EVENT_SMART_SELECTION_MULTI = 4;
+    /** Something else other than User or the default TextClassifier triggered a selection. */
+    public static final int EVENT_AUTO_SELECTION = 5;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({INVOCATION_MANUAL, INVOCATION_LINK, INVOCATION_UNKNOWN})
+    public @interface InvocationMethod {}
+
+    /** Selection was invoked by the user long pressing, double tapping, or dragging to select. */
+    public static final int INVOCATION_MANUAL = 1;
+    /** Selection was invoked by the user tapping on a link. */
+    public static final int INVOCATION_LINK = 2;
+    /** Unknown invocation method */
+    public static final int INVOCATION_UNKNOWN = 0;
+
+    static final String NO_SIGNATURE = "";
+
+    private final int mAbsoluteStart;
+    private final int mAbsoluteEnd;
+
+    private @EntityType String mEntityType;
+    private @EventType int mEventType;
+    private String mPackageName = "";
+    private String mWidgetType = TextClassifier.WIDGET_TYPE_UNKNOWN;
+    private @InvocationMethod int mInvocationMethod;
+    @Nullable private String mWidgetVersion;
+    @Nullable private String mResultId;
+    private long mEventTime;
+    private long mDurationSinceSessionStart;
+    private long mDurationSincePreviousEvent;
+    private int mEventIndex;
+    @Nullable private TextClassificationSessionId mSessionId;
+    private int mStart;
+    private int mEnd;
+    private int mSmartStart;
+    private int mSmartEnd;
+    @Nullable private SystemTextClassifierMetadata mSystemTcMetadata;
+
+    SelectionEvent(
+            int start, int end,
+            @EventType int eventType, @EntityType String entityType,
+            @InvocationMethod int invocationMethod, @Nullable String resultId) {
+        Preconditions.checkArgument(end >= start, "end cannot be less than start");
+        mAbsoluteStart = start;
+        mAbsoluteEnd = end;
+        mEventType = eventType;
+        mEntityType = Objects.requireNonNull(entityType);
+        mResultId = resultId;
+        mInvocationMethod = invocationMethod;
+    }
+
+    private SelectionEvent(Parcel in) {
+        mAbsoluteStart = in.readInt();
+        mAbsoluteEnd = in.readInt();
+        mEventType = in.readInt();
+        mEntityType = in.readString();
+        mWidgetVersion = in.readInt() > 0 ? in.readString() : null;
+        mPackageName = in.readString();
+        mWidgetType = in.readString();
+        mInvocationMethod = in.readInt();
+        mResultId = in.readString();
+        mEventTime = in.readLong();
+        mDurationSinceSessionStart = in.readLong();
+        mDurationSincePreviousEvent = in.readLong();
+        mEventIndex = in.readInt();
+        mSessionId = in.readInt() > 0
+                ? TextClassificationSessionId.CREATOR.createFromParcel(in) : null;
+        mStart = in.readInt();
+        mEnd = in.readInt();
+        mSmartStart = in.readInt();
+        mSmartEnd = in.readInt();
+        mSystemTcMetadata = in.readParcelable(null);
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mAbsoluteStart);
+        dest.writeInt(mAbsoluteEnd);
+        dest.writeInt(mEventType);
+        dest.writeString(mEntityType);
+        dest.writeInt(mWidgetVersion != null ? 1 : 0);
+        if (mWidgetVersion != null) {
+            dest.writeString(mWidgetVersion);
+        }
+        dest.writeString(mPackageName);
+        dest.writeString(mWidgetType);
+        dest.writeInt(mInvocationMethod);
+        dest.writeString(mResultId);
+        dest.writeLong(mEventTime);
+        dest.writeLong(mDurationSinceSessionStart);
+        dest.writeLong(mDurationSincePreviousEvent);
+        dest.writeInt(mEventIndex);
+        dest.writeInt(mSessionId != null ? 1 : 0);
+        if (mSessionId != null) {
+            mSessionId.writeToParcel(dest, flags);
+        }
+        dest.writeInt(mStart);
+        dest.writeInt(mEnd);
+        dest.writeInt(mSmartStart);
+        dest.writeInt(mSmartEnd);
+        dest.writeParcelable(mSystemTcMetadata, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Creates a "selection started" event.
+     *
+     * @param invocationMethod  the way the selection was triggered
+     * @param start  the index of the selected text
+     */
+    @NonNull
+    public static SelectionEvent createSelectionStartedEvent(
+            @SelectionEvent.InvocationMethod int invocationMethod, int start) {
+        return new SelectionEvent(
+                start, start + 1, SelectionEvent.EVENT_SELECTION_STARTED,
+                TextClassifier.TYPE_UNKNOWN, invocationMethod, NO_SIGNATURE);
+    }
+
+    /**
+     * Creates a "selection modified" event.
+     * Use when the user modifies the selection.
+     *
+     * @param start  the start (inclusive) index of the selection
+     * @param end  the end (exclusive) index of the selection
+     *
+     * @throws IllegalArgumentException if end is less than start
+     */
+    @NonNull
+    public static SelectionEvent createSelectionModifiedEvent(int start, int end) {
+        Preconditions.checkArgument(end >= start, "end cannot be less than start");
+        return new SelectionEvent(
+                start, end, SelectionEvent.EVENT_SELECTION_MODIFIED,
+                TextClassifier.TYPE_UNKNOWN, INVOCATION_UNKNOWN, NO_SIGNATURE);
+    }
+
+    /**
+     * Creates a "selection modified" event.
+     * Use when the user modifies the selection and the selection's entity type is known.
+     *
+     * @param start  the start (inclusive) index of the selection
+     * @param end  the end (exclusive) index of the selection
+     * @param classification  the TextClassification object returned by the TextClassifier that
+     *      classified the selected text
+     *
+     * @throws IllegalArgumentException if end is less than start
+     */
+    @NonNull
+    public static SelectionEvent createSelectionModifiedEvent(
+            int start, int end, @NonNull TextClassification classification) {
+        Preconditions.checkArgument(end >= start, "end cannot be less than start");
+        Objects.requireNonNull(classification);
+        final String entityType = classification.getEntityCount() > 0
+                ? classification.getEntity(0)
+                : TextClassifier.TYPE_UNKNOWN;
+        return new SelectionEvent(
+                start, end, SelectionEvent.EVENT_SELECTION_MODIFIED,
+                entityType, INVOCATION_UNKNOWN, classification.getId());
+    }
+
+    /**
+     * Creates a "selection modified" event.
+     * Use when a TextClassifier modifies the selection.
+     *
+     * @param start  the start (inclusive) index of the selection
+     * @param end  the end (exclusive) index of the selection
+     * @param selection  the TextSelection object returned by the TextClassifier for the
+     *      specified selection
+     *
+     * @throws IllegalArgumentException if end is less than start
+     */
+    @NonNull
+    public static SelectionEvent createSelectionModifiedEvent(
+            int start, int end, @NonNull TextSelection selection) {
+        Preconditions.checkArgument(end >= start, "end cannot be less than start");
+        Objects.requireNonNull(selection);
+        final String entityType = selection.getEntityCount() > 0
+                ? selection.getEntity(0)
+                : TextClassifier.TYPE_UNKNOWN;
+        return new SelectionEvent(
+                start, end, SelectionEvent.EVENT_AUTO_SELECTION,
+                entityType, INVOCATION_UNKNOWN, selection.getId());
+    }
+
+    /**
+     * Creates an event specifying an action taken on a selection.
+     * Use when the user clicks on an action to act on the selected text.
+     *
+     * @param start  the start (inclusive) index of the selection
+     * @param end  the end (exclusive) index of the selection
+     * @param actionType  the action that was performed on the selection
+     *
+     * @throws IllegalArgumentException if end is less than start
+     */
+    @NonNull
+    public static SelectionEvent createSelectionActionEvent(
+            int start, int end, @SelectionEvent.ActionType int actionType) {
+        Preconditions.checkArgument(end >= start, "end cannot be less than start");
+        checkActionType(actionType);
+        return new SelectionEvent(
+                start, end, actionType, TextClassifier.TYPE_UNKNOWN, INVOCATION_UNKNOWN,
+                NO_SIGNATURE);
+    }
+
+    /**
+     * Creates an event specifying an action taken on a selection.
+     * Use when the user clicks on an action to act on the selected text and the selection's
+     * entity type is known.
+     *
+     * @param start  the start (inclusive) index of the selection
+     * @param end  the end (exclusive) index of the selection
+     * @param actionType  the action that was performed on the selection
+     * @param classification  the TextClassification object returned by the TextClassifier that
+     *      classified the selected text
+     *
+     * @throws IllegalArgumentException if end is less than start
+     * @throws IllegalArgumentException If actionType is not a valid SelectionEvent actionType
+     */
+    @NonNull
+    public static SelectionEvent createSelectionActionEvent(
+            int start, int end, @SelectionEvent.ActionType int actionType,
+            @NonNull TextClassification classification) {
+        Preconditions.checkArgument(end >= start, "end cannot be less than start");
+        Objects.requireNonNull(classification);
+        checkActionType(actionType);
+        final String entityType = classification.getEntityCount() > 0
+                ? classification.getEntity(0)
+                : TextClassifier.TYPE_UNKNOWN;
+        return new SelectionEvent(start, end, actionType, entityType, INVOCATION_UNKNOWN,
+                classification.getId());
+    }
+
+    /**
+     * @throws IllegalArgumentException If eventType is not an {@link SelectionEvent.ActionType}
+     */
+    private static void checkActionType(@SelectionEvent.EventType int eventType)
+            throws IllegalArgumentException {
+        switch (eventType) {
+            case SelectionEvent.ACTION_OVERTYPE:  // fall through
+            case SelectionEvent.ACTION_COPY:  // fall through
+            case SelectionEvent.ACTION_PASTE:  // fall through
+            case SelectionEvent.ACTION_CUT:  // fall through
+            case SelectionEvent.ACTION_SHARE:  // fall through
+            case SelectionEvent.ACTION_SMART_SHARE:  // fall through
+            case SelectionEvent.ACTION_DRAG:  // fall through
+            case SelectionEvent.ACTION_ABANDON:  // fall through
+            case SelectionEvent.ACTION_SELECT_ALL:  // fall through
+            case SelectionEvent.ACTION_RESET:  // fall through
+            case SelectionEvent.ACTION_OTHER:  // fall through
+                return;
+            default:
+                throw new IllegalArgumentException(
+                        String.format(Locale.US, "%d is not an eventType", eventType));
+        }
+    }
+
+    int getAbsoluteStart() {
+        return mAbsoluteStart;
+    }
+
+    int getAbsoluteEnd() {
+        return mAbsoluteEnd;
+    }
+
+    /**
+     * Returns the type of event that was triggered. e.g. {@link #ACTION_COPY}.
+     */
+    @EventType
+    public int getEventType() {
+        return mEventType;
+    }
+
+    /**
+     * Sets the event type.
+     * @hide
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void setEventType(@EventType int eventType) {
+        mEventType = eventType;
+    }
+
+    /**
+     * Returns the type of entity that is associated with this event. e.g.
+     * {@link android.view.textclassifier.TextClassifier#TYPE_EMAIL}.
+     */
+    @EntityType
+    @NonNull
+    public String getEntityType() {
+        return mEntityType;
+    }
+
+    void setEntityType(@EntityType String entityType) {
+        mEntityType = Objects.requireNonNull(entityType);
+    }
+
+    /**
+     * Returns the package name of the app that this event originated in.
+     */
+    @NonNull
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * Sets the information about the {@link SystemTextClassifier} that sent this request.
+     *
+     * @hide
+     */
+    void setSystemTextClassifierMetadata(@Nullable SystemTextClassifierMetadata systemTcMetadata) {
+        mSystemTcMetadata = systemTcMetadata;
+    }
+
+    /**
+     * Returns the information about the {@link SystemTextClassifier} that sent this request.
+     *
+     * @hide
+     */
+    @Nullable
+    public SystemTextClassifierMetadata getSystemTextClassifierMetadata() {
+        return mSystemTcMetadata;
+    }
+
+    /**
+     * Returns the type of widget that was involved in triggering this event.
+     */
+    @WidgetType
+    @NonNull
+    public String getWidgetType() {
+        return mWidgetType;
+    }
+
+    /**
+     * Returns a string version info for the widget this event was triggered in.
+     */
+    @Nullable
+    public String getWidgetVersion() {
+        return mWidgetVersion;
+    }
+
+    /**
+     * Sets the {@link TextClassificationContext} for this event.
+     * @hide
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void setTextClassificationSessionContext(TextClassificationContext context) {
+        mPackageName = context.getPackageName();
+        mWidgetType = context.getWidgetType();
+        mWidgetVersion = context.getWidgetVersion();
+        mSystemTcMetadata = context.getSystemTextClassifierMetadata();
+    }
+
+    /**
+     * Returns the way the selection mode was invoked.
+     */
+    public @InvocationMethod int getInvocationMethod() {
+        return mInvocationMethod;
+    }
+
+    /**
+     * Sets the invocationMethod for this event.
+     * @hide
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void setInvocationMethod(@InvocationMethod int invocationMethod) {
+        mInvocationMethod = invocationMethod;
+    }
+
+    /**
+     * Returns the id of the text classifier result associated with this event.
+     */
+    @Nullable
+    public String getResultId() {
+        return mResultId;
+    }
+
+    SelectionEvent setResultId(@Nullable String resultId) {
+        mResultId = resultId;
+        return this;
+    }
+
+    /**
+     * Returns the time this event was triggered.
+     */
+    public long getEventTime() {
+        return mEventTime;
+    }
+
+    SelectionEvent setEventTime(long timeMs) {
+        mEventTime = timeMs;
+        return this;
+    }
+
+    /**
+     * Returns the duration in ms between when this event was triggered and when the first event in
+     * the selection session was triggered.
+     */
+    public long getDurationSinceSessionStart() {
+        return mDurationSinceSessionStart;
+    }
+
+    SelectionEvent setDurationSinceSessionStart(long durationMs) {
+        mDurationSinceSessionStart = durationMs;
+        return this;
+    }
+
+    /**
+     * Returns the duration in ms between when this event was triggered and when the previous event
+     * in the selection session was triggered.
+     */
+    public long getDurationSincePreviousEvent() {
+        return mDurationSincePreviousEvent;
+    }
+
+    SelectionEvent setDurationSincePreviousEvent(long durationMs) {
+        this.mDurationSincePreviousEvent = durationMs;
+        return this;
+    }
+
+    /**
+     * Returns the index (e.g. 1st event, 2nd event, etc.) of this event in the selection session.
+     */
+    public int getEventIndex() {
+        return mEventIndex;
+    }
+
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public SelectionEvent setEventIndex(int index) {
+        mEventIndex = index;
+        return this;
+    }
+
+    /**
+     * Returns the selection session id.
+     */
+    @Nullable
+    public TextClassificationSessionId getSessionId() {
+        return mSessionId;
+    }
+
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public SelectionEvent setSessionId(@Nullable TextClassificationSessionId id) {
+        mSessionId = id;
+        return this;
+    }
+
+    /**
+     * Returns the start index of this events relative to the index of the start selection
+     * event in the selection session.
+     */
+    public int getStart() {
+        return mStart;
+    }
+
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public SelectionEvent setStart(int start) {
+        mStart = start;
+        return this;
+    }
+
+    /**
+     * Returns the end index of this events relative to the index of the start selection
+     * event in the selection session.
+     */
+    public int getEnd() {
+        return mEnd;
+    }
+
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public SelectionEvent setEnd(int end) {
+        mEnd = end;
+        return this;
+    }
+
+    /**
+     * Returns the start index of this events relative to the index of the smart selection
+     * event in the selection session.
+     */
+    public int getSmartStart() {
+        return mSmartStart;
+    }
+
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public SelectionEvent setSmartStart(int start) {
+        this.mSmartStart = start;
+        return this;
+    }
+
+    /**
+     * Returns the end index of this events relative to the index of the smart selection
+     * event in the selection session.
+     */
+    public int getSmartEnd() {
+        return mSmartEnd;
+    }
+
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public SelectionEvent setSmartEnd(int end) {
+        mSmartEnd = end;
+        return this;
+    }
+
+    boolean isTerminal() {
+        return isTerminal(mEventType);
+    }
+
+    /**
+     * Returns true if the eventType is a terminal event type. Otherwise returns false.
+     * A terminal event is an event that ends a selection interaction.
+     */
+    public static boolean isTerminal(@EventType int eventType) {
+        switch (eventType) {
+            case ACTION_OVERTYPE:  // fall through
+            case ACTION_COPY:  // fall through
+            case ACTION_PASTE:  // fall through
+            case ACTION_CUT:  // fall through
+            case ACTION_SHARE:  // fall through
+            case ACTION_SMART_SHARE:  // fall through
+            case ACTION_DRAG:  // fall through
+            case ACTION_ABANDON:  // fall through
+            case ACTION_OTHER:  // fall through
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mAbsoluteStart, mAbsoluteEnd, mEventType, mEntityType,
+                mWidgetVersion, mPackageName, mWidgetType, mInvocationMethod, mResultId,
+                mEventTime, mDurationSinceSessionStart, mDurationSincePreviousEvent,
+                mEventIndex, mSessionId, mStart, mEnd, mSmartStart, mSmartEnd, mSystemTcMetadata);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof SelectionEvent)) {
+            return false;
+        }
+
+        final SelectionEvent other = (SelectionEvent) obj;
+        return mAbsoluteStart == other.mAbsoluteStart
+                && mAbsoluteEnd == other.mAbsoluteEnd
+                && mEventType == other.mEventType
+                && Objects.equals(mEntityType, other.mEntityType)
+                && Objects.equals(mWidgetVersion, other.mWidgetVersion)
+                && Objects.equals(mPackageName, other.mPackageName)
+                && Objects.equals(mWidgetType, other.mWidgetType)
+                && mInvocationMethod == other.mInvocationMethod
+                && Objects.equals(mResultId, other.mResultId)
+                && mEventTime == other.mEventTime
+                && mDurationSinceSessionStart == other.mDurationSinceSessionStart
+                && mDurationSincePreviousEvent == other.mDurationSincePreviousEvent
+                && mEventIndex == other.mEventIndex
+                && Objects.equals(mSessionId, other.mSessionId)
+                && mStart == other.mStart
+                && mEnd == other.mEnd
+                && mSmartStart == other.mSmartStart
+                && mSmartEnd == other.mSmartEnd
+                && mSystemTcMetadata == other.mSystemTcMetadata;
+    }
+
+    @Override
+    public String toString() {
+        return String.format(Locale.US,
+                "SelectionEvent {absoluteStart=%d, absoluteEnd=%d, eventType=%d, entityType=%s, "
+                        + "widgetVersion=%s, packageName=%s, widgetType=%s, invocationMethod=%s, "
+                        + "resultId=%s, eventTime=%d, durationSinceSessionStart=%d, "
+                        + "durationSincePreviousEvent=%d, eventIndex=%d,"
+                        + "sessionId=%s, start=%d, end=%d, smartStart=%d, smartEnd=%d, "
+                        + "systemTcMetadata=%s}",
+                mAbsoluteStart, mAbsoluteEnd, mEventType, mEntityType,
+                mWidgetVersion, mPackageName, mWidgetType, mInvocationMethod,
+                mResultId, mEventTime, mDurationSinceSessionStart, mDurationSincePreviousEvent,
+                mEventIndex, mSessionId, mStart, mEnd, mSmartStart, mSmartEnd, mSystemTcMetadata);
+    }
+
+    public static final @android.annotation.NonNull Creator<SelectionEvent> CREATOR = new Creator<SelectionEvent>() {
+        @Override
+        public SelectionEvent createFromParcel(Parcel in) {
+            return new SelectionEvent(in);
+        }
+
+        @Override
+        public SelectionEvent[] newArray(int size) {
+            return new SelectionEvent[size];
+        }
+    };
+}
diff --git a/android/view/textclassifier/SelectionSessionLogger.java b/android/view/textclassifier/SelectionSessionLogger.java
new file mode 100644
index 0000000..e7d896e
--- /dev/null
+++ b/android/view/textclassifier/SelectionSessionLogger.java
@@ -0,0 +1,55 @@
+/*
+ * 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.view.textclassifier;
+
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * A helper for logging selection session events.
+ *
+ * @hide
+ */
+public final class SelectionSessionLogger {
+    // Keep this in sync with the ResultIdUtils in libtextclassifier.
+    private static final String CLASSIFIER_ID = "androidtc";
+
+    static boolean isPlatformLocalTextClassifierSmartSelection(String signature) {
+        return SelectionSessionLogger.CLASSIFIER_ID.equals(
+                SelectionSessionLogger.SignatureParser.getClassifierId(signature));
+    }
+
+    /**
+     * Helper for creating and parsing string ids for
+     * {@link android.view.textclassifier.TextClassifierImpl}.
+     */
+    @VisibleForTesting
+    public static final class SignatureParser {
+
+        static String getClassifierId(@Nullable String signature) {
+            if (signature == null) {
+                return "";
+            }
+            final int end = signature.indexOf("|");
+            if (end >= 0) {
+                return signature.substring(0, end);
+            }
+            return "";
+        }
+    }
+}
diff --git a/android/view/textclassifier/SystemTextClassifier.java b/android/view/textclassifier/SystemTextClassifier.java
new file mode 100644
index 0000000..2c844eb
--- /dev/null
+++ b/android/view/textclassifier/SystemTextClassifier.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.WorkerThread;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.service.textclassifier.ITextClassifierCallback;
+import android.service.textclassifier.ITextClassifierService;
+import android.service.textclassifier.TextClassifierService;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * proxy to the request to TextClassifierService via the TextClassificationManagerService.
+ *
+ * @hide
+ */
+@VisibleForTesting(visibility = Visibility.PACKAGE)
+public final class SystemTextClassifier implements TextClassifier {
+
+    private static final String LOG_TAG = TextClassifier.LOG_TAG;
+
+    private final ITextClassifierService mManagerService;
+    private final TextClassificationConstants mSettings;
+    private final TextClassifier mFallback;
+    private TextClassificationSessionId mSessionId;
+    // NOTE: Always set this before sending a request to the manager service otherwise the
+    // manager service will throw a remote exception.
+    @NonNull
+    private final SystemTextClassifierMetadata mSystemTcMetadata;
+
+    /**
+     * Constructor of {@link SystemTextClassifier}
+     *
+     * @param context the context of the request.
+     * @param settings TextClassifier specific settings.
+     * @param useDefault whether to use the default text classifier to handle this request
+     */
+    public SystemTextClassifier(
+            Context context,
+            TextClassificationConstants settings,
+            boolean useDefault) throws ServiceManager.ServiceNotFoundException {
+        mManagerService = ITextClassifierService.Stub.asInterface(
+                ServiceManager.getServiceOrThrow(Context.TEXT_CLASSIFICATION_SERVICE));
+        mSettings = Objects.requireNonNull(settings);
+        mFallback = TextClassifier.NO_OP;
+        // NOTE: Always set this before sending a request to the manager service otherwise the
+        // manager service will throw a remote exception.
+        mSystemTcMetadata = new SystemTextClassifierMetadata(
+                Objects.requireNonNull(context.getOpPackageName()), context.getUserId(),
+                useDefault);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    @WorkerThread
+    public TextSelection suggestSelection(TextSelection.Request request) {
+        Objects.requireNonNull(request);
+        Utils.checkMainThread();
+        try {
+            request.setSystemTextClassifierMetadata(mSystemTcMetadata);
+            final BlockingCallback<TextSelection> callback =
+                    new BlockingCallback<>("textselection", mSettings);
+            mManagerService.onSuggestSelection(mSessionId, request, callback);
+            final TextSelection selection = callback.get();
+            if (selection != null) {
+                return selection;
+            }
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG, "Error suggesting selection for text. Using fallback.", e);
+        }
+        return mFallback.suggestSelection(request);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    @WorkerThread
+    public TextClassification classifyText(TextClassification.Request request) {
+        Objects.requireNonNull(request);
+        Utils.checkMainThread();
+        try {
+            request.setSystemTextClassifierMetadata(mSystemTcMetadata);
+            final BlockingCallback<TextClassification> callback =
+                    new BlockingCallback<>("textclassification", mSettings);
+            mManagerService.onClassifyText(mSessionId, request, callback);
+            final TextClassification classification = callback.get();
+            if (classification != null) {
+                return classification;
+            }
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG, "Error classifying text. Using fallback.", e);
+        }
+        return mFallback.classifyText(request);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    @WorkerThread
+    public TextLinks generateLinks(@NonNull TextLinks.Request request) {
+        Objects.requireNonNull(request);
+        Utils.checkMainThread();
+        if (!Utils.checkTextLength(request.getText(), getMaxGenerateLinksTextLength())) {
+            return mFallback.generateLinks(request);
+        }
+        if (!mSettings.isSmartLinkifyEnabled() && request.isLegacyFallback()) {
+            return Utils.generateLegacyLinks(request);
+        }
+
+        try {
+            request.setSystemTextClassifierMetadata(mSystemTcMetadata);
+            final BlockingCallback<TextLinks> callback =
+                    new BlockingCallback<>("textlinks", mSettings);
+            mManagerService.onGenerateLinks(mSessionId, request, callback);
+            final TextLinks links = callback.get();
+            if (links != null) {
+                return links;
+            }
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG, "Error generating links. Using fallback.", e);
+        }
+        return mFallback.generateLinks(request);
+    }
+
+    @Override
+    public void onSelectionEvent(SelectionEvent event) {
+        Objects.requireNonNull(event);
+        Utils.checkMainThread();
+
+        try {
+            event.setSystemTextClassifierMetadata(mSystemTcMetadata);
+            mManagerService.onSelectionEvent(mSessionId, event);
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG, "Error reporting selection event.", e);
+        }
+    }
+
+    @Override
+    public void onTextClassifierEvent(@NonNull TextClassifierEvent event) {
+        Objects.requireNonNull(event);
+        Utils.checkMainThread();
+
+        try {
+            final TextClassificationContext tcContext =
+                    event.getEventContext() == null ? new TextClassificationContext.Builder(
+                            mSystemTcMetadata.getCallingPackageName(), WIDGET_TYPE_UNKNOWN).build()
+                            : event.getEventContext();
+            tcContext.setSystemTextClassifierMetadata(mSystemTcMetadata);
+            event.setEventContext(tcContext);
+            mManagerService.onTextClassifierEvent(mSessionId, event);
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG, "Error reporting textclassifier event.", e);
+        }
+    }
+
+    @Override
+    public TextLanguage detectLanguage(TextLanguage.Request request) {
+        Objects.requireNonNull(request);
+        Utils.checkMainThread();
+
+        try {
+            request.setSystemTextClassifierMetadata(mSystemTcMetadata);
+            final BlockingCallback<TextLanguage> callback =
+                    new BlockingCallback<>("textlanguage", mSettings);
+            mManagerService.onDetectLanguage(mSessionId, request, callback);
+            final TextLanguage textLanguage = callback.get();
+            if (textLanguage != null) {
+                return textLanguage;
+            }
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG, "Error detecting language.", e);
+        }
+        return mFallback.detectLanguage(request);
+    }
+
+    @Override
+    public ConversationActions suggestConversationActions(ConversationActions.Request request) {
+        Objects.requireNonNull(request);
+        Utils.checkMainThread();
+
+        try {
+            request.setSystemTextClassifierMetadata(mSystemTcMetadata);
+            final BlockingCallback<ConversationActions> callback =
+                    new BlockingCallback<>("conversation-actions", mSettings);
+            mManagerService.onSuggestConversationActions(mSessionId, request, callback);
+            final ConversationActions conversationActions = callback.get();
+            if (conversationActions != null) {
+                return conversationActions;
+            }
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG, "Error reporting selection event.", e);
+        }
+        return mFallback.suggestConversationActions(request);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    @WorkerThread
+    public int getMaxGenerateLinksTextLength() {
+        // TODO: retrieve this from the bound service.
+        return mSettings.getGenerateLinksMaxTextLength();
+    }
+
+    @Override
+    public void destroy() {
+        try {
+            if (mSessionId != null) {
+                mManagerService.onDestroyTextClassificationSession(mSessionId);
+            }
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG, "Error destroying classification session.", e);
+        }
+    }
+
+    @Override
+    public void dump(@NonNull IndentingPrintWriter printWriter) {
+        printWriter.println("SystemTextClassifier:");
+        printWriter.increaseIndent();
+        printWriter.printPair("mFallback", mFallback);
+        printWriter.printPair("mSessionId", mSessionId);
+        printWriter.printPair("mSystemTcMetadata",  mSystemTcMetadata);
+        printWriter.decreaseIndent();
+        printWriter.println();
+    }
+
+    /**
+     * Attempts to initialize a new classification session.
+     *
+     * @param classificationContext the classification context
+     * @param sessionId the session's id
+     */
+    void initializeRemoteSession(
+            @NonNull TextClassificationContext classificationContext,
+            @NonNull TextClassificationSessionId sessionId) {
+        mSessionId = Objects.requireNonNull(sessionId);
+        try {
+            classificationContext.setSystemTextClassifierMetadata(mSystemTcMetadata);
+            mManagerService.onCreateTextClassificationSession(classificationContext, mSessionId);
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG, "Error starting a new classification session.", e);
+        }
+    }
+
+    private static final class BlockingCallback<T extends Parcelable>
+            extends ITextClassifierCallback.Stub {
+        private final ResponseReceiver<T> mReceiver;
+
+        BlockingCallback(String name, TextClassificationConstants settings) {
+            mReceiver = new ResponseReceiver<>(name, settings);
+        }
+
+        @Override
+        public void onSuccess(Bundle result) {
+            mReceiver.onSuccess(TextClassifierService.getResponse(result));
+        }
+
+        @Override
+        public void onFailure() {
+            mReceiver.onFailure();
+        }
+
+        public T get() {
+            return mReceiver.get();
+        }
+
+    }
+
+    private static final class ResponseReceiver<T> {
+
+        private final CountDownLatch mLatch = new CountDownLatch(1);
+        private final String mName;
+        private final TextClassificationConstants mSettings;
+        private T mResponse;
+
+        private ResponseReceiver(String name, TextClassificationConstants settings) {
+            mName = name;
+            mSettings = settings;
+        }
+
+        public void onSuccess(T response) {
+            mResponse = response;
+            mLatch.countDown();
+        }
+
+        public void onFailure() {
+            Log.e(LOG_TAG, "Request failed at " + mName, null);
+            mLatch.countDown();
+        }
+
+        @Nullable
+        public T get() {
+            // If this is running on the main thread, do not block for a response.
+            // The response will unfortunately be null and the TextClassifier should depend on its
+            // fallback.
+            // NOTE that TextClassifier calls should preferably always be called on a worker thread.
+            if (Looper.myLooper() != Looper.getMainLooper()) {
+                try {
+                    boolean success = mLatch.await(
+                            mSettings.getSystemTextClassifierApiTimeoutInSecond(),
+                            TimeUnit.SECONDS);
+                    if (!success) {
+                        Log.w(LOG_TAG, "Timeout in ResponseReceiver.get(): " + mName);
+                    }
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    Log.e(LOG_TAG, "Interrupted during ResponseReceiver.get(): " + mName, e);
+                }
+            }
+            return mResponse;
+        }
+    }
+}
diff --git a/android/view/textclassifier/SystemTextClassifierMetadata.java b/android/view/textclassifier/SystemTextClassifierMetadata.java
new file mode 100644
index 0000000..971e3e2
--- /dev/null
+++ b/android/view/textclassifier/SystemTextClassifierMetadata.java
@@ -0,0 +1,121 @@
+/*
+ * 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.view.textclassifier;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * SystemTextClassifier specific information.
+ * <p>
+ * This contains information requires for the TextClassificationManagerService to process the
+ * requests from the application, e.g. user id, calling package name and etc. Centrialize the data
+ * into this class helps to extend the scalability if we want to add new fields.
+ * @hide
+ */
+@VisibleForTesting(visibility = Visibility.PACKAGE)
+public final class SystemTextClassifierMetadata implements Parcelable {
+
+    /* The name of the package that sent the TC request. */
+    @NonNull
+    private final String mCallingPackageName;
+    /* The id of the user that sent the TC request. */
+    @UserIdInt
+    private final int mUserId;
+    /* Whether to use the default text classifier to handle the request. */
+    private final boolean mUseDefaultTextClassifier;
+
+    public SystemTextClassifierMetadata(@NonNull String packageName, @UserIdInt int userId,
+            boolean useDefaultTextClassifier) {
+        Objects.requireNonNull(packageName);
+        mCallingPackageName = packageName;
+        mUserId = userId;
+        mUseDefaultTextClassifier = useDefaultTextClassifier;
+    }
+
+    /**
+     * Returns the id of the user that sent the TC request.
+     */
+    @UserIdInt
+    public int getUserId() {
+        return mUserId;
+    }
+
+    /**
+     * Returns the name of the package that sent the TC request.
+     * This returns {@code null} if no calling package name is set.
+     */
+    @NonNull
+    public String getCallingPackageName() {
+        return mCallingPackageName;
+    }
+
+    /**
+     * Returns whether to use the default text classifier to handle TC request.
+     */
+    public boolean useDefaultTextClassifier() {
+        return mUseDefaultTextClassifier;
+    }
+
+    @Override
+    public String toString() {
+        return String.format(Locale.US,
+                "SystemTextClassifierMetadata {callingPackageName=%s, userId=%d, "
+                        + "useDefaultTextClassifier=%b}",
+                mCallingPackageName, mUserId, mUseDefaultTextClassifier);
+    }
+
+    private static SystemTextClassifierMetadata readFromParcel(Parcel in) {
+        final String packageName = in.readString();
+        final int userId = in.readInt();
+        final boolean useDefaultTextClassifier = in.readBoolean();
+        return new SystemTextClassifierMetadata(packageName, userId, useDefaultTextClassifier);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mCallingPackageName);
+        dest.writeInt(mUserId);
+        dest.writeBoolean(mUseDefaultTextClassifier);
+    }
+
+    public static final @NonNull Creator<SystemTextClassifierMetadata> CREATOR =
+            new Creator<SystemTextClassifierMetadata>() {
+        @Override
+        public SystemTextClassifierMetadata createFromParcel(Parcel in) {
+            return readFromParcel(in);
+        }
+
+        @Override
+        public SystemTextClassifierMetadata[] newArray(int size) {
+            return new SystemTextClassifierMetadata[size];
+        }
+    };
+}
diff --git a/android/view/textclassifier/TextClassification.java b/android/view/textclassifier/TextClassification.java
new file mode 100644
index 0000000..7db35d4
--- /dev/null
+++ b/android/view/textclassifier/TextClassification.java
@@ -0,0 +1,810 @@
+/*
+ * 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.view.textclassifier;
+
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.SpannedString;
+import android.util.ArrayMap;
+import android.view.View.OnClickListener;
+import android.view.textclassifier.TextClassifier.EntityType;
+import android.view.textclassifier.TextClassifier.Utils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Information for generating a widget to handle classified text.
+ *
+ * <p>A TextClassification object contains icons, labels, onClickListeners and intents that may
+ * be used to build a widget that can be used to act on classified text. There is the concept of a
+ * <i>primary action</i> and other <i>secondary actions</i>.
+ *
+ * <p>e.g. building a view that, when clicked, shares the classified text with the preferred app:
+ *
+ * <pre>{@code
+ *   // Called preferably outside the UiThread.
+ *   TextClassification classification = textClassifier.classifyText(allText, 10, 25);
+ *
+ *   // Called on the UiThread.
+ *   Button button = new Button(context);
+ *   button.setCompoundDrawablesWithIntrinsicBounds(classification.getIcon(), null, null, null);
+ *   button.setText(classification.getLabel());
+ *   button.setOnClickListener(v -> classification.getActions().get(0).getActionIntent().send());
+ * }</pre>
+ *
+ * <p>e.g. starting an action mode with menu items that can handle the classified text:
+ *
+ * <pre>{@code
+ *   // Called preferably outside the UiThread.
+ *   final TextClassification classification = textClassifier.classifyText(allText, 10, 25);
+ *
+ *   // Called on the UiThread.
+ *   view.startActionMode(new ActionMode.Callback() {
+ *
+ *       public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ *           for (int i = 0; i < classification.getActions().size(); ++i) {
+ *              RemoteAction action = classification.getActions().get(i);
+ *              menu.add(Menu.NONE, i, 20, action.getTitle())
+ *                 .setIcon(action.getIcon());
+ *           }
+ *           return true;
+ *       }
+ *
+ *       public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ *           classification.getActions().get(item.getItemId()).getActionIntent().send();
+ *           return true;
+ *       }
+ *
+ *       ...
+ *   });
+ * }</pre>
+ */
+public final class TextClassification implements Parcelable {
+
+    /**
+     * @hide
+     */
+    public static final TextClassification EMPTY = new TextClassification.Builder().build();
+
+    private static final String LOG_TAG = "TextClassification";
+    // TODO(toki): investigate a way to derive this based on device properties.
+    private static final int MAX_LEGACY_ICON_SIZE = 192;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {IntentType.UNSUPPORTED, IntentType.ACTIVITY, IntentType.SERVICE})
+    private @interface IntentType {
+        int UNSUPPORTED = -1;
+        int ACTIVITY = 0;
+        int SERVICE = 1;
+    }
+
+    @NonNull private final String mText;
+    @Nullable private final Drawable mLegacyIcon;
+    @Nullable private final String mLegacyLabel;
+    @Nullable private final Intent mLegacyIntent;
+    @Nullable private final OnClickListener mLegacyOnClickListener;
+    @NonNull private final List<RemoteAction> mActions;
+    @NonNull private final EntityConfidence mEntityConfidence;
+    @Nullable private final String mId;
+    @NonNull private final Bundle mExtras;
+
+    private TextClassification(
+            @Nullable String text,
+            @Nullable Drawable legacyIcon,
+            @Nullable String legacyLabel,
+            @Nullable Intent legacyIntent,
+            @Nullable OnClickListener legacyOnClickListener,
+            @NonNull List<RemoteAction> actions,
+            @NonNull EntityConfidence entityConfidence,
+            @Nullable String id,
+            @NonNull Bundle extras) {
+        mText = text;
+        mLegacyIcon = legacyIcon;
+        mLegacyLabel = legacyLabel;
+        mLegacyIntent = legacyIntent;
+        mLegacyOnClickListener = legacyOnClickListener;
+        mActions = Collections.unmodifiableList(actions);
+        mEntityConfidence = Objects.requireNonNull(entityConfidence);
+        mId = id;
+        mExtras = extras;
+    }
+
+    /**
+     * Gets the classified text.
+     */
+    @Nullable
+    public String getText() {
+        return mText;
+    }
+
+    /**
+     * Returns the number of entities found in the classified text.
+     */
+    @IntRange(from = 0)
+    public int getEntityCount() {
+        return mEntityConfidence.getEntities().size();
+    }
+
+    /**
+     * Returns the entity at the specified index. Entities are ordered from high confidence
+     * to low confidence.
+     *
+     * @throws IndexOutOfBoundsException if the specified index is out of range.
+     * @see #getEntityCount() for the number of entities available.
+     */
+    @NonNull
+    public @EntityType String getEntity(int index) {
+        return mEntityConfidence.getEntities().get(index);
+    }
+
+    /**
+     * Returns the confidence score for the specified entity. The value ranges from
+     * 0 (low confidence) to 1 (high confidence). 0 indicates that the entity was not found for the
+     * classified text.
+     */
+    @FloatRange(from = 0.0, to = 1.0)
+    public float getConfidenceScore(@EntityType String entity) {
+        return mEntityConfidence.getConfidenceScore(entity);
+    }
+
+    /**
+     * Returns a list of actions that may be performed on the text. The list is ordered based on
+     * the likelihood that a user will use the action, with the most likely action appearing first.
+     */
+    public List<RemoteAction> getActions() {
+        return mActions;
+    }
+
+    /**
+     * Returns an icon that may be rendered on a widget used to act on the classified text.
+     *
+     * <p><strong>NOTE: </strong>This field is not parcelable and only represents the icon of the
+     * first {@link RemoteAction} (if one exists) when this object is read from a parcel.
+     *
+     * @deprecated Use {@link #getActions()} instead.
+     */
+    @Deprecated
+    @Nullable
+    public Drawable getIcon() {
+        return mLegacyIcon;
+    }
+
+    /**
+     * Returns a label that may be rendered on a widget used to act on the classified text.
+     *
+     * <p><strong>NOTE: </strong>This field is not parcelable and only represents the label of the
+     * first {@link RemoteAction} (if one exists) when this object is read from a parcel.
+     *
+     * @deprecated Use {@link #getActions()} instead.
+     */
+    @Deprecated
+    @Nullable
+    public CharSequence getLabel() {
+        return mLegacyLabel;
+    }
+
+    /**
+     * Returns an intent that may be fired to act on the classified text.
+     *
+     * <p><strong>NOTE: </strong>This field is not parcelled and will always return null when this
+     * object is read from a parcel.
+     *
+     * @deprecated Use {@link #getActions()} instead.
+     */
+    @Deprecated
+    @Nullable
+    public Intent getIntent() {
+        return mLegacyIntent;
+    }
+
+    /**
+     * Returns the OnClickListener that may be triggered to act on the classified text.
+     *
+     * <p><strong>NOTE: </strong>This field is not parcelable and only represents the first
+     * {@link RemoteAction} (if one exists) when this object is read from a parcel.
+     *
+     * @deprecated Use {@link #getActions()} instead.
+     */
+    @Nullable
+    public OnClickListener getOnClickListener() {
+        return mLegacyOnClickListener;
+    }
+
+    /**
+     * Returns the id, if one exists, for this object.
+     */
+    @Nullable
+    public String getId() {
+        return mId;
+    }
+
+    /**
+     * Returns the extended data.
+     *
+     * <p><b>NOTE: </b>Do not modify this bundle.
+     */
+    @NonNull
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    /** @hide */
+    public Builder toBuilder() {
+        return new Builder()
+                .setId(mId)
+                .setText(mText)
+                .addActions(mActions)
+                .setEntityConfidence(mEntityConfidence)
+                .setIcon(mLegacyIcon)
+                .setLabel(mLegacyLabel)
+                .setIntent(mLegacyIntent)
+                .setOnClickListener(mLegacyOnClickListener)
+                .setExtras(mExtras);
+    }
+
+    @Override
+    public String toString() {
+        return String.format(Locale.US,
+                "TextClassification {text=%s, entities=%s, actions=%s, id=%s, extras=%s}",
+                mText, mEntityConfidence, mActions, mId, mExtras);
+    }
+
+    /**
+     * Creates an OnClickListener that triggers the specified PendingIntent.
+     *
+     * @hide
+     */
+    public static OnClickListener createIntentOnClickListener(@NonNull final PendingIntent intent) {
+        Objects.requireNonNull(intent);
+        return v -> {
+            try {
+                intent.send();
+            } catch (PendingIntent.CanceledException e) {
+                Log.e(LOG_TAG, "Error sending PendingIntent", e);
+            }
+        };
+    }
+
+    /**
+     * Creates a PendingIntent for the specified intent.
+     * Returns null if the intent is not supported for the specified context.
+     *
+     * @throws IllegalArgumentException if context or intent is null
+     * @hide
+     */
+    public static PendingIntent createPendingIntent(
+            @NonNull final Context context, @NonNull final Intent intent, int requestCode) {
+        return PendingIntent.getActivity(
+                context, requestCode, intent,
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+    }
+
+    /**
+     * Builder for building {@link TextClassification} objects.
+     *
+     * <p>e.g.
+     *
+     * <pre>{@code
+     *   TextClassification classification = new TextClassification.Builder()
+     *          .setText(classifiedText)
+     *          .setEntityType(TextClassifier.TYPE_EMAIL, 0.9)
+     *          .setEntityType(TextClassifier.TYPE_OTHER, 0.1)
+     *          .addAction(remoteAction1)
+     *          .addAction(remoteAction2)
+     *          .build();
+     * }</pre>
+     */
+    public static final class Builder {
+
+        @NonNull private final List<RemoteAction> mActions = new ArrayList<>();
+        @NonNull private final Map<String, Float> mTypeScoreMap = new ArrayMap<>();
+        @Nullable private String mText;
+        @Nullable private Drawable mLegacyIcon;
+        @Nullable private String mLegacyLabel;
+        @Nullable private Intent mLegacyIntent;
+        @Nullable private OnClickListener mLegacyOnClickListener;
+        @Nullable private String mId;
+        @Nullable private Bundle mExtras;
+
+        /**
+         * Sets the classified text.
+         */
+        @NonNull
+        public Builder setText(@Nullable String text) {
+            mText = text;
+            return this;
+        }
+
+        /**
+         * Sets an entity type for the classification result and assigns a confidence score.
+         * If a confidence score had already been set for the specified entity type, this will
+         * override that score.
+         *
+         * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence).
+         *      0 implies the entity does not exist for the classified text.
+         *      Values greater than 1 are clamped to 1.
+         */
+        @NonNull
+        public Builder setEntityType(
+                @NonNull @EntityType String type,
+                @FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
+            mTypeScoreMap.put(type, confidenceScore);
+            return this;
+        }
+
+        Builder setEntityConfidence(EntityConfidence scores) {
+            mTypeScoreMap.clear();
+            mTypeScoreMap.putAll(scores.toMap());
+            return this;
+        }
+
+        /** @hide */
+        public Builder clearEntityTypes() {
+            mTypeScoreMap.clear();
+            return this;
+        }
+
+        /**
+         * Adds an action that may be performed on the classified text. Actions should be added in
+         * order of likelihood that the user will use them, with the most likely action being added
+         * first.
+         */
+        @NonNull
+        public Builder addAction(@NonNull RemoteAction action) {
+            Preconditions.checkArgument(action != null);
+            mActions.add(action);
+            return this;
+        }
+
+        /** @hide */
+        public Builder addActions(Collection<RemoteAction> actions) {
+            Objects.requireNonNull(actions);
+            mActions.addAll(actions);
+            return this;
+        }
+
+        /** @hide */
+        public Builder clearActions() {
+            mActions.clear();
+            return this;
+        }
+
+        /**
+         * Sets the icon for the <i>primary</i> action that may be rendered on a widget used to act
+         * on the classified text.
+         *
+         * <p><strong>NOTE: </strong>This field is not parcelled. If read from a parcel, the
+         * returned icon represents the icon of the first {@link RemoteAction} (if one exists).
+         *
+         * @deprecated Use {@link #addAction(RemoteAction)} instead.
+         */
+        @Deprecated
+        @NonNull
+        public Builder setIcon(@Nullable Drawable icon) {
+            mLegacyIcon = icon;
+            return this;
+        }
+
+        /**
+         * Sets the label for the <i>primary</i> action that may be rendered on a widget used to
+         * act on the classified text.
+         *
+         * <p><strong>NOTE: </strong>This field is not parcelled. If read from a parcel, the
+         * returned label represents the label of the first {@link RemoteAction} (if one exists).
+         *
+         * @deprecated Use {@link #addAction(RemoteAction)} instead.
+         */
+        @Deprecated
+        @NonNull
+        public Builder setLabel(@Nullable String label) {
+            mLegacyLabel = label;
+            return this;
+        }
+
+        /**
+         * Sets the intent for the <i>primary</i> action that may be fired to act on the classified
+         * text.
+         *
+         * <p><strong>NOTE: </strong>This field is not parcelled.
+         *
+         * @deprecated Use {@link #addAction(RemoteAction)} instead.
+         */
+        @Deprecated
+        @NonNull
+        public Builder setIntent(@Nullable Intent intent) {
+            mLegacyIntent = intent;
+            return this;
+        }
+
+        /**
+         * Sets the OnClickListener for the <i>primary</i> action that may be triggered to act on
+         * the classified text.
+         *
+         * <p><strong>NOTE: </strong>This field is not parcelable. If read from a parcel, the
+         * returned OnClickListener represents the first {@link RemoteAction} (if one exists).
+         *
+         * @deprecated Use {@link #addAction(RemoteAction)} instead.
+         */
+        @Deprecated
+        @NonNull
+        public Builder setOnClickListener(@Nullable OnClickListener onClickListener) {
+            mLegacyOnClickListener = onClickListener;
+            return this;
+        }
+
+        /**
+         * Sets an id for the TextClassification object.
+         */
+        @NonNull
+        public Builder setId(@Nullable String id) {
+            mId = id;
+            return this;
+        }
+
+        /**
+         * Sets the extended data.
+         */
+        @NonNull
+        public Builder setExtras(@Nullable Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
+         * Builds and returns a {@link TextClassification} object.
+         */
+        @NonNull
+        public TextClassification build() {
+            EntityConfidence entityConfidence = new EntityConfidence(mTypeScoreMap);
+            return new TextClassification(mText, mLegacyIcon, mLegacyLabel, mLegacyIntent,
+                    mLegacyOnClickListener, mActions, entityConfidence, mId,
+                    mExtras == null ? Bundle.EMPTY : mExtras);
+        }
+    }
+
+    /**
+     * A request object for generating TextClassification.
+     */
+    public static final class Request implements Parcelable {
+
+        private final CharSequence mText;
+        private final int mStartIndex;
+        private final int mEndIndex;
+        @Nullable private final LocaleList mDefaultLocales;
+        @Nullable private final ZonedDateTime mReferenceTime;
+        @NonNull private final Bundle mExtras;
+        @Nullable private SystemTextClassifierMetadata mSystemTcMetadata;
+
+        private Request(
+                CharSequence text,
+                int startIndex,
+                int endIndex,
+                LocaleList defaultLocales,
+                ZonedDateTime referenceTime,
+                Bundle extras) {
+            mText = text;
+            mStartIndex = startIndex;
+            mEndIndex = endIndex;
+            mDefaultLocales = defaultLocales;
+            mReferenceTime = referenceTime;
+            mExtras = extras;
+        }
+
+        /**
+         * Returns the text providing context for the text to classify (which is specified
+         *      by the sub sequence starting at startIndex and ending at endIndex)
+         */
+        @NonNull
+        public CharSequence getText() {
+            return mText;
+        }
+
+        /**
+         * Returns start index of the text to classify.
+         */
+        @IntRange(from = 0)
+        public int getStartIndex() {
+            return mStartIndex;
+        }
+
+        /**
+         * Returns end index of the text to classify.
+         */
+        @IntRange(from = 0)
+        public int getEndIndex() {
+            return mEndIndex;
+        }
+
+        /**
+         * @return ordered list of locale preferences that can be used to disambiguate
+         *      the provided text.
+         */
+        @Nullable
+        public LocaleList getDefaultLocales() {
+            return mDefaultLocales;
+        }
+
+        /**
+         * @return reference time based on which relative dates (e.g. "tomorrow") should be
+         *      interpreted.
+         */
+        @Nullable
+        public ZonedDateTime getReferenceTime() {
+            return mReferenceTime;
+        }
+
+        /**
+         * Returns the name of the package that sent this request.
+         * This returns {@code null} if no calling package name is set.
+         */
+        @Nullable
+        public String getCallingPackageName() {
+            return mSystemTcMetadata != null ? mSystemTcMetadata.getCallingPackageName() : null;
+        }
+
+        /**
+         * Sets the information about the {@link SystemTextClassifier} that sent this request.
+         *
+         * @hide
+         */
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+        public void setSystemTextClassifierMetadata(
+                @Nullable SystemTextClassifierMetadata systemTcMetadata) {
+            mSystemTcMetadata = systemTcMetadata;
+        }
+
+        /**
+         * Returns the information about the {@link SystemTextClassifier} that sent this request.
+         *
+         * @hide
+         */
+        @Nullable
+        public SystemTextClassifierMetadata getSystemTextClassifierMetadata() {
+            return mSystemTcMetadata;
+        }
+
+        /**
+         * Returns the extended data.
+         *
+         * <p><b>NOTE: </b>Do not modify this bundle.
+         */
+        @NonNull
+        public Bundle getExtras() {
+            return mExtras;
+        }
+
+        /**
+         * A builder for building TextClassification requests.
+         */
+        public static final class Builder {
+
+            private final CharSequence mText;
+            private final int mStartIndex;
+            private final int mEndIndex;
+            private Bundle mExtras;
+
+            @Nullable private LocaleList mDefaultLocales;
+            @Nullable private ZonedDateTime mReferenceTime;
+
+            /**
+             * @param text text providing context for the text to classify (which is specified
+             *      by the sub sequence starting at startIndex and ending at endIndex)
+             * @param startIndex start index of the text to classify
+             * @param endIndex end index of the text to classify
+             */
+            public Builder(
+                    @NonNull CharSequence text,
+                    @IntRange(from = 0) int startIndex,
+                    @IntRange(from = 0) int endIndex) {
+                Utils.checkArgument(text, startIndex, endIndex);
+                mText = text;
+                mStartIndex = startIndex;
+                mEndIndex = endIndex;
+            }
+
+            /**
+             * @param defaultLocales ordered list of locale preferences that may be used to
+             *      disambiguate the provided text. If no locale preferences exist, set this to null
+             *      or an empty locale list.
+             *
+             * @return this builder
+             */
+            @NonNull
+            public Builder setDefaultLocales(@Nullable LocaleList defaultLocales) {
+                mDefaultLocales = defaultLocales;
+                return this;
+            }
+
+            /**
+             * @param referenceTime reference time based on which relative dates (e.g. "tomorrow"
+             *      should be interpreted. This should usually be the time when the text was
+             *      originally composed. If no reference time is set, now is used.
+             *
+             * @return this builder
+             */
+            @NonNull
+            public Builder setReferenceTime(@Nullable ZonedDateTime referenceTime) {
+                mReferenceTime = referenceTime;
+                return this;
+            }
+
+            /**
+             * Sets the extended data.
+             *
+             * @return this builder
+             */
+            @NonNull
+            public Builder setExtras(@Nullable Bundle extras) {
+                mExtras = extras;
+                return this;
+            }
+
+            /**
+             * Builds and returns the request object.
+             */
+            @NonNull
+            public Request build() {
+                return new Request(new SpannedString(mText), mStartIndex, mEndIndex,
+                        mDefaultLocales, mReferenceTime,
+                        mExtras == null ? Bundle.EMPTY : mExtras);
+            }
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeCharSequence(mText);
+            dest.writeInt(mStartIndex);
+            dest.writeInt(mEndIndex);
+            dest.writeParcelable(mDefaultLocales, flags);
+            dest.writeString(mReferenceTime == null ? null : mReferenceTime.toString());
+            dest.writeBundle(mExtras);
+            dest.writeParcelable(mSystemTcMetadata, flags);
+        }
+
+        private static Request readFromParcel(Parcel in) {
+            final CharSequence text = in.readCharSequence();
+            final int startIndex = in.readInt();
+            final int endIndex = in.readInt();
+            final LocaleList defaultLocales = in.readParcelable(null);
+            final String referenceTimeString = in.readString();
+            final ZonedDateTime referenceTime = referenceTimeString == null
+                    ? null : ZonedDateTime.parse(referenceTimeString);
+            final Bundle extras = in.readBundle();
+            final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null);
+
+            final Request request = new Request(text, startIndex, endIndex,
+                    defaultLocales, referenceTime, extras);
+            request.setSystemTextClassifierMetadata(systemTcMetadata);
+            return request;
+        }
+
+        public static final @android.annotation.NonNull Parcelable.Creator<Request> CREATOR =
+                new Parcelable.Creator<Request>() {
+                    @Override
+                    public Request createFromParcel(Parcel in) {
+                        return readFromParcel(in);
+                    }
+
+                    @Override
+                    public Request[] newArray(int size) {
+                        return new Request[size];
+                    }
+                };
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mText);
+        // NOTE: legacy fields are not parcelled.
+        dest.writeTypedList(mActions);
+        mEntityConfidence.writeToParcel(dest, flags);
+        dest.writeString(mId);
+        dest.writeBundle(mExtras);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<TextClassification> CREATOR =
+            new Parcelable.Creator<TextClassification>() {
+                @Override
+                public TextClassification createFromParcel(Parcel in) {
+                    return new TextClassification(in);
+                }
+
+                @Override
+                public TextClassification[] newArray(int size) {
+                    return new TextClassification[size];
+                }
+            };
+
+    private TextClassification(Parcel in) {
+        mText = in.readString();
+        mActions = in.createTypedArrayList(RemoteAction.CREATOR);
+        if (!mActions.isEmpty()) {
+            final RemoteAction action = mActions.get(0);
+            mLegacyIcon = maybeLoadDrawable(action.getIcon());
+            mLegacyLabel = action.getTitle().toString();
+            mLegacyOnClickListener = createIntentOnClickListener(mActions.get(0).getActionIntent());
+        } else {
+            mLegacyIcon = null;
+            mLegacyLabel = null;
+            mLegacyOnClickListener = null;
+        }
+        mLegacyIntent = null; // mLegacyIntent is not parcelled.
+        mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in);
+        mId = in.readString();
+        mExtras = in.readBundle();
+    }
+
+    // Best effort attempt to try to load a drawable from the provided icon.
+    @Nullable
+    private static Drawable maybeLoadDrawable(Icon icon) {
+        if (icon == null) {
+            return null;
+        }
+        switch (icon.getType()) {
+            case Icon.TYPE_BITMAP:
+                return new BitmapDrawable(Resources.getSystem(), icon.getBitmap());
+            case Icon.TYPE_ADAPTIVE_BITMAP:
+                return new AdaptiveIconDrawable(null,
+                        new BitmapDrawable(Resources.getSystem(), icon.getBitmap()));
+            case Icon.TYPE_DATA:
+                return new BitmapDrawable(
+                        Resources.getSystem(),
+                        BitmapFactory.decodeByteArray(
+                                icon.getDataBytes(), icon.getDataOffset(), icon.getDataLength()));
+        }
+        return null;
+    }
+}
diff --git a/android/view/textclassifier/TextClassificationConstants.java b/android/view/textclassifier/TextClassificationConstants.java
new file mode 100644
index 0000000..5f3159c
--- /dev/null
+++ b/android/view/textclassifier/TextClassificationConstants.java
@@ -0,0 +1,190 @@
+/*
+ * 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.view.textclassifier;
+
+import android.annotation.Nullable;
+import android.provider.DeviceConfig;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
+/**
+ * TextClassifier specific settings.
+ *
+ * <p>Currently, this class does not guarantee co-diverted flags are updated atomically.
+ *
+ * <pre>
+ * adb shell cmd device_config put textclassifier system_textclassifier_enabled true
+ * </pre>
+ *
+ * @see android.provider.DeviceConfig#NAMESPACE_TEXTCLASSIFIER
+ * @hide
+ */
+// TODO: Rename to TextClassifierSettings.
+public final class TextClassificationConstants {
+    /**
+     * Whether the smart linkify feature is enabled.
+     */
+    private static final String SMART_LINKIFY_ENABLED = "smart_linkify_enabled";
+    /**
+     * Whether SystemTextClassifier is enabled.
+     */
+    static final String SYSTEM_TEXT_CLASSIFIER_ENABLED = "system_textclassifier_enabled";
+    /**
+     * Whether TextClassifierImpl is enabled.
+     */
+    @VisibleForTesting
+    static final String LOCAL_TEXT_CLASSIFIER_ENABLED = "local_textclassifier_enabled";
+    /**
+     * Enable smart selection without a visible UI changes.
+     */
+    private static final String MODEL_DARK_LAUNCH_ENABLED = "model_dark_launch_enabled";
+    /**
+     * Whether the smart selection feature is enabled.
+     */
+    private static final String SMART_SELECTION_ENABLED = "smart_selection_enabled";
+    /**
+     * Whether the smart text share feature is enabled.
+     */
+    private static final String SMART_TEXT_SHARE_ENABLED = "smart_text_share_enabled";
+    /**
+     * Whether animation for smart selection is enabled.
+     */
+    private static final String SMART_SELECT_ANIMATION_ENABLED =
+            "smart_select_animation_enabled";
+    /**
+     * Max length of text that generateLinks can accept.
+     */
+    @VisibleForTesting
+    static final String GENERATE_LINKS_MAX_TEXT_LENGTH = "generate_links_max_text_length";
+    /**
+     * The TextClassifierService which would like to use. Example of setting the package:
+     * <pre>
+     * adb shell cmd device_config put textclassifier textclassifier_service_package_override \
+     *      com.android.textclassifier
+     * </pre>
+     */
+    @VisibleForTesting
+    static final String TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE =
+            "textclassifier_service_package_override";
+
+    /**
+     * The timeout value in seconds used by {@link SystemTextClassifier} for each TextClassifier
+     * API calls.
+     */
+    @VisibleForTesting
+    static final String SYSTEM_TEXT_CLASSIFIER_API_TIMEOUT_IN_SECOND =
+            "system_textclassifier_api_timeout_in_second";
+
+    /**
+     * The maximum amount of characters before and after the selected text that is passed to the
+     * TextClassifier for the smart selection. e.g. If this value is 100, then 100 characters before
+     * the selection and 100 characters after the selection will be passed to the TextClassifier.
+     */
+    private static final String SMART_SELECTION_TRIM_DELTA = "smart_selection_trim_delta";
+
+    private static final String DEFAULT_TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE = null;
+    private static final boolean LOCAL_TEXT_CLASSIFIER_ENABLED_DEFAULT = true;
+    private static final boolean SYSTEM_TEXT_CLASSIFIER_ENABLED_DEFAULT = true;
+    private static final boolean MODEL_DARK_LAUNCH_ENABLED_DEFAULT = false;
+    private static final boolean SMART_SELECTION_ENABLED_DEFAULT = true;
+    private static final boolean SMART_TEXT_SHARE_ENABLED_DEFAULT = true;
+    private static final boolean SMART_LINKIFY_ENABLED_DEFAULT = true;
+    private static final boolean SMART_SELECT_ANIMATION_ENABLED_DEFAULT = true;
+    private static final int GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT = 100 * 1000;
+    private static final long SYSTEM_TEXT_CLASSIFIER_API_TIMEOUT_IN_SECOND_DEFAULT = 60;
+    private static final int SMART_SELECTION_TRIM_DELTA_DEFAULT = 120;
+
+    @Nullable
+    public String getTextClassifierServicePackageOverride() {
+        return DeviceConfig.getString(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+                TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE,
+                DEFAULT_TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE);
+    }
+
+    public boolean isLocalTextClassifierEnabled() {
+        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+                LOCAL_TEXT_CLASSIFIER_ENABLED, LOCAL_TEXT_CLASSIFIER_ENABLED_DEFAULT);
+    }
+
+    public boolean isSystemTextClassifierEnabled() {
+        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+                SYSTEM_TEXT_CLASSIFIER_ENABLED,
+                SYSTEM_TEXT_CLASSIFIER_ENABLED_DEFAULT);
+    }
+
+    public boolean isModelDarkLaunchEnabled() {
+        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+                MODEL_DARK_LAUNCH_ENABLED, MODEL_DARK_LAUNCH_ENABLED_DEFAULT);
+    }
+
+    public boolean isSmartSelectionEnabled() {
+        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+                SMART_SELECTION_ENABLED, SMART_SELECTION_ENABLED_DEFAULT);
+    }
+
+    public boolean isSmartTextShareEnabled() {
+        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+                SMART_TEXT_SHARE_ENABLED, SMART_TEXT_SHARE_ENABLED_DEFAULT);
+    }
+
+    public boolean isSmartLinkifyEnabled() {
+        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, SMART_LINKIFY_ENABLED,
+                SMART_LINKIFY_ENABLED_DEFAULT);
+    }
+
+    public boolean isSmartSelectionAnimationEnabled() {
+        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+                SMART_SELECT_ANIMATION_ENABLED, SMART_SELECT_ANIMATION_ENABLED_DEFAULT);
+    }
+
+    public int getGenerateLinksMaxTextLength() {
+        return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+                GENERATE_LINKS_MAX_TEXT_LENGTH, GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT);
+    }
+
+    public long getSystemTextClassifierApiTimeoutInSecond() {
+        return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+                SYSTEM_TEXT_CLASSIFIER_API_TIMEOUT_IN_SECOND,
+                SYSTEM_TEXT_CLASSIFIER_API_TIMEOUT_IN_SECOND_DEFAULT);
+    }
+
+    public int getSmartSelectionTrimDelta() {
+        return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+                SMART_SELECTION_TRIM_DELTA,
+                SMART_SELECTION_TRIM_DELTA_DEFAULT);
+    }
+
+    void dump(IndentingPrintWriter pw) {
+        pw.println("TextClassificationConstants:");
+        pw.increaseIndent();
+        pw.print(GENERATE_LINKS_MAX_TEXT_LENGTH, getGenerateLinksMaxTextLength()).println();
+        pw.print(LOCAL_TEXT_CLASSIFIER_ENABLED, isLocalTextClassifierEnabled()).println();
+        pw.print(MODEL_DARK_LAUNCH_ENABLED, isModelDarkLaunchEnabled()).println();
+        pw.print(SMART_LINKIFY_ENABLED, isSmartLinkifyEnabled()).println();
+        pw.print(SMART_SELECT_ANIMATION_ENABLED, isSmartSelectionAnimationEnabled()).println();
+        pw.print(SMART_SELECTION_ENABLED, isSmartSelectionEnabled()).println();
+        pw.print(SMART_TEXT_SHARE_ENABLED, isSmartTextShareEnabled()).println();
+        pw.print(SYSTEM_TEXT_CLASSIFIER_ENABLED, isSystemTextClassifierEnabled()).println();
+        pw.print(TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE,
+                getTextClassifierServicePackageOverride()).println();
+        pw.print(SYSTEM_TEXT_CLASSIFIER_API_TIMEOUT_IN_SECOND,
+                getSystemTextClassifierApiTimeoutInSecond()).println();
+        pw.print(SMART_SELECTION_TRIM_DELTA, getSmartSelectionTrimDelta()).println();
+        pw.decreaseIndent();
+    }
+}
\ No newline at end of file
diff --git a/android/view/textclassifier/TextClassificationContext.java b/android/view/textclassifier/TextClassificationContext.java
new file mode 100644
index 0000000..5d5683f
--- /dev/null
+++ b/android/view/textclassifier/TextClassificationContext.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.textclassifier.TextClassifier.WidgetType;
+
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * A representation of the context in which text classification would be performed.
+ * @see TextClassificationManager#createTextClassificationSession(TextClassificationContext)
+ */
+public final class TextClassificationContext implements Parcelable {
+
+    private String mPackageName;
+    private final String mWidgetType;
+    @Nullable private final String mWidgetVersion;
+    private SystemTextClassifierMetadata mSystemTcMetadata;
+
+    private TextClassificationContext(
+            String packageName,
+            String widgetType,
+            String widgetVersion) {
+        mPackageName = Objects.requireNonNull(packageName);
+        mWidgetType = Objects.requireNonNull(widgetType);
+        mWidgetVersion = widgetVersion;
+    }
+
+    /**
+     * Returns the package name of the app that this context originated in.
+     */
+    @NonNull
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * Sets the information about the {@link SystemTextClassifier} that sent this request.
+     *
+     * @hide
+     */
+    void setSystemTextClassifierMetadata(@Nullable SystemTextClassifierMetadata systemTcMetadata) {
+        mSystemTcMetadata = systemTcMetadata;
+    }
+
+    /**
+     * Returns the information about the {@link SystemTextClassifier} that sent this request.
+     *
+     * @hide
+     */
+    @Nullable
+    public SystemTextClassifierMetadata getSystemTextClassifierMetadata() {
+        return mSystemTcMetadata;
+    }
+
+    /**
+     * Returns the widget type for this classification context.
+     */
+    @NonNull
+    @WidgetType
+    public String getWidgetType() {
+        return mWidgetType;
+    }
+
+    /**
+     * Returns a custom version string for the widget type.
+     *
+     * @see #getWidgetType()
+     */
+    @Nullable
+    public String getWidgetVersion() {
+        return mWidgetVersion;
+    }
+
+    @Override
+    public String toString() {
+        return String.format(Locale.US, "TextClassificationContext{"
+                + "packageName=%s, widgetType=%s, widgetVersion=%s, systemTcMetadata=%s}",
+                mPackageName, mWidgetType, mWidgetVersion, mSystemTcMetadata);
+    }
+
+    /**
+     * A builder for building a TextClassification context.
+     */
+    public static final class Builder {
+
+        private final String mPackageName;
+        private final String mWidgetType;
+
+        @Nullable private String mWidgetVersion;
+
+        /**
+         * Initializes a new builder for text classification context objects.
+         *
+         * @param packageName the name of the calling package
+         * @param widgetType the type of widget e.g. {@link TextClassifier#WIDGET_TYPE_TEXTVIEW}
+         *
+         * @return this builder
+         */
+        public Builder(@NonNull String packageName, @NonNull @WidgetType String widgetType) {
+            mPackageName = Objects.requireNonNull(packageName);
+            mWidgetType = Objects.requireNonNull(widgetType);
+        }
+
+        /**
+         * Sets an optional custom version string for the widget type.
+         *
+         * @return this builder
+         */
+        public Builder setWidgetVersion(@Nullable String widgetVersion) {
+            mWidgetVersion = widgetVersion;
+            return this;
+        }
+
+        /**
+         * Builds the text classification context object.
+         *
+         * @return the built TextClassificationContext object
+         */
+        @NonNull
+        public TextClassificationContext build() {
+            return new TextClassificationContext(mPackageName, mWidgetType, mWidgetVersion);
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeString(mPackageName);
+        parcel.writeString(mWidgetType);
+        parcel.writeString(mWidgetVersion);
+        parcel.writeParcelable(mSystemTcMetadata, flags);
+    }
+
+    private TextClassificationContext(Parcel in) {
+        mPackageName = in.readString();
+        mWidgetType = in.readString();
+        mWidgetVersion = in.readString();
+        mSystemTcMetadata = in.readParcelable(null);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<TextClassificationContext> CREATOR =
+            new Parcelable.Creator<TextClassificationContext>() {
+                @Override
+                public TextClassificationContext createFromParcel(Parcel parcel) {
+                    return new TextClassificationContext(parcel);
+                }
+
+                @Override
+                public TextClassificationContext[] newArray(int size) {
+                    return new TextClassificationContext[size];
+                }
+            };
+}
diff --git a/android/view/textclassifier/TextClassificationManager.java b/android/view/textclassifier/TextClassificationManager.java
new file mode 100644
index 0000000..b606340
--- /dev/null
+++ b/android/view/textclassifier/TextClassificationManager.java
@@ -0,0 +1,229 @@
+/*
+ * 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.view.textclassifier;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemService;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.Build;
+import android.os.ServiceManager;
+import android.view.textclassifier.TextClassifier.TextClassifierType;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.util.Objects;
+
+/**
+ * Interface to the text classification service.
+ */
+@SystemService(Context.TEXT_CLASSIFICATION_SERVICE)
+public final class TextClassificationManager {
+
+    private static final String LOG_TAG = TextClassifier.LOG_TAG;
+
+    private static final TextClassificationConstants sDefaultSettings =
+            new TextClassificationConstants();
+
+    private final Object mLock = new Object();
+    private final TextClassificationSessionFactory mDefaultSessionFactory =
+            classificationContext -> new TextClassificationSession(
+                    classificationContext, getTextClassifier());
+
+    private final Context mContext;
+
+    @GuardedBy("mLock")
+    @Nullable
+    private TextClassifier mCustomTextClassifier;
+    @GuardedBy("mLock")
+    private TextClassificationSessionFactory mSessionFactory;
+    @GuardedBy("mLock")
+    private TextClassificationConstants mSettings;
+
+    /** @hide */
+    public TextClassificationManager(Context context) {
+        mContext = Objects.requireNonNull(context);
+        mSessionFactory = mDefaultSessionFactory;
+    }
+
+    /**
+     * Returns the text classifier that was set via {@link #setTextClassifier(TextClassifier)}.
+     * If this is null, this method returns a default text classifier (i.e. either the system text
+     * classifier if one exists, or a local text classifier running in this process.)
+     * <p>
+     * Note that requests to the TextClassifier may be handled in an OEM-provided process rather
+     * than in the calling app's process.
+     *
+     * @see #setTextClassifier(TextClassifier)
+     */
+    @NonNull
+    public TextClassifier getTextClassifier() {
+        synchronized (mLock) {
+            if (mCustomTextClassifier != null) {
+                return mCustomTextClassifier;
+            } else if (getSettings().isSystemTextClassifierEnabled()) {
+                return getSystemTextClassifier(SystemTextClassifier.SYSTEM);
+            } else {
+                return getLocalTextClassifier();
+            }
+        }
+    }
+
+    /**
+     * Sets the text classifier.
+     * Set to null to use the system default text classifier.
+     * Set to {@link TextClassifier#NO_OP} to disable text classifier features.
+     */
+    public void setTextClassifier(@Nullable TextClassifier textClassifier) {
+        synchronized (mLock) {
+            mCustomTextClassifier = textClassifier;
+        }
+    }
+
+    /**
+     * Returns a specific type of text classifier.
+     * If the specified text classifier cannot be found, this returns {@link TextClassifier#NO_OP}.
+     *
+     * @see TextClassifier#LOCAL
+     * @see TextClassifier#SYSTEM
+     * @see TextClassifier#DEFAULT_SYSTEM
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public TextClassifier getTextClassifier(@TextClassifierType int type) {
+        switch (type) {
+            case TextClassifier.LOCAL:
+                return getLocalTextClassifier();
+            default:
+                return getSystemTextClassifier(type);
+        }
+    }
+
+    private TextClassificationConstants getSettings() {
+        synchronized (mLock) {
+            if (mSettings == null) {
+                mSettings = new TextClassificationConstants();
+            }
+            return mSettings;
+        }
+    }
+
+    /**
+     * Call this method to start a text classification session with the given context.
+     * A session is created with a context helping the classifier better understand
+     * what the user needs and consists of queries and feedback events. The queries
+     * are directly related to providing useful functionality to the user and the events
+     * are a feedback loop back to the classifier helping it learn and better serve
+     * future queries.
+     *
+     * <p> All interactions with the returned classifier are considered part of a single
+     * session and are logically grouped. For example, when a text widget is focused
+     * all user interactions around text editing (selection, editing, etc) can be
+     * grouped together to allow the classifier get better.
+     *
+     * @param classificationContext The context in which classification would occur
+     *
+     * @return An instance to perform classification in the given context
+     */
+    @NonNull
+    public TextClassifier createTextClassificationSession(
+            @NonNull TextClassificationContext classificationContext) {
+        Objects.requireNonNull(classificationContext);
+        final TextClassifier textClassifier =
+                mSessionFactory.createTextClassificationSession(classificationContext);
+        Objects.requireNonNull(textClassifier, "Session Factory should never return null");
+        return textClassifier;
+    }
+
+    /**
+     * @see #createTextClassificationSession(TextClassificationContext, TextClassifier)
+     * @hide
+     */
+    public TextClassifier createTextClassificationSession(
+            TextClassificationContext classificationContext, TextClassifier textClassifier) {
+        Objects.requireNonNull(classificationContext);
+        Objects.requireNonNull(textClassifier);
+        return new TextClassificationSession(classificationContext, textClassifier);
+    }
+
+    /**
+     * Sets a TextClassificationSessionFactory to be used to create session-aware TextClassifiers.
+     *
+     * @param factory the textClassification session factory. If this is null, the default factory
+     *      will be used.
+     */
+    public void setTextClassificationSessionFactory(
+            @Nullable TextClassificationSessionFactory factory) {
+        synchronized (mLock) {
+            if (factory != null) {
+                mSessionFactory = factory;
+            } else {
+                mSessionFactory = mDefaultSessionFactory;
+            }
+        }
+    }
+
+    /** @hide */
+    private TextClassifier getSystemTextClassifier(@TextClassifierType int type) {
+        synchronized (mLock) {
+            if (getSettings().isSystemTextClassifierEnabled()) {
+                try {
+                    Log.d(LOG_TAG, "Initializing SystemTextClassifier, type = "
+                            + TextClassifier.typeToString(type));
+                    return new SystemTextClassifier(
+                            mContext,
+                            getSettings(),
+                            /* useDefault= */ type == TextClassifier.DEFAULT_SYSTEM);
+                } catch (ServiceManager.ServiceNotFoundException e) {
+                    Log.e(LOG_TAG, "Could not initialize SystemTextClassifier", e);
+                }
+            }
+            return TextClassifier.NO_OP;
+        }
+    }
+
+    /**
+     * Returns a local textclassifier, which is running in this process.
+     */
+    @NonNull
+    private TextClassifier getLocalTextClassifier() {
+        Log.d(LOG_TAG, "Local text-classifier not supported. Returning a no-op text-classifier.");
+        return TextClassifier.NO_OP;
+    }
+
+    /** @hide **/
+    public void dump(IndentingPrintWriter pw) {
+        getSystemTextClassifier(TextClassifier.DEFAULT_SYSTEM).dump(pw);
+        getSystemTextClassifier(TextClassifier.SYSTEM).dump(pw);
+        getSettings().dump(pw);
+    }
+
+    /** @hide */
+    public static TextClassificationConstants getSettings(Context context) {
+        Objects.requireNonNull(context);
+        final TextClassificationManager tcm =
+                context.getSystemService(TextClassificationManager.class);
+        if (tcm != null) {
+            return tcm.getSettings();
+        } else {
+            // Use default settings if there is no tcm.
+            return sDefaultSettings;
+        }
+    }
+}
diff --git a/android/view/textclassifier/TextClassificationManagerPerfTest.java b/android/view/textclassifier/TextClassificationManagerPerfTest.java
new file mode 100644
index 0000000..46250d7
--- /dev/null
+++ b/android/view/textclassifier/TextClassificationManagerPerfTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.textclassifier;
+
+import android.content.Context;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.provider.DeviceConfig;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+
+@LargeTest
+public class TextClassificationManagerPerfTest {
+    private static final String WRITE_DEVICE_CONFIG_PERMISSION =
+            "android.permission.WRITE_DEVICE_CONFIG";
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    private String mOriginalSystemTextclassifierStatus;
+
+    @BeforeClass
+    public static void setUpClass() {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(
+                        WRITE_DEVICE_CONFIG_PERMISSION);
+    }
+
+    @AfterClass
+    public static void tearDownClass() {
+        InstrumentationRegistry
+                .getInstrumentation()
+                .getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    @Before
+    public void setUp() {
+        // Saves config original value.
+        mOriginalSystemTextclassifierStatus = DeviceConfig.getProperty(
+                DeviceConfig.NAMESPACE_TEXTCLASSIFIER, "system_textclassifier_enabled");
+    }
+
+    @After
+    public void tearDown() {
+        // Restores config original value.
+        enableSystemTextclassifier(mOriginalSystemTextclassifierStatus);
+    }
+
+    @Test
+    public void testGetTextClassifier_systemTextClassifierDisabled() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        enableSystemTextclassifier(String.valueOf(false));
+        TextClassificationManager textClassificationManager =
+                context.getSystemService(TextClassificationManager.class);
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            textClassificationManager.getTextClassifier();
+        }
+    }
+
+    @Test
+    public void testGetTextClassifier_systemTextClassifierEnabled() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        enableSystemTextclassifier(String.valueOf(true));
+        TextClassificationManager textClassificationManager =
+                context.getSystemService(TextClassificationManager.class);
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            textClassificationManager.getTextClassifier();
+        }
+    }
+
+    private void enableSystemTextclassifier(String enabled) {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+                "system_textclassifier_enabled", enabled, /* makeDefault */ false);
+    }
+}
diff --git a/android/view/textclassifier/TextClassificationSession.java b/android/view/textclassifier/TextClassificationSession.java
new file mode 100644
index 0000000..0008658
--- /dev/null
+++ b/android/view/textclassifier/TextClassificationSession.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.NonNull;
+import android.annotation.WorkerThread;
+import android.view.textclassifier.SelectionEvent.InvocationMethod;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+import java.util.function.Supplier;
+
+import sun.misc.Cleaner;
+
+/**
+ * Session-aware TextClassifier.
+ */
+@WorkerThread
+final class TextClassificationSession implements TextClassifier {
+
+    private static final String LOG_TAG = "TextClassificationSession";
+
+    private final TextClassifier mDelegate;
+    private final SelectionEventHelper mEventHelper;
+    private final TextClassificationSessionId mSessionId;
+    private final TextClassificationContext mClassificationContext;
+    private final Cleaner mCleaner;
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private boolean mDestroyed;
+
+    TextClassificationSession(TextClassificationContext context, TextClassifier delegate) {
+        mClassificationContext = Objects.requireNonNull(context);
+        mDelegate = Objects.requireNonNull(delegate);
+        mSessionId = new TextClassificationSessionId();
+        mEventHelper = new SelectionEventHelper(mSessionId, mClassificationContext);
+        initializeRemoteSession();
+        // This ensures destroy() is called if the client forgot to do so.
+        mCleaner = Cleaner.create(this, new CleanerRunnable(mEventHelper, mDelegate));
+    }
+
+    @Override
+    public TextSelection suggestSelection(TextSelection.Request request) {
+        return checkDestroyedAndRun(() -> mDelegate.suggestSelection(request));
+    }
+
+    private void initializeRemoteSession() {
+        if (mDelegate instanceof SystemTextClassifier) {
+            ((SystemTextClassifier) mDelegate).initializeRemoteSession(
+                    mClassificationContext, mSessionId);
+        }
+    }
+
+    @Override
+    public TextClassification classifyText(TextClassification.Request request) {
+        return checkDestroyedAndRun(() -> mDelegate.classifyText(request));
+    }
+
+    @Override
+    public TextLinks generateLinks(TextLinks.Request request) {
+        return checkDestroyedAndRun(() -> mDelegate.generateLinks(request));
+    }
+
+    @Override
+    public ConversationActions suggestConversationActions(ConversationActions.Request request) {
+        return checkDestroyedAndRun(() -> mDelegate.suggestConversationActions(request));
+    }
+
+    @Override
+    public TextLanguage detectLanguage(TextLanguage.Request request) {
+        return checkDestroyedAndRun(() -> mDelegate.detectLanguage(request));
+    }
+
+    @Override
+    public int getMaxGenerateLinksTextLength() {
+        return checkDestroyedAndRun(mDelegate::getMaxGenerateLinksTextLength);
+    }
+
+    @Override
+    public void onSelectionEvent(SelectionEvent event) {
+        checkDestroyedAndRun(() -> {
+            try {
+                if (mEventHelper.sanitizeEvent(event)) {
+                    mDelegate.onSelectionEvent(event);
+                }
+            } catch (Exception e) {
+                // Avoid crashing for event reporting.
+                Log.e(LOG_TAG, "Error reporting text classifier selection event", e);
+            }
+            return null;
+        });
+    }
+
+    @Override
+    public void onTextClassifierEvent(TextClassifierEvent event) {
+        checkDestroyedAndRun(() -> {
+            try {
+                event.mHiddenTempSessionId = mSessionId;
+                mDelegate.onTextClassifierEvent(event);
+            } catch (Exception e) {
+                // Avoid crashing for event reporting.
+                Log.e(LOG_TAG, "Error reporting text classifier event", e);
+            }
+            return null;
+        });
+    }
+
+    @Override
+    public void destroy() {
+        synchronized (mLock) {
+            if (!mDestroyed) {
+                mCleaner.clean();
+                mDestroyed = true;
+            }
+        }
+    }
+
+    @Override
+    public boolean isDestroyed() {
+        synchronized (mLock) {
+            return mDestroyed;
+        }
+    }
+
+    /**
+     * Check whether the TextClassification Session was destroyed before and after the actual API
+     * invocation, and return response if not.
+     *
+     * @param responseSupplier a Supplier that represents a TextClassifier call
+     * @return the response of the TextClassifier call
+     * @throws IllegalStateException if this TextClassification session was destroyed before the
+     *                               call returned
+     * @see #isDestroyed()
+     * @see #destroy()
+     */
+    private <T> T checkDestroyedAndRun(Supplier<T> responseSupplier) {
+        if (!isDestroyed()) {
+            T response = responseSupplier.get();
+            synchronized (mLock) {
+                if (!mDestroyed) {
+                    return response;
+                }
+            }
+        }
+        throw new IllegalStateException(
+                "This TextClassification session has been destroyed");
+    }
+
+    /**
+     * Helper class for updating SelectionEvent fields.
+     */
+    private static final class SelectionEventHelper {
+
+        private final TextClassificationSessionId mSessionId;
+        private final TextClassificationContext mContext;
+
+        @InvocationMethod
+        private int mInvocationMethod = SelectionEvent.INVOCATION_UNKNOWN;
+        private SelectionEvent mPrevEvent;
+        private SelectionEvent mSmartEvent;
+        private SelectionEvent mStartEvent;
+
+        SelectionEventHelper(
+                TextClassificationSessionId sessionId, TextClassificationContext context) {
+            mSessionId = Objects.requireNonNull(sessionId);
+            mContext = Objects.requireNonNull(context);
+        }
+
+        /**
+         * Updates the necessary fields in the event for the current session.
+         *
+         * @return true if the event should be reported. false if the event should be ignored
+         */
+        boolean sanitizeEvent(SelectionEvent event) {
+            updateInvocationMethod(event);
+            modifyAutoSelectionEventType(event);
+
+            if (event.getEventType() != SelectionEvent.EVENT_SELECTION_STARTED
+                    && mStartEvent == null) {
+                Log.d(LOG_TAG, "Selection session not yet started. Ignoring event");
+                return false;
+            }
+
+            final long now = System.currentTimeMillis();
+            switch (event.getEventType()) {
+                case SelectionEvent.EVENT_SELECTION_STARTED:
+                    Preconditions.checkArgument(
+                            event.getAbsoluteEnd() == event.getAbsoluteStart() + 1);
+                    event.setSessionId(mSessionId);
+                    mStartEvent = event;
+                    break;
+                case SelectionEvent.EVENT_SMART_SELECTION_SINGLE:  // fall through
+                case SelectionEvent.EVENT_SMART_SELECTION_MULTI:   // fall through
+                case SelectionEvent.EVENT_AUTO_SELECTION:
+                    mSmartEvent = event;
+                    break;
+                case SelectionEvent.ACTION_ABANDON:
+                case SelectionEvent.ACTION_OVERTYPE:
+                    if (mPrevEvent != null) {
+                        event.setEntityType(mPrevEvent.getEntityType());
+                    }
+                    break;
+                case SelectionEvent.EVENT_SELECTION_MODIFIED:
+                    if (mPrevEvent != null
+                            && mPrevEvent.getAbsoluteStart() == event.getAbsoluteStart()
+                            && mPrevEvent.getAbsoluteEnd() == event.getAbsoluteEnd()) {
+                        // Selection did not change. Ignore event.
+                        return false;
+                    }
+                    break;
+                default:
+                    // do nothing.
+            }
+
+            event.setEventTime(now);
+            if (mStartEvent != null) {
+                event.setSessionId(mStartEvent.getSessionId())
+                        .setDurationSinceSessionStart(now - mStartEvent.getEventTime())
+                        .setStart(event.getAbsoluteStart() - mStartEvent.getAbsoluteStart())
+                        .setEnd(event.getAbsoluteEnd() - mStartEvent.getAbsoluteStart());
+            }
+            if (mSmartEvent != null) {
+                event.setResultId(mSmartEvent.getResultId())
+                        .setSmartStart(
+                                mSmartEvent.getAbsoluteStart() - mStartEvent.getAbsoluteStart())
+                        .setSmartEnd(mSmartEvent.getAbsoluteEnd() - mStartEvent.getAbsoluteStart());
+            }
+            if (mPrevEvent != null) {
+                event.setDurationSincePreviousEvent(now - mPrevEvent.getEventTime())
+                        .setEventIndex(mPrevEvent.getEventIndex() + 1);
+            }
+            mPrevEvent = event;
+            return true;
+        }
+
+        void endSession() {
+            mPrevEvent = null;
+            mSmartEvent = null;
+            mStartEvent = null;
+        }
+
+        private void updateInvocationMethod(SelectionEvent event) {
+            event.setTextClassificationSessionContext(mContext);
+            if (event.getInvocationMethod() == SelectionEvent.INVOCATION_UNKNOWN) {
+                event.setInvocationMethod(mInvocationMethod);
+            } else {
+                mInvocationMethod = event.getInvocationMethod();
+            }
+        }
+
+        private void modifyAutoSelectionEventType(SelectionEvent event) {
+            switch (event.getEventType()) {
+                case SelectionEvent.EVENT_SMART_SELECTION_SINGLE:  // fall through
+                case SelectionEvent.EVENT_SMART_SELECTION_MULTI:  // fall through
+                case SelectionEvent.EVENT_AUTO_SELECTION:
+                    if (SelectionSessionLogger.isPlatformLocalTextClassifierSmartSelection(
+                            event.getResultId())) {
+                        if (event.getAbsoluteEnd() - event.getAbsoluteStart() > 1) {
+                            event.setEventType(SelectionEvent.EVENT_SMART_SELECTION_MULTI);
+                        } else {
+                            event.setEventType(SelectionEvent.EVENT_SMART_SELECTION_SINGLE);
+                        }
+                    } else {
+                        event.setEventType(SelectionEvent.EVENT_AUTO_SELECTION);
+                    }
+                    return;
+                default:
+                    return;
+            }
+        }
+    }
+
+    // We use a static nested class here to avoid retaining the object reference of the outer
+    // class. Otherwise. the Cleaner would never be triggered.
+    private static class CleanerRunnable implements Runnable {
+        @NonNull
+        private final SelectionEventHelper mEventHelper;
+        @NonNull
+        private final TextClassifier mDelegate;
+
+        CleanerRunnable(
+                @NonNull SelectionEventHelper eventHelper, @NonNull TextClassifier delegate) {
+            mEventHelper = Objects.requireNonNull(eventHelper);
+            mDelegate = Objects.requireNonNull(delegate);
+        }
+
+        @Override
+        public void run() {
+            mEventHelper.endSession();
+            mDelegate.destroy();
+        }
+    }
+}
diff --git a/android/view/textclassifier/TextClassificationSessionFactory.java b/android/view/textclassifier/TextClassificationSessionFactory.java
new file mode 100644
index 0000000..c0914b6
--- /dev/null
+++ b/android/view/textclassifier/TextClassificationSessionFactory.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.NonNull;
+
+/**
+ * An interface for creating a session-aware TextClassifier.
+ *
+ * @see TextClassificationManager#createTextClassificationSession(TextClassificationContext)
+ */
+public interface TextClassificationSessionFactory {
+
+    /**
+     * Creates and returns a session-aware TextClassifier.
+     *
+     * @param classificationContext the classification context
+     */
+    @NonNull
+    TextClassifier createTextClassificationSession(
+            @NonNull TextClassificationContext classificationContext);
+}
diff --git a/android/view/textclassifier/TextClassificationSessionId.java b/android/view/textclassifier/TextClassificationSessionId.java
new file mode 100644
index 0000000..5fdcc31
--- /dev/null
+++ b/android/view/textclassifier/TextClassificationSessionId.java
@@ -0,0 +1,117 @@
+/*
+ * 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.view.textclassifier;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Locale;
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * This class represents the id of a text classification session.
+ */
+public final class TextClassificationSessionId implements Parcelable {
+    @NonNull
+    private final String mValue;
+    @NonNull
+    private final IBinder mToken;
+
+    /**
+     * Creates a new instance.
+     *
+     * @hide
+     */
+    public TextClassificationSessionId() {
+        this(UUID.randomUUID().toString(), new Binder());
+    }
+
+    /**
+     * Creates a new instance.
+     *
+     * @param value The internal value.
+     *
+     * @hide
+     */
+    public TextClassificationSessionId(@NonNull String value, @NonNull IBinder token) {
+        mValue = Objects.requireNonNull(value);
+        mToken = Objects.requireNonNull(token);
+    }
+
+    /** @hide */
+    @NonNull
+    public IBinder getToken() {
+        return mToken;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        TextClassificationSessionId that = (TextClassificationSessionId) o;
+        return Objects.equals(mValue, that.mValue) && Objects.equals(mToken, that.mToken);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mValue, mToken);
+    }
+
+    @Override
+    public String toString() {
+        return String.format(Locale.US, "TextClassificationSessionId {%s}", mValue);
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeString(mValue);
+        parcel.writeStrongBinder(mToken);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Returns the value of this ID.
+     */
+    @NonNull
+    public String getValue() {
+        return mValue;
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<TextClassificationSessionId> CREATOR =
+            new Parcelable.Creator<TextClassificationSessionId>() {
+                @Override
+                public TextClassificationSessionId createFromParcel(Parcel parcel) {
+                    return new TextClassificationSessionId(
+                            parcel.readString(), parcel.readStrongBinder());
+                }
+
+                @Override
+                public TextClassificationSessionId[] newArray(int size) {
+                    return new TextClassificationSessionId[size];
+                }
+            };
+}
diff --git a/android/view/textclassifier/TextClassifier.java b/android/view/textclassifier/TextClassifier.java
new file mode 100644
index 0000000..ef50045
--- /dev/null
+++ b/android/view/textclassifier/TextClassifier.java
@@ -0,0 +1,798 @@
+/*
+ * 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.view.textclassifier;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringDef;
+import android.annotation.WorkerThread;
+import android.os.LocaleList;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.style.URLSpan;
+import android.text.util.Linkify;
+import android.text.util.Linkify.LinkifyMask;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.text.BreakIterator;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Interface for providing text classification related features.
+ * <p>
+ * The TextClassifier may be used to understand the meaning of text, as well as generating predicted
+ * next actions based on the text.
+ *
+ * <p><strong>NOTE: </strong>Unless otherwise stated, methods of this interface are blocking
+ * operations. Call on a worker thread.
+ */
+public interface TextClassifier {
+
+    /** @hide */
+    String LOG_TAG = "androidtc";
+
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {LOCAL, SYSTEM, DEFAULT_SYSTEM})
+    @interface TextClassifierType {}  // TODO: Expose as system APIs.
+    /** Specifies a TextClassifier that runs locally in the app's process. @hide */
+    int LOCAL = 0;
+    /** Specifies a TextClassifier that runs in the system process and serves all apps. @hide */
+    int SYSTEM = 1;
+    /** Specifies the default TextClassifier that runs in the system process. @hide */
+    int DEFAULT_SYSTEM = 2;
+
+    /** @hide */
+    static String typeToString(@TextClassifierType int type) {
+        switch (type) {
+            case LOCAL:
+                return "Local";
+            case SYSTEM:
+                return "System";
+            case DEFAULT_SYSTEM:
+                return "Default system";
+        }
+        return "Unknown";
+    }
+
+    /** The TextClassifier failed to run. */
+    String TYPE_UNKNOWN = "";
+    /** The classifier ran, but didn't recognize a known entity. */
+    String TYPE_OTHER = "other";
+    /** E-mail address (e.g. "[email protected]"). */
+    String TYPE_EMAIL = "email";
+    /** Phone number (e.g. "555-123 456"). */
+    String TYPE_PHONE = "phone";
+    /** Physical address. */
+    String TYPE_ADDRESS = "address";
+    /** Web URL. */
+    String TYPE_URL = "url";
+    /** Time reference that is no more specific than a date. May be absolute such as "01/01/2000" or
+     * relative like "tomorrow". **/
+    String TYPE_DATE = "date";
+    /** Time reference that includes a specific time. May be absolute such as "01/01/2000 5:30pm" or
+     * relative like "tomorrow at 5:30pm". **/
+    String TYPE_DATE_TIME = "datetime";
+    /** Flight number in IATA format. */
+    String TYPE_FLIGHT_NUMBER = "flight";
+    /**
+     * Word that users may be interested to look up for meaning.
+     * @hide
+     */
+    String TYPE_DICTIONARY = "dictionary";
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @StringDef(prefix = { "TYPE_" }, value = {
+            TYPE_UNKNOWN,
+            TYPE_OTHER,
+            TYPE_EMAIL,
+            TYPE_PHONE,
+            TYPE_ADDRESS,
+            TYPE_URL,
+            TYPE_DATE,
+            TYPE_DATE_TIME,
+            TYPE_FLIGHT_NUMBER,
+            TYPE_DICTIONARY
+    })
+    @interface EntityType {}
+
+    /** Designates that the text in question is editable. **/
+    String HINT_TEXT_IS_EDITABLE = "android.text_is_editable";
+    /** Designates that the text in question is not editable. **/
+    String HINT_TEXT_IS_NOT_EDITABLE = "android.text_is_not_editable";
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @StringDef(prefix = { "HINT_" }, value = {HINT_TEXT_IS_EDITABLE, HINT_TEXT_IS_NOT_EDITABLE})
+    @interface Hints {}
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @StringDef({WIDGET_TYPE_TEXTVIEW, WIDGET_TYPE_EDITTEXT, WIDGET_TYPE_UNSELECTABLE_TEXTVIEW,
+            WIDGET_TYPE_WEBVIEW, WIDGET_TYPE_EDIT_WEBVIEW, WIDGET_TYPE_CUSTOM_TEXTVIEW,
+            WIDGET_TYPE_CUSTOM_EDITTEXT, WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW,
+            WIDGET_TYPE_NOTIFICATION, WIDGET_TYPE_CLIPBOARD, WIDGET_TYPE_UNKNOWN })
+    @interface WidgetType {}
+
+    /** The widget involved in the text classification context is a standard
+     * {@link android.widget.TextView}. */
+    String WIDGET_TYPE_TEXTVIEW = "textview";
+    /** The widget involved in the text classification context is a standard
+     * {@link android.widget.EditText}. */
+    String WIDGET_TYPE_EDITTEXT = "edittext";
+    /** The widget involved in the text classification context is a standard non-selectable
+     * {@link android.widget.TextView}. */
+    String WIDGET_TYPE_UNSELECTABLE_TEXTVIEW = "nosel-textview";
+    /** The widget involved in the text classification context is a standard
+     * {@link android.webkit.WebView}. */
+    String WIDGET_TYPE_WEBVIEW = "webview";
+    /** The widget involved in the text classification context is a standard editable
+     * {@link android.webkit.WebView}. */
+    String WIDGET_TYPE_EDIT_WEBVIEW = "edit-webview";
+    /** The widget involved in the text classification context is a custom text widget. */
+    String WIDGET_TYPE_CUSTOM_TEXTVIEW = "customview";
+    /** The widget involved in the text classification context is a custom editable text widget. */
+    String WIDGET_TYPE_CUSTOM_EDITTEXT = "customedit";
+    /** The widget involved in the text classification context is a custom non-selectable text
+     * widget. */
+    String WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview";
+    /** The widget involved in the text classification context is a notification */
+    String WIDGET_TYPE_NOTIFICATION = "notification";
+    /** The text classification context is for use with the system clipboard. */
+    String WIDGET_TYPE_CLIPBOARD = "clipboard";
+    /** The widget involved in the text classification context is of an unknown/unspecified type. */
+    String WIDGET_TYPE_UNKNOWN = "unknown";
+
+    /**
+     * No-op TextClassifier.
+     * This may be used to turn off TextClassifier features.
+     */
+    TextClassifier NO_OP = new TextClassifier() {
+        @Override
+        public String toString() {
+            return "TextClassifier.NO_OP";
+        }
+    };
+
+    /**
+     * Extra that is included on activity intents coming from a TextClassifier when
+     * it suggests actions to its caller.
+     * <p>
+     * All {@link TextClassifier} implementations should make sure this extra exists in their
+     * generated intents.
+     */
+    String EXTRA_FROM_TEXT_CLASSIFIER = "android.view.textclassifier.extra.FROM_TEXT_CLASSIFIER";
+
+    /**
+     * Returns suggested text selection start and end indices, recognized entity types, and their
+     * associated confidence scores. The entity types are ordered from highest to lowest scoring.
+     *
+     * <p><strong>NOTE: </strong>Call on a worker thread.
+     *
+     * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
+     * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
+     *
+     * @param request the text selection request
+     */
+    @WorkerThread
+    @NonNull
+    default TextSelection suggestSelection(@NonNull TextSelection.Request request) {
+        Objects.requireNonNull(request);
+        Utils.checkMainThread();
+        return new TextSelection.Builder(request.getStartIndex(), request.getEndIndex()).build();
+    }
+
+    /**
+     * Returns suggested text selection start and end indices, recognized entity types, and their
+     * associated confidence scores. The entity types are ordered from highest to lowest scoring.
+     *
+     * <p><strong>NOTE: </strong>Call on a worker thread.
+     *
+     * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
+     * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
+     *
+     * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
+     * {@link #suggestSelection(TextSelection.Request)}. If that method calls this method,
+     * a stack overflow error will happen.
+     *
+     * @param text text providing context for the selected text (which is specified
+     *      by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
+     * @param selectionStartIndex start index of the selected part of text
+     * @param selectionEndIndex end index of the selected part of text
+     * @param defaultLocales ordered list of locale preferences that may be used to
+     *      disambiguate the provided text. If no locale preferences exist, set this to null
+     *      or an empty locale list.
+     *
+     * @throws IllegalArgumentException if text is null; selectionStartIndex is negative;
+     *      selectionEndIndex is greater than text.length() or not greater than selectionStartIndex
+     *
+     * @see #suggestSelection(TextSelection.Request)
+     */
+    @WorkerThread
+    @NonNull
+    default TextSelection suggestSelection(
+            @NonNull CharSequence text,
+            @IntRange(from = 0) int selectionStartIndex,
+            @IntRange(from = 0) int selectionEndIndex,
+            @Nullable LocaleList defaultLocales) {
+        final TextSelection.Request request = new TextSelection.Request.Builder(
+                text, selectionStartIndex, selectionEndIndex)
+                .setDefaultLocales(defaultLocales)
+                .build();
+        return suggestSelection(request);
+    }
+
+    /**
+     * Classifies the specified text and returns a {@link TextClassification} object that can be
+     * used to generate a widget for handling the classified text.
+     *
+     * <p><strong>NOTE: </strong>Call on a worker thread.
+     *
+     * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
+     * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
+     *
+     * @param request the text classification request
+     */
+    @WorkerThread
+    @NonNull
+    default TextClassification classifyText(@NonNull TextClassification.Request request) {
+        Objects.requireNonNull(request);
+        Utils.checkMainThread();
+        return TextClassification.EMPTY;
+    }
+
+    /**
+     * Classifies the specified text and returns a {@link TextClassification} object that can be
+     * used to generate a widget for handling the classified text.
+     *
+     * <p><strong>NOTE: </strong>Call on a worker thread.
+     *
+     * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
+     * {@link #classifyText(TextClassification.Request)}. If that method calls this method,
+     * a stack overflow error will happen.
+     *
+     * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
+     * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
+     *
+     * @param text text providing context for the text to classify (which is specified
+     *      by the sub sequence starting at startIndex and ending at endIndex)
+     * @param startIndex start index of the text to classify
+     * @param endIndex end index of the text to classify
+     * @param defaultLocales ordered list of locale preferences that may be used to
+     *      disambiguate the provided text. If no locale preferences exist, set this to null
+     *      or an empty locale list.
+     *
+     * @throws IllegalArgumentException if text is null; startIndex is negative;
+     *      endIndex is greater than text.length() or not greater than startIndex
+     *
+     * @see #classifyText(TextClassification.Request)
+     */
+    @WorkerThread
+    @NonNull
+    default TextClassification classifyText(
+            @NonNull CharSequence text,
+            @IntRange(from = 0) int startIndex,
+            @IntRange(from = 0) int endIndex,
+            @Nullable LocaleList defaultLocales) {
+        final TextClassification.Request request = new TextClassification.Request.Builder(
+                text, startIndex, endIndex)
+                .setDefaultLocales(defaultLocales)
+                .build();
+        return classifyText(request);
+    }
+
+    /**
+     * Generates and returns a {@link TextLinks} that may be applied to the text to annotate it with
+     * links information.
+     *
+     * <p><strong>NOTE: </strong>Call on a worker thread.
+     *
+     * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
+     * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
+     *
+     * @param request the text links request
+     *
+     * @see #getMaxGenerateLinksTextLength()
+     */
+    @WorkerThread
+    @NonNull
+    default TextLinks generateLinks(@NonNull TextLinks.Request request) {
+        Objects.requireNonNull(request);
+        Utils.checkMainThread();
+        return new TextLinks.Builder(request.getText().toString()).build();
+    }
+
+    /**
+     * Returns the maximal length of text that can be processed by generateLinks.
+     *
+     * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
+     * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
+     *
+     * @see #generateLinks(TextLinks.Request)
+     */
+    @WorkerThread
+    default int getMaxGenerateLinksTextLength() {
+        return Integer.MAX_VALUE;
+    }
+
+    /**
+     * Detects the language of the text in the given request.
+     *
+     * <p><strong>NOTE: </strong>Call on a worker thread.
+     *
+     *
+     * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
+     * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
+     *
+     * @param request the {@link TextLanguage} request.
+     * @return the {@link TextLanguage} result.
+     */
+    @WorkerThread
+    @NonNull
+    default TextLanguage detectLanguage(@NonNull TextLanguage.Request request) {
+        Objects.requireNonNull(request);
+        Utils.checkMainThread();
+        return TextLanguage.EMPTY;
+    }
+
+    /**
+     * Suggests and returns a list of actions according to the given conversation.
+     */
+    @WorkerThread
+    @NonNull
+    default ConversationActions suggestConversationActions(
+            @NonNull ConversationActions.Request request) {
+        Objects.requireNonNull(request);
+        Utils.checkMainThread();
+        return new ConversationActions(Collections.emptyList(), null);
+    }
+
+    /**
+     * <strong>NOTE: </strong>Use {@link #onTextClassifierEvent(TextClassifierEvent)} instead.
+     * <p>
+     * Reports a selection event.
+     *
+     * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
+     * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
+     */
+    default void onSelectionEvent(@NonNull SelectionEvent event) {
+        // TODO: Consider rerouting to onTextClassifierEvent()
+    }
+
+    /**
+     * Reports a text classifier event.
+     * <p>
+     * <strong>NOTE: </strong>Call on a worker thread.
+     *
+     * @throws IllegalStateException if this TextClassifier has been destroyed.
+     * @see #isDestroyed()
+     */
+    default void onTextClassifierEvent(@NonNull TextClassifierEvent event) {}
+
+    /**
+     * Destroys this TextClassifier.
+     *
+     * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to its methods should
+     * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
+     *
+     * <p>Subsequent calls to this method are no-ops.
+     */
+    default void destroy() {}
+
+    /**
+     * Returns whether or not this TextClassifier has been destroyed.
+     *
+     * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, caller should not interact
+     * with the classifier and an attempt to do so would throw an {@link IllegalStateException}.
+     * However, this method should never throw an {@link IllegalStateException}.
+     *
+     * @see #destroy()
+     */
+    default boolean isDestroyed() {
+        return false;
+    }
+
+    /** @hide **/
+    default void dump(@NonNull IndentingPrintWriter printWriter) {}
+
+    /**
+     * Configuration object for specifying what entity types to identify.
+     *
+     * Configs are initially based on a predefined preset, and can be modified from there.
+     */
+    final class EntityConfig implements Parcelable {
+        private final List<String> mIncludedTypes;
+        private final List<String> mExcludedTypes;
+        private final List<String> mHints;
+        private final boolean mIncludeTypesFromTextClassifier;
+
+        private EntityConfig(
+                List<String> includedEntityTypes,
+                List<String> excludedEntityTypes,
+                List<String> hints,
+                boolean includeTypesFromTextClassifier) {
+            mIncludedTypes = Objects.requireNonNull(includedEntityTypes);
+            mExcludedTypes = Objects.requireNonNull(excludedEntityTypes);
+            mHints = Objects.requireNonNull(hints);
+            mIncludeTypesFromTextClassifier = includeTypesFromTextClassifier;
+        }
+
+        private EntityConfig(Parcel in) {
+            mIncludedTypes = new ArrayList<>();
+            in.readStringList(mIncludedTypes);
+            mExcludedTypes = new ArrayList<>();
+            in.readStringList(mExcludedTypes);
+            List<String> tmpHints = new ArrayList<>();
+            in.readStringList(tmpHints);
+            mHints = Collections.unmodifiableList(tmpHints);
+            mIncludeTypesFromTextClassifier = in.readByte() != 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel parcel, int flags) {
+            parcel.writeStringList(mIncludedTypes);
+            parcel.writeStringList(mExcludedTypes);
+            parcel.writeStringList(mHints);
+            parcel.writeByte((byte) (mIncludeTypesFromTextClassifier ? 1 : 0));
+        }
+
+        /**
+         * Creates an EntityConfig.
+         *
+         * @param hints Hints for the TextClassifier to determine what types of entities to find.
+         *
+         * @deprecated Use {@link Builder} instead.
+         */
+        @Deprecated
+        public static EntityConfig createWithHints(@Nullable Collection<String> hints) {
+            return new EntityConfig.Builder()
+                    .includeTypesFromTextClassifier(true)
+                    .setHints(hints)
+                    .build();
+        }
+
+        /**
+         * Creates an EntityConfig.
+         *
+         * @param hints Hints for the TextClassifier to determine what types of entities to find
+         * @param includedEntityTypes Entity types, e.g. {@link #TYPE_EMAIL}, to explicitly include
+         * @param excludedEntityTypes Entity types, e.g. {@link #TYPE_PHONE}, to explicitly exclude
+         *
+         *
+         * Note that if an entity has been excluded, the exclusion will take precedence.
+         *
+         * @deprecated Use {@link Builder} instead.
+         */
+        @Deprecated
+        public static EntityConfig create(@Nullable Collection<String> hints,
+                @Nullable Collection<String> includedEntityTypes,
+                @Nullable Collection<String> excludedEntityTypes) {
+            return new EntityConfig.Builder()
+                    .setIncludedTypes(includedEntityTypes)
+                    .setExcludedTypes(excludedEntityTypes)
+                    .setHints(hints)
+                    .includeTypesFromTextClassifier(true)
+                    .build();
+        }
+
+        /**
+         * Creates an EntityConfig with an explicit entity list.
+         *
+         * @param entityTypes Complete set of entities, e.g. {@link #TYPE_URL} to find.
+         *
+         * @deprecated Use {@link Builder} instead.
+         */
+        @Deprecated
+        public static EntityConfig createWithExplicitEntityList(
+                @Nullable Collection<String> entityTypes) {
+            return new EntityConfig.Builder()
+                    .setIncludedTypes(entityTypes)
+                    .includeTypesFromTextClassifier(false)
+                    .build();
+        }
+
+        /**
+         * Returns a final list of entity types to find.
+         *
+         * @param entityTypes Entity types we think should be found before factoring in
+         *                    includes/excludes
+         *
+         * This method is intended for use by TextClassifier implementations.
+         */
+        public Collection<String> resolveEntityListModifications(
+                @NonNull Collection<String> entityTypes) {
+            final Set<String> finalSet = new HashSet<>();
+            if (mIncludeTypesFromTextClassifier) {
+                finalSet.addAll(entityTypes);
+            }
+            finalSet.addAll(mIncludedTypes);
+            finalSet.removeAll(mExcludedTypes);
+            return finalSet;
+        }
+
+        /**
+         * Retrieves the list of hints.
+         *
+         * @return An unmodifiable collection of the hints.
+         */
+        public Collection<String> getHints() {
+            return mHints;
+        }
+
+        /**
+         * Return whether the client allows the text classifier to include its own list of
+         * default types. If this function returns {@code true}, a default list of types suggested
+         * from a text classifier will be taking into account.
+         *
+         * <p>NOTE: This method is intended for use by a text classifier.
+         *
+         * @see #resolveEntityListModifications(Collection)
+         */
+        public boolean shouldIncludeTypesFromTextClassifier() {
+            return mIncludeTypesFromTextClassifier;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        public static final @android.annotation.NonNull Parcelable.Creator<EntityConfig> CREATOR =
+                new Parcelable.Creator<EntityConfig>() {
+                    @Override
+                    public EntityConfig createFromParcel(Parcel in) {
+                        return new EntityConfig(in);
+                    }
+
+                    @Override
+                    public EntityConfig[] newArray(int size) {
+                        return new EntityConfig[size];
+                    }
+                };
+
+
+
+        /** Builder class to construct the {@link EntityConfig} object. */
+        public static final class Builder {
+            @Nullable
+            private Collection<String> mIncludedTypes;
+            @Nullable
+            private Collection<String> mExcludedTypes;
+            @Nullable
+            private Collection<String> mHints;
+            private boolean mIncludeTypesFromTextClassifier = true;
+
+            /**
+             * Sets a collection of types that are explicitly included.
+             */
+            @NonNull
+            public Builder setIncludedTypes(@Nullable Collection<String> includedTypes) {
+                mIncludedTypes = includedTypes;
+                return this;
+            }
+
+            /**
+             * Sets a collection of types that are explicitly excluded.
+             */
+            @NonNull
+            public Builder setExcludedTypes(@Nullable Collection<String> excludedTypes) {
+                mExcludedTypes = excludedTypes;
+                return this;
+            }
+
+            /**
+             * Specifies whether or not to include the types suggested by the text classifier. By
+             * default, it is included.
+             */
+            @NonNull
+            public Builder includeTypesFromTextClassifier(boolean includeTypesFromTextClassifier) {
+                mIncludeTypesFromTextClassifier = includeTypesFromTextClassifier;
+                return this;
+            }
+
+
+            /**
+             * Sets the hints for the TextClassifier to determine what types of entities to find.
+             * These hints will only be used if {@link #includeTypesFromTextClassifier} is
+             * set to be true.
+             */
+            @NonNull
+            public Builder setHints(@Nullable Collection<String> hints) {
+                mHints = hints;
+                return this;
+            }
+
+            /**
+             * Combines all of the options that have been set and returns a new {@link EntityConfig}
+             * object.
+             */
+            @NonNull
+            public EntityConfig build() {
+                return new EntityConfig(
+                        mIncludedTypes == null
+                                ? Collections.emptyList()
+                                : new ArrayList<>(mIncludedTypes),
+                        mExcludedTypes == null
+                                ? Collections.emptyList()
+                                : new ArrayList<>(mExcludedTypes),
+                        mHints == null
+                                ? Collections.emptyList()
+                                : Collections.unmodifiableList(new ArrayList<>(mHints)),
+                        mIncludeTypesFromTextClassifier);
+            }
+        }
+    }
+
+    /**
+     * Utility functions for TextClassifier methods.
+     *
+     * <ul>
+     *  <li>Provides validation of input parameters to TextClassifier methods
+     * </ul>
+     *
+     * Intended to be used only for TextClassifier purposes.
+     * @hide
+     */
+    final class Utils {
+
+        @GuardedBy("WORD_ITERATOR")
+        private static final BreakIterator WORD_ITERATOR = BreakIterator.getWordInstance();
+
+        /**
+         * @throws IllegalArgumentException if text is null; startIndex is negative;
+         *      endIndex is greater than text.length() or is not greater than startIndex;
+         *      options is null
+         */
+        static void checkArgument(@NonNull CharSequence text, int startIndex, int endIndex) {
+            Preconditions.checkArgument(text != null);
+            Preconditions.checkArgument(startIndex >= 0);
+            Preconditions.checkArgument(endIndex <= text.length());
+            Preconditions.checkArgument(endIndex > startIndex);
+        }
+
+        /** Returns if the length of the text is within the range. */
+        static boolean checkTextLength(CharSequence text, int maxLength) {
+            int textLength = text.length();
+            return textLength >= 0 && textLength <= maxLength;
+        }
+
+        /**
+         * Returns the substring of {@code text} that contains at least text from index
+         * {@code start} <i>(inclusive)</i> to index {@code end} <i><(exclusive)/i> with the goal of
+         * returning text that is at least {@code minimumLength}. If {@code text} is not long
+         * enough, this will return {@code text}. This method returns text at word boundaries.
+         *
+         * @param text the source text
+         * @param start the start index of text that must be included
+         * @param end the end index of text that must be included
+         * @param minimumLength minimum length of text to return if {@code text} is long enough
+         */
+        public static String getSubString(
+                String text, int start, int end, int minimumLength) {
+            Preconditions.checkArgument(start >= 0);
+            Preconditions.checkArgument(end <= text.length());
+            Preconditions.checkArgument(start <= end);
+
+            if (text.length() < minimumLength) {
+                return text;
+            }
+
+            final int length = end - start;
+            if (length >= minimumLength) {
+                return text.substring(start, end);
+            }
+
+            final int offset = (minimumLength - length) / 2;
+            int iterStart = Math.max(0, Math.min(start - offset, text.length() - minimumLength));
+            int iterEnd = Math.min(text.length(), iterStart + minimumLength);
+
+            synchronized (WORD_ITERATOR) {
+                WORD_ITERATOR.setText(text);
+                iterStart = WORD_ITERATOR.isBoundary(iterStart)
+                        ? iterStart : Math.max(0, WORD_ITERATOR.preceding(iterStart));
+                iterEnd = WORD_ITERATOR.isBoundary(iterEnd)
+                        ? iterEnd : Math.max(iterEnd, WORD_ITERATOR.following(iterEnd));
+                WORD_ITERATOR.setText("");
+                return text.substring(iterStart, iterEnd);
+            }
+        }
+
+        /**
+         * Generates links using legacy {@link Linkify}.
+         */
+        public static TextLinks generateLegacyLinks(@NonNull TextLinks.Request request) {
+            final String string = request.getText().toString();
+            final TextLinks.Builder links = new TextLinks.Builder(string);
+
+            final Collection<String> entities = request.getEntityConfig()
+                    .resolveEntityListModifications(Collections.emptyList());
+            if (entities.contains(TextClassifier.TYPE_URL)) {
+                addLinks(links, string, TextClassifier.TYPE_URL);
+            }
+            if (entities.contains(TextClassifier.TYPE_PHONE)) {
+                addLinks(links, string, TextClassifier.TYPE_PHONE);
+            }
+            if (entities.contains(TextClassifier.TYPE_EMAIL)) {
+                addLinks(links, string, TextClassifier.TYPE_EMAIL);
+            }
+            // NOTE: Do not support MAP_ADDRESSES. Legacy version does not work well.
+            return links.build();
+        }
+
+        private static void addLinks(
+                TextLinks.Builder links, String string, @EntityType String entityType) {
+            final Spannable spannable = new SpannableString(string);
+            if (Linkify.addLinks(spannable, linkMask(entityType))) {
+                final URLSpan[] spans = spannable.getSpans(0, spannable.length(), URLSpan.class);
+                for (URLSpan urlSpan : spans) {
+                    links.addLink(
+                            spannable.getSpanStart(urlSpan),
+                            spannable.getSpanEnd(urlSpan),
+                            entityScores(entityType),
+                            urlSpan);
+                }
+            }
+        }
+
+        @LinkifyMask
+        private static int linkMask(@EntityType String entityType) {
+            switch (entityType) {
+                case TextClassifier.TYPE_URL:
+                    return Linkify.WEB_URLS;
+                case TextClassifier.TYPE_PHONE:
+                    return Linkify.PHONE_NUMBERS;
+                case TextClassifier.TYPE_EMAIL:
+                    return Linkify.EMAIL_ADDRESSES;
+                default:
+                    // NOTE: Do not support MAP_ADDRESSES. Legacy version does not work well.
+                    return 0;
+            }
+        }
+
+        private static Map<String, Float> entityScores(@EntityType String entityType) {
+            final Map<String, Float> scores = new ArrayMap<>();
+            scores.put(entityType, 1f);
+            return scores;
+        }
+
+        static void checkMainThread() {
+            if (Looper.myLooper() == Looper.getMainLooper()) {
+                Log.w(LOG_TAG, "TextClassifier called on main thread");
+            }
+        }
+    }
+}
diff --git a/android/view/textclassifier/TextClassifierEvent.java b/android/view/textclassifier/TextClassifierEvent.java
new file mode 100644
index 0000000..90667cf
--- /dev/null
+++ b/android/view/textclassifier/TextClassifierEvent.java
@@ -0,0 +1,1182 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.icu.util.ULocale;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * This class represents events that are sent by components to the {@link TextClassifier} to report
+ * something of note that relates to a feature powered by the TextClassifier. The TextClassifier may
+ * log these events or use them to improve future responses to queries.
+ * <p>
+ * Each category of events has its their own subclass. Events of each type have an associated
+ * set of related properties. You can find their specification in the subclasses.
+ */
+public abstract class TextClassifierEvent implements Parcelable {
+
+    private static final int PARCEL_TOKEN_TEXT_SELECTION_EVENT = 1;
+    private static final int PARCEL_TOKEN_TEXT_LINKIFY_EVENT = 2;
+    private static final int PARCEL_TOKEN_CONVERSATION_ACTION_EVENT = 3;
+    private static final int PARCEL_TOKEN_LANGUAGE_DETECTION_EVENT = 4;
+
+    /** @hide **/
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({CATEGORY_SELECTION, CATEGORY_LINKIFY,
+            CATEGORY_CONVERSATION_ACTIONS, CATEGORY_LANGUAGE_DETECTION})
+    public @interface Category {
+        // For custom event categories, use range 1000+.
+    }
+
+    /**
+     * Smart selection
+     *
+     * @see TextSelectionEvent
+     */
+    public static final int CATEGORY_SELECTION = 1;
+    /**
+     * Linkify
+     *
+     * @see TextLinkifyEvent
+     */
+    public static final int CATEGORY_LINKIFY = 2;
+    /**
+     *  Conversation actions
+     *
+     * @see ConversationActionsEvent
+     */
+    public static final int CATEGORY_CONVERSATION_ACTIONS = 3;
+    /**
+     * Language detection
+     *
+     * @see LanguageDetectionEvent
+     */
+    public static final int CATEGORY_LANGUAGE_DETECTION = 4;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({TYPE_SELECTION_STARTED, TYPE_SELECTION_MODIFIED,
+            TYPE_SMART_SELECTION_SINGLE, TYPE_SMART_SELECTION_MULTI, TYPE_AUTO_SELECTION,
+            TYPE_ACTIONS_SHOWN, TYPE_LINK_CLICKED, TYPE_OVERTYPE, TYPE_COPY_ACTION,
+            TYPE_PASTE_ACTION, TYPE_CUT_ACTION, TYPE_SHARE_ACTION, TYPE_SMART_ACTION,
+            TYPE_SELECTION_DRAG, TYPE_SELECTION_DESTROYED, TYPE_OTHER_ACTION, TYPE_SELECT_ALL,
+            TYPE_SELECTION_RESET, TYPE_MANUAL_REPLY, TYPE_ACTIONS_GENERATED, TYPE_LINKS_GENERATED,
+            TYPE_READ_CLIPBOARD})
+    public @interface Type {
+        // For custom event types, use range 1,000,000+.
+    }
+
+    // All these event type constants are required to match with those defined in
+    // textclassifier_enums.proto.
+    /** User started a new selection. */
+    public static final int TYPE_SELECTION_STARTED = 1;
+    /** User modified an existing selection. */
+    public static final int TYPE_SELECTION_MODIFIED = 2;
+    /** Smart selection triggered for a single token (word). */
+    public static final int TYPE_SMART_SELECTION_SINGLE = 3;
+    /** Smart selection triggered spanning multiple tokens (words). */
+    public static final int TYPE_SMART_SELECTION_MULTI = 4;
+    /** Something else other than user or the default TextClassifier triggered a selection. */
+    public static final int TYPE_AUTO_SELECTION = 5;
+    /** Smart actions shown to the user. */
+    public static final int TYPE_ACTIONS_SHOWN = 6;
+    /** User clicked a link. */
+    public static final int TYPE_LINK_CLICKED = 7;
+    /** User typed over the selection. */
+    public static final int TYPE_OVERTYPE = 8;
+    /** User clicked on Copy action. */
+    public static final int TYPE_COPY_ACTION = 9;
+    /** User clicked on Paste action. */
+    public static final int TYPE_PASTE_ACTION = 10;
+    /** User clicked on Cut action. */
+    public static final int TYPE_CUT_ACTION = 11;
+    /** User clicked on Share action. */
+    public static final int TYPE_SHARE_ACTION = 12;
+    /** User clicked on a Smart action. */
+    public static final int TYPE_SMART_ACTION = 13;
+    /** User dragged+dropped the selection. */
+    public static final int TYPE_SELECTION_DRAG = 14;
+    /** Selection is destroyed. */
+    public static final int TYPE_SELECTION_DESTROYED = 15;
+    /** User clicked on a custom action. */
+    public static final int TYPE_OTHER_ACTION = 16;
+    /** User clicked on Select All action */
+    public static final int TYPE_SELECT_ALL = 17;
+    /** User reset the smart selection. */
+    public static final int TYPE_SELECTION_RESET = 18;
+    /** User composed a reply. */
+    public static final int TYPE_MANUAL_REPLY = 19;
+    /** TextClassifier generated some actions */
+    public static final int TYPE_ACTIONS_GENERATED = 20;
+    /** Some text links were generated.*/
+    public static final int TYPE_LINKS_GENERATED = 21;
+    /**
+     * Read a clipboard.
+     * TODO: Make this public.
+     *
+     * @hide
+     */
+    public static final int TYPE_READ_CLIPBOARD = 22;
+
+    @Category
+    private final int mEventCategory;
+    @Type
+    private final int mEventType;
+    @Nullable
+    private final String[] mEntityTypes;
+    @Nullable
+    private TextClassificationContext mEventContext;
+    @Nullable
+    private final String mResultId;
+    private final int mEventIndex;
+    private final float[] mScores;
+    @Nullable
+    private final String mModelName;
+    private final int[] mActionIndices;
+    @Nullable
+    private final ULocale mLocale;
+    private final Bundle mExtras;
+
+    /**
+     * Session id holder to help with converting this event to the legacy SelectionEvent.
+     * @hide
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    @Nullable
+    public TextClassificationSessionId mHiddenTempSessionId;
+
+    private TextClassifierEvent(Builder builder) {
+        mEventCategory = builder.mEventCategory;
+        mEventType = builder.mEventType;
+        mEntityTypes = builder.mEntityTypes;
+        mEventContext = builder.mEventContext;
+        mResultId = builder.mResultId;
+        mEventIndex = builder.mEventIndex;
+        mScores = builder.mScores;
+        mModelName = builder.mModelName;
+        mActionIndices = builder.mActionIndices;
+        mLocale = builder.mLocale;
+        mExtras = builder.mExtras == null ? Bundle.EMPTY : builder.mExtras;
+    }
+
+    private TextClassifierEvent(Parcel in) {
+        mEventCategory = in.readInt();
+        mEventType = in.readInt();
+        mEntityTypes = in.readStringArray();
+        mEventContext = in.readParcelable(null);
+        mResultId = in.readString();
+        mEventIndex = in.readInt();
+        int scoresLength = in.readInt();
+        mScores = new float[scoresLength];
+        in.readFloatArray(mScores);
+        mModelName = in.readString();
+        mActionIndices = in.createIntArray();
+        final String languageTag = in.readString();
+        mLocale = languageTag == null ? null : ULocale.forLanguageTag(languageTag);
+        mExtras = in.readBundle();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    public static final Creator<TextClassifierEvent> CREATOR = new Creator<TextClassifierEvent>() {
+        @Override
+        public TextClassifierEvent createFromParcel(Parcel in) {
+            int token = in.readInt();
+            if (token == PARCEL_TOKEN_TEXT_SELECTION_EVENT) {
+                return new TextSelectionEvent(in);
+            }
+            if (token == PARCEL_TOKEN_TEXT_LINKIFY_EVENT) {
+                return new TextLinkifyEvent(in);
+            }
+            if (token == PARCEL_TOKEN_LANGUAGE_DETECTION_EVENT) {
+                return new LanguageDetectionEvent(in);
+            }
+            if (token == PARCEL_TOKEN_CONVERSATION_ACTION_EVENT) {
+                return new ConversationActionsEvent(in);
+            }
+            throw new IllegalStateException("Unexpected input event type token in parcel.");
+        }
+
+        @Override
+        public TextClassifierEvent[] newArray(int size) {
+            return new TextClassifierEvent[size];
+        }
+    };
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(getParcelToken());
+        dest.writeInt(mEventCategory);
+        dest.writeInt(mEventType);
+        dest.writeStringArray(mEntityTypes);
+        dest.writeParcelable(mEventContext, flags);
+        dest.writeString(mResultId);
+        dest.writeInt(mEventIndex);
+        dest.writeInt(mScores.length);
+        dest.writeFloatArray(mScores);
+        dest.writeString(mModelName);
+        dest.writeIntArray(mActionIndices);
+        dest.writeString(mLocale == null ? null : mLocale.toLanguageTag());
+        dest.writeBundle(mExtras);
+    }
+
+    private int getParcelToken() {
+        if (this instanceof TextSelectionEvent) {
+            return PARCEL_TOKEN_TEXT_SELECTION_EVENT;
+        }
+        if (this instanceof TextLinkifyEvent) {
+            return PARCEL_TOKEN_TEXT_LINKIFY_EVENT;
+        }
+        if (this instanceof LanguageDetectionEvent) {
+            return PARCEL_TOKEN_LANGUAGE_DETECTION_EVENT;
+        }
+        if (this instanceof ConversationActionsEvent) {
+            return PARCEL_TOKEN_CONVERSATION_ACTION_EVENT;
+        }
+        throw new IllegalArgumentException("Unexpected type: " + this.getClass().getSimpleName());
+    }
+
+    /**
+     * Returns the event category. e.g. {@link #CATEGORY_SELECTION}.
+     */
+    @Category
+    public int getEventCategory() {
+        return mEventCategory;
+    }
+
+    /**
+     * Returns the event type. e.g. {@link #TYPE_SELECTION_STARTED}.
+     */
+    @Type
+    public int getEventType() {
+        return mEventType;
+    }
+
+    /**
+     * Returns an array of entity types. e.g. {@link TextClassifier#TYPE_ADDRESS}.
+     *
+     * @see Builder#setEntityTypes(String...) for supported types.
+     */
+    @NonNull
+    public String[] getEntityTypes() {
+        return mEntityTypes;
+    }
+
+    /**
+     * Returns the event context.
+     */
+    @Nullable
+    public TextClassificationContext getEventContext() {
+        return mEventContext;
+    }
+
+    /**
+     * Sets the event context.
+     * <p>
+     * Package-private for SystemTextClassifier's use.
+     */
+    void setEventContext(@Nullable TextClassificationContext eventContext) {
+        mEventContext = eventContext;
+    }
+
+    /**
+     * Returns the id of the text classifier result related to this event.
+     */
+    @Nullable
+    public String getResultId() {
+        return mResultId;
+    }
+
+    /**
+     * Returns the index of this event in the series of event it belongs to.
+     */
+    public int getEventIndex() {
+        return mEventIndex;
+    }
+
+    /**
+     * Returns the scores of the suggestions.
+     */
+    @NonNull
+    public float[] getScores() {
+        return mScores;
+    }
+
+    /**
+     * Returns the model name.
+     */
+    @Nullable
+    public String getModelName() {
+        return mModelName;
+    }
+
+    /**
+     * Returns the indices of the actions relating to this event.
+     * Actions are usually returned by the text classifier in priority order with the most
+     * preferred action at index 0. This list gives an indication of the position of the actions
+     * that are being reported.
+     *
+     * @see Builder#setActionIndices(int...)
+     */
+    @NonNull
+    public int[] getActionIndices() {
+        return mActionIndices;
+    }
+
+    /**
+     * Returns the detected locale.
+     */
+    @Nullable
+    public ULocale getLocale() {
+        return mLocale;
+    }
+
+    /**
+     * Returns a bundle containing non-structured extra information about this event.
+     *
+     * <p><b>NOTE: </b>Do not modify this bundle.
+     */
+    @NonNull
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder out = new StringBuilder(128);
+        out.append(this.getClass().getSimpleName());
+        out.append("{");
+        out.append("mEventCategory=").append(mEventCategory);
+        out.append(", mEventType=").append(mEventType);
+        out.append(", mEntityTypes=").append(Arrays.toString(mEntityTypes));
+        out.append(", mEventContext=").append(mEventContext);
+        out.append(", mResultId=").append(mResultId);
+        out.append(", mEventIndex=").append(mEventIndex);
+        out.append(", mExtras=").append(mExtras);
+        out.append(", mScores=").append(Arrays.toString(mScores));
+        out.append(", mModelName=").append(mModelName);
+        out.append(", mActionIndices=").append(Arrays.toString(mActionIndices));
+        toString(out);
+        out.append("}");
+        return out.toString();
+    }
+
+    /**
+     * Overrides this to append extra fields to the output of {@link #toString()}.
+     * <p>
+     * Extra fields should be  formatted like this: ", {field_name}={field_value}".
+     */
+    void toString(StringBuilder out) {}
+
+    /**
+     * Returns a {@link SelectionEvent} equivalent of this event; or {@code null} if it can not be
+     * converted to a {@link SelectionEvent}.
+     * @hide
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    @Nullable
+    public final SelectionEvent toSelectionEvent() {
+        final int invocationMethod;
+        switch (getEventCategory()) {
+            case TextClassifierEvent.CATEGORY_SELECTION:
+                invocationMethod = SelectionEvent.INVOCATION_MANUAL;
+                break;
+            case TextClassifierEvent.CATEGORY_LINKIFY:
+                invocationMethod = SelectionEvent.INVOCATION_LINK;
+                break;
+            default:
+                // Cannot be converted to a SelectionEvent.
+                return null;
+        }
+
+        final String entityType = getEntityTypes().length > 0
+                ? getEntityTypes()[0] : TextClassifier.TYPE_UNKNOWN;
+        final SelectionEvent out = new SelectionEvent(
+                /* absoluteStart= */ 0,
+                /* absoluteEnd= */ 0,
+                /* eventType= */0,
+                entityType,
+                SelectionEvent.INVOCATION_UNKNOWN,
+                SelectionEvent.NO_SIGNATURE);
+        out.setInvocationMethod(invocationMethod);
+
+        final TextClassificationContext eventContext = getEventContext();
+        if (eventContext != null) {
+            out.setTextClassificationSessionContext(getEventContext());
+        }
+        out.setSessionId(mHiddenTempSessionId);
+        final String resultId = getResultId();
+        out.setResultId(resultId == null ? SelectionEvent.NO_SIGNATURE : resultId);
+        out.setEventIndex(getEventIndex());
+
+
+        final int eventType;
+        switch (getEventType()) {
+            case TextClassifierEvent.TYPE_SELECTION_STARTED:
+                eventType = SelectionEvent.EVENT_SELECTION_STARTED;
+                break;
+            case TextClassifierEvent.TYPE_SELECTION_MODIFIED:
+                eventType = SelectionEvent.EVENT_SELECTION_MODIFIED;
+                break;
+            case TextClassifierEvent.TYPE_SMART_SELECTION_SINGLE:
+                eventType = SelectionEvent.EVENT_SMART_SELECTION_SINGLE;
+                break;
+            case TextClassifierEvent.TYPE_SMART_SELECTION_MULTI:
+                eventType = SelectionEvent.EVENT_SMART_SELECTION_MULTI;
+                break;
+            case TextClassifierEvent.TYPE_AUTO_SELECTION:
+                eventType = SelectionEvent.EVENT_AUTO_SELECTION;
+                break;
+            case TextClassifierEvent.TYPE_OVERTYPE:
+                eventType = SelectionEvent.ACTION_OVERTYPE;
+                break;
+            case TextClassifierEvent.TYPE_COPY_ACTION:
+                eventType = SelectionEvent.ACTION_COPY;
+                break;
+            case TextClassifierEvent.TYPE_PASTE_ACTION:
+                eventType = SelectionEvent.ACTION_PASTE;
+                break;
+            case TextClassifierEvent.TYPE_CUT_ACTION:
+                eventType = SelectionEvent.ACTION_CUT;
+                break;
+            case TextClassifierEvent.TYPE_SHARE_ACTION:
+                eventType = SelectionEvent.ACTION_SHARE;
+                break;
+            case TextClassifierEvent.TYPE_SMART_ACTION:
+                eventType = SelectionEvent.ACTION_SMART_SHARE;
+                break;
+            case TextClassifierEvent.TYPE_SELECTION_DRAG:
+                eventType = SelectionEvent.ACTION_DRAG;
+                break;
+            case TextClassifierEvent.TYPE_SELECTION_DESTROYED:
+                eventType = SelectionEvent.ACTION_ABANDON;
+                break;
+            case TextClassifierEvent.TYPE_OTHER_ACTION:
+                eventType = SelectionEvent.ACTION_OTHER;
+                break;
+            case TextClassifierEvent.TYPE_SELECT_ALL:
+                eventType = SelectionEvent.ACTION_SELECT_ALL;
+                break;
+            case TextClassifierEvent.TYPE_SELECTION_RESET:
+                eventType = SelectionEvent.ACTION_RESET;
+                break;
+            default:
+                eventType = 0;
+                break;
+        }
+        out.setEventType(eventType);
+
+        if (this instanceof TextClassifierEvent.TextSelectionEvent) {
+            final TextClassifierEvent.TextSelectionEvent selEvent =
+                    (TextClassifierEvent.TextSelectionEvent) this;
+            // TODO: Ideally, we should have these fields in events of type
+            // TextClassifierEvent.TextLinkifyEvent events too but we're now past the API deadline
+            // and will have to do with these fields being set only in TextSelectionEvent events.
+            // Fix this at the next API bump.
+            out.setStart(selEvent.getRelativeWordStartIndex());
+            out.setEnd(selEvent.getRelativeWordEndIndex());
+            out.setSmartStart(selEvent.getRelativeSuggestedWordStartIndex());
+            out.setSmartEnd(selEvent.getRelativeSuggestedWordEndIndex());
+        }
+
+        return out;
+    }
+
+    /**
+     * Builder to build a text classifier event.
+     *
+     * @param <T> The subclass to be built.
+     */
+    public abstract static class Builder<T extends Builder<T>> {
+
+        private final int mEventCategory;
+        private final int mEventType;
+        private String[] mEntityTypes = new String[0];
+        @Nullable
+        private TextClassificationContext mEventContext;
+        @Nullable
+        private String mResultId;
+        private int mEventIndex;
+        private float[] mScores = new float[0];
+        @Nullable
+        private String mModelName;
+        private int[] mActionIndices = new int[0];
+        @Nullable
+        private ULocale mLocale;
+        @Nullable
+        private Bundle mExtras;
+
+        /**
+         * Creates a builder for building {@link TextClassifierEvent}s.
+         *
+         * @param eventCategory The event category. e.g. {@link #CATEGORY_SELECTION}
+         * @param eventType     The event type. e.g. {@link #TYPE_SELECTION_STARTED}
+         */
+        private Builder(@Category int eventCategory, @Type int eventType) {
+            mEventCategory = eventCategory;
+            mEventType = eventType;
+        }
+
+        /**
+         * Sets the entity types. e.g. {@link TextClassifier#TYPE_ADDRESS}.
+         * <p>
+         * Supported types:
+         * <p>See {@link TextClassifier.EntityType}
+         * <p>See {@link ConversationAction.ActionType}
+         * <p>See {@link ULocale#toLanguageTag()}
+         */
+        @NonNull
+        public T setEntityTypes(@NonNull String... entityTypes) {
+            Objects.requireNonNull(entityTypes);
+            mEntityTypes = new String[entityTypes.length];
+            System.arraycopy(entityTypes, 0, mEntityTypes, 0, entityTypes.length);
+            return self();
+        }
+
+        /**
+         * Sets the event context.
+         */
+        @NonNull
+        public T setEventContext(@Nullable TextClassificationContext eventContext) {
+            mEventContext = eventContext;
+            return self();
+        }
+
+        /**
+         * Sets the id of the text classifier result related to this event.
+         */
+        @NonNull
+        public T setResultId(@Nullable String resultId) {
+            mResultId = resultId;
+            return self();
+        }
+
+        /**
+         * Sets the index of this event in the series of events it belongs to.
+         */
+        @NonNull
+        public T setEventIndex(int eventIndex) {
+            mEventIndex = eventIndex;
+            return self();
+        }
+
+        /**
+         * Sets the scores of the suggestions.
+         */
+        @NonNull
+        public T setScores(@NonNull float... scores) {
+            Objects.requireNonNull(scores);
+            mScores = new float[scores.length];
+            System.arraycopy(scores, 0, mScores, 0, scores.length);
+            return self();
+        }
+
+        /**
+         * Sets the model name string.
+         */
+        @NonNull
+        public T setModelName(@Nullable String modelVersion) {
+            mModelName = modelVersion;
+            return self();
+        }
+
+        /**
+         * Sets the indices of the actions involved in this event. Actions are usually returned by
+         * the text classifier in priority order with the most preferred action at index 0.
+         * These indices give an indication of the position of the actions that are being reported.
+         * <p>
+         * E.g.
+         * <pre>
+         *   // 3 smart actions are shown at index 0, 1, 2 respectively in response to a link click.
+         *   new TextClassifierEvent.Builder(CATEGORY_LINKIFY, TYPE_ACTIONS_SHOWN)
+         *       .setEventIndex(0, 1, 2)
+         *       ...
+         *       .build();
+         *
+         *   ...
+         *
+         *   // Smart action at index 1 is activated.
+         *   new TextClassifierEvent.Builder(CATEGORY_LINKIFY, TYPE_SMART_ACTION)
+         *       .setEventIndex(1)
+         *       ...
+         *       .build();
+         * </pre>
+         *
+         * @see TextClassification#getActions()
+         */
+        @NonNull
+        public T setActionIndices(@NonNull int... actionIndices) {
+            mActionIndices = new int[actionIndices.length];
+            System.arraycopy(actionIndices, 0, mActionIndices, 0, actionIndices.length);
+            return self();
+        }
+
+        /**
+         * Sets the detected locale.
+         */
+        @NonNull
+        public T setLocale(@Nullable ULocale locale) {
+            mLocale = locale;
+            return self();
+        }
+
+        /**
+         * Sets a bundle containing non-structured extra information about the event.
+         *
+         * <p><b>NOTE: </b>Prefer to set only immutable values on the bundle otherwise, avoid
+         * updating the internals of this bundle as it may have unexpected consequences on the
+         * clients of the built event object. For similar reasons, avoid depending on mutable
+         * objects in this bundle.
+         */
+        @NonNull
+        public T setExtras(@NonNull Bundle extras) {
+            mExtras = Objects.requireNonNull(extras);
+            return self();
+        }
+
+        abstract T self();
+    }
+
+    /**
+     * This class represents events that are related to the smart text selection feature.
+     * <p>
+     * <pre>
+     *     // User started a selection. e.g. "York" in text "New York City, NY".
+     *     new TextSelectionEvent.Builder(TYPE_SELECTION_STARTED)
+     *         .setEventContext(classificationContext)
+     *         .setEventIndex(0)
+     *         .build();
+     *
+     *     // System smart-selects a recognized entity. e.g. "New York City".
+     *     new TextSelectionEvent.Builder(TYPE_SMART_SELECTION_MULTI)
+     *         .setEventContext(classificationContext)
+     *         .setResultId(textSelection.getId())
+     *         .setRelativeWordStartIndex(-1) // Goes back one word to "New" from "York".
+     *         .setRelativeWordEndIndex(2)    // Goes forward 2 words from "York" to start of ",".
+     *         .setEntityTypes(textClassification.getEntity(0))
+     *         .setScore(textClassification.getConfidenceScore(entityType))
+     *         .setEventIndex(1)
+     *         .build();
+     *
+     *     // User resets the selection to the original selection. i.e. "York".
+     *     new TextSelectionEvent.Builder(TYPE_SELECTION_RESET)
+     *         .setEventContext(classificationContext)
+     *         .setResultId(textSelection.getId())
+     *         .setRelativeSuggestedWordStartIndex(-1) // Repeated from above.
+     *         .setRelativeSuggestedWordEndIndex(2)    // Repeated from above.
+     *         .setRelativeWordStartIndex(0)           // Original selection is always at (0, 1].
+     *         .setRelativeWordEndIndex(1)
+     *         .setEntityTypes(textClassification.getEntity(0))
+     *         .setScore(textClassification.getConfidenceScore(entityType))
+     *         .setEventIndex(2)
+     *         .build();
+     *
+     *     // User modified the selection. e.g. "New".
+     *     new TextSelectionEvent.Builder(TYPE_SELECTION_MODIFIED)
+     *         .setEventContext(classificationContext)
+     *         .setResultId(textSelection.getId())
+     *         .setRelativeSuggestedWordStartIndex(-1) // Repeated from above.
+     *         .setRelativeSuggestedWordEndIndex(2)    // Repeated from above.
+     *         .setRelativeWordStartIndex(-1)          // Goes backward one word from "York" to
+     *         "New".
+     *         .setRelativeWordEndIndex(0)             // Goes backward one word to exclude "York".
+     *         .setEntityTypes(textClassification.getEntity(0))
+     *         .setScore(textClassification.getConfidenceScore(entityType))
+     *         .setEventIndex(3)
+     *         .build();
+     *
+     *     // Smart (contextual) actions (at indices, 0, 1, 2) presented to the user.
+     *     // e.g. "Map", "Ride share", "Explore".
+     *     new TextSelectionEvent.Builder(TYPE_ACTIONS_SHOWN)
+     *         .setEventContext(classificationContext)
+     *         .setResultId(textClassification.getId())
+     *         .setEntityTypes(textClassification.getEntity(0))
+     *         .setScore(textClassification.getConfidenceScore(entityType))
+     *         .setActionIndices(0, 1, 2)
+     *         .setEventIndex(4)
+     *         .build();
+     *
+     *     // User chooses the "Copy" action.
+     *     new TextSelectionEvent.Builder(TYPE_COPY_ACTION)
+     *         .setEventContext(classificationContext)
+     *         .setResultId(textClassification.getId())
+     *         .setEntityTypes(textClassification.getEntity(0))
+     *         .setScore(textClassification.getConfidenceScore(entityType))
+     *         .setEventIndex(5)
+     *         .build();
+     *
+     *     // User chooses smart action at index 1. i.e. "Ride share".
+     *     new TextSelectionEvent.Builder(TYPE_SMART_ACTION)
+     *         .setEventContext(classificationContext)
+     *         .setResultId(textClassification.getId())
+     *         .setEntityTypes(textClassification.getEntity(0))
+     *         .setScore(textClassification.getConfidenceScore(entityType))
+     *         .setActionIndices(1)
+     *         .setEventIndex(5)
+     *         .build();
+     *
+     *     // Selection dismissed.
+     *     new TextSelectionEvent.Builder(TYPE_SELECTION_DESTROYED)
+     *         .setEventContext(classificationContext)
+     *         .setResultId(textClassification.getId())
+     *         .setEntityTypes(textClassification.getEntity(0))
+     *         .setScore(textClassification.getConfidenceScore(entityType))
+     *         .setEventIndex(6)
+     *         .build();
+     * </pre>
+     * <p>
+     */
+    public static final class TextSelectionEvent extends TextClassifierEvent implements Parcelable {
+
+        @NonNull
+        public static final Creator<TextSelectionEvent> CREATOR =
+                new Creator<TextSelectionEvent>() {
+                    @Override
+                    public TextSelectionEvent createFromParcel(Parcel in) {
+                        in.readInt(); // skip token, we already know this is a TextSelectionEvent
+                        return new TextSelectionEvent(in);
+                    }
+
+                    @Override
+                    public TextSelectionEvent[] newArray(int size) {
+                        return new TextSelectionEvent[size];
+                    }
+                };
+
+        final int mRelativeWordStartIndex;
+        final int mRelativeWordEndIndex;
+        final int mRelativeSuggestedWordStartIndex;
+        final int mRelativeSuggestedWordEndIndex;
+
+        private TextSelectionEvent(TextSelectionEvent.Builder builder) {
+            super(builder);
+            mRelativeWordStartIndex = builder.mRelativeWordStartIndex;
+            mRelativeWordEndIndex = builder.mRelativeWordEndIndex;
+            mRelativeSuggestedWordStartIndex = builder.mRelativeSuggestedWordStartIndex;
+            mRelativeSuggestedWordEndIndex = builder.mRelativeSuggestedWordEndIndex;
+        }
+
+        private TextSelectionEvent(Parcel in) {
+            super(in);
+            mRelativeWordStartIndex = in.readInt();
+            mRelativeWordEndIndex = in.readInt();
+            mRelativeSuggestedWordStartIndex = in.readInt();
+            mRelativeSuggestedWordEndIndex = in.readInt();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeInt(mRelativeWordStartIndex);
+            dest.writeInt(mRelativeWordEndIndex);
+            dest.writeInt(mRelativeSuggestedWordStartIndex);
+            dest.writeInt(mRelativeSuggestedWordEndIndex);
+        }
+
+        /**
+         * Returns the relative word index of the start of the selection.
+         */
+        public int getRelativeWordStartIndex() {
+            return mRelativeWordStartIndex;
+        }
+
+        /**
+         * Returns the relative word (exclusive) index of the end of the selection.
+         */
+        public int getRelativeWordEndIndex() {
+            return mRelativeWordEndIndex;
+        }
+
+        /**
+         * Returns the relative word index of the start of the smart selection.
+         */
+        public int getRelativeSuggestedWordStartIndex() {
+            return mRelativeSuggestedWordStartIndex;
+        }
+
+        /**
+         * Returns the relative word (exclusive) index of the end of the
+         * smart selection.
+         */
+        public int getRelativeSuggestedWordEndIndex() {
+            return mRelativeSuggestedWordEndIndex;
+        }
+
+        @Override
+        void toString(StringBuilder out) {
+            out.append(", getRelativeWordStartIndex=").append(mRelativeWordStartIndex);
+            out.append(", getRelativeWordEndIndex=").append(mRelativeWordEndIndex);
+            out.append(", getRelativeSuggestedWordStartIndex=")
+                    .append(mRelativeSuggestedWordStartIndex);
+            out.append(", getRelativeSuggestedWordEndIndex=")
+                    .append(mRelativeSuggestedWordEndIndex);
+        }
+
+        /**
+         * Builder class for {@link TextSelectionEvent}.
+         */
+        public static final class Builder extends
+                TextClassifierEvent.Builder<TextSelectionEvent.Builder> {
+            int mRelativeWordStartIndex;
+            int mRelativeWordEndIndex;
+            int mRelativeSuggestedWordStartIndex;
+            int mRelativeSuggestedWordEndIndex;
+
+            /**
+             * Creates a builder for building {@link TextSelectionEvent}s.
+             *
+             * @param eventType     The event type. e.g. {@link #TYPE_SELECTION_STARTED}
+             */
+            public Builder(@Type int eventType) {
+                super(CATEGORY_SELECTION, eventType);
+            }
+
+            /**
+             * Sets the relative word index of the start of the selection.
+             */
+            @NonNull
+            public Builder setRelativeWordStartIndex(int relativeWordStartIndex) {
+                mRelativeWordStartIndex = relativeWordStartIndex;
+                return this;
+            }
+
+            /**
+             * Sets the relative word (exclusive) index of the end of the
+             * selection.
+             */
+            @NonNull
+            public Builder setRelativeWordEndIndex(int relativeWordEndIndex) {
+                mRelativeWordEndIndex = relativeWordEndIndex;
+                return this;
+            }
+
+            /**
+             * Sets the relative word index of the start of the smart
+             * selection.
+             */
+            @NonNull
+            public Builder setRelativeSuggestedWordStartIndex(int relativeSuggestedWordStartIndex) {
+                mRelativeSuggestedWordStartIndex = relativeSuggestedWordStartIndex;
+                return this;
+            }
+
+            /**
+             * Sets the relative word (exclusive) index of the end of the
+             * smart selection.
+             */
+            @NonNull
+            public Builder setRelativeSuggestedWordEndIndex(int relativeSuggestedWordEndIndex) {
+                mRelativeSuggestedWordEndIndex = relativeSuggestedWordEndIndex;
+                return this;
+            }
+
+            @Override
+            TextSelectionEvent.Builder self() {
+                return this;
+            }
+
+            /**
+             * Builds and returns a {@link TextSelectionEvent}.
+             */
+            @NonNull
+            public TextSelectionEvent build() {
+                return new TextSelectionEvent(this);
+            }
+        }
+    }
+
+    /**
+     * This class represents events that are related to the smart linkify feature.
+     * <p>
+     * <pre>
+     *     // User clicked on a link.
+     *     new TextLinkifyEvent.Builder(TYPE_LINK_CLICKED)
+     *         .setEventContext(classificationContext)
+     *         .setResultId(textClassification.getId())
+     *         .setEntityTypes(textClassification.getEntity(0))
+     *         .setScore(textClassification.getConfidenceScore(entityType))
+     *         .setEventIndex(0)
+     *         .build();
+     *
+     *     // Smart (contextual) actions presented to the user in response to a link click.
+     *     new TextLinkifyEvent.Builder(TYPE_ACTIONS_SHOWN)
+     *         .setEventContext(classificationContext)
+     *         .setResultId(textClassification.getId())
+     *         .setEntityTypes(textClassification.getEntity(0))
+     *         .setScore(textClassification.getConfidenceScore(entityType))
+     *         .setActionIndices(range(textClassification.getActions().size()))
+     *         .setEventIndex(1)
+     *         .build();
+     *
+     *     // User chooses smart action at index 0.
+     *     new TextLinkifyEvent.Builder(TYPE_SMART_ACTION)
+     *         .setEventContext(classificationContext)
+     *         .setResultId(textClassification.getId())
+     *         .setEntityTypes(textClassification.getEntity(0))
+     *         .setScore(textClassification.getConfidenceScore(entityType))
+     *         .setActionIndices(0)
+     *         .setEventIndex(2)
+     *         .build();
+     * </pre>
+     */
+    public static final class TextLinkifyEvent extends TextClassifierEvent implements Parcelable {
+
+        @NonNull
+        public static final Creator<TextLinkifyEvent> CREATOR =
+                new Creator<TextLinkifyEvent>() {
+                    @Override
+                    public TextLinkifyEvent createFromParcel(Parcel in) {
+                        in.readInt(); // skip token, we already know this is a TextLinkifyEvent
+                        return new TextLinkifyEvent(in);
+                    }
+
+                    @Override
+                    public TextLinkifyEvent[] newArray(int size) {
+                        return new TextLinkifyEvent[size];
+                    }
+                };
+
+        private TextLinkifyEvent(Parcel in) {
+            super(in);
+        }
+
+        private TextLinkifyEvent(TextLinkifyEvent.Builder builder) {
+            super(builder);
+        }
+
+        /**
+         * Builder class for {@link TextLinkifyEvent}.
+         */
+        public static final class Builder
+                extends TextClassifierEvent.Builder<TextLinkifyEvent.Builder> {
+            /**
+             * Creates a builder for building {@link TextLinkifyEvent}s.
+             *
+             * @param eventType The event type. e.g. {@link #TYPE_SMART_ACTION}
+             */
+            public Builder(@Type int eventType) {
+                super(TextClassifierEvent.CATEGORY_LINKIFY, eventType);
+            }
+
+            @Override
+            Builder self() {
+                return this;
+            }
+
+            /**
+             * Builds and returns a {@link TextLinkifyEvent}.
+             */
+            @NonNull
+            public TextLinkifyEvent build() {
+                return new TextLinkifyEvent(this);
+            }
+        }
+    }
+
+    /**
+     * This class represents events that are related to the language detection feature.
+     * <p>
+     * <pre>
+     *     // Translate action shown for foreign text.
+     *     new LanguageDetectionEvent.Builder(TYPE_ACTIONS_SHOWN)
+     *         .setEventContext(classificationContext)
+     *         .setResultId(textClassification.getId())
+     *         .setEntityTypes(language)
+     *         .setScore(score)
+     *         .setActionIndices(textClassification.getActions().indexOf(translateAction))
+     *         .setEventIndex(0)
+     *         .build();
+     *
+     *     // Translate action selected.
+     *     new LanguageDetectionEvent.Builder(TYPE_SMART_ACTION)
+     *         .setEventContext(classificationContext)
+     *         .setResultId(textClassification.getId())
+     *         .setEntityTypes(language)
+     *         .setScore(score)
+     *         .setActionIndices(textClassification.getActions().indexOf(translateAction))
+     *         .setEventIndex(1)
+     *         .build();
+     */
+    public static final class LanguageDetectionEvent extends TextClassifierEvent
+            implements Parcelable {
+
+        @NonNull
+        public static final Creator<LanguageDetectionEvent> CREATOR =
+                new Creator<LanguageDetectionEvent>() {
+                    @Override
+                    public LanguageDetectionEvent createFromParcel(Parcel in) {
+                        // skip token, we already know this is a LanguageDetectionEvent.
+                        in.readInt();
+                        return new LanguageDetectionEvent(in);
+                    }
+
+                    @Override
+                    public LanguageDetectionEvent[] newArray(int size) {
+                        return new LanguageDetectionEvent[size];
+                    }
+                };
+
+        private LanguageDetectionEvent(Parcel in) {
+            super(in);
+        }
+
+        private LanguageDetectionEvent(LanguageDetectionEvent.Builder builder) {
+            super(builder);
+        }
+
+        /**
+         * Builder class for {@link LanguageDetectionEvent}.
+         */
+        public static final class Builder
+                extends TextClassifierEvent.Builder<LanguageDetectionEvent.Builder> {
+
+            /**
+             * Creates a builder for building {@link TextSelectionEvent}s.
+             *
+             * @param eventType The event type. e.g. {@link #TYPE_SMART_ACTION}
+             */
+            public Builder(@Type int eventType) {
+                super(TextClassifierEvent.CATEGORY_LANGUAGE_DETECTION, eventType);
+            }
+
+            @Override
+            Builder self() {
+                return this;
+            }
+
+            /**
+             * Builds and returns a {@link LanguageDetectionEvent}.
+             */
+            @NonNull
+            public LanguageDetectionEvent build() {
+                return new LanguageDetectionEvent(this);
+            }
+        }
+    }
+
+    /**
+     * This class represents events that are related to the conversation actions feature.
+     * <p>
+     * <pre>
+     *     // Conversation (contextual) actions/replies generated.
+     *     new ConversationActionsEvent.Builder(TYPE_ACTIONS_GENERATED)
+     *         .setEventContext(classificationContext)
+     *         .setResultId(conversationActions.getId())
+     *         .setEntityTypes(getTypes(conversationActions))
+     *         .setActionIndices(range(conversationActions.getActions().size()))
+     *         .setEventIndex(0)
+     *         .build();
+     *
+     *     // Conversation actions/replies presented to user.
+     *     new ConversationActionsEvent.Builder(TYPE_ACTIONS_SHOWN)
+     *         .setEventContext(classificationContext)
+     *         .setResultId(conversationActions.getId())
+     *         .setEntityTypes(getTypes(conversationActions))
+     *         .setActionIndices(range(conversationActions.getActions().size()))
+     *         .setEventIndex(1)
+     *         .build();
+     *
+     *     // User clicked the "Reply" button to compose their custom reply.
+     *     new ConversationActionsEvent.Builder(TYPE_MANUAL_REPLY)
+     *         .setEventContext(classificationContext)
+     *         .setResultId(conversationActions.getId())
+     *         .setEventIndex(2)
+     *         .build();
+     *
+     *     // User selected a smart (contextual) action/reply.
+     *     new ConversationActionsEvent.Builder(TYPE_SMART_ACTION)
+     *         .setEventContext(classificationContext)
+     *         .setResultId(conversationActions.getId())
+     *         .setEntityTypes(conversationActions.get(1).getType())
+     *         .setScore(conversationAction.get(1).getConfidenceScore())
+     *         .setActionIndices(1)
+     *         .setEventIndex(2)
+     *         .build();
+     * </pre>
+     */
+    public static final class ConversationActionsEvent extends TextClassifierEvent
+            implements Parcelable {
+
+        @NonNull
+        public static final Creator<ConversationActionsEvent> CREATOR =
+                new Creator<ConversationActionsEvent>() {
+                    @Override
+                    public ConversationActionsEvent createFromParcel(Parcel in) {
+                        // skip token, we already know this is a ConversationActionsEvent.
+                        in.readInt();
+                        return new ConversationActionsEvent(in);
+                    }
+
+                    @Override
+                    public ConversationActionsEvent[] newArray(int size) {
+                        return new ConversationActionsEvent[size];
+                    }
+                };
+
+        private ConversationActionsEvent(Parcel in) {
+            super(in);
+        }
+
+        private ConversationActionsEvent(ConversationActionsEvent.Builder builder) {
+            super(builder);
+        }
+
+        /**
+         * Builder class for {@link ConversationActionsEvent}.
+         */
+        public static final class Builder
+                extends TextClassifierEvent.Builder<ConversationActionsEvent.Builder> {
+            /**
+             * Creates a builder for building {@link TextSelectionEvent}s.
+             *
+             * @param eventType The event type. e.g. {@link #TYPE_SMART_ACTION}
+             */
+            public Builder(@Type int eventType) {
+                super(TextClassifierEvent.CATEGORY_CONVERSATION_ACTIONS, eventType);
+            }
+
+            @Override
+            Builder self() {
+                return this;
+            }
+
+            /**
+             * Builds and returns a {@link ConversationActionsEvent}.
+             */
+            @NonNull
+            public ConversationActionsEvent build() {
+                return new ConversationActionsEvent(this);
+            }
+        }
+    }
+}
diff --git a/android/view/textclassifier/TextClassifierPerfTest.java b/android/view/textclassifier/TextClassifierPerfTest.java
new file mode 100644
index 0000000..324def8
--- /dev/null
+++ b/android/view/textclassifier/TextClassifierPerfTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.textclassifier;
+
+import android.content.Context;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.service.textclassifier.TextClassifierService;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.Collections;
+
+@LargeTest
+public class TextClassifierPerfTest {
+    private static final String TEXT = " Oh hi Mark, the number is (323) 654-6192.\n"
+            + "Anyway, I'll meet you at 1600 Pennsylvania Avenue NW.\n"
+            + "My flight is LX 38 and I'll arrive at 8:00pm.\n"
+            + "Also, check out www.google.com.\n";
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    private TextClassifier mTextClassifier;
+
+    @Before
+    public void setUp() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        mTextClassifier = TextClassifierService.getDefaultTextClassifierImplementation(context);
+    }
+
+    @Test
+    public void testClassifyText() {
+        TextClassification.Request request =
+                new TextClassification.Request.Builder(TEXT, 0, TEXT.length()).build();
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mTextClassifier.classifyText(request);
+        }
+    }
+
+    @Test
+    public void testSuggestSelection() {
+        // Trying to select the phone number.
+        TextSelection.Request request =
+                new TextSelection.Request.Builder(
+                        TEXT,
+                        /* startIndex= */ 28,
+                        /* endIndex= */29)
+                        .build();
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mTextClassifier.suggestSelection(request);
+        }
+    }
+
+    @Test
+    public void testGenerateLinks() {
+        TextLinks.Request request =
+                new TextLinks.Request.Builder(TEXT).build();
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mTextClassifier.generateLinks(request);
+        }
+    }
+
+    @Test
+    public void testSuggestConversationActions() {
+        ConversationActions.Message message =
+                new ConversationActions.Message.Builder(
+                        ConversationActions.Message.PERSON_USER_OTHERS)
+                        .setText(TEXT)
+                        .build();
+        ConversationActions.Request request = new ConversationActions.Request.Builder(
+                Collections.singletonList(message))
+                .build();
+
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mTextClassifier.suggestConversationActions(request);
+        }
+    }
+
+    @Test
+    public void testDetectLanguage() {
+        TextLanguage.Request request = new TextLanguage.Request.Builder(TEXT).build();
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mTextClassifier.detectLanguage(request);
+        }
+    }
+}
diff --git a/android/view/textclassifier/TextLanguage.java b/android/view/textclassifier/TextLanguage.java
new file mode 100644
index 0000000..1e8253d
--- /dev/null
+++ b/android/view/textclassifier/TextLanguage.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.icu.util.ULocale;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Represents the result of language detection of a piece of text.
+ * <p>
+ * This contains a list of locales, each paired with a confidence score, sorted in decreasing
+ * order of those scores. E.g., for a given input text, the model may return
+ * {@code [<"en", 0.85>, <"fr", 0.15>]}. This sample result means the model reports that it is
+ * 85% likely that the entire text is in English and 15% likely that the entire text is in French,
+ * etc. It does not mean that 85% of the input is in English and 15% is in French.
+ */
+public final class TextLanguage implements Parcelable {
+
+    public static final @android.annotation.NonNull Creator<TextLanguage> CREATOR = new Creator<TextLanguage>() {
+        @Override
+        public TextLanguage createFromParcel(Parcel in) {
+            return readFromParcel(in);
+        }
+
+        @Override
+        public TextLanguage[] newArray(int size) {
+            return new TextLanguage[size];
+        }
+    };
+
+    static final TextLanguage EMPTY = new Builder().build();
+
+    @Nullable private final String mId;
+    private final EntityConfidence mEntityConfidence;
+    private final Bundle mBundle;
+
+    private TextLanguage(
+            @Nullable String id,
+            EntityConfidence entityConfidence,
+            Bundle bundle) {
+        mId = id;
+        mEntityConfidence = entityConfidence;
+        mBundle = bundle;
+    }
+
+    /**
+     * Returns the id, if one exists, for this object.
+     */
+    @Nullable
+    public String getId() {
+        return mId;
+    }
+
+    /**
+     * Returns the number of possible locales for the processed text.
+     */
+    @IntRange(from = 0)
+    public int getLocaleHypothesisCount() {
+        return mEntityConfidence.getEntities().size();
+    }
+
+    /**
+     * Returns the language locale at the specified index. Locales are ordered from high
+     * confidence to low confidence.
+     * <p>
+     * See {@link #getLocaleHypothesisCount()} for the number of locales available.
+     *
+     * @throws IndexOutOfBoundsException if the specified index is out of range.
+     */
+    @NonNull
+    public ULocale getLocale(int index) {
+        return ULocale.forLanguageTag(mEntityConfidence.getEntities().get(index));
+    }
+
+    /**
+     * Returns the confidence score for the specified language locale. The value ranges from
+     * 0 (low confidence) to 1 (high confidence). 0 indicates that the locale was not found for
+     * the processed text.
+     */
+    @FloatRange(from = 0.0, to = 1.0)
+    public float getConfidenceScore(@NonNull ULocale locale) {
+        return mEntityConfidence.getConfidenceScore(locale.toLanguageTag());
+    }
+
+    /**
+     * Returns a bundle containing non-structured extra information about this result. What is
+     * returned in the extras is specific to the {@link TextClassifier} implementation.
+     *
+     * <p><b>NOTE: </b>Do not modify this bundle.
+     */
+    @NonNull
+    public Bundle getExtras() {
+        return mBundle;
+    }
+
+    @Override
+    public String toString() {
+        return String.format(
+                Locale.US,
+                "TextLanguage {id=%s, locales=%s, bundle=%s}",
+                mId, mEntityConfidence, mBundle);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mId);
+        mEntityConfidence.writeToParcel(dest, flags);
+        dest.writeBundle(mBundle);
+    }
+
+    private static TextLanguage readFromParcel(Parcel in) {
+        return new TextLanguage(
+                in.readString(),
+                EntityConfidence.CREATOR.createFromParcel(in),
+                in.readBundle());
+    }
+
+    /**
+     * Builder used to build TextLanguage objects.
+     */
+    public static final class Builder {
+
+        @Nullable private String mId;
+        private final Map<String, Float> mEntityConfidenceMap = new ArrayMap<>();
+        @Nullable private Bundle mBundle;
+
+        /**
+         * Sets a language locale for the processed text and assigns a confidence score. If the
+         * locale has already been set, this updates it.
+         *
+         * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence).
+         *      0 implies the locale does not exist for the processed text.
+         *      Values greater than 1 are clamped to 1.
+         */
+        @NonNull
+        public Builder putLocale(
+                @NonNull ULocale locale,
+                @FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
+            Preconditions.checkNotNull(locale);
+            mEntityConfidenceMap.put(locale.toLanguageTag(), confidenceScore);
+            return this;
+        }
+
+        /**
+         * Sets an optional id for the TextLanguage object.
+         */
+        @NonNull
+        public Builder setId(@Nullable String id) {
+            mId = id;
+            return this;
+        }
+
+        /**
+         * Sets a bundle containing non-structured extra information about the TextLanguage object.
+         */
+        @NonNull
+        public Builder setExtras(@NonNull Bundle bundle) {
+            mBundle = Preconditions.checkNotNull(bundle);
+            return this;
+        }
+
+        /**
+         * Builds and returns a new TextLanguage object.
+         * <p>
+         * If necessary, this method will verify fields, clamp them, and make them immutable.
+         */
+        @NonNull
+        public TextLanguage build() {
+            mBundle = mBundle == null ? Bundle.EMPTY : mBundle;
+            return new TextLanguage(
+                    mId,
+                    new EntityConfidence(mEntityConfidenceMap),
+                    mBundle);
+        }
+    }
+
+    /**
+     * A request object for detecting the language of a piece of text.
+     */
+    public static final class Request implements Parcelable {
+
+        public static final @android.annotation.NonNull Creator<Request> CREATOR = new Creator<Request>() {
+            @Override
+            public Request createFromParcel(Parcel in) {
+                return readFromParcel(in);
+            }
+
+            @Override
+            public Request[] newArray(int size) {
+                return new Request[size];
+            }
+        };
+
+        private final CharSequence mText;
+        private final Bundle mExtra;
+        @Nullable private SystemTextClassifierMetadata mSystemTcMetadata;
+
+        private Request(CharSequence text, Bundle bundle) {
+            mText = text;
+            mExtra = bundle;
+        }
+
+        /**
+         * Returns the text to process.
+         */
+        @NonNull
+        public CharSequence getText() {
+            return mText;
+        }
+
+        /**
+         * Returns the name of the package that sent this request.
+         * This returns null if no calling package name is set.
+         */
+        @Nullable
+        public String getCallingPackageName() {
+            return mSystemTcMetadata != null ? mSystemTcMetadata.getCallingPackageName() : null;
+        }
+
+        /**
+         * Sets the information about the {@link SystemTextClassifier} that sent this request.
+         *
+         * @hide
+         */
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+        public void setSystemTextClassifierMetadata(
+                @Nullable SystemTextClassifierMetadata systemTcMetadata) {
+            mSystemTcMetadata = systemTcMetadata;
+        }
+
+        /**
+         * Returns the information about the {@link SystemTextClassifier} that sent this request.
+         *
+         * @hide
+         */
+        @Nullable
+        public SystemTextClassifierMetadata getSystemTextClassifierMetadata() {
+            return mSystemTcMetadata;
+        }
+
+        /**
+         * Returns a bundle containing non-structured extra information about this request.
+         *
+         * <p><b>NOTE: </b>Do not modify this bundle.
+         */
+        @NonNull
+        public Bundle getExtras() {
+            return mExtra;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeCharSequence(mText);
+            dest.writeBundle(mExtra);
+            dest.writeParcelable(mSystemTcMetadata, flags);
+        }
+
+        private static Request readFromParcel(Parcel in) {
+            final CharSequence text = in.readCharSequence();
+            final Bundle extra = in.readBundle();
+            final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null);
+
+            final Request request = new Request(text, extra);
+            request.setSystemTextClassifierMetadata(systemTcMetadata);
+            return request;
+        }
+
+        /**
+         * A builder for building TextLanguage requests.
+         */
+        public static final class Builder {
+
+            private final CharSequence mText;
+            @Nullable private Bundle mBundle;
+
+            /**
+             * Creates a builder to build TextLanguage requests.
+             *
+             * @param text the text to process.
+             */
+            public Builder(@NonNull CharSequence text) {
+                mText = Preconditions.checkNotNull(text);
+            }
+
+            /**
+             * Sets a bundle containing non-structured extra information about the request.
+             */
+            @NonNull
+            public Builder setExtras(@NonNull Bundle bundle) {
+                mBundle = Preconditions.checkNotNull(bundle);
+                return this;
+            }
+
+            /**
+             * Builds and returns a new TextLanguage request object.
+             * <p>
+             * If necessary, this method will verify fields, clamp them, and make them immutable.
+             */
+            @NonNull
+            public Request build() {
+                return new Request(mText.toString(), mBundle == null ? Bundle.EMPTY : mBundle);
+            }
+        }
+    }
+}
diff --git a/android/view/textclassifier/TextLinks.java b/android/view/textclassifier/TextLinks.java
new file mode 100644
index 0000000..dea3a90
--- /dev/null
+++ b/android/view/textclassifier/TextLinks.java
@@ -0,0 +1,758 @@
+/*
+ * Copyright 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.view.textclassifier;
+
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.Spannable;
+import android.text.method.MovementMethod;
+import android.text.style.ClickableSpan;
+import android.text.style.URLSpan;
+import android.view.View;
+import android.view.textclassifier.TextClassifier.EntityConfig;
+import android.view.textclassifier.TextClassifier.EntityType;
+import android.widget.TextView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * A collection of links, representing subsequences of text and the entity types (phone number,
+ * address, url, etc) they may be.
+ */
+public final class TextLinks implements Parcelable {
+
+    /**
+     * Return status of an attempt to apply TextLinks to text.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({STATUS_LINKS_APPLIED, STATUS_NO_LINKS_FOUND, STATUS_NO_LINKS_APPLIED,
+            STATUS_DIFFERENT_TEXT, STATUS_UNSUPPORTED_CHARACTER})
+    public @interface Status {}
+
+    /** Links were successfully applied to the text. */
+    public static final int STATUS_LINKS_APPLIED = 0;
+
+    /** No links exist to apply to text. Links count is zero. */
+    public static final int STATUS_NO_LINKS_FOUND = 1;
+
+    /** No links applied to text. The links were filtered out. */
+    public static final int STATUS_NO_LINKS_APPLIED = 2;
+
+    /** The specified text does not match the text used to generate the links. */
+    public static final int STATUS_DIFFERENT_TEXT = 3;
+
+    /** The specified text contains unsupported characters. */
+    public static final int STATUS_UNSUPPORTED_CHARACTER = 4;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({APPLY_STRATEGY_IGNORE, APPLY_STRATEGY_REPLACE})
+    public @interface ApplyStrategy {}
+
+    /**
+     * Do not replace {@link ClickableSpan}s that exist where the {@link TextLinkSpan} needs to
+     * be applied to. Do not apply the TextLinkSpan.
+     */
+    public static final int APPLY_STRATEGY_IGNORE = 0;
+
+    /**
+     * Replace any {@link ClickableSpan}s that exist where the {@link TextLinkSpan} needs to be
+     * applied to.
+     */
+    public static final int APPLY_STRATEGY_REPLACE = 1;
+
+    private final String mFullText;
+    private final List<TextLink> mLinks;
+    private final Bundle mExtras;
+
+    private TextLinks(String fullText, ArrayList<TextLink> links, Bundle extras) {
+        mFullText = fullText;
+        mLinks = Collections.unmodifiableList(links);
+        mExtras = extras;
+    }
+
+    /**
+     * Returns the text that was used to generate these links.
+     */
+    @NonNull
+    public CharSequence getText() {
+        return mFullText;
+    }
+
+    /**
+     * Returns an unmodifiable Collection of the links.
+     */
+    @NonNull
+    public Collection<TextLink> getLinks() {
+        return mLinks;
+    }
+
+    /**
+     * Returns the extended data.
+     *
+     * <p><b>NOTE: </b>Do not modify this bundle.
+     */
+    @NonNull
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    /**
+     * Annotates the given text with the generated links. It will fail if the provided text doesn't
+     * match the original text used to create the TextLinks.
+     *
+     * <p><strong>NOTE: </strong>It may be necessary to set a LinkMovementMethod on the TextView
+     * widget to properly handle links. See {@link TextView#setMovementMethod(MovementMethod)}
+     *
+     * @param text the text to apply the links to. Must match the original text
+     * @param applyStrategy the apply strategy used to determine how to apply links to text.
+     *      e.g {@link TextLinks#APPLY_STRATEGY_IGNORE}
+     * @param spanFactory a custom span factory for converting TextLinks to TextLinkSpans.
+     *      Set to {@code null} to use the default span factory.
+     *
+     * @return a status code indicating whether or not the links were successfully applied
+     *      e.g. {@link #STATUS_LINKS_APPLIED}
+     */
+    @Status
+    public int apply(
+            @NonNull Spannable text,
+            @ApplyStrategy int applyStrategy,
+            @Nullable Function<TextLink, TextLinkSpan> spanFactory) {
+        Objects.requireNonNull(text);
+        return new TextLinksParams.Builder()
+                .setApplyStrategy(applyStrategy)
+                .setSpanFactory(spanFactory)
+                .build()
+                .apply(text, this);
+    }
+
+    @Override
+    public String toString() {
+        return String.format(Locale.US, "TextLinks{fullText=%s, links=%s}", mFullText, mLinks);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mFullText);
+        dest.writeTypedList(mLinks);
+        dest.writeBundle(mExtras);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<TextLinks> CREATOR =
+            new Parcelable.Creator<TextLinks>() {
+                @Override
+                public TextLinks createFromParcel(Parcel in) {
+                    return new TextLinks(in);
+                }
+
+                @Override
+                public TextLinks[] newArray(int size) {
+                    return new TextLinks[size];
+                }
+            };
+
+    private TextLinks(Parcel in) {
+        mFullText = in.readString();
+        mLinks = in.createTypedArrayList(TextLink.CREATOR);
+        mExtras = in.readBundle();
+    }
+
+    /**
+     * A link, identifying a substring of text and possible entity types for it.
+     */
+    public static final class TextLink implements Parcelable {
+        private final EntityConfidence mEntityScores;
+        private final int mStart;
+        private final int mEnd;
+        private final Bundle mExtras;
+        @Nullable private final URLSpan mUrlSpan;
+
+        /**
+         * Create a new TextLink.
+         *
+         * @param start The start index of the identified subsequence
+         * @param end The end index of the identified subsequence
+         * @param entityConfidence A mapping of entity type to confidence score
+         * @param extras A bundle containing custom data related to this TextLink
+         * @param urlSpan An optional URLSpan to delegate to. NOTE: Not parcelled
+         *
+         * @throws IllegalArgumentException if {@code entityConfidence} is null or empty
+         * @throws IllegalArgumentException if {@code start} is greater than {@code end}
+         */
+        private TextLink(int start, int end, @NonNull EntityConfidence entityConfidence,
+                @NonNull Bundle extras, @Nullable URLSpan urlSpan) {
+            Objects.requireNonNull(entityConfidence);
+            Preconditions.checkArgument(!entityConfidence.getEntities().isEmpty());
+            Preconditions.checkArgument(start <= end);
+            Objects.requireNonNull(extras);
+            mStart = start;
+            mEnd = end;
+            mEntityScores = entityConfidence;
+            mUrlSpan = urlSpan;
+            mExtras = extras;
+        }
+
+        /**
+         * Returns the start index of this link in the original text.
+         *
+         * @return the start index
+         */
+        public int getStart() {
+            return mStart;
+        }
+
+        /**
+         * Returns the end index of this link in the original text.
+         *
+         * @return the end index
+         */
+        public int getEnd() {
+            return mEnd;
+        }
+
+        /**
+         * Returns the number of entity types that have confidence scores.
+         *
+         * @return the entity count
+         */
+        public int getEntityCount() {
+            return mEntityScores.getEntities().size();
+        }
+
+        /**
+         * Returns the entity type at a given index. Entity types are sorted by confidence.
+         *
+         * @return the entity type at the provided index
+         */
+        @NonNull public @EntityType String getEntity(int index) {
+            return mEntityScores.getEntities().get(index);
+        }
+
+        /**
+         * Returns the confidence score for a particular entity type.
+         *
+         * @param entityType the entity type
+         */
+        public @FloatRange(from = 0.0, to = 1.0) float getConfidenceScore(
+                @EntityType String entityType) {
+            return mEntityScores.getConfidenceScore(entityType);
+        }
+
+        /**
+         * Returns a bundle containing custom data related to this TextLink.
+         */
+        @NonNull
+        public Bundle getExtras() {
+            return mExtras;
+        }
+
+        @Override
+        public String toString() {
+            return String.format(Locale.US,
+                    "TextLink{start=%s, end=%s, entityScores=%s, urlSpan=%s}",
+                    mStart, mEnd, mEntityScores, mUrlSpan);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            mEntityScores.writeToParcel(dest, flags);
+            dest.writeInt(mStart);
+            dest.writeInt(mEnd);
+            dest.writeBundle(mExtras);
+        }
+
+        private static TextLink readFromParcel(Parcel in) {
+            final EntityConfidence entityConfidence = EntityConfidence.CREATOR.createFromParcel(in);
+            final int start = in.readInt();
+            final int end = in.readInt();
+            final Bundle extras = in.readBundle();
+            return new TextLink(start, end, entityConfidence, extras, null /* urlSpan */);
+        }
+
+        public static final @android.annotation.NonNull Parcelable.Creator<TextLink> CREATOR =
+                new Parcelable.Creator<TextLink>() {
+                    @Override
+                    public TextLink createFromParcel(Parcel in) {
+                        return readFromParcel(in);
+                    }
+
+                    @Override
+                    public TextLink[] newArray(int size) {
+                        return new TextLink[size];
+                    }
+                };
+    }
+
+    /**
+     * A request object for generating TextLinks.
+     */
+    public static final class Request implements Parcelable {
+
+        private final CharSequence mText;
+        @Nullable private final LocaleList mDefaultLocales;
+        @Nullable private final EntityConfig mEntityConfig;
+        private final boolean mLegacyFallback;
+        private final Bundle mExtras;
+        @Nullable private final ZonedDateTime mReferenceTime;
+        @Nullable private SystemTextClassifierMetadata mSystemTcMetadata;
+
+        private Request(
+                CharSequence text,
+                LocaleList defaultLocales,
+                EntityConfig entityConfig,
+                boolean legacyFallback,
+                ZonedDateTime referenceTime,
+                Bundle extras) {
+            mText = text;
+            mDefaultLocales = defaultLocales;
+            mEntityConfig = entityConfig;
+            mLegacyFallback = legacyFallback;
+            mReferenceTime = referenceTime;
+            mExtras = extras;
+        }
+
+        /**
+         * Returns the text to generate links for.
+         */
+        @NonNull
+        public CharSequence getText() {
+            return mText;
+        }
+
+        /**
+         * Returns an ordered list of locale preferences that can be used to disambiguate the
+         * provided text.
+         */
+        @Nullable
+        public LocaleList getDefaultLocales() {
+            return mDefaultLocales;
+        }
+
+        /**
+         * Returns the config representing the set of entities to look for
+         *
+         * @see Builder#setEntityConfig(EntityConfig)
+         */
+        @Nullable
+        public EntityConfig getEntityConfig() {
+            return mEntityConfig;
+        }
+
+        /**
+         * Returns whether the TextClassifier can fallback to legacy links if smart linkify is
+         * disabled.
+         * <strong>Note: </strong>This is not parcelled.
+         * @hide
+         */
+        public boolean isLegacyFallback() {
+            return mLegacyFallback;
+        }
+
+        /**
+         * Returns reference time based on which relative dates (e.g. "tomorrow") should be
+         * interpreted.
+         */
+        @Nullable
+        public ZonedDateTime getReferenceTime() {
+            return mReferenceTime;
+        }
+
+        /**
+         * Returns the name of the package that sent this request.
+         * This returns {@code null} if no calling package name is set.
+         */
+        @Nullable
+        public String getCallingPackageName() {
+            return mSystemTcMetadata != null ? mSystemTcMetadata.getCallingPackageName() : null;
+        }
+
+        /**
+         * Sets the information about the {@link SystemTextClassifier} that sent this request.
+         *
+         * @hide
+         */
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+        public void setSystemTextClassifierMetadata(
+                @Nullable SystemTextClassifierMetadata systemTcMetadata) {
+            mSystemTcMetadata = systemTcMetadata;
+        }
+
+        /**
+         * Returns the information about the {@link SystemTextClassifier} that sent this request.
+         *
+         * @hide
+         */
+        @Nullable
+        public SystemTextClassifierMetadata getSystemTextClassifierMetadata() {
+            return mSystemTcMetadata;
+        }
+
+        /**
+         * Returns the extended data.
+         *
+         * <p><b>NOTE: </b>Do not modify this bundle.
+         */
+        @NonNull
+        public Bundle getExtras() {
+            return mExtras;
+        }
+
+        /**
+         * A builder for building TextLinks requests.
+         */
+        public static final class Builder {
+
+            private final CharSequence mText;
+
+            @Nullable private LocaleList mDefaultLocales;
+            @Nullable private EntityConfig mEntityConfig;
+            private boolean mLegacyFallback = true; // Use legacy fall back by default.
+            @Nullable private Bundle mExtras;
+            @Nullable private ZonedDateTime mReferenceTime;
+
+            public Builder(@NonNull CharSequence text) {
+                mText = Objects.requireNonNull(text);
+            }
+
+            /**
+             * Sets ordered list of locale preferences that may be used to disambiguate the
+             * provided text.
+             *
+             * @param defaultLocales ordered list of locale preferences that may be used to
+             *                       disambiguate the provided text. If no locale preferences exist,
+             *                       set this to null or an empty locale list.
+             * @return this builder
+             */
+            @NonNull
+            public Builder setDefaultLocales(@Nullable LocaleList defaultLocales) {
+                mDefaultLocales = defaultLocales;
+                return this;
+            }
+
+            /**
+             * Sets the entity configuration to use. This determines what types of entities the
+             * TextClassifier will look for.
+             * Set to {@code null} for the default entity config and teh TextClassifier will
+             * automatically determine what links to generate.
+             *
+             * @return this builder
+             */
+            @NonNull
+            public Builder setEntityConfig(@Nullable EntityConfig entityConfig) {
+                mEntityConfig = entityConfig;
+                return this;
+            }
+
+            /**
+             * Sets whether the TextClassifier can fallback to legacy links if smart linkify is
+             * disabled.
+             *
+             * <p><strong>Note: </strong>This is not parcelled.
+             *
+             * @return this builder
+             * @hide
+             */
+            @NonNull
+            public Builder setLegacyFallback(boolean legacyFallback) {
+                mLegacyFallback = legacyFallback;
+                return this;
+            }
+
+            /**
+             * Sets the extended data.
+             *
+             * @return this builder
+             */
+            public Builder setExtras(@Nullable Bundle extras) {
+                mExtras = extras;
+                return this;
+            }
+
+            /**
+             * Sets the reference time based on which relative dates (e.g.
+             * "tomorrow") should be interpreted.
+             *
+             * @param referenceTime reference time based on which relative dates. This should
+             *                      usually be the time when the text was originally composed.
+             *
+             * @return this builder
+             */
+            @NonNull
+            public Builder setReferenceTime(@Nullable ZonedDateTime referenceTime) {
+                mReferenceTime = referenceTime;
+                return this;
+            }
+
+            /**
+             * Builds and returns the request object.
+             */
+            @NonNull
+            public Request build() {
+                return new Request(
+                        mText, mDefaultLocales, mEntityConfig,
+                        mLegacyFallback, mReferenceTime,
+                        mExtras == null ? Bundle.EMPTY : mExtras);
+            }
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeString(mText.toString());
+            dest.writeParcelable(mDefaultLocales, flags);
+            dest.writeParcelable(mEntityConfig, flags);
+            dest.writeBundle(mExtras);
+            dest.writeString(mReferenceTime == null ? null : mReferenceTime.toString());
+            dest.writeParcelable(mSystemTcMetadata, flags);
+        }
+
+        private static Request readFromParcel(Parcel in) {
+            final String text = in.readString();
+            final LocaleList defaultLocales = in.readParcelable(null);
+            final EntityConfig entityConfig = in.readParcelable(null);
+            final Bundle extras = in.readBundle();
+            final String referenceTimeString = in.readString();
+            final ZonedDateTime referenceTime = referenceTimeString == null
+                    ? null : ZonedDateTime.parse(referenceTimeString);
+            final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null);
+
+            final Request request = new Request(text, defaultLocales, entityConfig,
+                    /* legacyFallback= */ true, referenceTime, extras);
+            request.setSystemTextClassifierMetadata(systemTcMetadata);
+            return request;
+        }
+
+        public static final @android.annotation.NonNull Parcelable.Creator<Request> CREATOR =
+                new Parcelable.Creator<Request>() {
+                    @Override
+                    public Request createFromParcel(Parcel in) {
+                        return readFromParcel(in);
+                    }
+
+                    @Override
+                    public Request[] newArray(int size) {
+                        return new Request[size];
+                    }
+                };
+    }
+
+    /**
+     * A ClickableSpan for a TextLink.
+     *
+     * <p>Applies only to TextViews.
+     */
+    public static class TextLinkSpan extends ClickableSpan {
+
+        /**
+         * How the clickspan is triggered.
+         * @hide
+         */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef({INVOCATION_METHOD_UNSPECIFIED, INVOCATION_METHOD_TOUCH,
+                INVOCATION_METHOD_KEYBOARD})
+        public @interface InvocationMethod {}
+
+        /** @hide */
+        public static final int INVOCATION_METHOD_UNSPECIFIED = -1;
+        /** @hide */
+        public static final int INVOCATION_METHOD_TOUCH = 0;
+        /** @hide */
+        public static final int INVOCATION_METHOD_KEYBOARD = 1;
+
+        private final TextLink mTextLink;
+
+        public TextLinkSpan(@NonNull TextLink textLink) {
+            mTextLink = textLink;
+        }
+
+        @Override
+        public void onClick(View widget) {
+            onClick(widget, INVOCATION_METHOD_UNSPECIFIED);
+        }
+
+        /** @hide */
+        public final void onClick(View widget, @InvocationMethod int invocationMethod) {
+            if (widget instanceof TextView) {
+                final TextView textView = (TextView) widget;
+                final Context context = textView.getContext();
+                if (TextClassificationManager.getSettings(context).isSmartLinkifyEnabled()) {
+                    switch (invocationMethod) {
+                        case INVOCATION_METHOD_TOUCH:
+                            textView.requestActionMode(this);
+                            break;
+                        case INVOCATION_METHOD_KEYBOARD:// fall though
+                        case INVOCATION_METHOD_UNSPECIFIED:  // fall through
+                        default:
+                            textView.handleClick(this);
+                            break;
+                    }
+                } else {
+                    if (mTextLink.mUrlSpan != null) {
+                        mTextLink.mUrlSpan.onClick(textView);
+                    } else {
+                        textView.handleClick(this);
+                    }
+                }
+            }
+        }
+
+        public final TextLink getTextLink() {
+            return mTextLink;
+        }
+
+        /** @hide */
+        @VisibleForTesting(visibility = Visibility.PRIVATE)
+        @Nullable
+        public final String getUrl() {
+            if (mTextLink.mUrlSpan != null) {
+                return mTextLink.mUrlSpan.getURL();
+            }
+            return null;
+        }
+    }
+
+    /**
+     * A builder to construct a TextLinks instance.
+     */
+    public static final class Builder {
+        private final String mFullText;
+        private final ArrayList<TextLink> mLinks;
+        private Bundle mExtras;
+
+        /**
+         * Create a new TextLinks.Builder.
+         *
+         * @param fullText The full text to annotate with links
+         */
+        public Builder(@NonNull String fullText) {
+            mFullText = Objects.requireNonNull(fullText);
+            mLinks = new ArrayList<>();
+        }
+
+        /**
+         * Adds a TextLink.
+         *
+         * @param start The start index of the identified subsequence
+         * @param end The end index of the identified subsequence
+         * @param entityScores A mapping of entity type to confidence score
+         *
+         * @throws IllegalArgumentException if entityScores is null or empty.
+         */
+        @NonNull
+        public Builder addLink(int start, int end, @NonNull Map<String, Float> entityScores) {
+            return addLink(start, end, entityScores, Bundle.EMPTY, null);
+        }
+
+        /**
+         * Adds a TextLink.
+         *
+         * @see #addLink(int, int, Map)
+         * @param extras An optional bundle containing custom data related to this TextLink
+         */
+        @NonNull
+        public Builder addLink(int start, int end, @NonNull Map<String, Float> entityScores,
+                @NonNull Bundle extras) {
+            return addLink(start, end, entityScores, extras, null);
+        }
+
+        /**
+         * Adds a TextLink.
+         *
+         * @see #addLink(int, int, Map)
+         * @param urlSpan An optional URLSpan to delegate to. NOTE: Not parcelled.
+         */
+        @NonNull
+        Builder addLink(int start, int end, @NonNull Map<String, Float> entityScores,
+                @Nullable URLSpan urlSpan) {
+            return addLink(start, end, entityScores, Bundle.EMPTY, urlSpan);
+        }
+
+        private Builder addLink(int start, int end, @NonNull Map<String, Float> entityScores,
+                @NonNull Bundle extras, @Nullable URLSpan urlSpan) {
+            mLinks.add(new TextLink(
+                    start, end, new EntityConfidence(entityScores), extras, urlSpan));
+            return this;
+        }
+
+        /**
+         * Removes all {@link TextLink}s.
+         */
+        @NonNull
+        public Builder clearTextLinks() {
+            mLinks.clear();
+            return this;
+        }
+
+        /**
+         * Sets the extended data.
+         *
+         * @return this builder
+         */
+        @NonNull
+        public Builder setExtras(@Nullable Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
+         * Constructs a TextLinks instance.
+         *
+         * @return the constructed TextLinks
+         */
+        @NonNull
+        public TextLinks build() {
+            return new TextLinks(mFullText, mLinks,
+                    mExtras == null ? Bundle.EMPTY : mExtras);
+        }
+    }
+}
diff --git a/android/view/textclassifier/TextLinksParams.java b/android/view/textclassifier/TextLinksParams.java
new file mode 100644
index 0000000..f12b0d7
--- /dev/null
+++ b/android/view/textclassifier/TextLinksParams.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.text.Spannable;
+import android.text.style.ClickableSpan;
+import android.text.util.Linkify;
+import android.text.util.Linkify.LinkifyMask;
+import android.view.textclassifier.TextLinks.TextLink;
+import android.view.textclassifier.TextLinks.TextLinkSpan;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * Parameters for generating and applying links.
+ * @hide
+ */
+public final class TextLinksParams {
+
+    /**
+     * A function to create spans from TextLinks.
+     */
+    private static final Function<TextLink, TextLinkSpan> DEFAULT_SPAN_FACTORY =
+            textLink -> new TextLinkSpan(textLink);
+
+    @TextLinks.ApplyStrategy
+    private final int mApplyStrategy;
+    private final Function<TextLink, TextLinkSpan> mSpanFactory;
+    private final TextClassifier.EntityConfig mEntityConfig;
+
+    private TextLinksParams(
+            @TextLinks.ApplyStrategy int applyStrategy,
+            Function<TextLink, TextLinkSpan> spanFactory) {
+        mApplyStrategy = applyStrategy;
+        mSpanFactory = spanFactory;
+        mEntityConfig = TextClassifier.EntityConfig.createWithHints(null);
+    }
+
+    /**
+     * Returns a new TextLinksParams object based on the specified link mask.
+     *
+     * @param mask the link mask
+     *      e.g. {@link LinkifyMask#PHONE_NUMBERS} | {@link LinkifyMask#EMAIL_ADDRESSES}
+     * @hide
+     */
+    @NonNull
+    public static TextLinksParams fromLinkMask(@LinkifyMask int mask) {
+        final List<String> entitiesToFind = new ArrayList<>();
+        if ((mask & Linkify.WEB_URLS) != 0) {
+            entitiesToFind.add(TextClassifier.TYPE_URL);
+        }
+        if ((mask & Linkify.EMAIL_ADDRESSES) != 0) {
+            entitiesToFind.add(TextClassifier.TYPE_EMAIL);
+        }
+        if ((mask & Linkify.PHONE_NUMBERS) != 0) {
+            entitiesToFind.add(TextClassifier.TYPE_PHONE);
+        }
+        if ((mask & Linkify.MAP_ADDRESSES) != 0) {
+            entitiesToFind.add(TextClassifier.TYPE_ADDRESS);
+        }
+        return new TextLinksParams.Builder().setEntityConfig(
+                TextClassifier.EntityConfig.createWithExplicitEntityList(entitiesToFind))
+                .build();
+    }
+
+    /**
+     * Returns the entity config used to determine what entity types to generate.
+     */
+    @NonNull
+    public TextClassifier.EntityConfig getEntityConfig() {
+        return mEntityConfig;
+    }
+
+    /**
+     * Annotates the given text with the generated links. It will fail if the provided text doesn't
+     * match the original text used to crete the TextLinks.
+     *
+     * @param text the text to apply the links to. Must match the original text
+     * @param textLinks the links to apply to the text
+     *
+     * @return a status code indicating whether or not the links were successfully applied
+     * @hide
+     */
+    @TextLinks.Status
+    public int apply(@NonNull Spannable text, @NonNull TextLinks textLinks) {
+        Objects.requireNonNull(text);
+        Objects.requireNonNull(textLinks);
+
+        final String textString = text.toString();
+
+        if (Linkify.containsUnsupportedCharacters(textString)) {
+            // Do not apply links to text containing unsupported characters.
+            android.util.EventLog.writeEvent(0x534e4554, "116321860", -1, "");
+            return TextLinks.STATUS_UNSUPPORTED_CHARACTER;
+        }
+
+        if (!textString.startsWith(textLinks.getText().toString())) {
+            return TextLinks.STATUS_DIFFERENT_TEXT;
+        }
+        if (textLinks.getLinks().isEmpty()) {
+            return TextLinks.STATUS_NO_LINKS_FOUND;
+        }
+
+        int applyCount = 0;
+        for (TextLink link : textLinks.getLinks()) {
+            final TextLinkSpan span = mSpanFactory.apply(link);
+            if (span != null) {
+                final ClickableSpan[] existingSpans = text.getSpans(
+                        link.getStart(), link.getEnd(), ClickableSpan.class);
+                if (existingSpans.length > 0) {
+                    if (mApplyStrategy == TextLinks.APPLY_STRATEGY_REPLACE) {
+                        for (ClickableSpan existingSpan : existingSpans) {
+                            text.removeSpan(existingSpan);
+                        }
+                        text.setSpan(span, link.getStart(), link.getEnd(),
+                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+                        applyCount++;
+                    }
+                } else {
+                    text.setSpan(span, link.getStart(), link.getEnd(),
+                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+                    applyCount++;
+                }
+            }
+        }
+        if (applyCount == 0) {
+            return TextLinks.STATUS_NO_LINKS_APPLIED;
+        }
+        return TextLinks.STATUS_LINKS_APPLIED;
+    }
+
+    /**
+     * A builder for building TextLinksParams.
+     */
+    public static final class Builder {
+
+        @TextLinks.ApplyStrategy
+        private int mApplyStrategy = TextLinks.APPLY_STRATEGY_IGNORE;
+        private Function<TextLink, TextLinkSpan> mSpanFactory = DEFAULT_SPAN_FACTORY;
+
+        /**
+         * Sets the apply strategy used to determine how to apply links to text.
+         *      e.g {@link TextLinks#APPLY_STRATEGY_IGNORE}
+         *
+         * @return this builder
+         */
+        public Builder setApplyStrategy(@TextLinks.ApplyStrategy int applyStrategy) {
+            mApplyStrategy = checkApplyStrategy(applyStrategy);
+            return this;
+        }
+
+        /**
+         * Sets a custom span factory for converting TextLinks to TextLinkSpans.
+         * Set to {@code null} to use the default span factory.
+         *
+         * @return this builder
+         */
+        public Builder setSpanFactory(@Nullable Function<TextLink, TextLinkSpan> spanFactory) {
+            mSpanFactory = spanFactory == null ? DEFAULT_SPAN_FACTORY : spanFactory;
+            return this;
+        }
+
+        /**
+         * Sets the entity configuration used to determine what entity types to generate.
+         * Set to {@code null} for the default entity config which will automatically determine
+         * what links to generate.
+         *
+         * @return this builder
+         */
+        public Builder setEntityConfig(@Nullable TextClassifier.EntityConfig entityConfig) {
+            return this;
+        }
+
+        /**
+         * Builds and returns a TextLinksParams object.
+         */
+        public TextLinksParams build() {
+            return new TextLinksParams(mApplyStrategy, mSpanFactory);
+        }
+    }
+
+    /** @throws IllegalArgumentException if the value is invalid */
+    @TextLinks.ApplyStrategy
+    private static int checkApplyStrategy(int applyStrategy) {
+        if (applyStrategy != TextLinks.APPLY_STRATEGY_IGNORE
+                && applyStrategy != TextLinks.APPLY_STRATEGY_REPLACE) {
+            throw new IllegalArgumentException(
+                    "Invalid apply strategy. See TextLinksParams.ApplyStrategy for options.");
+        }
+        return applyStrategy;
+    }
+}
+
diff --git a/android/view/textclassifier/TextSelection.java b/android/view/textclassifier/TextSelection.java
new file mode 100644
index 0000000..c1913f6
--- /dev/null
+++ b/android/view/textclassifier/TextSelection.java
@@ -0,0 +1,553 @@
+/*
+ * 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.view.textclassifier;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.SpannedString;
+import android.util.ArrayMap;
+import android.view.textclassifier.TextClassifier.EntityType;
+import android.view.textclassifier.TextClassifier.Utils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Information about where text selection should be.
+ */
+public final class TextSelection implements Parcelable {
+
+    private final int mStartIndex;
+    private final int mEndIndex;
+    private final EntityConfidence mEntityConfidence;
+    @Nullable private final String mId;
+    @Nullable
+    private final TextClassification mTextClassification;
+    private final Bundle mExtras;
+
+    private TextSelection(
+            int startIndex, int endIndex, Map<String, Float> entityConfidence, String id,
+            @Nullable TextClassification textClassification,
+            Bundle extras) {
+        mStartIndex = startIndex;
+        mEndIndex = endIndex;
+        mEntityConfidence = new EntityConfidence(entityConfidence);
+        mId = id;
+        mTextClassification = textClassification;
+        mExtras = extras;
+    }
+
+    /**
+     * Returns the start index of the text selection.
+     */
+    public int getSelectionStartIndex() {
+        return mStartIndex;
+    }
+
+    /**
+     * Returns the end index of the text selection.
+     */
+    public int getSelectionEndIndex() {
+        return mEndIndex;
+    }
+
+    /**
+     * Returns the number of entities found in the classified text.
+     */
+    @IntRange(from = 0)
+    public int getEntityCount() {
+        return mEntityConfidence.getEntities().size();
+    }
+
+    /**
+     * Returns the entity at the specified index. Entities are ordered from high confidence
+     * to low confidence.
+     *
+     * @throws IndexOutOfBoundsException if the specified index is out of range.
+     * @see #getEntityCount() for the number of entities available.
+     */
+    @NonNull
+    @EntityType
+    public String getEntity(int index) {
+        return mEntityConfidence.getEntities().get(index);
+    }
+
+    /**
+     * Returns the confidence score for the specified entity. The value ranges from
+     * 0 (low confidence) to 1 (high confidence). 0 indicates that the entity was not found for the
+     * classified text.
+     */
+    @FloatRange(from = 0.0, to = 1.0)
+    public float getConfidenceScore(@EntityType String entity) {
+        return mEntityConfidence.getConfidenceScore(entity);
+    }
+
+    /**
+     * Returns the id, if one exists, for this object.
+     */
+    @Nullable
+    public String getId() {
+        return mId;
+    }
+
+    /**
+     * Returns the text classification result of the suggested selection span. Enables the text
+     * classification by calling
+     * {@link TextSelection.Request.Builder#setIncludeTextClassification(boolean)}. If the text
+     * classifier does not support it, a {@code null} is returned.
+     *
+     * @see TextSelection.Request.Builder#setIncludeTextClassification(boolean)
+     */
+    @Nullable
+    public TextClassification getTextClassification() {
+        return mTextClassification;
+    }
+
+    /**
+     * Returns the extended data.
+     *
+     * <p><b>NOTE: </b>Do not modify this bundle.
+     */
+    @NonNull
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    /** @hide */
+    public TextSelection.Builder toBuilder() {
+        return new TextSelection.Builder(mStartIndex, mEndIndex)
+                .setId(mId)
+                .setEntityConfidence(mEntityConfidence)
+                .setTextClassification(mTextClassification)
+                .setExtras(mExtras);
+    }
+
+    @Override
+    public String toString() {
+        return String.format(
+                Locale.US,
+                "TextSelection {id=%s, startIndex=%d, endIndex=%d, entities=%s}",
+                mId, mStartIndex, mEndIndex, mEntityConfidence);
+    }
+
+    /**
+     * Builder used to build {@link TextSelection} objects.
+     */
+    public static final class Builder {
+
+        private final int mStartIndex;
+        private final int mEndIndex;
+        private final Map<String, Float> mEntityConfidence = new ArrayMap<>();
+        @Nullable private String mId;
+        @Nullable
+        private TextClassification mTextClassification;
+        @Nullable
+        private Bundle mExtras;
+
+        /**
+         * Creates a builder used to build {@link TextSelection} objects.
+         *
+         * @param startIndex the start index of the text selection.
+         * @param endIndex the end index of the text selection. Must be greater than startIndex
+         */
+        public Builder(@IntRange(from = 0) int startIndex, @IntRange(from = 0) int endIndex) {
+            Preconditions.checkArgument(startIndex >= 0);
+            Preconditions.checkArgument(endIndex > startIndex);
+            mStartIndex = startIndex;
+            mEndIndex = endIndex;
+        }
+
+        /**
+         * Sets an entity type for the classified text and assigns a confidence score.
+         *
+         * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence).
+         *      0 implies the entity does not exist for the classified text.
+         *      Values greater than 1 are clamped to 1.
+         */
+        @NonNull
+        public Builder setEntityType(
+                @NonNull @EntityType String type,
+                @FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
+            Objects.requireNonNull(type);
+            mEntityConfidence.put(type, confidenceScore);
+            return this;
+        }
+
+        Builder setEntityConfidence(EntityConfidence scores) {
+            mEntityConfidence.clear();
+            mEntityConfidence.putAll(scores.toMap());
+            return this;
+        }
+
+        /**
+         * Sets an id for the TextSelection object.
+         */
+        @NonNull
+        public Builder setId(@Nullable String id) {
+            mId = id;
+            return this;
+        }
+
+        /**
+         * Sets the text classification result of the suggested selection. If
+         * {@link Request#shouldIncludeTextClassification()} is {@code true}, set this value.
+         * Otherwise this value may be set to null. The intention of this method is to avoid
+         * doing expensive work if the client is not interested in such result.
+         *
+         * @return this builder
+         * @see Request#shouldIncludeTextClassification()
+         */
+        @NonNull
+        public Builder setTextClassification(@Nullable TextClassification textClassification) {
+            mTextClassification = textClassification;
+            return this;
+        }
+
+        /**
+         * Sets the extended data.
+         *
+         * @return this builder
+         */
+        @NonNull
+        public Builder setExtras(@Nullable Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
+         * Builds and returns {@link TextSelection} object.
+         */
+        @NonNull
+        public TextSelection build() {
+            return new TextSelection(
+                    mStartIndex, mEndIndex, mEntityConfidence, mId,
+                    mTextClassification, mExtras == null ? Bundle.EMPTY : mExtras);
+        }
+    }
+
+    /**
+     * A request object for generating TextSelection.
+     */
+    public static final class Request implements Parcelable {
+
+        private final CharSequence mText;
+        private final int mStartIndex;
+        private final int mEndIndex;
+        @Nullable private final LocaleList mDefaultLocales;
+        private final boolean mDarkLaunchAllowed;
+        private final boolean mIncludeTextClassification;
+        private final Bundle mExtras;
+        @Nullable private SystemTextClassifierMetadata mSystemTcMetadata;
+
+        private Request(
+                CharSequence text,
+                int startIndex,
+                int endIndex,
+                LocaleList defaultLocales,
+                boolean darkLaunchAllowed,
+                boolean includeTextClassification,
+                Bundle extras) {
+            mText = text;
+            mStartIndex = startIndex;
+            mEndIndex = endIndex;
+            mDefaultLocales = defaultLocales;
+            mDarkLaunchAllowed = darkLaunchAllowed;
+            mIncludeTextClassification = includeTextClassification;
+            mExtras = extras;
+        }
+
+        /**
+         * Returns the text providing context for the selected text (which is specified by the
+         * sub sequence starting at startIndex and ending at endIndex).
+         */
+        @NonNull
+        public CharSequence getText() {
+            return mText;
+        }
+
+        /**
+         * Returns start index of the selected part of text.
+         */
+        @IntRange(from = 0)
+        public int getStartIndex() {
+            return mStartIndex;
+        }
+
+        /**
+         * Returns end index of the selected part of text.
+         */
+        @IntRange(from = 0)
+        public int getEndIndex() {
+            return mEndIndex;
+        }
+
+        /**
+         * Returns true if the TextClassifier should return selection suggestions when "dark
+         * launched". Otherwise, returns false.
+         *
+         * @hide
+         */
+        public boolean isDarkLaunchAllowed() {
+            return mDarkLaunchAllowed;
+        }
+
+        /**
+         * @return ordered list of locale preferences that can be used to disambiguate the
+         * provided text.
+         */
+        @Nullable
+        public LocaleList getDefaultLocales() {
+            return mDefaultLocales;
+        }
+
+        /**
+         * Returns the name of the package that sent this request.
+         * This returns {@code null} if no calling package name is set.
+         */
+        @Nullable
+        public String getCallingPackageName() {
+            return mSystemTcMetadata != null ? mSystemTcMetadata.getCallingPackageName() : null;
+        }
+
+        /**
+         * Sets the information about the {@link SystemTextClassifier} that sent this request.
+         *
+         * @hide
+         */
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+        public void setSystemTextClassifierMetadata(
+                @Nullable SystemTextClassifierMetadata systemTcMetadata) {
+            mSystemTcMetadata = systemTcMetadata;
+        }
+
+        /**
+         * Returns the information about the {@link SystemTextClassifier} that sent this request.
+         *
+         * @hide
+         */
+        @Nullable
+        public SystemTextClassifierMetadata getSystemTextClassifierMetadata() {
+            return mSystemTcMetadata;
+        }
+
+        /**
+         * Returns true if the client wants the text classifier to classify the text as well and
+         * include a {@link TextClassification} object in the result.
+         */
+        public boolean shouldIncludeTextClassification() {
+            return mIncludeTextClassification;
+        }
+
+        /**
+         * Returns the extended data.
+         *
+         * <p><b>NOTE: </b>Do not modify this bundle.
+         */
+        @NonNull
+        public Bundle getExtras() {
+            return mExtras;
+        }
+
+        /**
+         * A builder for building TextSelection requests.
+         */
+        public static final class Builder {
+
+            private final CharSequence mText;
+            private final int mStartIndex;
+            private final int mEndIndex;
+            @Nullable private LocaleList mDefaultLocales;
+            private boolean mDarkLaunchAllowed;
+            private boolean mIncludeTextClassification;
+            private Bundle mExtras;
+
+            /**
+             * @param text text providing context for the selected text (which is specified by the
+             *      sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
+             * @param startIndex start index of the selected part of text
+             * @param endIndex end index of the selected part of text
+             */
+            public Builder(
+                    @NonNull CharSequence text,
+                    @IntRange(from = 0) int startIndex,
+                    @IntRange(from = 0) int endIndex) {
+                Utils.checkArgument(text, startIndex, endIndex);
+                mText = text;
+                mStartIndex = startIndex;
+                mEndIndex = endIndex;
+            }
+
+            /**
+             * @param defaultLocales ordered list of locale preferences that may be used to
+             *      disambiguate the provided text. If no locale preferences exist, set this to null
+             *      or an empty locale list.
+             *
+             * @return this builder.
+             */
+            @NonNull
+            public Builder setDefaultLocales(@Nullable LocaleList defaultLocales) {
+                mDefaultLocales = defaultLocales;
+                return this;
+            }
+
+            /**
+             * @param allowed whether or not the TextClassifier should return selection suggestions
+             *      when "dark launched". When a TextClassifier is dark launched, it can suggest
+             *      selection changes that should not be used to actually change the user's
+             *      selection. Instead, the suggested selection is logged, compared with the user's
+             *      selection interaction, and used to generate quality metrics for the
+             *      TextClassifier. Not parceled.
+             *
+             * @return this builder.
+             * @hide
+             */
+            @NonNull
+            public Builder setDarkLaunchAllowed(boolean allowed) {
+                mDarkLaunchAllowed = allowed;
+                return this;
+            }
+
+            /**
+             * @param includeTextClassification If true, suggests the TextClassifier to classify the
+             *     text in the suggested selection span and include a TextClassification object in
+             *     the result. The TextClassifier may not support this and in which case,
+             *     {@link TextSelection#getTextClassification()} returns {@code null}.
+             *
+             * @return this builder.
+             * @see TextSelection#getTextClassification()
+             */
+            @NonNull
+            public Builder setIncludeTextClassification(boolean includeTextClassification) {
+                mIncludeTextClassification = includeTextClassification;
+                return this;
+            }
+
+            /**
+             * Sets the extended data.
+             *
+             * @return this builder
+             */
+            @NonNull
+            public Builder setExtras(@Nullable Bundle extras) {
+                mExtras = extras;
+                return this;
+            }
+
+            /**
+             * Builds and returns the request object.
+             */
+            @NonNull
+            public Request build() {
+                return new Request(new SpannedString(mText), mStartIndex, mEndIndex,
+                        mDefaultLocales, mDarkLaunchAllowed,
+                        mIncludeTextClassification,
+                        mExtras == null ? Bundle.EMPTY : mExtras);
+            }
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeCharSequence(mText);
+            dest.writeInt(mStartIndex);
+            dest.writeInt(mEndIndex);
+            dest.writeParcelable(mDefaultLocales, flags);
+            dest.writeBundle(mExtras);
+            dest.writeParcelable(mSystemTcMetadata, flags);
+            dest.writeBoolean(mIncludeTextClassification);
+        }
+
+        private static Request readFromParcel(Parcel in) {
+            final CharSequence text = in.readCharSequence();
+            final int startIndex = in.readInt();
+            final int endIndex = in.readInt();
+            final LocaleList defaultLocales = in.readParcelable(null);
+            final Bundle extras = in.readBundle();
+            final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null);
+            final boolean includeTextClassification = in.readBoolean();
+
+            final Request request = new Request(text, startIndex, endIndex, defaultLocales,
+                    /* darkLaunchAllowed= */ false, includeTextClassification, extras);
+            request.setSystemTextClassifierMetadata(systemTcMetadata);
+            return request;
+        }
+
+        public static final @android.annotation.NonNull Parcelable.Creator<Request> CREATOR =
+                new Parcelable.Creator<Request>() {
+                    @Override
+                    public Request createFromParcel(Parcel in) {
+                        return readFromParcel(in);
+                    }
+
+                    @Override
+                    public Request[] newArray(int size) {
+                        return new Request[size];
+                    }
+                };
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mStartIndex);
+        dest.writeInt(mEndIndex);
+        mEntityConfidence.writeToParcel(dest, flags);
+        dest.writeString(mId);
+        dest.writeBundle(mExtras);
+        dest.writeParcelable(mTextClassification, flags);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<TextSelection> CREATOR =
+            new Parcelable.Creator<TextSelection>() {
+                @Override
+                public TextSelection createFromParcel(Parcel in) {
+                    return new TextSelection(in);
+                }
+
+                @Override
+                public TextSelection[] newArray(int size) {
+                    return new TextSelection[size];
+                }
+            };
+
+    private TextSelection(Parcel in) {
+        mStartIndex = in.readInt();
+        mEndIndex = in.readInt();
+        mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in);
+        mId = in.readString();
+        mExtras = in.readBundle();
+        mTextClassification = in.readParcelable(TextClassification.class.getClassLoader());
+    }
+}
diff --git a/android/view/textservice/SentenceSuggestionsInfo.java b/android/view/textservice/SentenceSuggestionsInfo.java
new file mode 100644
index 0000000..0d37632
--- /dev/null
+++ b/android/view/textservice/SentenceSuggestionsInfo.java
@@ -0,0 +1,144 @@
+/*
+ * 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.view.textservice;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+
+/**
+ * This class contains a metadata of suggestions returned from a text service
+ * (e.g. {@link android.service.textservice.SpellCheckerService}).
+ * The text service uses this class to return the suggestions
+ * for a sentence. See {@link SuggestionsInfo} which is used for suggestions for a word.
+ * This class extends the functionality of {@link SuggestionsInfo} as far as this class enables
+ * you to put multiple {@link SuggestionsInfo}s on a sentence with the offsets and the lengths
+ * of all {@link SuggestionsInfo}s.
+ */
+public final class SentenceSuggestionsInfo implements Parcelable {
+
+    private final SuggestionsInfo[] mSuggestionsInfos;
+    private final int[] mOffsets;
+    private final int[] mLengths;
+
+    /**
+     * Constructor.
+     * @param suggestionsInfos from the text service
+     * @param offsets the array of offsets of suggestions
+     * @param lengths the array of lengths of suggestions
+     */
+    public SentenceSuggestionsInfo(
+            SuggestionsInfo[] suggestionsInfos, int[] offsets, int[] lengths) {
+        if (suggestionsInfos == null || offsets == null || lengths == null) {
+            throw new NullPointerException();
+        }
+        if (suggestionsInfos.length != offsets.length || offsets.length != lengths.length) {
+            throw new IllegalArgumentException();
+        }
+        final int infoSize = suggestionsInfos.length;
+        mSuggestionsInfos = Arrays.copyOf(suggestionsInfos, infoSize);
+        mOffsets = Arrays.copyOf(offsets, infoSize);
+        mLengths = Arrays.copyOf(lengths, infoSize);
+    }
+
+    public SentenceSuggestionsInfo(Parcel source) {
+        final int infoSize = source.readInt();
+        mSuggestionsInfos = new SuggestionsInfo[infoSize];
+        source.readTypedArray(mSuggestionsInfos, SuggestionsInfo.CREATOR);
+        mOffsets = new int[mSuggestionsInfos.length];
+        source.readIntArray(mOffsets);
+        mLengths = new int[mSuggestionsInfos.length];
+        source.readIntArray(mLengths);
+    }
+
+    /**
+     * Used to package this object into a {@link Parcel}.
+     *
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        final int infoSize = mSuggestionsInfos.length;
+        dest.writeInt(infoSize);
+        dest.writeTypedArray(mSuggestionsInfos, 0);
+        dest.writeIntArray(mOffsets);
+        dest.writeIntArray(mLengths);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * @return the count of {@link SuggestionsInfo}s this instance holds.
+     */
+    public int getSuggestionsCount() {
+        return mSuggestionsInfos.length;
+    }
+
+    /**
+     * @param i the id of {@link SuggestionsInfo}s this instance holds.
+     * @return a {@link SuggestionsInfo} at the specified id
+     */
+    public SuggestionsInfo getSuggestionsInfoAt(int i) {
+        if (i >= 0 && i < mSuggestionsInfos.length) {
+            return mSuggestionsInfos[i];
+        }
+        return null;
+    }
+
+    /**
+     * @param i the id of {@link SuggestionsInfo}s this instance holds
+     * @return the offset of the specified {@link SuggestionsInfo}
+     */
+    public int getOffsetAt(int i) {
+        if (i >= 0 && i < mOffsets.length) {
+            return mOffsets[i];
+        }
+        return -1;
+    }
+
+    /**
+     * @param i the id of {@link SuggestionsInfo}s this instance holds
+     * @return the length of the specified {@link SuggestionsInfo}
+     */
+    public int getLengthAt(int i) {
+        if (i >= 0 && i < mLengths.length) {
+            return mLengths[i];
+        }
+        return -1;
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final @android.annotation.NonNull Parcelable.Creator<SentenceSuggestionsInfo> CREATOR
+            = new Parcelable.Creator<SentenceSuggestionsInfo>() {
+        @Override
+        public SentenceSuggestionsInfo createFromParcel(Parcel source) {
+            return new SentenceSuggestionsInfo(source);
+        }
+
+        @Override
+        public SentenceSuggestionsInfo[] newArray(int size) {
+            return new SentenceSuggestionsInfo[size];
+        }
+    };
+}
diff --git a/android/view/textservice/SpellCheckerInfo.java b/android/view/textservice/SpellCheckerInfo.java
new file mode 100644
index 0000000..13d44da
--- /dev/null
+++ b/android/view/textservice/SpellCheckerInfo.java
@@ -0,0 +1,290 @@
+/*
+ * 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.view.textservice;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+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.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.PrintWriterPrinter;
+import android.util.Slog;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * This class is used to specify meta information of a spell checker.
+ */
+public final class SpellCheckerInfo implements Parcelable {
+    private static final String TAG = SpellCheckerInfo.class.getSimpleName();
+    private final ResolveInfo mService;
+    private final String mId;
+    private final int mLabel;
+
+    /**
+     * The spell checker setting activity's name, used by the system settings to
+     * launch the setting activity.
+     */
+    private final String mSettingsActivityName;
+
+    /**
+     * The array of subtypes.
+     */
+    private final ArrayList<SpellCheckerSubtype> mSubtypes = new ArrayList<>();
+
+    /**
+     * Constructor.
+     * @hide
+     */
+    public SpellCheckerInfo(Context context, ResolveInfo service)
+            throws XmlPullParserException, IOException {
+        mService = service;
+        ServiceInfo si = service.serviceInfo;
+        mId = new ComponentName(si.packageName, si.name).flattenToShortString();
+
+        final PackageManager pm = context.getPackageManager();
+        int label = 0;
+        String settingsActivityComponent = null;
+
+        XmlResourceParser parser = null;
+        try {
+            parser = si.loadXmlMetaData(pm, SpellCheckerSession.SERVICE_META_DATA);
+            if (parser == null) {
+                throw new XmlPullParserException("No "
+                        + SpellCheckerSession.SERVICE_META_DATA + " meta-data");
+            }
+
+            final Resources res = pm.getResourcesForApplication(si.applicationInfo);
+            final AttributeSet attrs = Xml.asAttributeSet(parser);
+            int type;
+            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+                    && type != XmlPullParser.START_TAG) {
+            }
+
+            final String nodeName = parser.getName();
+            if (!"spell-checker".equals(nodeName)) {
+                throw new XmlPullParserException(
+                        "Meta-data does not start with spell-checker tag");
+            }
+
+            TypedArray sa = res.obtainAttributes(attrs,
+                    com.android.internal.R.styleable.SpellChecker);
+            label = sa.getResourceId(com.android.internal.R.styleable.SpellChecker_label, 0);
+            settingsActivityComponent = sa.getString(
+                    com.android.internal.R.styleable.SpellChecker_settingsActivity);
+            sa.recycle();
+
+            final int depth = parser.getDepth();
+            // Parse all subtypes
+            while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+                    && type != XmlPullParser.END_DOCUMENT) {
+                if (type == XmlPullParser.START_TAG) {
+                    final String subtypeNodeName = parser.getName();
+                    if (!"subtype".equals(subtypeNodeName)) {
+                        throw new XmlPullParserException(
+                                "Meta-data in spell-checker does not start with subtype tag");
+                    }
+                    final TypedArray a = res.obtainAttributes(
+                            attrs, com.android.internal.R.styleable.SpellChecker_Subtype);
+                    SpellCheckerSubtype subtype = new SpellCheckerSubtype(
+                            a.getResourceId(com.android.internal.R.styleable
+                                    .SpellChecker_Subtype_label, 0),
+                            a.getString(com.android.internal.R.styleable
+                                    .SpellChecker_Subtype_subtypeLocale),
+                            a.getString(com.android.internal.R.styleable
+                                    .SpellChecker_Subtype_languageTag),
+                            a.getString(com.android.internal.R.styleable
+                                    .SpellChecker_Subtype_subtypeExtraValue),
+                            a.getInt(com.android.internal.R.styleable
+                                    .SpellChecker_Subtype_subtypeId, 0));
+                    mSubtypes.add(subtype);
+                }
+            }
+        } catch (Exception e) {
+            Slog.e(TAG, "Caught exception: " + e);
+            throw new XmlPullParserException(
+                    "Unable to create context for: " + si.packageName);
+        } finally {
+            if (parser != null) parser.close();
+        }
+        mLabel = label;
+        mSettingsActivityName = settingsActivityComponent;
+    }
+
+    /**
+     * Constructor.
+     * @hide
+     */
+    public SpellCheckerInfo(Parcel source) {
+        mLabel = source.readInt();
+        mId = source.readString();
+        mSettingsActivityName = source.readString();
+        mService = ResolveInfo.CREATOR.createFromParcel(source);
+        source.readTypedList(mSubtypes, SpellCheckerSubtype.CREATOR);
+    }
+
+    /**
+     * Return a unique ID for this spell checker.  The ID is generated from
+     * the package and class name implementing the method.
+     */
+    public String getId() {
+        return mId;
+    }
+
+    /**
+     * Return the component of the service that implements.
+     */
+    public ComponentName getComponent() {
+        return new ComponentName(
+                mService.serviceInfo.packageName, mService.serviceInfo.name);
+    }
+
+    /**
+     * Return the .apk package that implements this.
+     */
+    public String getPackageName() {
+        return mService.serviceInfo.packageName;
+    }
+
+    /**
+     * Used to package this object into a {@link Parcel}.
+     *
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mLabel);
+        dest.writeString(mId);
+        dest.writeString(mSettingsActivityName);
+        mService.writeToParcel(dest, flags);
+        dest.writeTypedList(mSubtypes);
+    }
+
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final @android.annotation.NonNull Parcelable.Creator<SpellCheckerInfo> CREATOR
+            = new Parcelable.Creator<SpellCheckerInfo>() {
+        @Override
+        public SpellCheckerInfo createFromParcel(Parcel source) {
+            return new SpellCheckerInfo(source);
+        }
+
+        @Override
+        public SpellCheckerInfo[] newArray(int size) {
+            return new SpellCheckerInfo[size];
+        }
+    };
+
+    /**
+     * Load the user-displayed label for this spell checker.
+     *
+     * @param pm Supply a PackageManager used to load the spell checker's resources.
+     */
+    public CharSequence loadLabel(PackageManager pm) {
+        if (mLabel == 0 || pm == null) return "";
+        return pm.getText(getPackageName(), mLabel, mService.serviceInfo.applicationInfo);
+    }
+
+    /**
+     * Load the user-displayed icon for this spell checker.
+     *
+     * @param pm Supply a PackageManager used to load the spell checker's resources.
+     */
+    public Drawable loadIcon(PackageManager pm) {
+        return mService.loadIcon(pm);
+    }
+
+
+    /**
+     * Return the raw information about the Service implementing this
+     * spell checker.  Do not modify the returned object.
+     */
+    public ServiceInfo getServiceInfo() {
+        return mService.serviceInfo;
+    }
+
+    /**
+     * Return the class name of an activity that provides a settings UI.
+     * You can launch this activity be starting it with
+     * an {@link android.content.Intent} whose action is MAIN and with an
+     * explicit {@link android.content.ComponentName}
+     * composed of {@link #getPackageName} and the class name returned here.
+     *
+     * <p>A null will be returned if there is no settings activity.
+     */
+    public String getSettingsActivity() {
+        return mSettingsActivityName;
+    }
+
+    /**
+     * Return the count of the subtypes.
+     */
+    public int getSubtypeCount() {
+        return mSubtypes.size();
+    }
+
+    /**
+     * Return the subtype at the specified index.
+     *
+     * @param index the index of the subtype to return.
+     */
+    public SpellCheckerSubtype getSubtypeAt(int index) {
+        return mSubtypes.get(index);
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * @hide
+     */
+    public void dump(final PrintWriter pw, final String prefix) {
+        pw.println(prefix + "mId=" + mId);
+        pw.println(prefix + "mSettingsActivityName=" + mSettingsActivityName);
+        pw.println(prefix + "Service:");
+        mService.dump(new PrintWriterPrinter(pw), prefix + "  ");
+        final int N = getSubtypeCount();
+        for (int i = 0; i < N; i++) {
+            final SpellCheckerSubtype st = getSubtypeAt(i);
+            pw.println(prefix + "  " + "Subtype #" + i + ":");
+            pw.println(prefix + "    " + "locale=" + st.getLocale()
+                    + " languageTag=" + st.getLanguageTag());
+            pw.println(prefix + "    " + "extraValue=" + st.getExtraValue());
+        }
+    }
+}
diff --git a/android/view/textservice/SpellCheckerSession.java b/android/view/textservice/SpellCheckerSession.java
new file mode 100644
index 0000000..6cfb938
--- /dev/null
+++ b/android/view/textservice/SpellCheckerSession.java
@@ -0,0 +1,748 @@
+/*
+ * 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.view.textservice;
+
+import android.annotation.BinderThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.inputmethod.InputMethodManager;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.textservice.ISpellCheckerSession;
+import com.android.internal.textservice.ISpellCheckerSessionListener;
+import com.android.internal.textservice.ITextServicesSessionListener;
+
+import dalvik.system.CloseGuard;
+
+import java.util.LinkedList;
+import java.util.Locale;
+import java.util.Queue;
+import java.util.concurrent.Executor;
+
+/**
+ * The SpellCheckerSession interface provides the per client functionality of SpellCheckerService.
+ *
+ *
+ * <a name="Applications"></a>
+ * <h3>Applications</h3>
+ *
+ * <p>In most cases, applications that are using the standard
+ * {@link android.widget.TextView} or its subclasses will have little they need
+ * to do to work well with spell checker services.  The main things you need to
+ * be aware of are:</p>
+ *
+ * <ul>
+ * <li> Properly set the {@link android.R.attr#inputType} in your editable
+ * text views, so that the spell checker will have enough context to help the
+ * user in editing text in them.
+ * </ul>
+ *
+ * <p>For the rare people amongst us writing client applications that use the spell checker service
+ * directly, you will need to use {@link #getSuggestions(TextInfo, int)} or
+ * {@link #getSuggestions(TextInfo[], int, boolean)} for obtaining results from the spell checker
+ * service by yourself.</p>
+ *
+ * <h3>Security</h3>
+ *
+ * <p>There are a lot of security issues associated with spell checkers,
+ * since they could monitor all the text being sent to them
+ * through, for instance, {@link android.widget.TextView}.
+ * The Android spell checker framework also allows
+ * arbitrary third party spell checkers, so care must be taken to restrict their
+ * selection and interactions.</p>
+ *
+ * <p>Here are some key points about the security architecture behind the
+ * spell checker framework:</p>
+ *
+ * <ul>
+ * <li>Only the system is allowed to directly access a spell checker framework's
+ * {@link android.service.textservice.SpellCheckerService} interface, via the
+ * {@link android.Manifest.permission#BIND_TEXT_SERVICE} permission.  This is
+ * enforced in the system by not binding to a spell checker service that does
+ * not require this permission.
+ *
+ * <li>The user must explicitly enable a new spell checker in settings before
+ * they can be enabled, to confirm with the system that they know about it
+ * and want to make it available for use.
+ * </ul>
+ *
+ */
+public class SpellCheckerSession {
+    private static final String TAG = SpellCheckerSession.class.getSimpleName();
+    private static final boolean DBG = false;
+    /**
+     * Name under which a SpellChecker service component publishes information about itself.
+     * This meta-data must reference an XML resource.
+     **/
+    public static final String SERVICE_META_DATA = "android.view.textservice.scs";
+
+    private static final int MSG_ON_GET_SUGGESTION_MULTIPLE = 1;
+    private static final int MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE = 2;
+
+    private final InternalListener mInternalListener;
+    private final TextServicesManager mTextServicesManager;
+    private final SpellCheckerInfo mSpellCheckerInfo;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private final SpellCheckerSessionListener mSpellCheckerSessionListener;
+    private final SpellCheckerSessionListenerImpl mSpellCheckerSessionListenerImpl;
+    private final Executor mExecutor;
+
+    private final CloseGuard mGuard = CloseGuard.get();
+
+    /**
+     * Constructor
+     * @hide
+     */
+    public SpellCheckerSession(
+            SpellCheckerInfo info, TextServicesManager tsm, SpellCheckerSessionListener listener,
+            Executor executor) {
+        if (info == null || listener == null || tsm == null) {
+            throw new NullPointerException();
+        }
+        mSpellCheckerInfo = info;
+        mSpellCheckerSessionListenerImpl = new SpellCheckerSessionListenerImpl(this);
+        mInternalListener = new InternalListener(mSpellCheckerSessionListenerImpl);
+        mTextServicesManager = tsm;
+        mSpellCheckerSessionListener = listener;
+        mExecutor = executor;
+
+        mGuard.open("finishSession");
+    }
+
+    /**
+     * @return true if the connection to a text service of this session is disconnected and not
+     * alive.
+     */
+    public boolean isSessionDisconnected() {
+        return mSpellCheckerSessionListenerImpl.isDisconnected();
+    }
+
+    /**
+     * Get the spell checker service info this spell checker session has.
+     * @return SpellCheckerInfo for the specified locale.
+     */
+    public SpellCheckerInfo getSpellChecker() {
+        return mSpellCheckerInfo;
+    }
+
+    /**
+     * Cancel pending and running spell check tasks
+     */
+    public void cancel() {
+        mSpellCheckerSessionListenerImpl.cancel();
+    }
+
+    /**
+     * Finish this session and allow TextServicesManagerService to disconnect the bound spell
+     * checker.
+     */
+    public void close() {
+        mGuard.close();
+        mSpellCheckerSessionListenerImpl.close();
+        mTextServicesManager.finishSpellCheckerService(mSpellCheckerSessionListenerImpl);
+    }
+
+    /**
+     * Get suggestions from the specified sentences
+     * @param textInfos an array of text metadata for a spell checker
+     * @param suggestionsLimit the maximum number of suggestions that will be returned
+     */
+    public void getSentenceSuggestions(TextInfo[] textInfos, int suggestionsLimit) {
+        final InputMethodManager imm = mTextServicesManager.getInputMethodManager();
+        if (imm != null && imm.isInputMethodSuppressingSpellChecker()) {
+            handleOnGetSentenceSuggestionsMultiple(new SentenceSuggestionsInfo[0]);
+            return;
+        }
+        mSpellCheckerSessionListenerImpl.getSentenceSuggestionsMultiple(
+                textInfos, suggestionsLimit);
+    }
+
+    /**
+     * Get candidate strings for a substring of the specified text.
+     * @param textInfo text metadata for a spell checker
+     * @param suggestionsLimit the maximum number of suggestions that will be returned
+     * @deprecated use {@link SpellCheckerSession#getSentenceSuggestions(TextInfo[], int)} instead
+     */
+    @Deprecated
+    public void getSuggestions(TextInfo textInfo, int suggestionsLimit) {
+        getSuggestions(new TextInfo[] {textInfo}, suggestionsLimit, false);
+    }
+
+    /**
+     * A batch process of getSuggestions
+     * @param textInfos an array of text metadata for a spell checker
+     * @param suggestionsLimit the maximum number of suggestions that will be returned
+     * @param sequentialWords true if textInfos can be treated as sequential words.
+     * @deprecated use {@link SpellCheckerSession#getSentenceSuggestions(TextInfo[], int)} instead
+     */
+    @Deprecated
+    public void getSuggestions(
+            TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) {
+        if (DBG) {
+            Log.w(TAG, "getSuggestions from " + mSpellCheckerInfo.getId());
+        }
+        final InputMethodManager imm = mTextServicesManager.getInputMethodManager();
+        if (imm != null && imm.isInputMethodSuppressingSpellChecker()) {
+            handleOnGetSuggestionsMultiple(new SuggestionsInfo[0]);
+            return;
+        }
+        mSpellCheckerSessionListenerImpl.getSuggestionsMultiple(
+                textInfos, suggestionsLimit, sequentialWords);
+    }
+
+    void handleOnGetSuggestionsMultiple(SuggestionsInfo[] suggestionsInfos) {
+        mExecutor.execute(() -> mSpellCheckerSessionListener.onGetSuggestions(suggestionsInfos));
+    }
+
+    void handleOnGetSentenceSuggestionsMultiple(SentenceSuggestionsInfo[] suggestionsInfos) {
+        mExecutor.execute(() ->
+                mSpellCheckerSessionListener.onGetSentenceSuggestions(suggestionsInfos));
+    }
+
+    private static final class SpellCheckerSessionListenerImpl
+            extends ISpellCheckerSessionListener.Stub {
+        private static final int TASK_CANCEL = 1;
+        private static final int TASK_GET_SUGGESTIONS_MULTIPLE = 2;
+        private static final int TASK_CLOSE = 3;
+        private static final int TASK_GET_SUGGESTIONS_MULTIPLE_FOR_SENTENCE = 4;
+        private static String taskToString(int task) {
+            switch (task) {
+                case TASK_CANCEL:
+                    return "TASK_CANCEL";
+                case TASK_GET_SUGGESTIONS_MULTIPLE:
+                    return "TASK_GET_SUGGESTIONS_MULTIPLE";
+                case TASK_CLOSE:
+                    return "TASK_CLOSE";
+                case TASK_GET_SUGGESTIONS_MULTIPLE_FOR_SENTENCE:
+                    return "TASK_GET_SUGGESTIONS_MULTIPLE_FOR_SENTENCE";
+                default:
+                    return "Unexpected task=" + task;
+            }
+        }
+
+        private final Queue<SpellCheckerParams> mPendingTasks = new LinkedList<>();
+        @GuardedBy("SpellCheckerSessionListenerImpl.this")
+        private SpellCheckerSession mSpellCheckerSession;
+
+        private static final int STATE_WAIT_CONNECTION = 0;
+        private static final int STATE_CONNECTED = 1;
+        private static final int STATE_CLOSED_AFTER_CONNECTION = 2;
+        private static final int STATE_CLOSED_BEFORE_CONNECTION = 3;
+        private static String stateToString(int state) {
+            switch (state) {
+                case STATE_WAIT_CONNECTION: return "STATE_WAIT_CONNECTION";
+                case STATE_CONNECTED: return "STATE_CONNECTED";
+                case STATE_CLOSED_AFTER_CONNECTION: return "STATE_CLOSED_AFTER_CONNECTION";
+                case STATE_CLOSED_BEFORE_CONNECTION: return "STATE_CLOSED_BEFORE_CONNECTION";
+                default: return "Unexpected state=" + state;
+            }
+        }
+        private int mState = STATE_WAIT_CONNECTION;
+
+        private ISpellCheckerSession mISpellCheckerSession;
+        private HandlerThread mThread;
+        private Handler mAsyncHandler;
+
+        SpellCheckerSessionListenerImpl(SpellCheckerSession spellCheckerSession) {
+            mSpellCheckerSession = spellCheckerSession;
+        }
+
+        private static class SpellCheckerParams {
+            public final int mWhat;
+            public final TextInfo[] mTextInfos;
+            public final int mSuggestionsLimit;
+            public final boolean mSequentialWords;
+            public ISpellCheckerSession mSession;
+            public SpellCheckerParams(int what, TextInfo[] textInfos, int suggestionsLimit,
+                    boolean sequentialWords) {
+                mWhat = what;
+                mTextInfos = textInfos;
+                mSuggestionsLimit = suggestionsLimit;
+                mSequentialWords = sequentialWords;
+            }
+        }
+
+        private void processTask(ISpellCheckerSession session, SpellCheckerParams scp,
+                boolean async) {
+            if (DBG) {
+                synchronized (this) {
+                    Log.d(TAG, "entering processTask:"
+                            + " session.hashCode()=#" + Integer.toHexString(session.hashCode())
+                            + " scp.mWhat=" + taskToString(scp.mWhat) + " async=" + async
+                            + " mAsyncHandler=" + mAsyncHandler
+                            + " mState=" + stateToString(mState));
+                }
+            }
+            if (async || mAsyncHandler == null) {
+                switch (scp.mWhat) {
+                    case TASK_CANCEL:
+                        try {
+                            session.onCancel();
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "Failed to cancel " + e);
+                        }
+                        break;
+                    case TASK_GET_SUGGESTIONS_MULTIPLE:
+                        try {
+                            session.onGetSuggestionsMultiple(scp.mTextInfos,
+                                    scp.mSuggestionsLimit, scp.mSequentialWords);
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "Failed to get suggestions " + e);
+                        }
+                        break;
+                    case TASK_GET_SUGGESTIONS_MULTIPLE_FOR_SENTENCE:
+                        try {
+                            session.onGetSentenceSuggestionsMultiple(
+                                    scp.mTextInfos, scp.mSuggestionsLimit);
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "Failed to get suggestions " + e);
+                        }
+                        break;
+                    case TASK_CLOSE:
+                        try {
+                            session.onClose();
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "Failed to close " + e);
+                        }
+                        break;
+                }
+            } else {
+                // The interface is to a local object, so need to execute it
+                // asynchronously.
+                scp.mSession = session;
+                mAsyncHandler.sendMessage(Message.obtain(mAsyncHandler, 1, scp));
+            }
+
+            if (scp.mWhat == TASK_CLOSE) {
+                // If we are closing, we want to clean up our state now even
+                // if it is pending as an async operation.
+                synchronized (this) {
+                    processCloseLocked();
+                }
+            }
+        }
+
+        @GuardedBy("SpellCheckerSessionListenerImpl.this")
+        private void processCloseLocked() {
+            if (DBG) Log.d(TAG, "entering processCloseLocked:"
+                    + " session" + (mISpellCheckerSession != null ? ".hashCode()=#"
+                            + Integer.toHexString(mISpellCheckerSession.hashCode()) : "=null")
+                    + " mState=" + stateToString(mState));
+            mISpellCheckerSession = null;
+            if (mThread != null) {
+                mThread.quit();
+            }
+            mSpellCheckerSession = null;
+            mPendingTasks.clear();
+            mThread = null;
+            mAsyncHandler = null;
+            switch (mState) {
+                case STATE_WAIT_CONNECTION:
+                    mState = STATE_CLOSED_BEFORE_CONNECTION;
+                    break;
+                case STATE_CONNECTED:
+                    mState = STATE_CLOSED_AFTER_CONNECTION;
+                    break;
+                default:
+                    Log.e(TAG, "processCloseLocked is called unexpectedly. mState=" +
+                            stateToString(mState));
+                    break;
+            }
+        }
+
+        public void onServiceConnected(ISpellCheckerSession session) {
+            synchronized (this) {
+                switch (mState) {
+                    case STATE_WAIT_CONNECTION:
+                        // OK, go ahead.
+                        break;
+                    case STATE_CLOSED_BEFORE_CONNECTION:
+                        // This is possible, and not an error.  The client no longer is interested
+                        // in this connection. OK to ignore.
+                        if (DBG) Log.i(TAG, "ignoring onServiceConnected since the session is"
+                                + " already closed.");
+                        return;
+                    default:
+                        Log.e(TAG, "ignoring onServiceConnected due to unexpected mState="
+                                + stateToString(mState));
+                        return;
+                }
+                if (session == null) {
+                    Log.e(TAG, "ignoring onServiceConnected due to session=null");
+                    return;
+                }
+                mISpellCheckerSession = session;
+                if (session.asBinder() instanceof Binder && mThread == null) {
+                    if (DBG) Log.d(TAG, "starting HandlerThread in onServiceConnected.");
+                    // If this is a local object, we need to do our own threading
+                    // to make sure we handle it asynchronously.
+                    mThread = new HandlerThread("SpellCheckerSession",
+                            Process.THREAD_PRIORITY_BACKGROUND);
+                    mThread.start();
+                    mAsyncHandler = new Handler(mThread.getLooper()) {
+                        @Override public void handleMessage(Message msg) {
+                            SpellCheckerParams scp = (SpellCheckerParams)msg.obj;
+                            processTask(scp.mSession, scp, true);
+                        }
+                    };
+                }
+                mState = STATE_CONNECTED;
+                if (DBG) {
+                    Log.d(TAG, "processed onServiceConnected: mISpellCheckerSession.hashCode()=#"
+                            + Integer.toHexString(mISpellCheckerSession.hashCode())
+                            + " mPendingTasks.size()=" + mPendingTasks.size());
+                }
+                while (!mPendingTasks.isEmpty()) {
+                    processTask(session, mPendingTasks.poll(), false);
+                }
+            }
+        }
+
+        public void cancel() {
+            processOrEnqueueTask(new SpellCheckerParams(TASK_CANCEL, null, 0, false));
+        }
+
+        public void getSuggestionsMultiple(
+                TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) {
+            processOrEnqueueTask(
+                    new SpellCheckerParams(TASK_GET_SUGGESTIONS_MULTIPLE, textInfos,
+                            suggestionsLimit, sequentialWords));
+        }
+
+        public void getSentenceSuggestionsMultiple(TextInfo[] textInfos, int suggestionsLimit) {
+            processOrEnqueueTask(
+                    new SpellCheckerParams(TASK_GET_SUGGESTIONS_MULTIPLE_FOR_SENTENCE,
+                            textInfos, suggestionsLimit, false));
+        }
+
+        public void close() {
+            processOrEnqueueTask(new SpellCheckerParams(TASK_CLOSE, null, 0, false));
+        }
+
+        public boolean isDisconnected() {
+            synchronized (this) {
+                return mState != STATE_CONNECTED;
+            }
+        }
+
+        private void processOrEnqueueTask(SpellCheckerParams scp) {
+            ISpellCheckerSession session;
+            synchronized (this) {
+                if (scp.mWhat == TASK_CLOSE && (mState == STATE_CLOSED_AFTER_CONNECTION
+                        || mState == STATE_CLOSED_BEFORE_CONNECTION)) {
+                    // It is OK to call SpellCheckerSession#close() multiple times.
+                    // Don't output confusing/misleading warning messages.
+                    return;
+                }
+                if (mState != STATE_WAIT_CONNECTION && mState != STATE_CONNECTED) {
+                    Log.e(TAG, "ignoring processOrEnqueueTask due to unexpected mState="
+                            + stateToString(mState)
+                            + " scp.mWhat=" + taskToString(scp.mWhat));
+                    return;
+                }
+
+                if (mState == STATE_WAIT_CONNECTION) {
+                    // If we are still waiting for the connection. Need to pay special attention.
+                    if (scp.mWhat == TASK_CLOSE) {
+                        processCloseLocked();
+                        return;
+                    }
+                    // Enqueue the task to task queue.
+                    SpellCheckerParams closeTask = null;
+                    if (scp.mWhat == TASK_CANCEL) {
+                        if (DBG) Log.d(TAG, "canceling pending tasks in processOrEnqueueTask.");
+                        while (!mPendingTasks.isEmpty()) {
+                            final SpellCheckerParams tmp = mPendingTasks.poll();
+                            if (tmp.mWhat == TASK_CLOSE) {
+                                // Only one close task should be processed, while we need to remove
+                                // all close tasks from the queue
+                                closeTask = tmp;
+                            }
+                        }
+                    }
+                    mPendingTasks.offer(scp);
+                    if (closeTask != null) {
+                        mPendingTasks.offer(closeTask);
+                    }
+                    if (DBG) Log.d(TAG, "queueing tasks in processOrEnqueueTask since the"
+                            + " connection is not established."
+                            + " mPendingTasks.size()=" + mPendingTasks.size());
+                    return;
+                }
+
+                session = mISpellCheckerSession;
+            }
+            // session must never be null here.
+            processTask(session, scp, false);
+        }
+
+        @BinderThread
+        @Override
+        public void onGetSuggestions(SuggestionsInfo[] results) {
+            SpellCheckerSession session = getSpellCheckerSession();
+            if (session != null) {
+                // Lock should not be held when calling callback, in order to avoid deadlock.
+                session.handleOnGetSuggestionsMultiple(results);
+            }
+        }
+
+        @BinderThread
+        @Override
+        public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) {
+            SpellCheckerSession session = getSpellCheckerSession();
+            if (session != null) {
+                // Lock should not be held when calling callback, in order to avoid deadlock.
+                session.handleOnGetSentenceSuggestionsMultiple(results);
+            }
+        }
+
+        @Nullable
+        private SpellCheckerSession getSpellCheckerSession() {
+            synchronized (SpellCheckerSessionListenerImpl.this) {
+                return mSpellCheckerSession;
+            }
+        }
+    }
+
+    /** Parameters used to create a {@link SpellCheckerSession}. */
+    public static class SpellCheckerSessionParams {
+        @Nullable
+        private final Locale mLocale;
+        private final boolean mShouldReferToSpellCheckerLanguageSettings;
+        private final @SuggestionsInfo.ResultAttrs int mSupportedAttributes;
+        private final Bundle mExtras;
+
+        private SpellCheckerSessionParams(Locale locale,
+                boolean referToSpellCheckerLanguageSettings, int supportedAttributes,
+                Bundle extras) {
+            mLocale = locale;
+            mShouldReferToSpellCheckerLanguageSettings = referToSpellCheckerLanguageSettings;
+            mSupportedAttributes = supportedAttributes;
+            mExtras = extras;
+        }
+
+        /**
+         * Returns the locale in which the spell checker should operate.
+         *
+         * @see android.service.textservice.SpellCheckerService.Session#getLocale()
+         */
+        @SuppressLint("UseIcu")
+        @Nullable
+        public Locale getLocale() {
+            return mLocale;
+        }
+
+        /**
+         * Returns true if the user's spell checker language settings should be used to determine
+         * the spell checker locale.
+         */
+        public boolean shouldReferToSpellCheckerLanguageSettings() {
+            return mShouldReferToSpellCheckerLanguageSettings;
+        }
+
+        /**
+         * Returns a bitmask of {@link SuggestionsInfo} attributes that the spell checker can set
+         * in {@link SuggestionsInfo} it returns.
+         *
+         * @see android.service.textservice.SpellCheckerService.Session#getSupportedAttributes()
+         */
+        public @SuggestionsInfo.ResultAttrs int getSupportedAttributes() {
+            return mSupportedAttributes;
+        }
+
+        /**
+         * Returns a bundle containing extra parameters for the spell checker.
+         *
+         * <p>This bundle can be used to pass implementation-specific parameters to the
+         * {@link android.service.textservice.SpellCheckerService} implementation.
+         *
+         * @see android.service.textservice.SpellCheckerService.Session#getBundle()
+         */
+        @NonNull
+        public Bundle getExtras() {
+            return mExtras;
+        }
+
+        /** Builder of {@link SpellCheckerSessionParams}. */
+        public static final class Builder {
+            @Nullable
+            private Locale mLocale;
+            private boolean mShouldReferToSpellCheckerLanguageSettings = false;
+            private @SuggestionsInfo.ResultAttrs int mSupportedAttributes = 0;
+            private Bundle mExtras = Bundle.EMPTY;
+
+            /** Constructs a {@code Builder}. */
+            public Builder() {
+            }
+
+            /**
+             * Returns constructed {@link SpellCheckerSession} instance.
+             *
+             * <p>Before calling this method, either {@link #setLocale(Locale)} should be called
+             * with a non-null locale or
+             * {@link #setShouldReferToSpellCheckerLanguageSettings(boolean)} should be called with
+             * {@code true}.
+             */
+            @NonNull
+            public SpellCheckerSessionParams build() {
+                if (mLocale == null && !mShouldReferToSpellCheckerLanguageSettings) {
+                    throw new IllegalArgumentException("mLocale should not be null if "
+                            + " mShouldReferToSpellCheckerLanguageSettings is false.");
+                }
+                return new SpellCheckerSessionParams(mLocale,
+                        mShouldReferToSpellCheckerLanguageSettings, mSupportedAttributes, mExtras);
+            }
+
+            /**
+             * Sets the locale in which the spell checker should operate.
+             *
+             * @see android.service.textservice.SpellCheckerService.Session#getLocale()
+             */
+            @NonNull
+            public Builder setLocale(@SuppressLint("UseIcu") @Nullable Locale locale) {
+                mLocale = locale;
+                return this;
+            }
+
+            /**
+             * Sets whether or not the user's spell checker language settings should be used to
+             * determine spell checker locale.
+             *
+             * <p>If {@code shouldReferToSpellCheckerLanguageSettings} is true, the exact way of
+             * determining spell checker locale differs based on {@code locale} specified in
+             * {@link #setLocale(Locale)}.
+             * If {@code shouldReferToSpellCheckerLanguageSettings} is true and {@code locale} is
+             * null, the locale specified in Settings will be used. If
+             * {@code shouldReferToSpellCheckerLanguageSettings} is true and {@code locale} is not
+             * null, {@link SpellCheckerSession} can be created only when the locale specified in
+             * Settings is the same as {@code locale}. Exceptionally, if
+             * {@code shouldReferToSpellCheckerLanguageSettings} is true and {@code locale} is
+             * language only (e.g. "en"), the specified locale in Settings (e.g. "en_US") will be
+             * used.
+             *
+             * @see #setLocale(Locale)
+             */
+            @NonNull
+            public Builder setShouldReferToSpellCheckerLanguageSettings(
+                    boolean shouldReferToSpellCheckerLanguageSettings) {
+                mShouldReferToSpellCheckerLanguageSettings =
+                        shouldReferToSpellCheckerLanguageSettings;
+                return this;
+            }
+
+            /**
+             * Sets a bitmask of {@link SuggestionsInfo} attributes that the spell checker can set
+             * in {@link SuggestionsInfo} it returns.
+             *
+             * @see android.service.textservice.SpellCheckerService.Session#getSupportedAttributes()
+             */
+            @NonNull
+            public Builder setSupportedAttributes(
+                    @SuggestionsInfo.ResultAttrs int supportedAttributes) {
+                mSupportedAttributes = supportedAttributes;
+                return this;
+            }
+
+            /**
+             * Sets a bundle containing extra parameters for the spell checker.
+             *
+             * <p>This bundle can be used to pass implementation-specific parameters to the
+             * {@link android.service.textservice.SpellCheckerService} implementation.
+             *
+             * @see android.service.textservice.SpellCheckerService.Session#getBundle()
+             */
+            @NonNull
+            public Builder setExtras(@NonNull Bundle extras) {
+                mExtras = extras;
+                return this;
+            }
+        }
+    }
+
+    /**
+     * Callback for getting results from text services
+     */
+    public interface SpellCheckerSessionListener {
+        /**
+         * Callback for {@link SpellCheckerSession#getSuggestions(TextInfo, int)}
+         * and {@link SpellCheckerSession#getSuggestions(TextInfo[], int, boolean)}
+         * @param results an array of {@link SuggestionsInfo}s.
+         * These results are suggestions for {@link TextInfo}s queried by
+         * {@link SpellCheckerSession#getSuggestions(TextInfo, int)} or
+         * {@link SpellCheckerSession#getSuggestions(TextInfo[], int, boolean)}
+         */
+        public void onGetSuggestions(SuggestionsInfo[] results);
+        /**
+         * Callback for {@link SpellCheckerSession#getSentenceSuggestions(TextInfo[], int)}
+         * @param results an array of {@link SentenceSuggestionsInfo}s.
+         * These results are suggestions for {@link TextInfo}s
+         * queried by {@link SpellCheckerSession#getSentenceSuggestions(TextInfo[], int)}.
+         */
+        public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results);
+    }
+
+    private static final class InternalListener extends ITextServicesSessionListener.Stub {
+        private final SpellCheckerSessionListenerImpl mParentSpellCheckerSessionListenerImpl;
+
+        public InternalListener(SpellCheckerSessionListenerImpl spellCheckerSessionListenerImpl) {
+            mParentSpellCheckerSessionListenerImpl = spellCheckerSessionListenerImpl;
+        }
+
+        @Override
+        public void onServiceConnected(ISpellCheckerSession session) {
+            mParentSpellCheckerSessionListenerImpl.onServiceConnected(session);
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            // Note that mGuard will be null if the constructor threw.
+            if (mGuard != null) {
+                mGuard.warnIfOpen();
+                close();
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public ITextServicesSessionListener getTextServicesSessionListener() {
+        return mInternalListener;
+    }
+
+    /**
+     * @hide
+     */
+    public ISpellCheckerSessionListener getSpellCheckerSessionListener() {
+        return mSpellCheckerSessionListenerImpl;
+    }
+}
diff --git a/android/view/textservice/SpellCheckerSubtype.java b/android/view/textservice/SpellCheckerSubtype.java
new file mode 100644
index 0000000..38a140f
--- /dev/null
+++ b/android/view/textservice/SpellCheckerSubtype.java
@@ -0,0 +1,323 @@
+/*
+ * 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.view.textservice;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.inputmethod.SubtypeLocaleUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * This class is used to specify meta information of a subtype contained in a spell checker.
+ * Subtype can describe locale (e.g. en_US, fr_FR...) used for settings.
+ *
+ * @see SpellCheckerInfo
+ *
+ * @attr ref android.R.styleable#SpellChecker_Subtype_label
+ * @attr ref android.R.styleable#SpellChecker_Subtype_languageTag
+ * @attr ref android.R.styleable#SpellChecker_Subtype_subtypeLocale
+ * @attr ref android.R.styleable#SpellChecker_Subtype_subtypeExtraValue
+ * @attr ref android.R.styleable#SpellChecker_Subtype_subtypeId
+ */
+public final class SpellCheckerSubtype implements Parcelable {
+    private static final String TAG = SpellCheckerSubtype.class.getSimpleName();
+    private static final String EXTRA_VALUE_PAIR_SEPARATOR = ",";
+    private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "=";
+    /**
+     * @hide
+     */
+    @TestApi
+    public static final int SUBTYPE_ID_NONE = 0;
+    private static final String SUBTYPE_LANGUAGE_TAG_NONE = "";
+
+    private final int mSubtypeId;
+    private final int mSubtypeHashCode;
+    private final int mSubtypeNameResId;
+    private final String mSubtypeLocale;
+    private final String mSubtypeLanguageTag;
+    private final String mSubtypeExtraValue;
+    private HashMap<String, String> mExtraValueHashMapCache;
+
+    /**
+     * Constructor.
+     *
+     * <p>There is no public API that requires developers to instantiate custom
+     * {@link SpellCheckerSubtype} object.  Hence so far there is no need to make this constructor
+     * available in public API.</p>
+     *
+     * @param nameId The name of the subtype
+     * @param locale The locale supported by the subtype
+     * @param languageTag The BCP-47 Language Tag associated with this subtype.
+     * @param extraValue The extra value of the subtype
+     * @param subtypeId The subtype ID that is supposed to be stable during package update.
+     *
+     * @hide
+     */
+    public SpellCheckerSubtype(int nameId, String locale, String languageTag, String extraValue,
+            int subtypeId) {
+        mSubtypeNameResId = nameId;
+        mSubtypeLocale = locale != null ? locale : "";
+        mSubtypeLanguageTag = languageTag != null ? languageTag : SUBTYPE_LANGUAGE_TAG_NONE;
+        mSubtypeExtraValue = extraValue != null ? extraValue : "";
+        mSubtypeId = subtypeId;
+        mSubtypeHashCode = mSubtypeId != SUBTYPE_ID_NONE ?
+                mSubtypeId : hashCodeInternal(mSubtypeLocale, mSubtypeExtraValue);
+    }
+
+    /**
+     * Constructor.
+     * @param nameId The name of the subtype
+     * @param locale The locale supported by the subtype
+     * @param extraValue The extra value of the subtype
+     *
+     * @deprecated There is no public API that requires developers to directly instantiate custom
+     * {@link SpellCheckerSubtype} objects right now.  Hence only the system is expected to be able
+     * to instantiate {@link SpellCheckerSubtype} object.
+     */
+    @Deprecated
+    public SpellCheckerSubtype(int nameId, String locale, String extraValue) {
+        this(nameId, locale, SUBTYPE_LANGUAGE_TAG_NONE, extraValue, SUBTYPE_ID_NONE);
+    }
+
+    SpellCheckerSubtype(Parcel source) {
+        String s;
+        mSubtypeNameResId = source.readInt();
+        s = source.readString();
+        mSubtypeLocale = s != null ? s : "";
+        s = source.readString();
+        mSubtypeLanguageTag = s != null ? s : "";
+        s = source.readString();
+        mSubtypeExtraValue = s != null ? s : "";
+        mSubtypeId = source.readInt();
+        mSubtypeHashCode = mSubtypeId != SUBTYPE_ID_NONE ?
+                mSubtypeId : hashCodeInternal(mSubtypeLocale, mSubtypeExtraValue);
+    }
+
+    /**
+     * @return the name of the subtype
+     */
+    public int getNameResId() {
+        return mSubtypeNameResId;
+    }
+
+    /**
+     * @return the locale of the subtype
+     *
+     * @deprecated Use {@link #getLanguageTag()} instead.
+     */
+    @Deprecated
+    @NonNull
+    public String getLocale() {
+        return mSubtypeLocale;
+    }
+
+    /**
+     * @return the BCP-47 Language Tag of the subtype.  Returns an empty string when no Language Tag
+     * is specified.
+     *
+     * @see Locale#forLanguageTag(String)
+     */
+    @NonNull
+    public String getLanguageTag() {
+        return mSubtypeLanguageTag;
+    }
+
+    /**
+     * @return the extra value of the subtype
+     */
+    public String getExtraValue() {
+        return mSubtypeExtraValue;
+    }
+
+    private HashMap<String, String> getExtraValueHashMap() {
+        if (mExtraValueHashMapCache == null) {
+            mExtraValueHashMapCache = new HashMap<String, String>();
+            final String[] pairs = mSubtypeExtraValue.split(EXTRA_VALUE_PAIR_SEPARATOR);
+            final int N = pairs.length;
+            for (int i = 0; i < N; ++i) {
+                final String[] pair = pairs[i].split(EXTRA_VALUE_KEY_VALUE_SEPARATOR);
+                if (pair.length == 1) {
+                    mExtraValueHashMapCache.put(pair[0], null);
+                } else if (pair.length > 1) {
+                    if (pair.length > 2) {
+                        Slog.w(TAG, "ExtraValue has two or more '='s");
+                    }
+                    mExtraValueHashMapCache.put(pair[0], pair[1]);
+                }
+            }
+        }
+        return mExtraValueHashMapCache;
+    }
+
+    /**
+     * The string of ExtraValue in subtype should be defined as follows:
+     * example: key0,key1=value1,key2,key3,key4=value4
+     * @param key the key of extra value
+     * @return the subtype contains specified the extra value
+     */
+    public boolean containsExtraValueKey(String key) {
+        return getExtraValueHashMap().containsKey(key);
+    }
+
+    /**
+     * The string of ExtraValue in subtype should be defined as follows:
+     * example: key0,key1=value1,key2,key3,key4=value4
+     * @param key the key of extra value
+     * @return the value of the specified key
+     */
+    public String getExtraValueOf(String key) {
+        return getExtraValueHashMap().get(key);
+    }
+
+    @Override
+    public int hashCode() {
+        return mSubtypeHashCode;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (o instanceof SpellCheckerSubtype) {
+            SpellCheckerSubtype subtype = (SpellCheckerSubtype) o;
+            if (subtype.mSubtypeId != SUBTYPE_ID_NONE || mSubtypeId != SUBTYPE_ID_NONE) {
+                return (subtype.hashCode() == hashCode());
+            }
+            return (subtype.hashCode() == hashCode())
+                    && (subtype.getNameResId() == getNameResId())
+                    && (subtype.getLocale().equals(getLocale()))
+                    && (subtype.getLanguageTag().equals(getLanguageTag()))
+                    && (subtype.getExtraValue().equals(getExtraValue()));
+        }
+        return false;
+    }
+
+    /**
+     * @return {@link Locale} constructed from {@link #getLanguageTag()}. If the Language Tag is not
+     * specified, then try to construct from {@link #getLocale()}
+     *
+     * <p>TODO: Consider to make this a public API, or move this to support lib.</p>
+     * @hide
+     */
+    @Nullable
+    public Locale getLocaleObject() {
+        if (!TextUtils.isEmpty(mSubtypeLanguageTag)) {
+            return Locale.forLanguageTag(mSubtypeLanguageTag);
+        }
+        return SubtypeLocaleUtils.constructLocaleFromString(mSubtypeLocale);
+    }
+
+    /**
+     * @param context Context will be used for getting Locale and PackageManager.
+     * @param packageName The package name of the spell checker
+     * @param appInfo The application info of the spell checker
+     * @return a display name for this subtype. The string resource of the label (mSubtypeNameResId)
+     * can have only one %s in it. If there is, the %s part will be replaced with the locale's
+     * display name by the formatter. If there is not, this method simply returns the string
+     * specified by mSubtypeNameResId. If mSubtypeNameResId is not specified (== 0), it's up to the
+     * framework to generate an appropriate display name.
+     */
+    public CharSequence getDisplayName(
+            Context context, String packageName, ApplicationInfo appInfo) {
+        final Locale locale = getLocaleObject();
+        final String localeStr = locale != null ? locale.getDisplayName() : mSubtypeLocale;
+        if (mSubtypeNameResId == 0) {
+            return localeStr;
+        }
+        final CharSequence subtypeName = context.getPackageManager().getText(
+                packageName, mSubtypeNameResId, appInfo);
+        if (!TextUtils.isEmpty(subtypeName)) {
+            return String.format(subtypeName.toString(), localeStr);
+        } else {
+            return localeStr;
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int parcelableFlags) {
+        dest.writeInt(mSubtypeNameResId);
+        dest.writeString(mSubtypeLocale);
+        dest.writeString(mSubtypeLanguageTag);
+        dest.writeString(mSubtypeExtraValue);
+        dest.writeInt(mSubtypeId);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<SpellCheckerSubtype> CREATOR
+            = new Parcelable.Creator<SpellCheckerSubtype>() {
+        @Override
+        public SpellCheckerSubtype createFromParcel(Parcel source) {
+            return new SpellCheckerSubtype(source);
+        }
+
+        @Override
+        public SpellCheckerSubtype[] newArray(int size) {
+            return new SpellCheckerSubtype[size];
+        }
+    };
+
+    private static int hashCodeInternal(String locale, String extraValue) {
+        return Arrays.hashCode(new Object[] {locale, extraValue});
+    }
+
+    /**
+     * Sort the list of subtypes
+     * @param context Context will be used for getting localized strings
+     * @param flags Flags for the sort order
+     * @param sci SpellCheckerInfo of which subtypes are subject to be sorted
+     * @param subtypeList List which will be sorted
+     * @return Sorted list of subtypes
+     * @hide
+     */
+    public static List<SpellCheckerSubtype> sort(Context context, int flags, SpellCheckerInfo sci,
+            List<SpellCheckerSubtype> subtypeList) {
+        if (sci == null) return subtypeList;
+        final HashSet<SpellCheckerSubtype> subtypesSet = new HashSet<SpellCheckerSubtype>(
+                subtypeList);
+        final ArrayList<SpellCheckerSubtype> sortedList = new ArrayList<SpellCheckerSubtype>();
+        int N = sci.getSubtypeCount();
+        for (int i = 0; i < N; ++i) {
+            SpellCheckerSubtype subtype = sci.getSubtypeAt(i);
+            if (subtypesSet.contains(subtype)) {
+                sortedList.add(subtype);
+                subtypesSet.remove(subtype);
+            }
+        }
+        // If subtypes in subtypesSet remain, that means these subtypes are not
+        // contained in sci, so the remaining subtypes will be appended.
+        for (SpellCheckerSubtype subtype: subtypesSet) {
+            sortedList.add(subtype);
+        }
+        return sortedList;
+    }
+}
diff --git a/android/view/textservice/SuggestionsInfo.java b/android/view/textservice/SuggestionsInfo.java
new file mode 100644
index 0000000..775a6bd
--- /dev/null
+++ b/android/view/textservice/SuggestionsInfo.java
@@ -0,0 +1,223 @@
+/*
+ * 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.view.textservice;
+
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class contains a metadata of suggestions from the text service
+ */
+public final class SuggestionsInfo implements Parcelable {
+    private static final String[] EMPTY = ArrayUtils.emptyArray(String.class);
+
+    /**
+     * An internal annotation to indicate that one ore more combinations of
+     * <code>RESULT_ATTR_</code> flags are expected.
+     *
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "RESULT_ATTR_" }, value = {
+            RESULT_ATTR_IN_THE_DICTIONARY,
+            RESULT_ATTR_LOOKS_LIKE_TYPO,
+            RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS,
+            RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR,
+            RESULT_ATTR_DONT_SHOW_UI_FOR_SUGGESTIONS,
+    })
+    public @interface ResultAttrs {}
+
+    /**
+     * Flag of the attributes of the suggestions that can be obtained by
+     * {@link #getSuggestionsAttributes}: this tells that the requested word was found
+     * in the dictionary in the text service.
+     */
+    public static final int RESULT_ATTR_IN_THE_DICTIONARY = 0x0001;
+    /**
+     * Flag of the attributes of the suggestions that can be obtained by
+     * {@link #getSuggestionsAttributes}: this tells that the text service thinks the requested
+     * word looks like a typo.
+     */
+    public static final int RESULT_ATTR_LOOKS_LIKE_TYPO = 0x0002;
+    /**
+     * Flag of the attributes of the suggestions that can be obtained by
+     * {@link #getSuggestionsAttributes}: this tells that the text service thinks
+     * the result suggestions include highly recommended ones.
+     */
+    public static final int RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS = 0x0004;
+
+    /**
+     * Flag of the attributes of the suggestions that can be obtained by
+     * {@link #getSuggestionsAttributes}: this tells that the text service thinks the requested
+     * sentence contains a grammar error.
+     */
+    public static final int RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR = 0x0008;
+
+    /**
+     * Flag of the attributes of the suggestions that can be obtained by
+     * {@link #getSuggestionsAttributes}: this tells that the text service has an alternative way to
+     * show UI for the list of correction suggestions to the user. When this flag is set, the
+     * receiver of the result suggestions should mark the erroneous part of the text with a text
+     * signifier (for example, underline), but should not show any UI for the list of correction
+     * suggestions to the user (for example, in a popup window).
+     */
+    public static final int RESULT_ATTR_DONT_SHOW_UI_FOR_SUGGESTIONS = 0x0010;
+
+    private final @ResultAttrs int mSuggestionsAttributes;
+    private final String[] mSuggestions;
+    private final boolean mSuggestionsAvailable;
+    private int mCookie;
+    private int mSequence;
+
+    /**
+     * Constructor.
+     * @param suggestionsAttributes from the text service
+     * @param suggestions from the text service
+     */
+    public SuggestionsInfo(int suggestionsAttributes, String[] suggestions) {
+        this(suggestionsAttributes, suggestions, 0, 0);
+    }
+
+    /**
+     * Constructor.
+     * @param suggestionsAttributes from the text service
+     * @param suggestions from the text service
+     * @param cookie the cookie of the input TextInfo
+     * @param sequence the cookie of the input TextInfo
+     */
+    public SuggestionsInfo(@ResultAttrs int suggestionsAttributes, String[] suggestions, int cookie,
+            int sequence) {
+        if (suggestions == null) {
+            mSuggestions = EMPTY;
+            mSuggestionsAvailable = false;
+        } else {
+            mSuggestions = suggestions;
+            mSuggestionsAvailable = true;
+        }
+        mSuggestionsAttributes = suggestionsAttributes;
+        mCookie = cookie;
+        mSequence = sequence;
+    }
+
+    public SuggestionsInfo(Parcel source) {
+        mSuggestionsAttributes = source.readInt();
+        mSuggestions = source.readStringArray();
+        mCookie = source.readInt();
+        mSequence = source.readInt();
+        mSuggestionsAvailable = source.readInt() == 1;
+    }
+
+    /**
+     * Used to package this object into a {@link Parcel}.
+     *
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mSuggestionsAttributes);
+        dest.writeStringArray(mSuggestions);
+        dest.writeInt(mCookie);
+        dest.writeInt(mSequence);
+        dest.writeInt(mSuggestionsAvailable ? 1 : 0);
+    }
+
+    /**
+     * Set the cookie and the sequence of SuggestionsInfo which are set to TextInfo from a client
+     * application
+     * @param cookie the cookie of an input TextInfo
+     * @param sequence the cookie of an input TextInfo
+     */
+    public void setCookieAndSequence(int cookie, int sequence) {
+        mCookie = cookie;
+        mSequence = sequence;
+    }
+
+    /**
+     * @return the cookie which may be set by a client application
+     */
+    public int getCookie() {
+        return mCookie;
+    }
+
+    /**
+     * @return the sequence which may be set by a client application
+     */
+    public int getSequence() {
+        return mSequence;
+    }
+
+    /**
+     * @return the attributes of suggestions. This includes whether the spell checker has the word
+     * in its dictionary or not and whether the spell checker has confident suggestions for the
+     * word or not.
+     */
+    public @ResultAttrs int getSuggestionsAttributes() {
+        return mSuggestionsAttributes;
+    }
+
+    /**
+     * @return the count of the suggestions. If there's no suggestions at all, this method returns
+     * -1. Even if this method returns 0, it doesn't necessarily mean that there are no suggestions
+     * for the requested word. For instance, the caller could have been asked to limit the maximum
+     * number of suggestions returned.
+     */
+    public int getSuggestionsCount() {
+        if (!mSuggestionsAvailable) {
+            return -1;
+        }
+        return mSuggestions.length;
+    }
+
+    /**
+     * @param i the id of suggestions
+     * @return the suggestion at the specified id
+     */
+    public String getSuggestionAt(int i) {
+        return mSuggestions[i];
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final @android.annotation.NonNull Parcelable.Creator<SuggestionsInfo> CREATOR
+            = new Parcelable.Creator<SuggestionsInfo>() {
+        @Override
+        public SuggestionsInfo createFromParcel(Parcel source) {
+            return new SuggestionsInfo(source);
+        }
+
+        @Override
+        public SuggestionsInfo[] newArray(int size) {
+            return new SuggestionsInfo[size];
+        }
+    };
+
+    /**
+     * Used to make this class parcelable.
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/android/view/textservice/TextInfo.java b/android/view/textservice/TextInfo.java
new file mode 100644
index 0000000..571bdf3
--- /dev/null
+++ b/android/view/textservice/TextInfo.java
@@ -0,0 +1,161 @@
+/*
+ * 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.view.textservice;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.ParcelableSpan;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.text.style.SpellCheckSpan;
+
+/**
+ * This class contains a metadata of the input of TextService
+ */
+public final class TextInfo implements Parcelable {
+    private final CharSequence mCharSequence;
+    private final int mCookie;
+    private final int mSequenceNumber;
+
+    private static final int DEFAULT_COOKIE = 0;
+    private static final int DEFAULT_SEQUENCE_NUMBER = 0;
+
+    /**
+     * Constructor.
+     * @param text the text which will be input to TextService
+     */
+    public TextInfo(String text) {
+        this(text, 0, getStringLengthOrZero(text), DEFAULT_COOKIE, DEFAULT_SEQUENCE_NUMBER);
+    }
+
+    /**
+     * Constructor.
+     * @param text the text which will be input to TextService
+     * @param cookie the cookie for this TextInfo
+     * @param sequenceNumber the sequence number for this TextInfo
+     */
+    public TextInfo(String text, int cookie, int sequenceNumber) {
+        this(text, 0, getStringLengthOrZero(text), cookie, sequenceNumber);
+    }
+
+    private static int getStringLengthOrZero(final String text) {
+        return TextUtils.isEmpty(text) ? 0 : text.length();
+    }
+
+    /**
+     * Constructor.
+     * @param charSequence the text which will be input to TextService. Attached spans that
+     * implement {@link ParcelableSpan} will also be marshaled alongside with the text.
+     * @param start the beginning of the range of text (inclusive).
+     * @param end the end of the range of text (exclusive).
+     * @param cookie the cookie for this TextInfo
+     * @param sequenceNumber the sequence number for this TextInfo
+     */
+    public TextInfo(CharSequence charSequence, int start, int end, int cookie, int sequenceNumber) {
+        if (TextUtils.isEmpty(charSequence)) {
+            throw new IllegalArgumentException("charSequence is empty");
+        }
+        // Create a snapshot of the text including spans in case they are updated outside later.
+        final SpannableStringBuilder spannableString =
+                new SpannableStringBuilder(charSequence, start, end);
+        // SpellCheckSpan is for internal use. We do not want to marshal this for TextService.
+        final SpellCheckSpan[] spans = spannableString.getSpans(0, spannableString.length(),
+                SpellCheckSpan.class);
+        for (int i = 0; i < spans.length; ++i) {
+            spannableString.removeSpan(spans[i]);
+        }
+
+        mCharSequence = spannableString;
+        mCookie = cookie;
+        mSequenceNumber = sequenceNumber;
+    }
+
+    public TextInfo(Parcel source) {
+        mCharSequence = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+        mCookie = source.readInt();
+        mSequenceNumber = source.readInt();
+    }
+
+    /**
+     * Used to package this object into a {@link Parcel}.
+     *
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        TextUtils.writeToParcel(mCharSequence, dest, flags);
+        dest.writeInt(mCookie);
+        dest.writeInt(mSequenceNumber);
+    }
+
+    /**
+     * @return the text which is an input of a text service
+     */
+    public String getText() {
+        if (mCharSequence == null) {
+            return null;
+        }
+        return mCharSequence.toString();
+    }
+
+    /**
+     * @return the charSequence which is an input of a text service. This may have some parcelable
+     * spans.
+     */
+    public CharSequence getCharSequence() {
+        return mCharSequence;
+    }
+
+    /**
+     * @return the cookie of TextInfo
+     */
+    public int getCookie() {
+        return mCookie;
+    }
+
+    /**
+     * @return the sequence of TextInfo
+     */
+    public int getSequence() {
+        return mSequenceNumber;
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final @android.annotation.NonNull Parcelable.Creator<TextInfo> CREATOR
+            = new Parcelable.Creator<TextInfo>() {
+        @Override
+        public TextInfo createFromParcel(Parcel source) {
+            return new TextInfo(source);
+        }
+
+        @Override
+        public TextInfo[] newArray(int size) {
+            return new TextInfo[size];
+        }
+    };
+
+    /**
+     * Used to make this class parcelable.
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/android/view/textservice/TextServicesManager.java b/android/view/textservice/TextServicesManager.java
new file mode 100644
index 0000000..8e1f218
--- /dev/null
+++ b/android/view/textservice/TextServicesManager.java
@@ -0,0 +1,72 @@
+/*
+ * 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.view.textservice;
+
+import android.os.Bundle;
+import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener;
+
+import java.util.Locale;
+
+/**
+ * A stub class of TextServicesManager for Layout-Lib.
+ */
+public final class TextServicesManager {
+    private static final TextServicesManager sInstance = new TextServicesManager();
+    private static final SpellCheckerInfo[] EMPTY_SPELL_CHECKER_INFO = new SpellCheckerInfo[0];
+
+    /**
+     * Retrieve the global TextServicesManager instance, creating it if it doesn't already exist.
+     * @hide
+     */
+    public static TextServicesManager getInstance() {
+        return sInstance;
+    }
+
+    public SpellCheckerSession newSpellCheckerSession(Bundle bundle, Locale locale,
+            SpellCheckerSessionListener listener, boolean referToSpellCheckerLanguageSettings) {
+        return null;
+    }
+
+    /**
+     * @hide
+     */
+    public SpellCheckerInfo[] getEnabledSpellCheckers() {
+        return EMPTY_SPELL_CHECKER_INFO;
+    }
+
+    /**
+     * @hide
+     */
+    public SpellCheckerInfo getCurrentSpellChecker() {
+        return null;
+    }
+
+    /**
+     * @hide
+     */
+    public SpellCheckerSubtype getCurrentSpellCheckerSubtype(
+            boolean allowImplicitlySelectedSubtype) {
+        return null;
+    }
+
+    /**
+     * @hide
+     */
+    public boolean isSpellCheckerEnabled() {
+        return false;
+    }
+}
diff --git a/android/view/translation/TranslationCapability.java b/android/view/translation/TranslationCapability.java
new file mode 100644
index 0000000..65b749a
--- /dev/null
+++ b/android/view/translation/TranslationCapability.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.translation;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Capability class holding information for a pair of {@link TranslationSpec}s.
+ *
+ * <p>Holds information and limitations on how to create a {@link TranslationContext} which can
+ * be used by
+ * {@link TranslationManager#createOnDeviceTranslator(TranslationContext, Executor, Consumer)}.
+ */
+@DataClass(genHiddenConstDefs = true, genToString = true, genConstructor = false)
+public final class TranslationCapability implements Parcelable {
+
+    /**
+     * TODO: fill in javadoc
+     */
+    public static final @ModelState int STATE_AVAILABLE_TO_DOWNLOAD = 1;
+    /**
+     * TODO: fill in javadoc
+     */
+    public static final @ModelState int STATE_DOWNLOADING = 2;
+    /**
+     * TODO: fill in javadoc
+     */
+    public static final @ModelState int STATE_ON_DEVICE = 3;
+    /**
+     * The translation service does not support translation between the source and target specs.
+     *
+     * <p>Note: This state is not returned from calling
+     * {@link TranslationManager#getOnDeviceTranslationCapabilities}. This state will only appear as
+     * part of capability updates from
+     * {@link TranslationManager#addOnDeviceTranslationCapabilityUpdateListener} if existing support
+     * was dropped.</p>
+     */
+    public static final @ModelState int STATE_NOT_AVAILABLE = 4;
+    /**
+     * The translation between the source and target specs were removed from the system, but is
+     * still available to be downloaded again.
+     *
+     * @hide
+     */
+    public static final @ModelState int STATE_REMOVED_AND_AVAILABLE = 1000;
+
+    /**
+     * The state of translation readiness between {@code mSourceSpec} and {@code mTargetSpec}.
+     */
+    private final @ModelState int mState;
+
+    /**
+     * {@link TranslationSpec} describing the source data specs for this
+     * capability.
+     */
+    @NonNull
+    private final TranslationSpec mSourceSpec;
+
+    /**
+     * {@link TranslationSpec} describing the target data specs for this
+     * capability.
+     */
+    @NonNull
+    private final TranslationSpec mTargetSpec;
+
+    /**
+     * Whether ui translation for the source-target {@link TranslationSpec}s is enabled.
+     *
+     * <p>Translation service will still support translation requests for this capability.</p>
+     */
+    private final boolean mUiTranslationEnabled;
+
+    /**
+     * Translation flags for settings that are supported by the
+     * {@link android.service.translation.TranslationService} between the {@link TranslationSpec}s
+     * provided in this capability.
+     */
+    private final @TranslationContext.TranslationFlag int mSupportedTranslationFlags;
+
+    /**
+     * Constructor for creating a {@link TranslationCapability}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public TranslationCapability(@ModelState int state, @NonNull TranslationSpec sourceSpec,
+            @NonNull TranslationSpec targetSpec, boolean uiTranslationEnabled,
+            @TranslationContext.TranslationFlag int supportedTranslationFlags) {
+        Objects.requireNonNull(sourceSpec, "sourceSpec should not be null");
+        Objects.requireNonNull(targetSpec, "targetSpec should not be null");
+
+        this.mState = state;
+        this.mSourceSpec = sourceSpec;
+        this.mTargetSpec = targetSpec;
+        this.mUiTranslationEnabled = uiTranslationEnabled;
+        this.mSupportedTranslationFlags = supportedTranslationFlags;
+    }
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/translation/TranslationCapability.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /** @hide */
+    @IntDef(prefix = "STATE_", value = {
+        STATE_AVAILABLE_TO_DOWNLOAD,
+        STATE_DOWNLOADING,
+        STATE_ON_DEVICE,
+        STATE_NOT_AVAILABLE,
+        STATE_REMOVED_AND_AVAILABLE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface ModelState {}
+
+    /** @hide */
+    @DataClass.Generated.Member
+    public static String modelStateToString(@ModelState int value) {
+        switch (value) {
+            case STATE_AVAILABLE_TO_DOWNLOAD:
+                    return "STATE_AVAILABLE_TO_DOWNLOAD";
+            case STATE_DOWNLOADING:
+                    return "STATE_DOWNLOADING";
+            case STATE_ON_DEVICE:
+                    return "STATE_ON_DEVICE";
+            case STATE_NOT_AVAILABLE:
+                    return "STATE_NOT_AVAILABLE";
+            case STATE_REMOVED_AND_AVAILABLE:
+                    return "STATE_REMOVED_AND_AVAILABLE";
+            default: return Integer.toHexString(value);
+        }
+    }
+
+    /**
+     * The state of translation readiness between {@code mSourceSpec} and {@code mTargetSpec}.
+     */
+    @DataClass.Generated.Member
+    public @ModelState int getState() {
+        return mState;
+    }
+
+    /**
+     * {@link TranslationSpec} describing the source data specs for this
+     * capability.
+     */
+    @DataClass.Generated.Member
+    public @NonNull TranslationSpec getSourceSpec() {
+        return mSourceSpec;
+    }
+
+    /**
+     * {@link TranslationSpec} describing the target data specs for this
+     * capability.
+     */
+    @DataClass.Generated.Member
+    public @NonNull TranslationSpec getTargetSpec() {
+        return mTargetSpec;
+    }
+
+    /**
+     * Whether ui translation for the source-target {@link TranslationSpec}s is enabled.
+     *
+     * <p>Translation service will still support translation requests for this capability.</p>
+     */
+    @DataClass.Generated.Member
+    public boolean isUiTranslationEnabled() {
+        return mUiTranslationEnabled;
+    }
+
+    /**
+     * Translation flags for settings that are supported by the
+     * {@link android.service.translation.TranslationService} between the {@link TranslationSpec}s
+     * provided in this capability.
+     */
+    @DataClass.Generated.Member
+    public @TranslationContext.TranslationFlag int getSupportedTranslationFlags() {
+        return mSupportedTranslationFlags;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "TranslationCapability { " +
+                "state = " + modelStateToString(mState) + ", " +
+                "sourceSpec = " + mSourceSpec + ", " +
+                "targetSpec = " + mTargetSpec + ", " +
+                "uiTranslationEnabled = " + mUiTranslationEnabled + ", " +
+                "supportedTranslationFlags = " + mSupportedTranslationFlags +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mUiTranslationEnabled) flg |= 0x8;
+        dest.writeByte(flg);
+        dest.writeInt(mState);
+        dest.writeTypedObject(mSourceSpec, flags);
+        dest.writeTypedObject(mTargetSpec, flags);
+        dest.writeInt(mSupportedTranslationFlags);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ TranslationCapability(@NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        boolean uiTranslationEnabled = (flg & 0x8) != 0;
+        int state = in.readInt();
+        TranslationSpec sourceSpec = (TranslationSpec) in.readTypedObject(TranslationSpec.CREATOR);
+        TranslationSpec targetSpec = (TranslationSpec) in.readTypedObject(TranslationSpec.CREATOR);
+        int supportedTranslationFlags = in.readInt();
+
+        this.mState = state;
+
+        if (!(mState == STATE_AVAILABLE_TO_DOWNLOAD)
+                && !(mState == STATE_DOWNLOADING)
+                && !(mState == STATE_ON_DEVICE)
+                && !(mState == STATE_NOT_AVAILABLE)
+                && !(mState == STATE_REMOVED_AND_AVAILABLE)) {
+            throw new java.lang.IllegalArgumentException(
+                    "state was " + mState + " but must be one of: "
+                            + "STATE_AVAILABLE_TO_DOWNLOAD(" + STATE_AVAILABLE_TO_DOWNLOAD + "), "
+                            + "STATE_DOWNLOADING(" + STATE_DOWNLOADING + "), "
+                            + "STATE_ON_DEVICE(" + STATE_ON_DEVICE + "), "
+                            + "STATE_NOT_AVAILABLE(" + STATE_NOT_AVAILABLE + "), "
+                            + "STATE_REMOVED_AND_AVAILABLE(" + STATE_REMOVED_AND_AVAILABLE + ")");
+        }
+
+        this.mSourceSpec = sourceSpec;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mSourceSpec);
+        this.mTargetSpec = targetSpec;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mTargetSpec);
+        this.mUiTranslationEnabled = uiTranslationEnabled;
+        this.mSupportedTranslationFlags = supportedTranslationFlags;
+        com.android.internal.util.AnnotationValidations.validate(
+                TranslationContext.TranslationFlag.class, null, mSupportedTranslationFlags);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<TranslationCapability> CREATOR
+            = new Parcelable.Creator<TranslationCapability>() {
+        @Override
+        public TranslationCapability[] newArray(int size) {
+            return new TranslationCapability[size];
+        }
+
+        @Override
+        public TranslationCapability createFromParcel(@NonNull android.os.Parcel in) {
+            return new TranslationCapability(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1624307114468L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/view/translation/TranslationCapability.java",
+            inputSignatures = "public static final @android.view.translation.TranslationCapability.ModelState int STATE_AVAILABLE_TO_DOWNLOAD\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_DOWNLOADING\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_ON_DEVICE\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_NOT_AVAILABLE\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_REMOVED_AND_AVAILABLE\nprivate final @android.view.translation.TranslationCapability.ModelState int mState\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mSourceSpec\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mTargetSpec\nprivate final  boolean mUiTranslationEnabled\nprivate final @android.view.translation.TranslationContext.TranslationFlag int mSupportedTranslationFlags\nclass TranslationCapability extends java.lang.Object implements [android.os.Parcelable]\[email protected](genHiddenConstDefs=true, genToString=true, genConstructor=false)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/android/view/translation/TranslationContext.java b/android/view/translation/TranslationContext.java
new file mode 100644
index 0000000..210fe32
--- /dev/null
+++ b/android/view/translation/TranslationContext.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.translation;
+
+import android.annotation.NonNull;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Info class holding information for {@link Translator}s and used by
+ * {@link TranslationManager#createOnDeviceTranslator(TranslationContext, Executor, Consumer)}.
+ */
+@DataClass(genHiddenConstDefs = true, genToString = true, genBuilder = true)
+public final class TranslationContext implements Parcelable {
+
+    /**
+     * This context will perform translations in low latency mode.
+     */
+    public static final @TranslationFlag int FLAG_LOW_LATENCY = 0x1;
+    /**
+     * This context will enable the {@link Translator} to return transliteration results.
+     */
+    public static final @TranslationFlag int FLAG_TRANSLITERATION = 0x2;
+    /**
+     * This context will enable the {@link Translator} to return dictionary definitions.
+     */
+    public static final @TranslationFlag int FLAG_DEFINITIONS = 0x4;
+
+    /**
+     * {@link TranslationSpec} describing the source data to be translated.
+     */
+    @NonNull
+    private final TranslationSpec mSourceSpec;
+
+    /**
+     * {@link TranslationSpec} describing the target translated data.
+     */
+    @NonNull
+    private final TranslationSpec mTargetSpec;
+
+    /**
+     * Translation flags to be used by the {@link Translator}
+     */
+    private final @TranslationFlag int mTranslationFlags;
+
+    private static int defaultTranslationFlags() {
+        return 0;
+    }
+
+    @DataClass.Suppress({"setSourceSpec", "setTargetSpec"})
+    abstract static class BaseBuilder {
+
+    }
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/translation/TranslationContext.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /** @hide */
+    @android.annotation.IntDef(flag = true, prefix = "FLAG_", value = {
+        FLAG_LOW_LATENCY,
+        FLAG_TRANSLITERATION,
+        FLAG_DEFINITIONS
+    })
+    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface TranslationFlag {}
+
+    /** @hide */
+    @DataClass.Generated.Member
+    public static String translationFlagToString(@TranslationFlag int value) {
+        return com.android.internal.util.BitUtils.flagsToString(
+                value, TranslationContext::singleTranslationFlagToString);
+    }
+
+    @DataClass.Generated.Member
+    static String singleTranslationFlagToString(@TranslationFlag int value) {
+        switch (value) {
+            case FLAG_LOW_LATENCY:
+                    return "FLAG_LOW_LATENCY";
+            case FLAG_TRANSLITERATION:
+                    return "FLAG_TRANSLITERATION";
+            case FLAG_DEFINITIONS:
+                    return "FLAG_DEFINITIONS";
+            default: return Integer.toHexString(value);
+        }
+    }
+
+    @DataClass.Generated.Member
+    /* package-private */ TranslationContext(
+            @NonNull TranslationSpec sourceSpec,
+            @NonNull TranslationSpec targetSpec,
+            @TranslationFlag int translationFlags) {
+        this.mSourceSpec = sourceSpec;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mSourceSpec);
+        this.mTargetSpec = targetSpec;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mTargetSpec);
+        this.mTranslationFlags = translationFlags;
+
+        com.android.internal.util.Preconditions.checkFlagsArgument(
+                mTranslationFlags,
+                FLAG_LOW_LATENCY
+                        | FLAG_TRANSLITERATION
+                        | FLAG_DEFINITIONS);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * {@link TranslationSpec} describing the source data to be translated.
+     */
+    @DataClass.Generated.Member
+    public @NonNull TranslationSpec getSourceSpec() {
+        return mSourceSpec;
+    }
+
+    /**
+     * {@link TranslationSpec} describing the target translated data.
+     */
+    @DataClass.Generated.Member
+    public @NonNull TranslationSpec getTargetSpec() {
+        return mTargetSpec;
+    }
+
+    /**
+     * Translation flags to be used by the {@link Translator}
+     */
+    @DataClass.Generated.Member
+    public @TranslationFlag int getTranslationFlags() {
+        return mTranslationFlags;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "TranslationContext { " +
+                "sourceSpec = " + mSourceSpec + ", " +
+                "targetSpec = " + mTargetSpec + ", " +
+                "translationFlags = " + translationFlagToString(mTranslationFlags) +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeTypedObject(mSourceSpec, flags);
+        dest.writeTypedObject(mTargetSpec, flags);
+        dest.writeInt(mTranslationFlags);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ TranslationContext(@NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        TranslationSpec sourceSpec = (TranslationSpec) in.readTypedObject(TranslationSpec.CREATOR);
+        TranslationSpec targetSpec = (TranslationSpec) in.readTypedObject(TranslationSpec.CREATOR);
+        int translationFlags = in.readInt();
+
+        this.mSourceSpec = sourceSpec;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mSourceSpec);
+        this.mTargetSpec = targetSpec;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mTargetSpec);
+        this.mTranslationFlags = translationFlags;
+
+        com.android.internal.util.Preconditions.checkFlagsArgument(
+                mTranslationFlags,
+                FLAG_LOW_LATENCY
+                        | FLAG_TRANSLITERATION
+                        | FLAG_DEFINITIONS);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<TranslationContext> CREATOR
+            = new Parcelable.Creator<TranslationContext>() {
+        @Override
+        public TranslationContext[] newArray(int size) {
+            return new TranslationContext[size];
+        }
+
+        @Override
+        public TranslationContext createFromParcel(@NonNull android.os.Parcel in) {
+            return new TranslationContext(in);
+        }
+    };
+
+    /**
+     * A builder for {@link TranslationContext}
+     */
+    @SuppressWarnings("WeakerAccess")
+    @DataClass.Generated.Member
+    public static final class Builder extends BaseBuilder {
+
+        private @NonNull TranslationSpec mSourceSpec;
+        private @NonNull TranslationSpec mTargetSpec;
+        private @TranslationFlag int mTranslationFlags;
+
+        private long mBuilderFieldsSet = 0L;
+
+        /**
+         * Creates a new Builder.
+         *
+         * @param sourceSpec
+         *   {@link TranslationSpec} describing the source data to be translated.
+         * @param targetSpec
+         *   {@link TranslationSpec} describing the target translated data.
+         */
+        public Builder(
+                @NonNull TranslationSpec sourceSpec,
+                @NonNull TranslationSpec targetSpec) {
+            mSourceSpec = sourceSpec;
+            com.android.internal.util.AnnotationValidations.validate(
+                    NonNull.class, null, mSourceSpec);
+            mTargetSpec = targetSpec;
+            com.android.internal.util.AnnotationValidations.validate(
+                    NonNull.class, null, mTargetSpec);
+        }
+
+        /**
+         * Translation flags to be used by the {@link Translator}
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setTranslationFlags(@TranslationFlag int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x4;
+            mTranslationFlags = value;
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        public @NonNull TranslationContext build() {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x8; // Mark builder used
+
+            if ((mBuilderFieldsSet & 0x4) == 0) {
+                mTranslationFlags = defaultTranslationFlags();
+            }
+            TranslationContext o = new TranslationContext(
+                    mSourceSpec,
+                    mTargetSpec,
+                    mTranslationFlags);
+            return o;
+        }
+
+        private void checkNotUsed() {
+            if ((mBuilderFieldsSet & 0x8) != 0) {
+                throw new IllegalStateException(
+                        "This Builder should not be reused. Use a new Builder instance instead");
+            }
+        }
+    }
+
+    @DataClass.Generated(
+            time = 1621545292157L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/view/translation/TranslationContext.java",
+            inputSignatures = "public static final @android.view.translation.TranslationContext.TranslationFlag int FLAG_LOW_LATENCY\npublic static final @android.view.translation.TranslationContext.TranslationFlag int FLAG_TRANSLITERATION\npublic static final @android.view.translation.TranslationContext.TranslationFlag int FLAG_DEFINITIONS\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mSourceSpec\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mTargetSpec\nprivate final @android.view.translation.TranslationContext.TranslationFlag int mTranslationFlags\nprivate static  int defaultTranslationFlags()\nclass TranslationContext extends java.lang.Object implements [android.os.Parcelable]\nclass BaseBuilder extends java.lang.Object implements []\[email protected](genHiddenConstDefs=true, genToString=true, genBuilder=true)\nclass BaseBuilder extends java.lang.Object implements []")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/android/view/translation/TranslationManager.java b/android/view/translation/TranslationManager.java
new file mode 100644
index 0000000..54c455c
--- /dev/null
+++ b/android/view/translation/TranslationManager.java
@@ -0,0 +1,505 @@
+/*
+ * 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.view.translation;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemService;
+import android.annotation.WorkerThread;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IRemoteCallback;
+import android.os.Looper;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.SynchronousResultReceiver;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.SyncResultReceiver;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+/**
+ * The {@link TranslationManager} class provides ways for apps to integrate and use the
+ * translation framework.
+ *
+ * <p>The TranslationManager manages {@link Translator}s and help bridge client calls to
+ * the server {@link android.service.translation.TranslationService} </p>
+ */
+@SystemService(Context.TRANSLATION_MANAGER_SERVICE)
+public final class TranslationManager {
+
+    private static final String TAG = "TranslationManager";
+
+    /**
+     * Timeout for calls to system_server, default 1 minute.
+     */
+    static final int SYNC_CALLS_TIMEOUT_MS = 60_000;
+    /**
+     * The result code from result receiver success.
+     * @hide
+     */
+    public static final int STATUS_SYNC_CALL_SUCCESS = 1;
+    /**
+     * The result code from result receiver fail.
+     * @hide
+     */
+    public static final int STATUS_SYNC_CALL_FAIL = 2;
+
+    /**
+     * Name of the extra used to pass the translation capabilities.
+     * @hide
+     */
+    public static final String EXTRA_CAPABILITIES = "translation_capabilities";
+
+    @GuardedBy("mLock")
+    private final ArrayMap<Pair<Integer, Integer>, ArrayList<PendingIntent>>
+            mTranslationCapabilityUpdateListeners = new ArrayMap<>();
+
+    @GuardedBy("mLock")
+    private final Map<Consumer<TranslationCapability>, IRemoteCallback> mCapabilityCallbacks =
+            new ArrayMap<>();
+
+    private static final Random ID_GENERATOR = new Random();
+    private final Object mLock = new Object();
+
+    @NonNull
+    private final Context mContext;
+
+    private final ITranslationManager mService;
+
+    @NonNull
+    @GuardedBy("mLock")
+    private final SparseArray<Translator> mTranslators = new SparseArray<>();
+
+    @NonNull
+    @GuardedBy("mLock")
+    private final ArrayMap<TranslationContext, Integer> mTranslatorIds =
+            new ArrayMap<>();
+
+    @NonNull
+    private final Handler mHandler;
+
+    private static final AtomicInteger sAvailableRequestId = new AtomicInteger(1);
+
+    /**
+     * @hide
+     */
+    public TranslationManager(@NonNull Context context, ITranslationManager service) {
+        mContext = Objects.requireNonNull(context, "context cannot be null");
+        mService = service;
+
+        mHandler = Handler.createAsync(Looper.getMainLooper());
+    }
+
+    /**
+     * Creates an on-device Translator for natural language translation.
+     *
+     * @param translationContext {@link TranslationContext} containing the specs for creating the
+     *                                                     Translator.
+     * @param executor Executor to run callback operations
+     * @param callback {@link Consumer} to receive the translator. A {@code null} value is returned
+     *                                 if the service could not create the translator.
+     */
+    public void createOnDeviceTranslator(@NonNull TranslationContext translationContext,
+            @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Translator> callback) {
+        Objects.requireNonNull(translationContext, "translationContext cannot be null");
+        Objects.requireNonNull(executor, "executor cannot be null");
+        Objects.requireNonNull(callback, "callback cannot be null");
+
+        synchronized (mLock) {
+            // TODO(b/176464808): Disallow multiple Translator now, it will throw
+            //  IllegalStateException. Need to discuss if we can allow multiple Translators.
+            if (mTranslatorIds.containsKey(translationContext)) {
+                executor.execute(() -> callback.accept(
+                        mTranslators.get(mTranslatorIds.get(translationContext))));
+                return;
+            }
+
+            int translatorId;
+            do {
+                translatorId = Math.abs(ID_GENERATOR.nextInt());
+            } while (translatorId == 0 || mTranslators.indexOfKey(translatorId) >= 0);
+            final int tId = translatorId;
+
+            new Translator(mContext, translationContext, translatorId, this, mHandler, mService,
+                    new Consumer<Translator>() {
+                        @Override
+                        public void accept(Translator translator) {
+                            if (translator == null) {
+                                final long token = Binder.clearCallingIdentity();
+                                try {
+                                    executor.execute(() -> callback.accept(null));
+                                } finally {
+                                    Binder.restoreCallingIdentity(token);
+                                }
+                                return;
+                            }
+
+                            synchronized (mLock) {
+                                mTranslators.put(tId, translator);
+                                mTranslatorIds.put(translationContext, tId);
+                            }
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                                executor.execute(() -> callback.accept(translator));
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
+                        }
+                    });
+        }
+    }
+
+    /**
+     * Creates an on-device Translator for natural language translation.
+     *
+     * <p><strong>NOTE: </strong>Call on a worker thread.
+     *
+     * @removed use {@link #createOnDeviceTranslator(TranslationContext, Executor, Consumer)}
+     * instead.
+     *
+     * @param translationContext {@link TranslationContext} containing the specs for creating the
+     *                                                     Translator.
+     */
+    @Deprecated
+    @Nullable
+    @WorkerThread
+    public Translator createOnDeviceTranslator(@NonNull TranslationContext translationContext) {
+        Objects.requireNonNull(translationContext, "translationContext cannot be null");
+
+        synchronized (mLock) {
+            // TODO(b/176464808): Disallow multiple Translator now, it will throw
+            //  IllegalStateException. Need to discuss if we can allow multiple Translators.
+            if (mTranslatorIds.containsKey(translationContext)) {
+                return mTranslators.get(mTranslatorIds.get(translationContext));
+            }
+
+            int translatorId;
+            do {
+                translatorId = Math.abs(ID_GENERATOR.nextInt());
+            } while (translatorId == 0 || mTranslators.indexOfKey(translatorId) >= 0);
+
+            final Translator newTranslator = new Translator(mContext, translationContext,
+                    translatorId, this, mHandler, mService);
+            // Start the Translator session and wait for the result
+            newTranslator.start();
+            try {
+                if (!newTranslator.isSessionCreated()) {
+                    return null;
+                }
+                mTranslators.put(translatorId, newTranslator);
+                mTranslatorIds.put(translationContext, translatorId);
+                return newTranslator;
+            } catch (Translator.ServiceBinderReceiver.TimeoutException e) {
+                // TODO(b/176464808): maybe make SyncResultReceiver.TimeoutException constructor
+                //  public and use it.
+                Log.e(TAG, "Timed out getting create session: " + e);
+                return null;
+            }
+        }
+    }
+
+    /** @removed Use {@link #createOnDeviceTranslator(TranslationContext)} */
+    @Deprecated
+    @Nullable
+    @WorkerThread
+    public Translator createTranslator(@NonNull TranslationContext translationContext) {
+        return createOnDeviceTranslator(translationContext);
+    }
+
+    /**
+     * Returns a set of {@link TranslationCapability}s describing the capabilities for on-device
+     * {@link Translator}s.
+     *
+     * <p>These translation capabilities contains a source and target {@link TranslationSpec}
+     * representing the data expected for both ends of translation process. The capabilities
+     * provides the information and limitations for generating a {@link TranslationContext}.
+     * The context object can then be used by
+     * {@link #createOnDeviceTranslator(TranslationContext, Executor, Consumer)} to obtain a
+     * {@link Translator} for translations.</p>
+     *
+     * <p><strong>NOTE: </strong>Call on a worker thread.
+     *
+     * @param sourceFormat data format for the input data to be translated.
+     * @param targetFormat data format for the expected translated output data.
+     * @return A set of {@link TranslationCapability}s.
+     */
+    @NonNull
+    @WorkerThread
+    public Set<TranslationCapability> getOnDeviceTranslationCapabilities(
+            @TranslationSpec.DataFormat int sourceFormat,
+            @TranslationSpec.DataFormat int targetFormat) {
+        try {
+            final SynchronousResultReceiver receiver = new SynchronousResultReceiver();
+            mService.onTranslationCapabilitiesRequest(sourceFormat, targetFormat, receiver,
+                    mContext.getUserId());
+            final SynchronousResultReceiver.Result result =
+                    receiver.awaitResult(SYNC_CALLS_TIMEOUT_MS);
+            if (result.resultCode != STATUS_SYNC_CALL_SUCCESS) {
+                return Collections.emptySet();
+            }
+            Parcelable[] parcelables = result.bundle.getParcelableArray(EXTRA_CAPABILITIES);
+            ArraySet<TranslationCapability> capabilities = new ArraySet();
+            for (Parcelable obj : parcelables) {
+                if (obj instanceof TranslationCapability) {
+                    capabilities.add((TranslationCapability) obj);
+                }
+            }
+            return capabilities;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (TimeoutException e) {
+            Log.e(TAG, "Timed out getting supported translation capabilities: " + e);
+            return Collections.emptySet();
+        }
+    }
+
+    /** @removed Use {@link #getOnDeviceTranslationCapabilities(int, int)} */
+    @Deprecated
+    @NonNull
+    @WorkerThread
+    public Set<TranslationCapability> getTranslationCapabilities(
+            @TranslationSpec.DataFormat int sourceFormat,
+            @TranslationSpec.DataFormat int targetFormat) {
+        return getOnDeviceTranslationCapabilities(sourceFormat, targetFormat);
+    }
+
+    /**
+     * Adds a {@link TranslationCapability} Consumer to listen for updates on states of on-device
+     * {@link TranslationCapability}s.
+     *
+     * @param capabilityListener a {@link TranslationCapability} Consumer to receive the updated
+     * {@link TranslationCapability} from the on-device translation service.
+     */
+    public void addOnDeviceTranslationCapabilityUpdateListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<TranslationCapability> capabilityListener) {
+        Objects.requireNonNull(executor, "executor should not be null");
+        Objects.requireNonNull(capabilityListener, "capability listener should not be null");
+
+        synchronized (mLock) {
+            if (mCapabilityCallbacks.containsKey(capabilityListener)) {
+                Log.w(TAG, "addOnDeviceTranslationCapabilityUpdateListener: the listener for "
+                        + capabilityListener + " already registered; ignoring.");
+                return;
+            }
+            final IRemoteCallback remoteCallback = new TranslationCapabilityRemoteCallback(executor,
+                    capabilityListener);
+            try {
+                mService.registerTranslationCapabilityCallback(remoteCallback,
+                        mContext.getUserId());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+            mCapabilityCallbacks.put(capabilityListener, remoteCallback);
+        }
+    }
+
+
+    /**
+     * @removed Use {@link TranslationManager#addOnDeviceTranslationCapabilityUpdateListener(
+     * java.util.concurrent.Executor, java.util.function.Consumer)}
+     */
+    @Deprecated
+    public void addOnDeviceTranslationCapabilityUpdateListener(
+            @TranslationSpec.DataFormat int sourceFormat,
+            @TranslationSpec.DataFormat int targetFormat,
+            @NonNull PendingIntent pendingIntent) {
+        Objects.requireNonNull(pendingIntent, "pending intent should not be null");
+
+        synchronized (mLock) {
+            final Pair<Integer, Integer> formatPair = new Pair<>(sourceFormat, targetFormat);
+            mTranslationCapabilityUpdateListeners.computeIfAbsent(formatPair,
+                    (formats) -> new ArrayList<>()).add(pendingIntent);
+        }
+    }
+
+    /**
+     * @removed Use {@link TranslationManager#addOnDeviceTranslationCapabilityUpdateListener(
+     * java.util.concurrent.Executor, java.util.function.Consumer)}
+     */
+    @Deprecated
+    public void addTranslationCapabilityUpdateListener(
+            @TranslationSpec.DataFormat int sourceFormat,
+            @TranslationSpec.DataFormat int targetFormat,
+            @NonNull PendingIntent pendingIntent) {
+        addOnDeviceTranslationCapabilityUpdateListener(sourceFormat, targetFormat, pendingIntent);
+    }
+
+    /**
+     * Removes a {@link TranslationCapability} Consumer to listen for updates on states of
+     * on-device {@link TranslationCapability}s.
+     *
+     * @param capabilityListener the {@link TranslationCapability} Consumer to unregister
+     */
+    public void removeOnDeviceTranslationCapabilityUpdateListener(
+            @NonNull Consumer<TranslationCapability> capabilityListener) {
+        Objects.requireNonNull(capabilityListener, "capability callback should not be null");
+
+        synchronized (mLock) {
+            final IRemoteCallback remoteCallback = mCapabilityCallbacks.get(capabilityListener);
+            if (remoteCallback == null) {
+                Log.w(TAG, "removeOnDeviceTranslationCapabilityUpdateListener: the capability "
+                        + "listener not found; ignoring.");
+                return;
+            }
+            try {
+                mService.unregisterTranslationCapabilityCallback(remoteCallback,
+                        mContext.getUserId());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+            mCapabilityCallbacks.remove(capabilityListener);
+        }
+    }
+
+    /**
+     * @removed Use {@link #removeOnDeviceTranslationCapabilityUpdateListener(
+     * java.util.function.Consumer)}.
+     */
+    @Deprecated
+    public void removeOnDeviceTranslationCapabilityUpdateListener(
+            @TranslationSpec.DataFormat int sourceFormat,
+            @TranslationSpec.DataFormat int targetFormat,
+            @NonNull PendingIntent pendingIntent) {
+        Objects.requireNonNull(pendingIntent, "pending intent should not be null");
+
+        synchronized (mLock) {
+            final Pair<Integer, Integer> formatPair = new Pair<>(sourceFormat, targetFormat);
+            if (mTranslationCapabilityUpdateListeners.containsKey(formatPair)) {
+                final ArrayList<PendingIntent> intents =
+                        mTranslationCapabilityUpdateListeners.get(formatPair);
+                if (intents.contains(pendingIntent)) {
+                    intents.remove(pendingIntent);
+                } else {
+                    Log.w(TAG, "pending intent=" + pendingIntent + " does not exist in "
+                            + "mTranslationCapabilityUpdateListeners");
+                }
+            } else {
+                Log.w(TAG, "format pair=" + formatPair + " does not exist in "
+                        + "mTranslationCapabilityUpdateListeners");
+            }
+        }
+    }
+
+    /**
+     * @removed Use {@link #removeOnDeviceTranslationCapabilityUpdateListener(
+     * java.util.function.Consumer)}.
+     */
+    @Deprecated
+    public void removeTranslationCapabilityUpdateListener(
+            @TranslationSpec.DataFormat int sourceFormat,
+            @TranslationSpec.DataFormat int targetFormat,
+            @NonNull PendingIntent pendingIntent) {
+        removeOnDeviceTranslationCapabilityUpdateListener(
+                sourceFormat, targetFormat, pendingIntent);
+    }
+
+    /**
+     * Returns an immutable PendingIntent which can be used to launch an activity to view/edit
+     * on-device translation settings.
+     *
+     * @return An immutable PendingIntent or {@code null} if one of reason met:
+     * <ul>
+     *     <li>Device manufacturer (OEM) does not provide TranslationService.</li>
+     *     <li>The TranslationService doesn't provide the Settings.</li>
+     * </ul>
+     **/
+    @Nullable
+    public PendingIntent getOnDeviceTranslationSettingsActivityIntent() {
+        final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
+        try {
+            mService.getServiceSettingsActivity(resultReceiver, mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        try {
+            return resultReceiver.getParcelableResult();
+        } catch (SyncResultReceiver.TimeoutException e) {
+            Log.e(TAG, "Fail to get translation service settings activity.");
+            return null;
+        }
+    }
+
+    /** @removed Use {@link #getOnDeviceTranslationSettingsActivityIntent()} */
+    @Deprecated
+    @Nullable
+    public PendingIntent getTranslationSettingsActivityIntent() {
+        return getOnDeviceTranslationSettingsActivityIntent();
+    }
+
+    void removeTranslator(int id) {
+        synchronized (mLock) {
+            mTranslators.remove(id);
+            for (int i = 0; i < mTranslatorIds.size(); i++) {
+                if (mTranslatorIds.valueAt(i) == id) {
+                    mTranslatorIds.removeAt(i);
+                    break;
+                }
+            }
+        }
+    }
+
+    AtomicInteger getAvailableRequestId() {
+        synchronized (mLock) {
+            return sAvailableRequestId;
+        }
+    }
+
+    private static class TranslationCapabilityRemoteCallback extends
+            IRemoteCallback.Stub {
+        private final Executor mExecutor;
+        private final Consumer<TranslationCapability> mListener;
+
+        TranslationCapabilityRemoteCallback(Executor executor,
+                Consumer<TranslationCapability> listener) {
+            mExecutor = executor;
+            mListener = listener;
+        }
+
+        @Override
+        public void sendResult(Bundle bundle) {
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(() -> onTranslationCapabilityUpdate(bundle)));
+        }
+
+        private void onTranslationCapabilityUpdate(Bundle bundle) {
+            TranslationCapability capability =
+                    (TranslationCapability) bundle.getParcelable(EXTRA_CAPABILITIES);
+            mListener.accept(capability);
+        }
+    }
+}
diff --git a/android/view/translation/TranslationRequest.java b/android/view/translation/TranslationRequest.java
new file mode 100644
index 0000000..df4836e
--- /dev/null
+++ b/android/view/translation/TranslationRequest.java
@@ -0,0 +1,408 @@
+/*
+ * 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.view.translation;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Translation request sent to the {@link android.service.translation.TranslationService} by the
+ * {@link android.view.translation.Translator} which contains the text to be translated.
+ */
+@DataClass(genToString = true, genHiddenConstDefs = true, genBuilder = true)
+public final class TranslationRequest implements Parcelable {
+
+    /**
+     * Indicates this request wants to receive the standard translation result.
+     */
+    public static final @RequestFlags int FLAG_TRANSLATION_RESULT = 0x1;
+    /**
+     * Indicates this request wants to receive the dictionary result.
+     * TODO: describe the structure of the result.
+     */
+    public static final @RequestFlags int FLAG_DICTIONARY_RESULT = 0x2;
+    /**
+     * Indicates this request wants to receive the transliteration result.
+     * TODO: describe the structure of the result.
+     */
+    public static final @RequestFlags int FLAG_TRANSLITERATION_RESULT = 0x4;
+    /**
+     * Indicates this request is willing to accept partial responses.
+     *
+     * <p>The partial responses can be accessed by
+     * {@link TranslationResponse#getTranslationResponseValues()} or
+     * {@link TranslationResponse#getViewTranslationResponses()}. These responses will each contain
+     * only a subset of the corresponding translated values.
+     *
+     * <p>The are no guarantees to the number of translated values or the order in which these
+     * values are returned in the {@link TranslationResponse}.
+     *
+     * <p>This flag denotes the client can expect multiple partial responses, but there may not
+     * necessarily be multiple responses.</p>
+     */
+    public static final @RequestFlags int FLAG_PARTIAL_RESPONSES = 0x8;
+
+    /**
+     * Request flags. {@link #FLAG_TRANSLATION_RESULT} by default.
+     */
+    private final @RequestFlags int mFlags;
+
+    /**
+     * List of {@link TranslationRequestValue}s to be translated. The index of entries in this list
+     * will be their respective key in the {@link android.util.SparseArray} returned by calling
+     * {@link TranslationResponse#getTranslationResponseValues()}.
+     */
+    @NonNull
+    @DataClass.PluralOf("translationRequestValue")
+    private final List<TranslationRequestValue> mTranslationRequestValues;
+
+    /**
+     * List of {@link ViewTranslationRequest}s to be translated. The index of entries in this list
+     * will be their respective key in the {@link android.util.SparseArray} returned by calling
+     * {@link TranslationResponse#getViewTranslationResponses()}.
+     */
+    @NonNull
+    @DataClass.PluralOf("viewTranslationRequest")
+    private final List<ViewTranslationRequest> mViewTranslationRequests;
+
+    private static int defaultFlags() {
+        return FLAG_TRANSLATION_RESULT;
+    }
+
+    private static List<TranslationRequestValue> defaultTranslationRequestValues() {
+        return Collections.emptyList();
+    }
+
+    private static List<ViewTranslationRequest> defaultViewTranslationRequests() {
+        return Collections.emptyList();
+    }
+
+    abstract static class BaseBuilder {
+        /**
+         * @removed use {@link Builder#setTranslationRequestValues(List)}.
+         */
+        @Deprecated
+        public abstract Builder addTranslationRequestValue(
+                @NonNull TranslationRequestValue value);
+
+        /**
+         * @removed use {@link Builder#setViewTranslationRequests(List)}.
+         */
+        @Deprecated
+        public abstract Builder addViewTranslationRequest(
+                @NonNull ViewTranslationRequest value);
+
+    }
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/translation/TranslationRequest.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /** @hide */
+    @android.annotation.IntDef(flag = true, prefix = "FLAG_", value = {
+        FLAG_TRANSLATION_RESULT,
+        FLAG_DICTIONARY_RESULT,
+        FLAG_TRANSLITERATION_RESULT,
+        FLAG_PARTIAL_RESPONSES
+    })
+    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface RequestFlags {}
+
+    /** @hide */
+    @DataClass.Generated.Member
+    public static String requestFlagsToString(@RequestFlags int value) {
+        return com.android.internal.util.BitUtils.flagsToString(
+                value, TranslationRequest::singleRequestFlagsToString);
+    }
+
+    @DataClass.Generated.Member
+    static String singleRequestFlagsToString(@RequestFlags int value) {
+        switch (value) {
+            case FLAG_TRANSLATION_RESULT:
+                    return "FLAG_TRANSLATION_RESULT";
+            case FLAG_DICTIONARY_RESULT:
+                    return "FLAG_DICTIONARY_RESULT";
+            case FLAG_TRANSLITERATION_RESULT:
+                    return "FLAG_TRANSLITERATION_RESULT";
+            case FLAG_PARTIAL_RESPONSES:
+                    return "FLAG_PARTIAL_RESPONSES";
+            default: return Integer.toHexString(value);
+        }
+    }
+
+    @DataClass.Generated.Member
+    /* package-private */ TranslationRequest(
+            @RequestFlags int flags,
+            @NonNull List<TranslationRequestValue> translationRequestValues,
+            @NonNull List<ViewTranslationRequest> viewTranslationRequests) {
+        this.mFlags = flags;
+
+        com.android.internal.util.Preconditions.checkFlagsArgument(
+                mFlags,
+                FLAG_TRANSLATION_RESULT
+                        | FLAG_DICTIONARY_RESULT
+                        | FLAG_TRANSLITERATION_RESULT
+                        | FLAG_PARTIAL_RESPONSES);
+        this.mTranslationRequestValues = translationRequestValues;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mTranslationRequestValues);
+        this.mViewTranslationRequests = viewTranslationRequests;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mViewTranslationRequests);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * Request flags. {@link #FLAG_TRANSLATION_RESULT} by default.
+     */
+    @DataClass.Generated.Member
+    public @RequestFlags int getFlags() {
+        return mFlags;
+    }
+
+    /**
+     * List of {@link TranslationRequestValue}s to be translated. The index of entries in this list
+     * will be their respective key in the {@link android.util.SparseArray} returned by calling
+     * {@link TranslationResponse#getTranslationResponseValues()}.
+     */
+    @DataClass.Generated.Member
+    public @NonNull List<TranslationRequestValue> getTranslationRequestValues() {
+        return mTranslationRequestValues;
+    }
+
+    /**
+     * List of {@link ViewTranslationRequest}s to be translated. The index of entries in this list
+     * will be their respective key in the {@link android.util.SparseArray} returned by calling
+     * {@link TranslationResponse#getViewTranslationResponses()}.
+     */
+    @DataClass.Generated.Member
+    public @NonNull List<ViewTranslationRequest> getViewTranslationRequests() {
+        return mViewTranslationRequests;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "TranslationRequest { " +
+                "flags = " + requestFlagsToString(mFlags) + ", " +
+                "translationRequestValues = " + mTranslationRequestValues + ", " +
+                "viewTranslationRequests = " + mViewTranslationRequests +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeInt(mFlags);
+        dest.writeParcelableList(mTranslationRequestValues, flags);
+        dest.writeParcelableList(mViewTranslationRequests, flags);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ TranslationRequest(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        int flags = in.readInt();
+        List<TranslationRequestValue> translationRequestValues = new ArrayList<>();
+        in.readParcelableList(translationRequestValues, TranslationRequestValue.class.getClassLoader());
+        List<ViewTranslationRequest> viewTranslationRequests = new ArrayList<>();
+        in.readParcelableList(viewTranslationRequests, ViewTranslationRequest.class.getClassLoader());
+
+        this.mFlags = flags;
+
+        com.android.internal.util.Preconditions.checkFlagsArgument(
+                mFlags,
+                FLAG_TRANSLATION_RESULT
+                        | FLAG_DICTIONARY_RESULT
+                        | FLAG_TRANSLITERATION_RESULT
+                        | FLAG_PARTIAL_RESPONSES);
+        this.mTranslationRequestValues = translationRequestValues;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mTranslationRequestValues);
+        this.mViewTranslationRequests = viewTranslationRequests;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mViewTranslationRequests);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<TranslationRequest> CREATOR
+            = new Parcelable.Creator<TranslationRequest>() {
+        @Override
+        public TranslationRequest[] newArray(int size) {
+            return new TranslationRequest[size];
+        }
+
+        @Override
+        public TranslationRequest createFromParcel(@NonNull Parcel in) {
+            return new TranslationRequest(in);
+        }
+    };
+
+    /**
+     * A builder for {@link TranslationRequest}
+     */
+    @SuppressWarnings("WeakerAccess")
+    @DataClass.Generated.Member
+    public static final class Builder extends BaseBuilder {
+
+        private @RequestFlags int mFlags;
+        private @NonNull List<TranslationRequestValue> mTranslationRequestValues;
+        private @NonNull List<ViewTranslationRequest> mViewTranslationRequests;
+
+        private long mBuilderFieldsSet = 0L;
+
+        public Builder() {
+        }
+
+        /**
+         * Request flags. {@link #FLAG_TRANSLATION_RESULT} by default.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setFlags(@RequestFlags int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x1;
+            mFlags = value;
+            return this;
+        }
+
+        /**
+         * List of {@link TranslationRequestValue}s to be translated. The index of entries in this list
+         * will be their respective key in the {@link android.util.SparseArray} returned by calling
+         * {@link TranslationResponse#getTranslationResponseValues()}.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setTranslationRequestValues(@NonNull List<TranslationRequestValue> value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x2;
+            mTranslationRequestValues = value;
+            return this;
+        }
+
+        /** @see #setTranslationRequestValues
+         * @removed
+         */
+        @DataClass.Generated.Member
+        @Override
+        @Deprecated
+        public @NonNull Builder addTranslationRequestValue(@NonNull TranslationRequestValue value) {
+            if (mTranslationRequestValues == null) setTranslationRequestValues(new ArrayList<>());
+            mTranslationRequestValues.add(value);
+            return this;
+        }
+
+        /**
+         * List of {@link ViewTranslationRequest}s to be translated. The index of entries in this list
+         * will be their respective key in the {@link android.util.SparseArray} returned by calling
+         * {@link TranslationResponse#getViewTranslationResponses()}.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setViewTranslationRequests(@NonNull List<ViewTranslationRequest> value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x4;
+            mViewTranslationRequests = value;
+            return this;
+        }
+
+        /** @see #setViewTranslationRequests
+         * @removed
+         */
+        @DataClass.Generated.Member
+        @Override
+        @Deprecated
+        public @NonNull Builder addViewTranslationRequest(@NonNull ViewTranslationRequest value) {
+            if (mViewTranslationRequests == null) setViewTranslationRequests(new ArrayList<>());
+            mViewTranslationRequests.add(value);
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        public @NonNull TranslationRequest build() {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x8; // Mark builder used
+
+            if ((mBuilderFieldsSet & 0x1) == 0) {
+                mFlags = defaultFlags();
+            }
+            if ((mBuilderFieldsSet & 0x2) == 0) {
+                mTranslationRequestValues = defaultTranslationRequestValues();
+            }
+            if ((mBuilderFieldsSet & 0x4) == 0) {
+                mViewTranslationRequests = defaultViewTranslationRequests();
+            }
+            TranslationRequest o = new TranslationRequest(
+                    mFlags,
+                    mTranslationRequestValues,
+                    mViewTranslationRequests);
+            return o;
+        }
+
+        private void checkNotUsed() {
+            if ((mBuilderFieldsSet & 0x8) != 0) {
+                throw new IllegalStateException(
+                        "This Builder should not be reused. Use a new Builder instance instead");
+            }
+        }
+    }
+
+    @DataClass.Generated(
+            time = 1620429997487L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/view/translation/TranslationRequest.java",
+            inputSignatures = "public static final @android.view.translation.TranslationRequest.RequestFlags int FLAG_TRANSLATION_RESULT\npublic static final @android.view.translation.TranslationRequest.RequestFlags int FLAG_DICTIONARY_RESULT\npublic static final @android.view.translation.TranslationRequest.RequestFlags int FLAG_TRANSLITERATION_RESULT\npublic static final @android.view.translation.TranslationRequest.RequestFlags int FLAG_PARTIAL_RESPONSES\nprivate final @android.view.translation.TranslationRequest.RequestFlags int mFlags\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"translationRequestValue\") java.util.List<android.view.translation.TranslationRequestValue> mTranslationRequestValues\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"viewTranslationRequest\") java.util.List<android.view.translation.ViewTranslationRequest> mViewTranslationRequests\nprivate static  int defaultFlags()\nprivate static  java.util.List<android.view.translation.TranslationRequestValue> defaultTranslationRequestValues()\nprivate static  java.util.List<android.view.translation.ViewTranslationRequest> defaultViewTranslationRequests()\nclass TranslationRequest extends java.lang.Object implements [android.os.Parcelable]\npublic abstract @java.lang.Deprecated android.view.translation.TranslationRequest.Builder addTranslationRequestValue(android.view.translation.TranslationRequestValue)\npublic abstract @java.lang.Deprecated android.view.translation.TranslationRequest.Builder addViewTranslationRequest(android.view.translation.ViewTranslationRequest)\nclass BaseBuilder extends java.lang.Object implements []\[email protected](genToString=true, genHiddenConstDefs=true, genBuilder=true)\npublic abstract @java.lang.Deprecated android.view.translation.TranslationRequest.Builder addTranslationRequestValue(android.view.translation.TranslationRequestValue)\npublic abstract @java.lang.Deprecated android.view.translation.TranslationRequest.Builder addViewTranslationRequest(android.view.translation.ViewTranslationRequest)\nclass BaseBuilder extends java.lang.Object implements []")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/android/view/translation/TranslationRequestValue.java b/android/view/translation/TranslationRequestValue.java
new file mode 100644
index 0000000..5178cb1
--- /dev/null
+++ b/android/view/translation/TranslationRequestValue.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.translation;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.util.Objects;
+
+/**
+ * A value to be translated via {@link android.view.translation.Translator}.
+ */
+@DataClass(genHiddenConstructor = true, genToString = true, genEqualsHashCode = true)
+public final class TranslationRequestValue implements Parcelable {
+
+    @Nullable
+    private final CharSequence mText;
+
+    /**
+     * Creates a {@link TranslationRequestValue} with a {@link CharSequence} value;
+     *
+     * @param text the text to be translated.
+     */
+    @NonNull
+    public static TranslationRequestValue forText(@NonNull CharSequence text) {
+        Objects.requireNonNull(text, "text should not be null");
+        return new TranslationRequestValue(text);
+    }
+
+    /**
+     * @return the text value as a {@link CharSequence} or {@code null} if the value is not of type
+     * text.
+     */
+    @Nullable
+    public CharSequence getText() {
+        return mText;
+    }
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/translation/TranslationRequestValue.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new TranslationRequestValue.
+     *
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public TranslationRequestValue(
+            @Nullable CharSequence text) {
+        this.mText = text;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "TranslationRequestValue { " +
+                "text = " + mText +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(TranslationRequestValue other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        TranslationRequestValue that = (TranslationRequestValue) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && Objects.equals(mText, that.mText);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + Objects.hashCode(mText);
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mText != null) flg |= 0x1;
+        dest.writeByte(flg);
+        if (mText != null) dest.writeCharSequence(mText);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ TranslationRequestValue(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        CharSequence text = (flg & 0x1) == 0 ? null : (CharSequence) in.readCharSequence();
+
+        this.mText = text;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<TranslationRequestValue> CREATOR
+            = new Parcelable.Creator<TranslationRequestValue>() {
+        @Override
+        public TranslationRequestValue[] newArray(int size) {
+            return new TranslationRequestValue[size];
+        }
+
+        @Override
+        public TranslationRequestValue createFromParcel(@NonNull Parcel in) {
+            return new TranslationRequestValue(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1620259864154L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/view/translation/TranslationRequestValue.java",
+            inputSignatures = "private final @android.annotation.Nullable java.lang.CharSequence mText\npublic static @android.annotation.NonNull android.view.translation.TranslationRequestValue forText(java.lang.CharSequence)\npublic @android.annotation.Nullable java.lang.CharSequence getText()\nclass TranslationRequestValue extends java.lang.Object implements [android.os.Parcelable]\[email protected](genHiddenConstructor=true, genToString=true, genEqualsHashCode=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/android/view/translation/TranslationResponse.java b/android/view/translation/TranslationResponse.java
new file mode 100644
index 0000000..b77f2e2
--- /dev/null
+++ b/android/view/translation/TranslationResponse.java
@@ -0,0 +1,462 @@
+/*
+ * 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.view.translation;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.service.translation.TranslationService;
+import android.util.SparseArray;
+
+import com.android.internal.util.DataClass;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Response from the {@link TranslationService}, which contains the translated result.
+ */
+@DataClass(genBuilder = true, genToString = true, genHiddenConstDefs = true)
+public final class TranslationResponse implements Parcelable {
+
+    /**
+     * The {@link TranslationService} was successful in translating.
+     */
+    public static final int TRANSLATION_STATUS_SUCCESS = 0;
+    /**
+     * The {@link TranslationService} returned unknown translation result.
+     */
+    public static final int TRANSLATION_STATUS_UNKNOWN_ERROR = 1;
+    /**
+     * The languages of the request is not available to be translated.
+     */
+    public static final int TRANSLATION_STATUS_CONTEXT_UNSUPPORTED = 2;
+
+    /**
+     * The translation result status code.
+     */
+    private final @TranslationStatus int mTranslationStatus;
+
+    /**
+     * List of translated {@link TranslationResponseValue}s. The key of entries in this list
+     * will be their respective index in {@link TranslationRequest#getTranslationRequestValues()}.
+     */
+    @NonNull
+    private final SparseArray<TranslationResponseValue> mTranslationResponseValues;
+
+    /**
+     * List of translated {@link ViewTranslationResponse}s. The key of entries in this list
+     * will be their respective index in {@link TranslationRequest#getViewTranslationRequests()}.
+     */
+    @NonNull
+    private final SparseArray<ViewTranslationResponse> mViewTranslationResponses;
+
+    /**
+     * Whether this response contains complete translated values, or is the final response in a
+     * series of partial responses.
+     *
+     * <p>This is {@code true} by default.</p>
+     */
+    private final boolean mFinalResponse;
+
+    abstract static class BaseBuilder {
+
+        /**
+         * @removed Use {@link Builder#Builder(int)}.
+         * @hide
+         */
+        @Deprecated
+        public abstract Builder setTranslationStatus(@TranslationStatus int value);
+
+        /**
+         * Adds {@link TranslationResponseValue} to be translated. The input
+         * TranslationResponseValue format should match those provided by the
+         * {@link android.view.translation.Translator}'s targetSpec.
+         *
+         * @param value the translated value.
+         * @return this Builder.
+         */
+        @NonNull
+        @SuppressWarnings("MissingGetterMatchingBuilder")
+        public Builder setTranslationResponseValue(int index,
+                @NonNull TranslationResponseValue value) {
+            Objects.requireNonNull(value, "value should not be null");
+            final Builder builder = (Builder) this;
+
+            if (builder.mTranslationResponseValues == null) {
+                builder.setTranslationResponseValues(new SparseArray<>());
+            }
+            builder.mTranslationResponseValues.put(index, value);
+            return builder;
+        }
+
+        /**
+         * Sets the list of {@link ViewTranslationResponse} to be translated. The input
+         * ViewTranslationResponse contains {@link TranslationResponseValue}s whose  format should
+         * match those provided by the {@link android.view.translation.Translator}'s targetSpec.
+         *
+         * @param response the translated response.
+         * @return this Builder.
+         */
+        @NonNull
+        @SuppressWarnings("MissingGetterMatchingBuilder")
+        public Builder setViewTranslationResponse(int index,
+                @NonNull ViewTranslationResponse response) {
+            Objects.requireNonNull(response, "value should not be null");
+            final Builder builder = (Builder) this;
+
+            if (builder.mViewTranslationResponses == null) {
+                builder.setViewTranslationResponses(new SparseArray<>());
+            }
+            builder.mViewTranslationResponses.put(index, response);
+            return builder;
+        }
+    }
+
+    private static SparseArray<TranslationResponseValue> defaultTranslationResponseValues() {
+        return new SparseArray<>();
+    }
+
+    private static SparseArray<ViewTranslationResponse> defaultViewTranslationResponses() {
+        return new SparseArray<>();
+    }
+
+    private static boolean defaultFinalResponse() {
+        return true;
+    }
+
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/translation/TranslationResponse.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /** @hide */
+    @IntDef(prefix = "TRANSLATION_STATUS_", value = {
+        TRANSLATION_STATUS_SUCCESS,
+        TRANSLATION_STATUS_UNKNOWN_ERROR,
+        TRANSLATION_STATUS_CONTEXT_UNSUPPORTED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface TranslationStatus {}
+
+    /** @hide */
+    @DataClass.Generated.Member
+    public static String translationStatusToString(@TranslationStatus int value) {
+        switch (value) {
+            case TRANSLATION_STATUS_SUCCESS:
+                    return "TRANSLATION_STATUS_SUCCESS";
+            case TRANSLATION_STATUS_UNKNOWN_ERROR:
+                    return "TRANSLATION_STATUS_UNKNOWN_ERROR";
+            case TRANSLATION_STATUS_CONTEXT_UNSUPPORTED:
+                    return "TRANSLATION_STATUS_CONTEXT_UNSUPPORTED";
+            default: return Integer.toHexString(value);
+        }
+    }
+
+    @DataClass.Generated.Member
+    /* package-private */ TranslationResponse(
+            @TranslationStatus int translationStatus,
+            @NonNull SparseArray<TranslationResponseValue> translationResponseValues,
+            @NonNull SparseArray<ViewTranslationResponse> viewTranslationResponses,
+            boolean finalResponse) {
+        this.mTranslationStatus = translationStatus;
+
+        if (!(mTranslationStatus == TRANSLATION_STATUS_SUCCESS)
+                && !(mTranslationStatus == TRANSLATION_STATUS_UNKNOWN_ERROR)
+                && !(mTranslationStatus == TRANSLATION_STATUS_CONTEXT_UNSUPPORTED)) {
+            throw new java.lang.IllegalArgumentException(
+                    "translationStatus was " + mTranslationStatus + " but must be one of: "
+                            + "TRANSLATION_STATUS_SUCCESS(" + TRANSLATION_STATUS_SUCCESS + "), "
+                            + "TRANSLATION_STATUS_UNKNOWN_ERROR(" + TRANSLATION_STATUS_UNKNOWN_ERROR + "), "
+                            + "TRANSLATION_STATUS_CONTEXT_UNSUPPORTED(" + TRANSLATION_STATUS_CONTEXT_UNSUPPORTED + ")");
+        }
+
+        this.mTranslationResponseValues = translationResponseValues;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mTranslationResponseValues);
+        this.mViewTranslationResponses = viewTranslationResponses;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mViewTranslationResponses);
+        this.mFinalResponse = finalResponse;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * The translation result status code.
+     */
+    @DataClass.Generated.Member
+    public @TranslationStatus int getTranslationStatus() {
+        return mTranslationStatus;
+    }
+
+    /**
+     * List of translated {@link TranslationResponseValue}s. The key of entries in this list
+     * will be their respective index in {@link TranslationRequest#getTranslationRequestValues()}.
+     */
+    @DataClass.Generated.Member
+    public @NonNull SparseArray<TranslationResponseValue> getTranslationResponseValues() {
+        return mTranslationResponseValues;
+    }
+
+    /**
+     * List of translated {@link ViewTranslationResponse}s. The key of entries in this list
+     * will be their respective index in {@link TranslationRequest#getViewTranslationRequests()}.
+     */
+    @DataClass.Generated.Member
+    public @NonNull SparseArray<ViewTranslationResponse> getViewTranslationResponses() {
+        return mViewTranslationResponses;
+    }
+
+    /**
+     * Whether this response contains complete translated values, or is the final response in a
+     * series of partial responses.
+     *
+     * <p>This is {@code true} by default.</p>
+     */
+    @DataClass.Generated.Member
+    public boolean isFinalResponse() {
+        return mFinalResponse;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "TranslationResponse { " +
+                "translationStatus = " + translationStatusToString(mTranslationStatus) + ", " +
+                "translationResponseValues = " + mTranslationResponseValues + ", " +
+                "viewTranslationResponses = " + mViewTranslationResponses + ", " +
+                "finalResponse = " + mFinalResponse +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mFinalResponse) flg |= 0x8;
+        dest.writeByte(flg);
+        dest.writeInt(mTranslationStatus);
+        dest.writeSparseArray(mTranslationResponseValues);
+        dest.writeSparseArray(mViewTranslationResponses);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ TranslationResponse(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        boolean finalResponse = (flg & 0x8) != 0;
+        int translationStatus = in.readInt();
+        SparseArray<TranslationResponseValue> translationResponseValues = (SparseArray) in.readSparseArray(TranslationResponseValue.class.getClassLoader());
+        SparseArray<ViewTranslationResponse> viewTranslationResponses = (SparseArray) in.readSparseArray(ViewTranslationResponse.class.getClassLoader());
+
+        this.mTranslationStatus = translationStatus;
+
+        if (!(mTranslationStatus == TRANSLATION_STATUS_SUCCESS)
+                && !(mTranslationStatus == TRANSLATION_STATUS_UNKNOWN_ERROR)
+                && !(mTranslationStatus == TRANSLATION_STATUS_CONTEXT_UNSUPPORTED)) {
+            throw new java.lang.IllegalArgumentException(
+                    "translationStatus was " + mTranslationStatus + " but must be one of: "
+                            + "TRANSLATION_STATUS_SUCCESS(" + TRANSLATION_STATUS_SUCCESS + "), "
+                            + "TRANSLATION_STATUS_UNKNOWN_ERROR(" + TRANSLATION_STATUS_UNKNOWN_ERROR + "), "
+                            + "TRANSLATION_STATUS_CONTEXT_UNSUPPORTED(" + TRANSLATION_STATUS_CONTEXT_UNSUPPORTED + ")");
+        }
+
+        this.mTranslationResponseValues = translationResponseValues;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mTranslationResponseValues);
+        this.mViewTranslationResponses = viewTranslationResponses;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mViewTranslationResponses);
+        this.mFinalResponse = finalResponse;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<TranslationResponse> CREATOR
+            = new Parcelable.Creator<TranslationResponse>() {
+        @Override
+        public TranslationResponse[] newArray(int size) {
+            return new TranslationResponse[size];
+        }
+
+        @Override
+        public TranslationResponse createFromParcel(@NonNull Parcel in) {
+            return new TranslationResponse(in);
+        }
+    };
+
+    /**
+     * A builder for {@link TranslationResponse}
+     */
+    @SuppressWarnings("WeakerAccess")
+    @DataClass.Generated.Member
+    public static final class Builder extends BaseBuilder {
+
+        private @TranslationStatus int mTranslationStatus;
+        private @NonNull SparseArray<TranslationResponseValue> mTranslationResponseValues;
+        private @NonNull SparseArray<ViewTranslationResponse> mViewTranslationResponses;
+        private boolean mFinalResponse;
+
+        private long mBuilderFieldsSet = 0L;
+
+        /**
+         * Creates a new Builder.
+         *
+         * @param translationStatus
+         *   The translation result status code.
+         */
+        public Builder(
+                @TranslationStatus int translationStatus) {
+            mTranslationStatus = translationStatus;
+
+            if (!(mTranslationStatus == TRANSLATION_STATUS_SUCCESS)
+                    && !(mTranslationStatus == TRANSLATION_STATUS_UNKNOWN_ERROR)
+                    && !(mTranslationStatus == TRANSLATION_STATUS_CONTEXT_UNSUPPORTED)) {
+                throw new java.lang.IllegalArgumentException(
+                        "translationStatus was " + mTranslationStatus + " but must be one of: "
+                                + "TRANSLATION_STATUS_SUCCESS(" + TRANSLATION_STATUS_SUCCESS + "), "
+                                + "TRANSLATION_STATUS_UNKNOWN_ERROR(" + TRANSLATION_STATUS_UNKNOWN_ERROR + "), "
+                                + "TRANSLATION_STATUS_CONTEXT_UNSUPPORTED(" + TRANSLATION_STATUS_CONTEXT_UNSUPPORTED + ")");
+            }
+
+        }
+
+        /**
+         * The translation result status code.
+         * @removed
+         */
+        @DataClass.Generated.Member
+        @Override
+        @Deprecated
+        public @NonNull Builder setTranslationStatus(@TranslationStatus int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x1;
+            mTranslationStatus = value;
+            return this;
+        }
+
+        /**
+         * List of translated {@link TranslationResponseValue}s. The key of entries in this list
+         * will be their respective index in {@link TranslationRequest#getTranslationRequestValues()}.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setTranslationResponseValues(@NonNull SparseArray<TranslationResponseValue> value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x2;
+            mTranslationResponseValues = value;
+            return this;
+        }
+
+        /**
+         * List of translated {@link ViewTranslationResponse}s. The key of entries in this list
+         * will be their respective index in {@link TranslationRequest#getViewTranslationRequests()}.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setViewTranslationResponses(@NonNull SparseArray<ViewTranslationResponse> value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x4;
+            mViewTranslationResponses = value;
+            return this;
+        }
+
+        /**
+         * Whether this response contains complete translated values, or is the final response in a
+         * series of partial responses.
+         *
+         * <p>This is {@code true} by default.</p>
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setFinalResponse(boolean value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x8;
+            mFinalResponse = value;
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        public @NonNull TranslationResponse build() {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x10; // Mark builder used
+
+            if ((mBuilderFieldsSet & 0x2) == 0) {
+                mTranslationResponseValues = defaultTranslationResponseValues();
+            }
+            if ((mBuilderFieldsSet & 0x4) == 0) {
+                mViewTranslationResponses = defaultViewTranslationResponses();
+            }
+            if ((mBuilderFieldsSet & 0x8) == 0) {
+                mFinalResponse = defaultFinalResponse();
+            }
+            TranslationResponse o = new TranslationResponse(
+                    mTranslationStatus,
+                    mTranslationResponseValues,
+                    mViewTranslationResponses,
+                    mFinalResponse);
+            return o;
+        }
+
+        private void checkNotUsed() {
+            if ((mBuilderFieldsSet & 0x10) != 0) {
+                throw new IllegalStateException(
+                        "This Builder should not be reused. Use a new Builder instance instead");
+            }
+        }
+    }
+
+    @DataClass.Generated(
+            time = 1621972659130L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/view/translation/TranslationResponse.java",
+            inputSignatures = "public static final  int TRANSLATION_STATUS_SUCCESS\npublic static final  int TRANSLATION_STATUS_UNKNOWN_ERROR\npublic static final  int TRANSLATION_STATUS_CONTEXT_UNSUPPORTED\nprivate final @android.view.translation.TranslationResponse.TranslationStatus int mTranslationStatus\nprivate final @android.annotation.NonNull android.util.SparseArray<android.view.translation.TranslationResponseValue> mTranslationResponseValues\nprivate final @android.annotation.NonNull android.util.SparseArray<android.view.translation.ViewTranslationResponse> mViewTranslationResponses\nprivate final  boolean mFinalResponse\nprivate static  android.util.SparseArray<android.view.translation.TranslationResponseValue> defaultTranslationResponseValues()\nprivate static  android.util.SparseArray<android.view.translation.ViewTranslationResponse> defaultViewTranslationResponses()\nprivate static  boolean defaultFinalResponse()\nclass TranslationResponse extends java.lang.Object implements [android.os.Parcelable]\npublic abstract @java.lang.Deprecated android.view.translation.TranslationResponse.Builder setTranslationStatus(int)\npublic @android.annotation.NonNull @java.lang.SuppressWarnings android.view.translation.TranslationResponse.Builder setTranslationResponseValue(int,android.view.translation.TranslationResponseValue)\npublic @android.annotation.NonNull @java.lang.SuppressWarnings android.view.translation.TranslationResponse.Builder setViewTranslationResponse(int,android.view.translation.ViewTranslationResponse)\nclass BaseBuilder extends java.lang.Object implements []\[email protected](genBuilder=true, genToString=true, genHiddenConstDefs=true)\npublic abstract @java.lang.Deprecated android.view.translation.TranslationResponse.Builder setTranslationStatus(int)\npublic @android.annotation.NonNull @java.lang.SuppressWarnings android.view.translation.TranslationResponse.Builder setTranslationResponseValue(int,android.view.translation.TranslationResponseValue)\npublic @android.annotation.NonNull @java.lang.SuppressWarnings android.view.translation.TranslationResponse.Builder setViewTranslationResponse(int,android.view.translation.ViewTranslationResponse)\nclass BaseBuilder extends java.lang.Object implements []")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/android/view/translation/TranslationResponseValue.java b/android/view/translation/TranslationResponseValue.java
new file mode 100644
index 0000000..a24dbc3
--- /dev/null
+++ b/android/view/translation/TranslationResponseValue.java
@@ -0,0 +1,462 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.translation;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.util.Objects;
+
+/**
+ * A translated response value from {@link android.service.translation.TranslationService}.
+ */
+@DataClass(genBuilder = true, genToString = true, genEqualsHashCode = true,
+        genHiddenConstDefs = true)
+public final class TranslationResponseValue implements Parcelable {
+
+    /**
+     * This value was successfully translated.
+     */
+    public static final int STATUS_SUCCESS = 0;
+    /**
+     * This value was not successfully translated. No value can be obtained with {@link #getText()}.
+     */
+    public static final int STATUS_ERROR = 1;
+
+    /**
+     * Name in the result of {@link #getExtras()} to pass dictionary definitions of the text
+     * categorized by parts of speech.
+     *
+     * <p>The dictionary definitions consists of groups of terms keyed by their corresponding parts
+     * of speech. This map-like structure is stored in a {@link Bundle}. The individual parts of
+     * speech can be traversed by {@link Bundle#keySet()} and used to get the corresponding list
+     * of terms as {@link CharSequence}s.
+     *
+     * <ul>
+     *     <li>"noun" -> ["def1", "def2", ...]</li>
+     *     <li>"verb" -> ["def3", "def4", ...]</li>
+     *     <li>...</li>
+     * </ul>
+     *
+     * The set of parts of speech can then be used by
+     * {@link Bundle#getCharSequenceArrayList(String)} to get the list of terms.
+     *
+     * <b>Example</b>:
+     *
+     * {@code for (String partOfSpeech : extras.getBundle(EXTRA_DEFINITIONS).keySet()) {
+     *    ArrayList<CharSequence> terms =
+     *            extras.getBundle(EXTRA_DEFINITIONS).getCharSequenceArrayList(partOfSpeech);
+     *    ...
+     * }}</p>
+     */
+    public static final String EXTRA_DEFINITIONS = "android.view.translation.extra.DEFINITIONS";
+
+    /**
+     * The status code of this {@link TranslationResponseValue}.
+     *
+     * <p>If the status code is {@link #STATUS_ERROR}, no values are attached, and all getters will
+     * return {@code null}.
+     */
+    private final @Status int mStatusCode;
+
+    /**
+     * The translated text result.
+     */
+    @Nullable
+    private final CharSequence mText;
+
+    /**
+     * Extra results associated with the translated text.
+     *
+     * <p>The bundle includes {@link #EXTRA_DEFINITIONS}, obtained by {@link Bundle#getBundle}.
+     * </p>
+     */
+    @NonNull
+    private final Bundle mExtras;
+
+    /**
+     * The transliteration result of the translated text.
+     * TODO: Describe the result structure.
+     */
+    @Nullable
+    private final CharSequence mTransliteration;
+
+    /**
+     * Creates a {@link TranslationResponseValue} with the {@link #STATUS_ERROR} result;
+     */
+    @NonNull
+    public static TranslationResponseValue forError() {
+        return new TranslationResponseValue(STATUS_ERROR, null, Bundle.EMPTY, null);
+    }
+
+    private static CharSequence defaultText() {
+        return null;
+    }
+
+    private static Bundle defaultExtras() {
+        return Bundle.EMPTY;
+    }
+
+    private boolean extrasEquals(Bundle other) {
+        // TODO: Also compare the contents.
+        return Objects.equals(mExtras, other) || (mExtras.isEmpty() && other.isEmpty());
+    }
+
+    private static CharSequence defaultTransliteration() {
+        return null;
+    }
+
+    @DataClass.Suppress("setStatusCode")
+    abstract static class BaseBuilder {
+
+    }
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/translation/TranslationResponseValue.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /** @hide */
+    @android.annotation.IntDef(prefix = "STATUS_", value = {
+        STATUS_SUCCESS,
+        STATUS_ERROR
+    })
+    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface Status {}
+
+    /** @hide */
+    @DataClass.Generated.Member
+    public static String statusToString(@Status int value) {
+        switch (value) {
+            case STATUS_SUCCESS:
+                    return "STATUS_SUCCESS";
+            case STATUS_ERROR:
+                    return "STATUS_ERROR";
+            default: return Integer.toHexString(value);
+        }
+    }
+
+    @DataClass.Generated.Member
+    /* package-private */ TranslationResponseValue(
+            @Status int statusCode,
+            @Nullable CharSequence text,
+            @NonNull Bundle extras,
+            @Nullable CharSequence transliteration) {
+        this.mStatusCode = statusCode;
+
+        if (!(mStatusCode == STATUS_SUCCESS)
+                && !(mStatusCode == STATUS_ERROR)) {
+            throw new java.lang.IllegalArgumentException(
+                    "statusCode was " + mStatusCode + " but must be one of: "
+                            + "STATUS_SUCCESS(" + STATUS_SUCCESS + "), "
+                            + "STATUS_ERROR(" + STATUS_ERROR + ")");
+        }
+
+        this.mText = text;
+        this.mExtras = extras;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mExtras);
+        this.mTransliteration = transliteration;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * The status code of this {@link TranslationResponseValue}.
+     *
+     * <p>If the status code is {@link #STATUS_ERROR}, no values are attached, and all getters will
+     * return {@code null}.
+     */
+    @DataClass.Generated.Member
+    public @Status int getStatusCode() {
+        return mStatusCode;
+    }
+
+    /**
+     * The translated text result.
+     */
+    @DataClass.Generated.Member
+    public @Nullable CharSequence getText() {
+        return mText;
+    }
+
+    /**
+     * Extra results associated with the translated text.
+     *
+     * <p>The bundle includes {@link #EXTRA_DEFINITIONS}, obtained by {@link Bundle#getBundle}.
+     * </p>
+     */
+    @DataClass.Generated.Member
+    public @NonNull Bundle getExtras() {
+        return mExtras;
+    }
+
+    /**
+     * The transliteration result of the translated text.
+     * TODO: Describe the result structure.
+     */
+    @DataClass.Generated.Member
+    public @Nullable CharSequence getTransliteration() {
+        return mTransliteration;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "TranslationResponseValue { " +
+                "statusCode = " + statusToString(mStatusCode) + ", " +
+                "text = " + mText + ", " +
+                "extras = " + mExtras + ", " +
+                "transliteration = " + mTransliteration +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(TranslationResponseValue other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        TranslationResponseValue that = (TranslationResponseValue) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && mStatusCode == that.mStatusCode
+                && Objects.equals(mText, that.mText)
+                && extrasEquals(that.mExtras)
+                && Objects.equals(mTransliteration, that.mTransliteration);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + mStatusCode;
+        _hash = 31 * _hash + Objects.hashCode(mText);
+        _hash = 31 * _hash + Objects.hashCode(mExtras);
+        _hash = 31 * _hash + Objects.hashCode(mTransliteration);
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mText != null) flg |= 0x2;
+        if (mTransliteration != null) flg |= 0x8;
+        dest.writeByte(flg);
+        dest.writeInt(mStatusCode);
+        if (mText != null) dest.writeCharSequence(mText);
+        dest.writeBundle(mExtras);
+        if (mTransliteration != null) dest.writeCharSequence(mTransliteration);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ TranslationResponseValue(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        int statusCode = in.readInt();
+        CharSequence text = (flg & 0x2) == 0 ? null : (CharSequence) in.readCharSequence();
+        Bundle extras = in.readBundle();
+        CharSequence transliteration = (flg & 0x8) == 0 ? null : (CharSequence) in.readCharSequence();
+
+        this.mStatusCode = statusCode;
+
+        if (!(mStatusCode == STATUS_SUCCESS)
+                && !(mStatusCode == STATUS_ERROR)) {
+            throw new java.lang.IllegalArgumentException(
+                    "statusCode was " + mStatusCode + " but must be one of: "
+                            + "STATUS_SUCCESS(" + STATUS_SUCCESS + "), "
+                            + "STATUS_ERROR(" + STATUS_ERROR + ")");
+        }
+
+        this.mText = text;
+        this.mExtras = extras;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mExtras);
+        this.mTransliteration = transliteration;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<TranslationResponseValue> CREATOR
+            = new Parcelable.Creator<TranslationResponseValue>() {
+        @Override
+        public TranslationResponseValue[] newArray(int size) {
+            return new TranslationResponseValue[size];
+        }
+
+        @Override
+        public TranslationResponseValue createFromParcel(@NonNull Parcel in) {
+            return new TranslationResponseValue(in);
+        }
+    };
+
+    /**
+     * A builder for {@link TranslationResponseValue}
+     */
+    @SuppressWarnings("WeakerAccess")
+    @DataClass.Generated.Member
+    public static final class Builder extends BaseBuilder {
+
+        private @Status int mStatusCode;
+        private @Nullable CharSequence mText;
+        private @NonNull Bundle mExtras;
+        private @Nullable CharSequence mTransliteration;
+
+        private long mBuilderFieldsSet = 0L;
+
+        /**
+         * Creates a new Builder.
+         *
+         * @param statusCode
+         *   The status code of this {@link TranslationResponseValue}.
+         *
+         *   <p>If the status code is {@link #STATUS_ERROR}, no values are attached, and all getters will
+         *   return {@code null}.
+         */
+        public Builder(
+                @Status int statusCode) {
+            mStatusCode = statusCode;
+
+            if (!(mStatusCode == STATUS_SUCCESS)
+                    && !(mStatusCode == STATUS_ERROR)) {
+                throw new java.lang.IllegalArgumentException(
+                        "statusCode was " + mStatusCode + " but must be one of: "
+                                + "STATUS_SUCCESS(" + STATUS_SUCCESS + "), "
+                                + "STATUS_ERROR(" + STATUS_ERROR + ")");
+            }
+
+        }
+
+        /**
+         * The translated text result.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setText(@NonNull CharSequence value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x2;
+            mText = value;
+            return this;
+        }
+
+        /**
+         * Extra results associated with the translated text.
+         *
+         * <p>The bundle includes {@link #EXTRA_DEFINITIONS}, obtained by {@link Bundle#getBundle}.
+         * </p>
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setExtras(@NonNull Bundle value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x4;
+            mExtras = value;
+            return this;
+        }
+
+        /**
+         * The transliteration result of the translated text.
+         * TODO: Describe the result structure.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setTransliteration(@NonNull CharSequence value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x8;
+            mTransliteration = value;
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        public @NonNull TranslationResponseValue build() {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x10; // Mark builder used
+
+            if ((mBuilderFieldsSet & 0x2) == 0) {
+                mText = defaultText();
+            }
+            if ((mBuilderFieldsSet & 0x4) == 0) {
+                mExtras = defaultExtras();
+            }
+            if ((mBuilderFieldsSet & 0x8) == 0) {
+                mTransliteration = defaultTransliteration();
+            }
+            TranslationResponseValue o = new TranslationResponseValue(
+                    mStatusCode,
+                    mText,
+                    mExtras,
+                    mTransliteration);
+            return o;
+        }
+
+        private void checkNotUsed() {
+            if ((mBuilderFieldsSet & 0x10) != 0) {
+                throw new IllegalStateException(
+                        "This Builder should not be reused. Use a new Builder instance instead");
+            }
+        }
+    }
+
+    @DataClass.Generated(
+            time = 1622133051937L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/view/translation/TranslationResponseValue.java",
+            inputSignatures = "public static final  int STATUS_SUCCESS\npublic static final  int STATUS_ERROR\npublic static final  java.lang.String EXTRA_DEFINITIONS\nprivate final @android.view.translation.TranslationResponseValue.Status int mStatusCode\nprivate final @android.annotation.Nullable java.lang.CharSequence mText\nprivate final @android.annotation.NonNull android.os.Bundle mExtras\nprivate final @android.annotation.Nullable java.lang.CharSequence mTransliteration\npublic static @android.annotation.NonNull android.view.translation.TranslationResponseValue forError()\nprivate static  java.lang.CharSequence defaultText()\nprivate static  android.os.Bundle defaultExtras()\nprivate  boolean extrasEquals(android.os.Bundle)\nprivate static  java.lang.CharSequence defaultTransliteration()\nclass TranslationResponseValue extends java.lang.Object implements [android.os.Parcelable]\nclass BaseBuilder extends java.lang.Object implements []\[email protected](genBuilder=true, genToString=true, genEqualsHashCode=true, genHiddenConstDefs=true)\nclass BaseBuilder extends java.lang.Object implements []")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/android/view/translation/TranslationSpec.java b/android/view/translation/TranslationSpec.java
new file mode 100644
index 0000000..efc3d8b
--- /dev/null
+++ b/android/view/translation/TranslationSpec.java
@@ -0,0 +1,240 @@
+/*
+ * 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.view.translation;
+
+import android.annotation.NonNull;
+import android.icu.util.ULocale;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.util.Objects;
+
+/**
+ * Specs and additional info for the translation data.
+ *
+ * <p>This spec help specify information such as the language/locale for the translation, as well
+ * as the data format for the translation (text, audio, etc.)</p>
+ */
+@DataClass(genEqualsHashCode = true, genHiddenConstDefs = true, genToString = true,
+        genConstructor = false)
+public final class TranslationSpec implements Parcelable {
+
+    /** Data format for translation is text. */
+    public static final int DATA_FORMAT_TEXT = 1;
+
+    /** @hide */
+    @android.annotation.IntDef(prefix = "DATA_FORMAT_", value = {
+            DATA_FORMAT_TEXT
+    })
+    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface DataFormat {}
+
+    /**
+     * @removed use {@code mLocale} instead.
+     */
+    @Deprecated
+    private final @NonNull String mLanguage;
+
+    /**
+     * {@link ULocale} representing locale information of this spec.
+     */
+    private final @NonNull ULocale mLocale;
+
+    private final @DataFormat int mDataFormat;
+
+    void parcelLocale(Parcel dest, int flags) {
+        dest.writeSerializable(mLocale);
+    }
+
+    static ULocale unparcelLocale(Parcel in) {
+        return (ULocale) in.readSerializable();
+    }
+
+    /**
+     * @removed use {@link #TranslationSpec(ULocale, int)} instead.
+     */
+    @Deprecated
+    public TranslationSpec(@NonNull String language, @DataFormat int dataFormat) {
+        mLanguage = language;
+        mDataFormat = dataFormat;
+        mLocale = new ULocale.Builder().setLanguage(language).build();
+    }
+
+    /**
+     * Constructs a translation spec with the given locale and data format.
+     *
+     * @param locale locale of the associated translation data.
+     * @param dataFormat data format of the associated translation data.
+     */
+    public TranslationSpec(@NonNull ULocale locale, @DataFormat int dataFormat) {
+        Objects.requireNonNull(locale);
+        mLanguage = locale.getLanguage();
+        mLocale = locale;
+        mDataFormat = dataFormat;
+    }
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/translation/TranslationSpec.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * @removed use {@code mLocale} instead.
+     */
+    @DataClass.Generated.Member
+    public @Deprecated @NonNull String getLanguage() {
+        return mLanguage;
+    }
+
+    /**
+     * {@link ULocale} representing locale information of this spec.
+     */
+    @DataClass.Generated.Member
+    public @NonNull ULocale getLocale() {
+        return mLocale;
+    }
+
+    @DataClass.Generated.Member
+    public @DataFormat int getDataFormat() {
+        return mDataFormat;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "TranslationSpec { " +
+                "language = " + mLanguage + ", " +
+                "locale = " + mLocale + ", " +
+                "dataFormat = " + mDataFormat +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@android.annotation.Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(TranslationSpec other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        TranslationSpec that = (TranslationSpec) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && Objects.equals(mLanguage, that.mLanguage)
+                && Objects.equals(mLocale, that.mLocale)
+                && mDataFormat == that.mDataFormat;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + Objects.hashCode(mLanguage);
+        _hash = 31 * _hash + Objects.hashCode(mLocale);
+        _hash = 31 * _hash + mDataFormat;
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeString(mLanguage);
+        parcelLocale(dest, flags);
+        dest.writeInt(mDataFormat);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ TranslationSpec(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        String language = in.readString();
+        ULocale locale = unparcelLocale(in);
+        int dataFormat = in.readInt();
+
+        this.mLanguage = language;
+        com.android.internal.util.AnnotationValidations.validate(
+                Deprecated.class, null, mLanguage);
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mLanguage);
+        this.mLocale = locale;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mLocale);
+        this.mDataFormat = dataFormat;
+        com.android.internal.util.AnnotationValidations.validate(
+                DataFormat.class, null, mDataFormat);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<TranslationSpec> CREATOR
+            = new Parcelable.Creator<TranslationSpec>() {
+        @Override
+        public TranslationSpec[] newArray(int size) {
+            return new TranslationSpec[size];
+        }
+
+        @Override
+        public TranslationSpec createFromParcel(@NonNull Parcel in) {
+            return new TranslationSpec(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1620089623334L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/view/translation/TranslationSpec.java",
+            inputSignatures = "public static final  int DATA_FORMAT_TEXT\nprivate final @java.lang.Deprecated @android.annotation.NonNull java.lang.String mLanguage\nprivate final @android.annotation.NonNull android.icu.util.ULocale mLocale\nprivate final @android.view.translation.TranslationSpec.DataFormat int mDataFormat\n  void parcelLocale(android.os.Parcel,int)\nstatic  android.icu.util.ULocale unparcelLocale(android.os.Parcel)\nclass TranslationSpec extends java.lang.Object implements [android.os.Parcelable]\[email protected](genEqualsHashCode=true, genHiddenConstDefs=true, genToString=true, genConstructor=false)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/android/view/translation/Translator.java b/android/view/translation/Translator.java
new file mode 100644
index 0000000..606f39d
--- /dev/null
+++ b/android/view/translation/Translator.java
@@ -0,0 +1,421 @@
+/*
+ * 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.view.translation;
+
+import static android.view.translation.TranslationManager.STATUS_SYNC_CALL_FAIL;
+import static android.view.translation.TranslationManager.SYNC_CALLS_TIMEOUT_MS;
+import static android.view.translation.UiTranslationController.DEBUG;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.RemoteException;
+import android.service.translation.ITranslationCallback;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.IResultReceiver;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * The {@link Translator} for translation, defined by a {@link TranslationContext}.
+ */
+@SuppressLint("NotCloseable")
+public class Translator {
+
+    private static final String TAG = "Translator";
+
+    // TODO: make this configurable and cross the Translation component
+    private static boolean sDEBUG = false;
+
+    private final Object mLock = new Object();
+
+    private int mId;
+
+    @NonNull
+    private final Context mContext;
+
+    @NonNull
+    private final TranslationContext mTranslationContext;
+
+    @NonNull
+    private final TranslationManager mManager;
+
+    @NonNull
+    private final Handler mHandler;
+
+    /**
+     * Interface to the system_server binder object.
+     */
+    private ITranslationManager mSystemServerBinder;
+
+    /**
+     * Direct interface to the TranslationService binder object.
+     */
+    @Nullable
+    private ITranslationDirectManager mDirectServiceBinder;
+
+    @NonNull
+    private final ServiceBinderReceiver mServiceBinderReceiver;
+
+    @GuardedBy("mLock")
+    private boolean mDestroyed;
+
+    /**
+     * Name of the {@link IResultReceiver} extra used to pass the binder interface to Translator.
+     * @hide
+     */
+    public static final String EXTRA_SERVICE_BINDER = "binder";
+    /**
+     * Name of the extra used to pass the session id to Translator.
+     * @hide
+     */
+    public static final String EXTRA_SESSION_ID = "sessionId";
+
+    static class ServiceBinderReceiver extends IResultReceiver.Stub {
+        // TODO: refactor how translator is instantiated after removing deprecated createTranslator.
+        private final Translator mTranslator;
+        private final CountDownLatch mLatch = new CountDownLatch(1);
+        private int mSessionId;
+
+        private Consumer<Translator> mCallback;
+
+        ServiceBinderReceiver(Translator translator, Consumer<Translator> callback) {
+            mTranslator = translator;
+            mCallback = callback;
+        }
+
+        ServiceBinderReceiver(Translator translator) {
+            mTranslator = translator;
+        }
+
+        int getSessionStateResult() throws TimeoutException {
+            try {
+                if (!mLatch.await(SYNC_CALLS_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    throw new TimeoutException(
+                            "Session not created in " + SYNC_CALLS_TIMEOUT_MS + "ms");
+                }
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                throw new TimeoutException("Session not created because interrupted");
+            }
+            return mSessionId;
+        }
+
+        @Override
+        public void send(int resultCode, Bundle resultData) {
+            if (resultCode == STATUS_SYNC_CALL_FAIL) {
+                mLatch.countDown();
+                if (mCallback != null) {
+                    mCallback.accept(null);
+                }
+                return;
+            }
+            final IBinder binder;
+            if (resultData != null) {
+                mSessionId = resultData.getInt(EXTRA_SESSION_ID);
+                binder = resultData.getBinder(EXTRA_SERVICE_BINDER);
+                if (binder == null) {
+                    Log.wtf(TAG, "No " + EXTRA_SERVICE_BINDER + " extra result");
+                    return;
+                }
+            } else {
+                binder = null;
+            }
+            mTranslator.setServiceBinder(binder);
+            mLatch.countDown();
+            if (mCallback != null) {
+                mCallback.accept(mTranslator);
+            }
+        }
+
+        // TODO(b/176464808): maybe make SyncResultReceiver.TimeoutException constructor public
+        //  and use it.
+        static final class TimeoutException extends Exception {
+            private TimeoutException(String msg) {
+                super(msg);
+            }
+        }
+    }
+
+    /**
+     * Create the Translator.
+     *
+     * @hide
+     */
+    public Translator(@NonNull Context context,
+            @NonNull TranslationContext translationContext, int sessionId,
+            @NonNull TranslationManager translationManager, @NonNull Handler handler,
+            @Nullable ITranslationManager systemServerBinder,
+            @NonNull Consumer<Translator> callback) {
+        mContext = context;
+        mTranslationContext = translationContext;
+        mId = sessionId;
+        mManager = translationManager;
+        mHandler = handler;
+        mSystemServerBinder = systemServerBinder;
+        mServiceBinderReceiver = new ServiceBinderReceiver(this, callback);
+
+        try {
+            mSystemServerBinder.onSessionCreated(mTranslationContext, mId,
+                    mServiceBinderReceiver, mContext.getUserId());
+        } catch (RemoteException e) {
+            Log.w(TAG, "RemoteException calling startSession(): " + e);
+        }
+    }
+
+    /**
+     * Create the Translator.
+     *
+     * @hide
+     */
+    public Translator(@NonNull Context context,
+            @NonNull TranslationContext translationContext, int sessionId,
+            @NonNull TranslationManager translationManager, @NonNull Handler handler,
+            @Nullable ITranslationManager systemServerBinder) {
+        mContext = context;
+        mTranslationContext = translationContext;
+        mId = sessionId;
+        mManager = translationManager;
+        mHandler = handler;
+        mSystemServerBinder = systemServerBinder;
+        mServiceBinderReceiver = new ServiceBinderReceiver(this);
+    }
+
+    /**
+     * Starts this Translator session.
+     */
+    void start() {
+        try {
+            mSystemServerBinder.onSessionCreated(mTranslationContext, mId,
+                    mServiceBinderReceiver, mContext.getUserId());
+        } catch (RemoteException e) {
+            Log.w(TAG, "RemoteException calling startSession(): " + e);
+        }
+    }
+
+    /**
+     * Wait this Translator session created.
+     *
+     * @return {@code true} if the session is created successfully.
+     */
+    boolean isSessionCreated() throws ServiceBinderReceiver.TimeoutException {
+        int receivedId = mServiceBinderReceiver.getSessionStateResult();
+        return receivedId > 0;
+    }
+
+    private int getNextRequestId() {
+        // Get from manager to keep the request id unique to different Translators
+        return mManager.getAvailableRequestId().getAndIncrement();
+    }
+
+    private void setServiceBinder(@Nullable IBinder binder) {
+        synchronized (mLock) {
+            if (mDirectServiceBinder != null) {
+                return;
+            }
+            if (binder != null) {
+                mDirectServiceBinder = ITranslationDirectManager.Stub.asInterface(binder);
+            }
+        }
+    }
+
+    /** @hide */
+    public TranslationContext getTranslationContext() {
+        return mTranslationContext;
+    }
+
+    /** @hide */
+    public int getTranslatorId() {
+        return mId;
+    }
+
+    /** @hide */
+    public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
+        pw.print(prefix); pw.print("translationContext: "); pw.println(mTranslationContext);
+    }
+
+    /**
+     * Requests a translation for the provided {@link TranslationRequest} using the Translator's
+     * source spec and destination spec.
+     *
+     * @param request {@link TranslationRequest} request to be translate.
+     *
+     * @throws IllegalStateException if this Translator session was destroyed when called.
+     *
+     * @removed use {@link #translate(TranslationRequest, CancellationSignal,
+     *             Executor, Consumer)} instead.
+     */
+    @Deprecated
+    @Nullable
+    public void translate(@NonNull TranslationRequest request,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<TranslationResponse> callback) {
+        Objects.requireNonNull(request, "Translation request cannot be null");
+        Objects.requireNonNull(executor, "Executor cannot be null");
+        Objects.requireNonNull(callback, "Callback cannot be null");
+
+        if (isDestroyed()) {
+            // TODO(b/176464808): Disallow multiple Translator now, it will throw
+            //  IllegalStateException. Need to discuss if we can allow multiple Translators.
+            throw new IllegalStateException(
+                    "This translator has been destroyed");
+        }
+
+        final ITranslationCallback responseCallback =
+                new TranslationResponseCallbackImpl(callback, executor);
+        try {
+            mDirectServiceBinder.onTranslationRequest(request, mId,
+                    CancellationSignal.createTransport(), responseCallback);
+        } catch (RemoteException e) {
+            Log.w(TAG, "RemoteException calling requestTranslate(): " + e);
+        }
+    }
+
+    /**
+     * Requests a translation for the provided {@link TranslationRequest} using the Translator's
+     * source spec and destination spec.
+     *
+     * @param request {@link TranslationRequest} request to be translate.
+     * @param cancellationSignal signal to cancel the operation in progress.
+     * @param executor Executor to run callback operations
+     * @param callback {@link Consumer} to receive the translation response. Multiple responses may
+     *                 be received if {@link TranslationRequest#FLAG_PARTIAL_RESPONSES} is set.
+     *
+     * @throws IllegalStateException if this Translator session was destroyed when called.
+     */
+    @Nullable
+    public void translate(@NonNull TranslationRequest request,
+            @Nullable CancellationSignal cancellationSignal,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<TranslationResponse> callback) {
+        Objects.requireNonNull(request, "Translation request cannot be null");
+        Objects.requireNonNull(executor, "Executor cannot be null");
+        Objects.requireNonNull(callback, "Callback cannot be null");
+
+        if (isDestroyed()) {
+            // TODO(b/176464808): Disallow multiple Translator now, it will throw
+            //  IllegalStateException. Need to discuss if we can allow multiple Translators.
+            throw new IllegalStateException(
+                    "This translator has been destroyed");
+        }
+
+        ICancellationSignal transport = null;
+        if (cancellationSignal != null) {
+            transport = CancellationSignal.createTransport();
+            cancellationSignal.setRemote(transport);
+        }
+        final ITranslationCallback responseCallback =
+                new TranslationResponseCallbackImpl(callback, executor);
+
+        try {
+            mDirectServiceBinder.onTranslationRequest(request, mId, transport,
+                    responseCallback);
+        } catch (RemoteException e) {
+            Log.w(TAG, "RemoteException calling requestTranslate(): " + e);
+        }
+    }
+
+    /**
+     * Destroy this Translator.
+     */
+    public void destroy() {
+        synchronized (mLock) {
+            if (mDestroyed) {
+                return;
+            }
+            mDestroyed = true;
+            try {
+                mDirectServiceBinder.onFinishTranslationSession(mId);
+            } catch (RemoteException e) {
+                Log.w(TAG, "RemoteException calling onSessionFinished");
+            }
+            mDirectServiceBinder = null;
+            mManager.removeTranslator(mId);
+        }
+    }
+
+    /**
+     * Returns whether or not this Translator has been destroyed.
+     *
+     * @see #destroy()
+     */
+    public boolean isDestroyed() {
+        synchronized (mLock) {
+            return mDestroyed;
+        }
+    }
+
+    // TODO: add methods for UI-toolkit case.
+    /** @hide */
+    public void requestUiTranslate(@NonNull TranslationRequest request,
+            @NonNull Executor executor,
+            @NonNull Consumer<TranslationResponse> callback) {
+        if (mDirectServiceBinder == null) {
+            Log.wtf(TAG, "Translator created without proper initialization.");
+            return;
+        }
+        final ITranslationCallback translationCallback =
+                new TranslationResponseCallbackImpl(callback, executor);
+        try {
+            mDirectServiceBinder.onTranslationRequest(request, mId,
+                    CancellationSignal.createTransport(), translationCallback);
+        } catch (RemoteException e) {
+            Log.w(TAG, "RemoteException calling flushRequest");
+        }
+    }
+
+    private static class TranslationResponseCallbackImpl extends ITranslationCallback.Stub {
+
+        private final Consumer<TranslationResponse> mCallback;
+        private final Executor mExecutor;
+
+        TranslationResponseCallbackImpl(Consumer<TranslationResponse> callback, Executor executor) {
+            mCallback = callback;
+            mExecutor = executor;
+        }
+
+        @Override
+        public void onTranslationResponse(TranslationResponse response) throws RemoteException {
+            if (DEBUG) {
+                Log.i(TAG, "onTranslationResponse called.");
+            }
+            final Runnable runnable =
+                    () -> mCallback.accept(response);
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(runnable);
+            } finally {
+                restoreCallingIdentity(token);
+            }
+        }
+    }
+}
diff --git a/android/view/translation/UiTranslationController.java b/android/view/translation/UiTranslationController.java
new file mode 100644
index 0000000..a833591
--- /dev/null
+++ b/android/view/translation/UiTranslationController.java
@@ -0,0 +1,726 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.translation;
+
+import static android.view.translation.UiTranslationManager.STATE_UI_TRANSLATION_FINISHED;
+import static android.view.translation.UiTranslationManager.STATE_UI_TRANSLATION_PAUSED;
+import static android.view.translation.UiTranslationManager.STATE_UI_TRANSLATION_RESUMED;
+import static android.view.translation.UiTranslationManager.STATE_UI_TRANSLATION_STARTED;
+
+import android.annotation.NonNull;
+import android.annotation.WorkerThread;
+import android.app.Activity;
+import android.app.assist.ActivityId;
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.IntArray;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewRootImpl;
+import android.view.WindowManagerGlobal;
+import android.view.autofill.AutofillId;
+import android.view.translation.UiTranslationManager.UiTranslationState;
+import android.widget.TextView;
+import android.widget.TextViewTranslationCallback;
+
+import com.android.internal.util.function.pooled.PooledLambda;
+
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+
+/**
+ * A controller to manage the ui translation requests for the {@link Activity}.
+ *
+ * @hide
+ */
+public class UiTranslationController {
+
+    public static final boolean DEBUG = Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG);
+
+    private static final String TAG = "UiTranslationController";
+    @NonNull
+    private final Activity mActivity;
+    @NonNull
+    private final Context mContext;
+    @NonNull
+    private final Object mLock = new Object();
+
+    // Each Translator is distinguished by sourceSpec and desSepc.
+    @NonNull
+    private final ArrayMap<Pair<TranslationSpec, TranslationSpec>, Translator> mTranslators;
+    @NonNull
+    private final ArrayMap<AutofillId, WeakReference<View>> mViews;
+    /**
+     * Views for which {@link UiTranslationSpec#shouldPadContentForCompat()} is true.
+     */
+    @NonNull
+    private final ArraySet<AutofillId> mViewsToPadContent;
+    @NonNull
+    private final HandlerThread mWorkerThread;
+    @NonNull
+    private final Handler mWorkerHandler;
+    private int mCurrentState;
+    @NonNull
+    private ArraySet<AutofillId> mLastRequestAutofillIds;
+
+    public UiTranslationController(Activity activity, Context context) {
+        mActivity = activity;
+        mContext = context;
+        mViews = new ArrayMap<>();
+        mTranslators = new ArrayMap<>();
+        mViewsToPadContent = new ArraySet<>();
+
+        mWorkerThread =
+                new HandlerThread("UiTranslationController_" + mActivity.getComponentName(),
+                        Process.THREAD_PRIORITY_FOREGROUND);
+        mWorkerThread.start();
+        mWorkerHandler = mWorkerThread.getThreadHandler();
+    }
+
+    /**
+     * Update the Ui translation state.
+     */
+    public void updateUiTranslationState(@UiTranslationState int state, TranslationSpec sourceSpec,
+            TranslationSpec targetSpec, List<AutofillId> views,
+            UiTranslationSpec uiTranslationSpec) {
+        if (!mActivity.isResumed() && (state == STATE_UI_TRANSLATION_STARTED
+                || state == STATE_UI_TRANSLATION_RESUMED)) {
+            return;
+        }
+
+        Log.i(TAG, "updateUiTranslationState state: " + stateToString(state)
+                + (DEBUG ? (", views: " + views + ", spec: " + uiTranslationSpec) : ""));
+        synchronized (mLock) {
+            mCurrentState = state;
+            if (views != null) {
+                setLastRequestAutofillIdsLocked(views);
+            }
+        }
+        switch (state) {
+            case STATE_UI_TRANSLATION_STARTED:
+                if (uiTranslationSpec != null && uiTranslationSpec.shouldPadContentForCompat()) {
+                    synchronized (mLock) {
+                        mViewsToPadContent.addAll(views);
+                        // TODO: Cleanup disappeared views from mViews and mViewsToPadContent at
+                        //  some appropriate place.
+                    }
+                }
+                final Pair<TranslationSpec, TranslationSpec> specs =
+                        new Pair<>(sourceSpec, targetSpec);
+                if (!mTranslators.containsKey(specs)) {
+                    mWorkerHandler.sendMessage(PooledLambda.obtainMessage(
+                            UiTranslationController::createTranslatorAndStart,
+                            UiTranslationController.this, sourceSpec, targetSpec, views));
+                } else {
+                    onUiTranslationStarted(mTranslators.get(specs), views);
+                }
+                break;
+            case STATE_UI_TRANSLATION_PAUSED:
+                runForEachView((view, callback) -> callback.onHideTranslation(view));
+                break;
+            case STATE_UI_TRANSLATION_RESUMED:
+                runForEachView((view, callback) -> callback.onShowTranslation(view));
+                break;
+            case STATE_UI_TRANSLATION_FINISHED:
+                destroyTranslators();
+                runForEachView((view, callback) -> {
+                    callback.onClearTranslation(view);
+                    view.clearViewTranslationResponse();
+                    if (view.hasTranslationTransientState()) {
+                        view.setHasTransientState(false);
+                        view.setHasTranslationTransientState(false);
+                    }
+                });
+                notifyTranslationFinished(/* activityDestroyed= */ false);
+                synchronized (mLock) {
+                    mViews.clear();
+                }
+                break;
+            default:
+                Log.w(TAG, "onAutoTranslationStateChange(): unknown state: " + state);
+        }
+    }
+
+    /**
+     * Called when the Activity is destroyed.
+     */
+    public void onActivityDestroyed() {
+        synchronized (mLock) {
+            if (DEBUG) {
+                Log.i(TAG,
+                        "onActivityDestroyed(): mCurrentState is " + stateToString(mCurrentState));
+            }
+            if (mCurrentState != STATE_UI_TRANSLATION_FINISHED) {
+                notifyTranslationFinished(/* activityDestroyed= */ true);
+            }
+            mViews.clear();
+            destroyTranslators();
+            mWorkerThread.quitSafely();
+        }
+    }
+
+    private void notifyTranslationFinished(boolean activityDestroyed) {
+        UiTranslationManager manager = mContext.getSystemService(UiTranslationManager.class);
+        if (manager != null) {
+            manager.onTranslationFinished(activityDestroyed,
+                    new ActivityId(mActivity.getTaskId(), mActivity.getShareableActivityToken()),
+                    mActivity.getComponentName());
+        }
+    }
+
+    private void setLastRequestAutofillIdsLocked(List<AutofillId> views) {
+        if (mLastRequestAutofillIds == null) {
+            mLastRequestAutofillIds = new ArraySet<>();
+        }
+        if (mLastRequestAutofillIds.size() > 0) {
+            mLastRequestAutofillIds.clear();
+        }
+        mLastRequestAutofillIds.addAll(views);
+    }
+
+    /**
+     * Called to dump the translation information for Activity.
+     */
+    public void dump(String outerPrefix, PrintWriter pw) {
+        pw.print(outerPrefix); pw.println("UiTranslationController:");
+        final String pfx = outerPrefix + "  ";
+        pw.print(pfx); pw.print("activity: "); pw.print(mActivity);
+        pw.print(pfx); pw.print("resumed: "); pw.println(mActivity.isResumed());
+        pw.print(pfx); pw.print("current state: "); pw.println(mCurrentState);
+        final int translatorSize = mTranslators.size();
+        pw.print(outerPrefix); pw.print("number translator: "); pw.println(translatorSize);
+        for (int i = 0; i < translatorSize; i++) {
+            pw.print(outerPrefix); pw.print("#"); pw.println(i);
+            final Translator translator = mTranslators.valueAt(i);
+            translator.dump(outerPrefix, pw);
+            pw.println();
+        }
+        synchronized (mLock) {
+            final int viewSize = mViews.size();
+            pw.print(outerPrefix); pw.print("number views: "); pw.println(viewSize);
+            for (int i = 0; i < viewSize; i++) {
+                pw.print(outerPrefix); pw.print("#"); pw.println(i);
+                final AutofillId autofillId = mViews.keyAt(i);
+                final View view = mViews.valueAt(i).get();
+                pw.print(pfx); pw.print("autofillId: "); pw.println(autofillId);
+                pw.print(pfx); pw.print("view:"); pw.println(view);
+            }
+            pw.print(outerPrefix); pw.print("padded views: "); pw.println(mViewsToPadContent);
+        }
+        if (DEBUG) {
+            dumpViewByTraversal(outerPrefix, pw);
+        }
+    }
+
+    private void dumpViewByTraversal(String outerPrefix, PrintWriter pw) {
+        final ArrayList<ViewRootImpl> roots =
+                WindowManagerGlobal.getInstance().getRootViews(mActivity.getActivityToken());
+        pw.print(outerPrefix); pw.println("Dump views:");
+        for (int rootNum = 0; rootNum < roots.size(); rootNum++) {
+            final View rootView = roots.get(rootNum).getView();
+            if (rootView instanceof ViewGroup) {
+                dumpChildren((ViewGroup) rootView, outerPrefix, pw);
+            } else {
+                dumpViewInfo(rootView, outerPrefix, pw);
+            }
+        }
+    }
+
+    private void dumpChildren(ViewGroup viewGroup, String outerPrefix, PrintWriter pw) {
+        final int childCount = viewGroup.getChildCount();
+        for (int i = 0; i < childCount; ++i) {
+            final View child = viewGroup.getChildAt(i);
+            if (child instanceof ViewGroup) {
+                pw.print(outerPrefix); pw.println("Children: ");
+                pw.print(outerPrefix); pw.print(outerPrefix); pw.println(child);
+                dumpChildren((ViewGroup) child, outerPrefix, pw);
+            } else {
+                pw.print(outerPrefix); pw.println("End Children: ");
+                pw.print(outerPrefix); pw.print(outerPrefix); pw.print(child);
+                dumpViewInfo(child, outerPrefix, pw);
+            }
+        }
+    }
+
+    private void dumpViewInfo(View view, String outerPrefix, PrintWriter pw) {
+        final AutofillId autofillId = view.getAutofillId();
+        pw.print(outerPrefix); pw.print("autofillId: "); pw.print(autofillId);
+        // TODO: print TranslationTransformation
+        boolean isContainsView = false;
+        boolean isRequestedView = false;
+        synchronized (mLock) {
+            if (mLastRequestAutofillIds.contains(autofillId)) {
+                isRequestedView = true;
+            }
+            final WeakReference<View> viewRef = mViews.get(autofillId);
+            if (viewRef != null && viewRef.get() != null) {
+                isContainsView = true;
+            }
+        }
+        pw.print(outerPrefix); pw.print("isContainsView: "); pw.print(isContainsView);
+        pw.print(outerPrefix); pw.print("isRequestedView: "); pw.println(isRequestedView);
+    }
+
+    /**
+     * The method is used by {@link Translator}, it will be called when the translation is done. The
+     * translation result can be get from here.
+     */
+    public void onTranslationCompleted(TranslationResponse response) {
+        if (response == null || response.getTranslationStatus()
+                != TranslationResponse.TRANSLATION_STATUS_SUCCESS) {
+            Log.w(TAG, "Fail result from TranslationService, status=" + (response == null
+                    ? "null"
+                    : response.getTranslationStatus()));
+            return;
+        }
+        final SparseArray<ViewTranslationResponse> translatedResult =
+                response.getViewTranslationResponses();
+        final SparseArray<ViewTranslationResponse> viewsResult = new SparseArray<>();
+        final SparseArray<LongSparseArray<ViewTranslationResponse>> virtualViewsResult =
+                new SparseArray<>();
+        final IntArray viewIds = new IntArray(1);
+        for (int i = 0; i < translatedResult.size(); i++) {
+            final ViewTranslationResponse result = translatedResult.valueAt(i);
+            final AutofillId autofillId = result.getAutofillId();
+            if (viewIds.indexOf(autofillId.getViewId()) < 0) {
+                viewIds.add(autofillId.getViewId());
+            }
+            if (autofillId.isNonVirtual()) {
+                viewsResult.put(translatedResult.keyAt(i), result);
+            } else {
+                final boolean isVirtualViewAdded =
+                        virtualViewsResult.indexOfKey(autofillId.getViewId()) >= 0;
+                final LongSparseArray<ViewTranslationResponse> childIds =
+                        isVirtualViewAdded ? virtualViewsResult.get(autofillId.getViewId())
+                                : new LongSparseArray<>();
+                childIds.put(autofillId.getVirtualChildLongId(), result);
+                if (!isVirtualViewAdded) {
+                    virtualViewsResult.put(autofillId.getViewId(), childIds);
+                }
+            }
+        }
+        // Traverse tree and get views by the responsed AutofillId
+        findViewsTraversalByAutofillIds(viewIds);
+
+        if (viewsResult.size() > 0) {
+            onTranslationCompleted(viewsResult);
+        }
+        if (virtualViewsResult.size() > 0) {
+            onVirtualViewTranslationCompleted(virtualViewsResult);
+        }
+    }
+
+    /**
+     * The method is used to handle the translation result for the vertual views.
+     */
+    private void onVirtualViewTranslationCompleted(
+            SparseArray<LongSparseArray<ViewTranslationResponse>> translatedResult) {
+        if (!mActivity.isResumed()) {
+            if (DEBUG) {
+                Log.v(TAG, "onTranslationCompleted: Activity is not resumed.");
+            }
+            return;
+        }
+        synchronized (mLock) {
+            if (mCurrentState == STATE_UI_TRANSLATION_FINISHED) {
+                Log.w(TAG, "onTranslationCompleted: the translation state is finished now. "
+                        + "Skip to show the translated text.");
+                return;
+            }
+            for (int i = 0; i < translatedResult.size(); i++) {
+                final AutofillId autofillId = new AutofillId(translatedResult.keyAt(i));
+                final View view = mViews.get(autofillId).get();
+                if (view == null) {
+                    Log.w(TAG, "onTranslationCompleted: the view for autofill id " + autofillId
+                            + " may be gone.");
+                    continue;
+                }
+                final LongSparseArray<ViewTranslationResponse> virtualChildResponse =
+                        translatedResult.valueAt(i);
+                if (DEBUG) {
+                    Log.v(TAG, "onVirtualViewTranslationCompleted: received response for "
+                            + "AutofillId " + autofillId);
+                }
+                mActivity.runOnUiThread(() -> {
+                    if (view.getViewTranslationCallback() == null) {
+                        if (DEBUG) {
+                            Log.d(TAG, view + " doesn't support showing translation because of "
+                                    + "null ViewTranslationCallback.");
+                        }
+                        return;
+                    }
+                    view.onVirtualViewTranslationResponses(virtualChildResponse);
+                    if (view.getViewTranslationCallback() != null) {
+                        view.getViewTranslationCallback().onShowTranslation(view);
+                    }
+                });
+            }
+        }
+    }
+
+    /**
+     * The method is used to handle the translation result for non-vertual views.
+     */
+    private void onTranslationCompleted(SparseArray<ViewTranslationResponse> translatedResult) {
+        if (!mActivity.isResumed()) {
+            if (DEBUG) {
+                Log.v(TAG, "onTranslationCompleted: Activity is not resumed.");
+            }
+            return;
+        }
+        final int resultCount = translatedResult.size();
+        if (DEBUG) {
+            Log.v(TAG, "onTranslationCompleted: receive " + resultCount + " responses.");
+        }
+        synchronized (mLock) {
+            if (mCurrentState == STATE_UI_TRANSLATION_FINISHED) {
+                Log.w(TAG, "onTranslationCompleted: the translation state is finished now. "
+                        + "Skip to show the translated text.");
+                return;
+            }
+            for (int i = 0; i < resultCount; i++) {
+                final ViewTranslationResponse response = translatedResult.valueAt(i);
+                if (DEBUG) {
+                    Log.v(TAG, "onTranslationCompleted: "
+                            + sanitizedViewTranslationResponse(response));
+                }
+                final AutofillId autofillId = response.getAutofillId();
+                if (autofillId == null) {
+                    Log.w(TAG, "No AutofillId is set in ViewTranslationResponse");
+                    continue;
+                }
+                final View view = mViews.get(autofillId).get();
+                if (view == null) {
+                    Log.w(TAG, "onTranslationCompleted: the view for autofill id " + autofillId
+                            + " may be gone.");
+                    continue;
+                }
+                mActivity.runOnUiThread(() -> {
+                    if (view.getViewTranslationResponse() != null
+                            && view.getViewTranslationResponse().equals(response)) {
+                        if (DEBUG) {
+                            Log.d(TAG, "Duplicate ViewTranslationResponse for " + autofillId
+                                    + ". Ignoring.");
+                        }
+                        return;
+                    }
+                    ViewTranslationCallback callback = view.getViewTranslationCallback();
+                    if (callback == null) {
+                        if (view instanceof TextView) {
+                            // developer doesn't provide their override, we set the default TextView
+                            // implementation.
+                            callback = new TextViewTranslationCallback();
+                            view.setViewTranslationCallback(callback);
+                        } else {
+                            if (DEBUG) {
+                                Log.d(TAG, view + " doesn't support showing translation because of "
+                                        + "null ViewTranslationCallback.");
+                            }
+                            return;
+                        }
+                    }
+                    callback.setAnimationDurationMillis(ANIMATION_DURATION_MILLIS);
+                    if (mViewsToPadContent.contains(autofillId)) {
+                        callback.enableContentPadding();
+                    }
+                    view.onViewTranslationResponse(response);
+                    callback.onShowTranslation(view);
+                });
+            }
+        }
+    }
+
+    // TODO: Use a device config value.
+    private static final int ANIMATION_DURATION_MILLIS = 250;
+
+    /**
+     * Creates a Translator for the given source and target translation specs and start the ui
+     * translation when the Translator is created successfully.
+     */
+    @WorkerThread
+    private void createTranslatorAndStart(TranslationSpec sourceSpec, TranslationSpec targetSpec,
+            List<AutofillId> views) {
+        // Create Translator
+        final Translator translator = createTranslatorIfNeeded(sourceSpec, targetSpec);
+        if (translator == null) {
+            Log.w(TAG, "Can not create Translator for sourceSpec:" + sourceSpec + " targetSpec:"
+                    + targetSpec);
+            return;
+        }
+        onUiTranslationStarted(translator, views);
+    }
+
+    @WorkerThread
+    private void sendTranslationRequest(Translator translator,
+            List<ViewTranslationRequest> requests) {
+        if (requests.size() == 0) {
+            Log.w(TAG, "No ViewTranslationRequest was collected.");
+            return;
+        }
+        final TranslationRequest request = new TranslationRequest.Builder()
+                .setViewTranslationRequests(requests)
+                .build();
+        if (DEBUG) {
+            StringBuilder msg = new StringBuilder("sendTranslationRequest:{requests=[");
+            for (ViewTranslationRequest viewRequest: requests) {
+                msg.append("{request=")
+                        .append(sanitizedViewTranslationRequest(viewRequest))
+                        .append("}, ");
+            }
+            Log.d(TAG, "sendTranslationRequest: " + msg.toString());
+        }
+        translator.requestUiTranslate(request, (r) -> r.run(), this::onTranslationCompleted);
+    }
+
+    /**
+     * Called when there is an ui translation request comes to request view translation.
+     */
+    private void onUiTranslationStarted(Translator translator, List<AutofillId> views) {
+        synchronized (mLock) {
+            // Filter the request views' AutofillId
+            SparseIntArray virtualViewChildCount = getRequestVirtualViewChildCount(views);
+            Map<AutofillId, long[]> viewIds = new ArrayMap<>();
+            Map<AutofillId, Integer> unusedIndices = null;
+            for (int i = 0; i < views.size(); i++) {
+                AutofillId autofillId = views.get(i);
+                if (autofillId.isNonVirtual()) {
+                    viewIds.put(autofillId, null);
+                } else {
+                    if (unusedIndices == null) {
+                        unusedIndices = new ArrayMap<>();
+                    }
+                    // The virtual id get from content capture is long, see getVirtualChildLongId()
+                    // e.g. 1001, 1001:2, 1002:1 -> 1001, <1,2>; 1002, <1>
+                    AutofillId virtualViewAutofillId = new AutofillId(autofillId.getViewId());
+                    long[] childs;
+                    int end = 0;
+                    if (viewIds.containsKey(virtualViewAutofillId)) {
+                        childs = viewIds.get(virtualViewAutofillId);
+                        end = unusedIndices.get(virtualViewAutofillId);
+                    } else {
+                        int childCount = virtualViewChildCount.get(autofillId.getViewId());
+                        childs = new long[childCount];
+                        viewIds.put(virtualViewAutofillId, childs);
+                    }
+                    unusedIndices.put(virtualViewAutofillId, end + 1);
+                    childs[end] = autofillId.getVirtualChildLongId();
+                }
+            }
+            ArrayList<ViewTranslationRequest> requests = new ArrayList<>();
+            int[] supportedFormats = getSupportedFormatsLocked();
+            ArrayList<ViewRootImpl> roots =
+                    WindowManagerGlobal.getInstance().getRootViews(mActivity.getActivityToken());
+            TranslationCapability capability =
+                    getTranslationCapability(translator.getTranslationContext());
+            mActivity.runOnUiThread(() -> {
+                // traverse the hierarchy to collect ViewTranslationRequests
+                for (int rootNum = 0; rootNum < roots.size(); rootNum++) {
+                    View rootView = roots.get(rootNum).getView();
+                    rootView.dispatchCreateViewTranslationRequest(viewIds, supportedFormats,
+                            capability, requests);
+                }
+                mWorkerHandler.sendMessage(PooledLambda.obtainMessage(
+                        UiTranslationController::sendTranslationRequest,
+                        UiTranslationController.this, translator, requests));
+            });
+        }
+    }
+
+    private SparseIntArray getRequestVirtualViewChildCount(List<AutofillId> views) {
+        SparseIntArray virtualViewCount = new SparseIntArray();
+        for (int i = 0; i < views.size(); i++) {
+            AutofillId autofillId = views.get(i);
+            if (!autofillId.isNonVirtual()) {
+                int virtualViewId = autofillId.getViewId();
+                if (virtualViewCount.indexOfKey(virtualViewId) < 0) {
+                    virtualViewCount.put(virtualViewId, 1);
+                } else {
+                    virtualViewCount.put(virtualViewId, (virtualViewCount.get(virtualViewId) + 1));
+                }
+            }
+        }
+        return virtualViewCount;
+    }
+
+    private int[] getSupportedFormatsLocked() {
+        // We only support text now
+        return new int[] {TranslationSpec.DATA_FORMAT_TEXT};
+    }
+
+    private TranslationCapability getTranslationCapability(TranslationContext translationContext) {
+        // We only support text to text capability now, we will query real status from service when
+        // we support more translation capabilities.
+        return new TranslationCapability(TranslationCapability.STATE_ON_DEVICE,
+                translationContext.getSourceSpec(),
+                translationContext.getTargetSpec(), /* uiTranslationEnabled= */ true,
+                /* supportedTranslationFlags= */ 0);
+    }
+
+    private void findViewsTraversalByAutofillIds(IntArray sourceViewIds) {
+        final ArrayList<ViewRootImpl> roots =
+                WindowManagerGlobal.getInstance().getRootViews(mActivity.getActivityToken());
+        for (int rootNum = 0; rootNum < roots.size(); rootNum++) {
+            final View rootView = roots.get(rootNum).getView();
+            if (rootView instanceof ViewGroup) {
+                findViewsTraversalByAutofillIds((ViewGroup) rootView, sourceViewIds);
+            } else {
+                addViewIfNeeded(sourceViewIds, rootView);
+            }
+        }
+    }
+
+    private void findViewsTraversalByAutofillIds(ViewGroup viewGroup,
+            IntArray sourceViewIds) {
+        final int childCount = viewGroup.getChildCount();
+        for (int i = 0; i < childCount; ++i) {
+            final View child = viewGroup.getChildAt(i);
+            if (child instanceof ViewGroup) {
+                findViewsTraversalByAutofillIds((ViewGroup) child, sourceViewIds);
+            } else {
+                addViewIfNeeded(sourceViewIds, child);
+            }
+        }
+    }
+
+    private void addViewIfNeeded(IntArray sourceViewIds, View view) {
+        final AutofillId autofillId = view.getAutofillId();
+        if ((sourceViewIds.indexOf(autofillId.getViewId()) >= 0)
+                && !mViews.containsKey(autofillId)) {
+            mViews.put(autofillId, new WeakReference<>(view));
+        }
+    }
+
+    private void runForEachView(BiConsumer<View, ViewTranslationCallback> action) {
+        synchronized (mLock) {
+            final ArrayMap<AutofillId, WeakReference<View>> views = new ArrayMap<>(mViews);
+            if (views.size() == 0) {
+                Log.w(TAG, "No views can be excuted for runForEachView.");
+            }
+            mActivity.runOnUiThread(() -> {
+                final int viewCounts = views.size();
+                for (int i = 0; i < viewCounts; i++) {
+                    final View view = views.valueAt(i).get();
+                    if (DEBUG) {
+                        Log.d(TAG, "runForEachView for autofillId = " + (view != null
+                                ? view.getAutofillId() : " null"));
+                    }
+                    if (view == null || view.getViewTranslationCallback() == null) {
+                        if (DEBUG) {
+                            Log.d(TAG, "View was gone or ViewTranslationCallback for autofillId "
+                                    + "= " + views.keyAt(i));
+                        }
+                        continue;
+                    }
+                    action.accept(view, view.getViewTranslationCallback());
+                }
+            });
+        }
+    }
+
+    private Translator createTranslatorIfNeeded(
+            TranslationSpec sourceSpec, TranslationSpec targetSpec) {
+        final TranslationManager tm = mContext.getSystemService(TranslationManager.class);
+        if (tm == null) {
+            Log.e(TAG, "Can not find TranslationManager when trying to create translator.");
+            return null;
+        }
+        final TranslationContext translationContext = new TranslationContext(sourceSpec,
+                targetSpec, /* translationFlags= */ 0);
+        final Translator translator = tm.createTranslator(translationContext);
+        if (translator != null) {
+            final Pair<TranslationSpec, TranslationSpec> specs = new Pair<>(sourceSpec, targetSpec);
+            mTranslators.put(specs, translator);
+        }
+        return translator;
+    }
+
+    private void destroyTranslators() {
+        synchronized (mLock) {
+            final int count = mTranslators.size();
+            for (int i = 0; i < count; i++) {
+                Translator translator = mTranslators.valueAt(i);
+                translator.destroy();
+            }
+            mTranslators.clear();
+        }
+    }
+
+    /**
+     * Returns a string representation of the state.
+     */
+    public static String stateToString(@UiTranslationState int state) {
+        switch (state) {
+            case STATE_UI_TRANSLATION_STARTED:
+                return "UI_TRANSLATION_STARTED";
+            case STATE_UI_TRANSLATION_PAUSED:
+                return "UI_TRANSLATION_PAUSED";
+            case STATE_UI_TRANSLATION_RESUMED:
+                return "UI_TRANSLATION_RESUMED";
+            case STATE_UI_TRANSLATION_FINISHED:
+                return "UI_TRANSLATION_FINISHED";
+            default:
+                return "Unknown state (" + state + ")";
+        }
+    }
+
+    /**
+     * Returns a sanitized string representation of {@link ViewTranslationRequest};
+     */
+    private static String sanitizedViewTranslationRequest(@NonNull ViewTranslationRequest request) {
+        StringBuilder msg = new StringBuilder("ViewTranslationRequest:{values=[");
+        for (String key: request.getKeys()) {
+            final TranslationRequestValue value = request.getValue(key);
+            msg.append("{text=").append(value.getText() == null
+                    ? "null"
+                    : "string[" + value.getText().length() + "]}, ");
+        }
+        return msg.toString();
+    }
+
+    /**
+     * Returns a sanitized string representation of {@link ViewTranslationResponse};
+     */
+    private static String sanitizedViewTranslationResponse(
+            @NonNull ViewTranslationResponse response) {
+        StringBuilder msg = new StringBuilder("ViewTranslationResponse:{values=[");
+        for (String key: response.getKeys()) {
+            final TranslationResponseValue value = response.getValue(key);
+            msg.append("{status=").append(value.getStatusCode()).append(", ");
+            msg.append("text=").append(value.getText() == null
+                    ? "null"
+                    : "string[" + value.getText().length() + "], ");
+            //TODO: append dictionary results.
+            msg.append("transliteration=").append(value.getTransliteration() == null
+                    ? "null"
+                    : "string[" + value.getTransliteration().length() + "]}, ");
+        }
+        return msg.toString();
+    }
+}
diff --git a/android/view/translation/UiTranslationManager.java b/android/view/translation/UiTranslationManager.java
new file mode 100644
index 0000000..b9ed32c
--- /dev/null
+++ b/android/view/translation/UiTranslationManager.java
@@ -0,0 +1,375 @@
+/*
+ * 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.view.translation;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.app.assist.ActivityId;
+import android.content.ComponentName;
+import android.content.Context;
+import android.icu.util.ULocale;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.View;
+import android.view.autofill.AutofillId;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+// TODO(b/178044703): Describe what UI Translation is.
+/**
+ * The {@link UiTranslationManager} class provides ways for apps to use the ui translation
+ * function in framework.
+ */
+public final class UiTranslationManager {
+
+    private static final String TAG = "UiTranslationManager";
+
+    /**
+     * The tag which uses for enabling debug log dump. To enable it, we can use command "adb shell
+     * setprop log.tag.UiTranslation DEBUG".
+     *
+     * @hide
+     */
+    public static final String LOG_TAG = "UiTranslation";
+
+    /**
+     * The state caller request to disable utranslation,, it is no longer need to ui translation.
+     *
+     * @hide
+     */
+    public static final int STATE_UI_TRANSLATION_STARTED = 0;
+    /**
+     * The state caller request to pause ui translation, it will switch back to the original text.
+     *
+     * @hide
+     */
+    public static final int STATE_UI_TRANSLATION_PAUSED = 1;
+    /**
+     * The state caller request to resume the paused ui translation, it will show the translated
+     * text again if the text had been translated.
+     *
+     * @hide
+     */
+    public static final int STATE_UI_TRANSLATION_RESUMED = 2;
+    /**
+     * The state the caller request to enable ui translation.
+     *
+     * @hide
+     */
+    public static final int STATE_UI_TRANSLATION_FINISHED = 3;
+    /**
+     * @hide
+     */
+    @IntDef(prefix = {"STATE__TRANSLATION"}, value = {
+            STATE_UI_TRANSLATION_STARTED,
+            STATE_UI_TRANSLATION_PAUSED,
+            STATE_UI_TRANSLATION_RESUMED,
+            STATE_UI_TRANSLATION_FINISHED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface UiTranslationState {
+    }
+
+    // Keys for the data transmitted in the internal UI Translation state callback.
+    /** @hide */
+    public static final String EXTRA_STATE = "state";
+    /** @hide */
+    public static final String EXTRA_SOURCE_LOCALE = "source_locale";
+    /** @hide */
+    public static final String EXTRA_TARGET_LOCALE = "target_locale";
+
+    @NonNull
+    private final Context mContext;
+
+    private final ITranslationManager mService;
+
+    /**
+     * @hide
+     */
+    public UiTranslationManager(@NonNull Context context, ITranslationManager service) {
+        mContext = Objects.requireNonNull(context);
+        mService = service;
+    }
+
+    /**
+     * @removed Use {@link #startTranslation(TranslationSpec, TranslationSpec, List, ActivityId,
+     * UiTranslationSpec)} instead.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
+    @Deprecated
+    @SystemApi
+    public void startTranslation(@NonNull TranslationSpec sourceSpec,
+            @NonNull TranslationSpec targetSpec, @NonNull List<AutofillId> viewIds,
+            @NonNull ActivityId activityId) {
+        startTranslation(
+                sourceSpec, targetSpec, viewIds, activityId,
+                new UiTranslationSpec.Builder().setShouldPadContentForCompat(true).build());
+    }
+
+    /**
+     * Request ui translation for a given Views.
+     *
+     * @param sourceSpec {@link TranslationSpec} for the data to be translated.
+     * @param targetSpec {@link TranslationSpec} for the translated data.
+     * @param viewIds A list of the {@link View}'s {@link AutofillId} which needs to be translated
+     * @param activityId the identifier for the Activity which needs ui translation
+     * @param uiTranslationSpec configuration for translation of the specified views
+     * @throws IllegalArgumentException if the no {@link View}'s {@link AutofillId} in the list
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
+    @SystemApi
+    public void startTranslation(@NonNull TranslationSpec sourceSpec,
+            @NonNull TranslationSpec targetSpec, @NonNull List<AutofillId> viewIds,
+            @NonNull ActivityId activityId, @NonNull UiTranslationSpec uiTranslationSpec) {
+        // TODO(b/177789967): Return result code or find a way to notify the status.
+        Objects.requireNonNull(sourceSpec);
+        Objects.requireNonNull(targetSpec);
+        Objects.requireNonNull(viewIds);
+        Objects.requireNonNull(activityId);
+        Objects.requireNonNull(activityId.getToken());
+        Objects.requireNonNull(uiTranslationSpec);
+        if (viewIds.size() == 0) {
+            throw new IllegalArgumentException("Invalid empty views: " + viewIds);
+        }
+        try {
+            mService.updateUiTranslationState(STATE_UI_TRANSLATION_STARTED, sourceSpec,
+                    targetSpec, viewIds, activityId.getToken(), activityId.getTaskId(),
+                    uiTranslationSpec,
+                    mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Request to disable the ui translation. It will destroy all the {@link Translator}s and no
+     * longer to show to show the translated text.
+     *
+     * @param activityId the identifier for the Activity which needs ui translation
+     * @throws NullPointerException the activityId or
+     *         {@link android.app.assist.ActivityId#getToken()} is {@code null}
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
+    @SystemApi
+    public void finishTranslation(@NonNull ActivityId activityId) {
+        try {
+            Objects.requireNonNull(activityId);
+            Objects.requireNonNull(activityId.getToken());
+            mService.updateUiTranslationState(STATE_UI_TRANSLATION_FINISHED,
+                    null /* sourceSpec */, null /* targetSpec */, null /* viewIds */,
+                    activityId.getToken(), activityId.getTaskId(), null /* uiTranslationSpec */,
+                    mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Request to pause the current ui translation's {@link Translator} which will switch back to
+     * the original language.
+     *
+     * @param activityId the identifier for the Activity which needs ui translation
+     * @throws NullPointerException the activityId or
+     *         {@link android.app.assist.ActivityId#getToken()} is {@code null}
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
+    @SystemApi
+    public void pauseTranslation(@NonNull ActivityId activityId) {
+        try {
+            Objects.requireNonNull(activityId);
+            Objects.requireNonNull(activityId.getToken());
+            mService.updateUiTranslationState(STATE_UI_TRANSLATION_PAUSED,
+                    null /* sourceSpec */, null /* targetSpec */, null /* viewIds */,
+                    activityId.getToken(), activityId.getTaskId(), null /* uiTranslationSpec */,
+                    mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Request to resume the paused ui translation's {@link Translator} which will switch to the
+     * translated language if the text had been translated.
+     *
+     * @param activityId the identifier for the Activity which needs ui translation
+     * @throws NullPointerException the activityId or
+     *         {@link android.app.assist.ActivityId#getToken()} is {@code null}
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
+    @SystemApi
+    public void resumeTranslation(@NonNull ActivityId activityId) {
+        try {
+            Objects.requireNonNull(activityId);
+            Objects.requireNonNull(activityId.getToken());
+            mService.updateUiTranslationState(STATE_UI_TRANSLATION_RESUMED,
+                    null /* sourceSpec */, null /* targetSpec */, null /* viewIds */,
+                    activityId.getToken(), activityId.getTaskId(), null /* uiTranslationSpec */,
+                    mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    // TODO(b/178044703): Fix the View API link when it becomes public.
+    /**
+     * Register for notifications of UI Translation state changes on the foreground activity. This
+     * is available to the owning application itself and also the current input method.
+     * <p>
+     * The application whose UI is being translated can use this to customize the UI Translation
+     * behavior in ways that aren't made easy by methods like
+     * View#onCreateTranslationRequest().
+     * <p>
+     * Input methods can use this to offer complementary features to UI Translation; for example,
+     * enabling outgoing message translation when the system is translating incoming messages in a
+     * communication app.
+     *
+     * @param callback the callback to register for receiving the state change
+     *         notifications
+     */
+    public void registerUiTranslationStateCallback(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull UiTranslationStateCallback callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        synchronized (mCallbacks) {
+            if (mCallbacks.containsKey(callback)) {
+                Log.w(TAG, "registerUiTranslationStateCallback: callback already registered;"
+                        + " ignoring.");
+                return;
+            }
+            final IRemoteCallback remoteCallback =
+                    new UiTranslationStateRemoteCallback(executor, callback);
+            try {
+                mService.registerUiTranslationStateCallback(remoteCallback, mContext.getUserId());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+            mCallbacks.put(callback, remoteCallback);
+        }
+    }
+
+    /**
+     * Unregister {@code callback}.
+     *
+     * @see #registerUiTranslationStateCallback(Executor, UiTranslationStateCallback)
+     */
+    public void unregisterUiTranslationStateCallback(@NonNull UiTranslationStateCallback callback) {
+        Objects.requireNonNull(callback);
+
+        synchronized (mCallbacks) {
+            final IRemoteCallback remoteCallback = mCallbacks.get(callback);
+            if (remoteCallback == null) {
+                Log.w(TAG, "unregisterUiTranslationStateCallback: callback not found; ignoring.");
+                return;
+            }
+            try {
+                mService.unregisterUiTranslationStateCallback(remoteCallback, mContext.getUserId());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+            mCallbacks.remove(callback);
+        }
+    }
+
+    /**
+     * Notify apps the translation is finished because {@link #finishTranslation(ActivityId)} is
+     * called or Activity is destroyed.
+     *
+     * @param activityDestroyed if the ui translation is finished because of activity destroyed.
+     * @param activityId the identifier for the Activity which needs ui translation
+     * @param componentName the ui translated Activity componentName.
+     *
+     * @hide
+     */
+    public void onTranslationFinished(boolean activityDestroyed, ActivityId activityId,
+            ComponentName componentName) {
+        try {
+            mService.onTranslationFinished(activityDestroyed,
+                    activityId.getToken(), componentName, mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @NonNull
+    @GuardedBy("mCallbacks")
+    private final Map<UiTranslationStateCallback, IRemoteCallback> mCallbacks = new ArrayMap<>();
+
+    private static class UiTranslationStateRemoteCallback extends IRemoteCallback.Stub {
+        private final Executor mExecutor;
+        private final UiTranslationStateCallback mCallback;
+        private ULocale mSourceLocale;
+        private ULocale mTargetLocale;
+
+        UiTranslationStateRemoteCallback(Executor executor,
+                UiTranslationStateCallback callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void sendResult(Bundle bundle) {
+            Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> onStateChange(bundle)));
+        }
+
+        private void onStateChange(Bundle bundle) {
+            int state = bundle.getInt(EXTRA_STATE);
+            switch (state) {
+                case STATE_UI_TRANSLATION_STARTED:
+                    mSourceLocale = (ULocale) bundle.getSerializable(EXTRA_SOURCE_LOCALE);
+                    mTargetLocale = (ULocale) bundle.getSerializable(EXTRA_TARGET_LOCALE);
+                    mCallback.onStarted(mSourceLocale, mTargetLocale);
+                    break;
+                case STATE_UI_TRANSLATION_RESUMED:
+                    mCallback.onResumed(mSourceLocale, mTargetLocale);
+                    break;
+                case STATE_UI_TRANSLATION_PAUSED:
+                    mCallback.onPaused();
+                    break;
+                case STATE_UI_TRANSLATION_FINISHED:
+                    mCallback.onFinished();
+                    break;
+                default:
+                    Log.wtf(TAG, "Unexpected translation state:" + state);
+            }
+        }
+    }
+}
diff --git a/android/view/translation/UiTranslationSpec.java b/android/view/translation/UiTranslationSpec.java
new file mode 100644
index 0000000..3d1ebfd
--- /dev/null
+++ b/android/view/translation/UiTranslationSpec.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.translation;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.widget.TextView;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Specifications for configuring UI translation.
+ *
+ * @hide
+ */
+@DataClass(
+        genBuilder = true, genEqualsHashCode = true, genHiddenConstDefs = true, genToString = true)
[email protected]("isShouldPadContentForCompat")
+@SystemApi
+public final class UiTranslationSpec implements Parcelable {
+
+    /**
+     * Whether the original content of the view should be made to appear as if it is the same size
+     * as the translated content. Defaults to {@code false}.
+     * <p>
+     * For {@link TextView}, the system does not directly modify the original text, rather
+     * changes the displayed content using a {@link android.text.method.TransformationMethod}. This
+     * can cause issues in apps that do not account for length-changing TransformationMethods. For
+     * example, an app using DynamicLayout may use the calculated line-offsets to operate on the
+     * original text, but this can cause crashes when the layout was calculated on translated text
+     * with a different length.
+     * <p>
+     * If this is {@code true}, for a TextView the default implementation appends spaces to the
+     * result of {@link TextView#getText()} to make the length the same as the translated text.
+     * <p>
+     * This only affects apps with target SDK R or lower.
+     */
+    private boolean mShouldPadContentForCompat = false;
+
+    /**
+     * Whether the original content of the view should be made to appear as if it is the same size
+     * as the translated content.
+     * <p>
+     * For {@link TextView}, the system does not directly modify the original text, rather
+     * changes the displayed content using a {@link android.text.method.TransformationMethod}. This
+     * can cause issues in apps that do not account for length-changing TransformationMethods. For
+     * example, an app using DynamicLayout may use the calculated line-offsets to operate on the
+     * original text, but this can cause crashes when the layout was calculated on translated text
+     * with a different length.
+     * <p>
+     * If this is {@code true}, for a TextView the default implementation appends spaces to the
+     * result of {@link TextView#getText()} to make the length the same as the translated text.
+     * <p>
+     * This only affects apps with target SDK R or lower.
+     */
+    public boolean shouldPadContentForCompat() {
+        return mShouldPadContentForCompat;
+    }
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/translation/UiTranslationSpec.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    @DataClass.Generated.Member
+    /* package-private */ UiTranslationSpec(
+            boolean shouldPadContentForCompat) {
+        this.mShouldPadContentForCompat = shouldPadContentForCompat;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "UiTranslationSpec { " +
+                "shouldPadContentForCompat = " + mShouldPadContentForCompat +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@android.annotation.Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(UiTranslationSpec other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        UiTranslationSpec that = (UiTranslationSpec) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && mShouldPadContentForCompat == that.mShouldPadContentForCompat;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + Boolean.hashCode(mShouldPadContentForCompat);
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mShouldPadContentForCompat) flg |= 0x1;
+        dest.writeByte(flg);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ UiTranslationSpec(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        boolean shouldPadContentForCompat = (flg & 0x1) != 0;
+
+        this.mShouldPadContentForCompat = shouldPadContentForCompat;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<UiTranslationSpec> CREATOR
+            = new Parcelable.Creator<UiTranslationSpec>() {
+        @Override
+        public UiTranslationSpec[] newArray(int size) {
+            return new UiTranslationSpec[size];
+        }
+
+        @Override
+        public UiTranslationSpec createFromParcel(@NonNull Parcel in) {
+            return new UiTranslationSpec(in);
+        }
+    };
+
+    /**
+     * A builder for {@link UiTranslationSpec}
+     */
+    @SuppressWarnings("WeakerAccess")
+    @DataClass.Generated.Member
+    public static final class Builder {
+
+        private boolean mShouldPadContentForCompat;
+
+        private long mBuilderFieldsSet = 0L;
+
+        public Builder() {
+        }
+
+        /**
+         * Whether the original content of the view should be made to appear as if it is the same size
+         * as the translated content. Defaults to {@code false}.
+         * <p>
+         * For {@link TextView}, the system does not directly modify the original text, rather
+         * changes the displayed content using a {@link android.text.method.TransformationMethod}. This
+         * can cause issues in apps that do not account for length-changing TransformationMethods. For
+         * example, an app using DynamicLayout may use the calculated line-offsets to operate on the
+         * original text, but this can cause crashes when the layout was calculated on translated text
+         * with a different length.
+         * <p>
+         * If this is {@code true}, for a TextView the default implementation appends spaces to the
+         * result of {@link TextView#getText()} to make the length the same as the translated text.
+         * <p>
+         * This only affects apps with target SDK R or lower.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setShouldPadContentForCompat(boolean value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x1;
+            mShouldPadContentForCompat = value;
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        public @NonNull UiTranslationSpec build() {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x2; // Mark builder used
+
+            if ((mBuilderFieldsSet & 0x1) == 0) {
+                mShouldPadContentForCompat = false;
+            }
+            UiTranslationSpec o = new UiTranslationSpec(
+                    mShouldPadContentForCompat);
+            return o;
+        }
+
+        private void checkNotUsed() {
+            if ((mBuilderFieldsSet & 0x2) != 0) {
+                throw new IllegalStateException(
+                        "This Builder should not be reused. Use a new Builder instance instead");
+            }
+        }
+    }
+
+    @DataClass.Generated(
+            time = 1620790033058L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/view/translation/UiTranslationSpec.java",
+            inputSignatures = "private  boolean mShouldPadContentForCompat\npublic  boolean shouldPadContentForCompat()\nclass UiTranslationSpec extends java.lang.Object implements [android.os.Parcelable]\[email protected](genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genToString=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/android/view/translation/UiTranslationStateCallback.java b/android/view/translation/UiTranslationStateCallback.java
new file mode 100644
index 0000000..d7dc284
--- /dev/null
+++ b/android/view/translation/UiTranslationStateCallback.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.translation;
+
+import android.annotation.NonNull;
+import android.icu.util.ULocale;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Callback for listening to UI Translation state changes. See {@link
+ * UiTranslationManager#registerUiTranslationStateCallback(Executor, UiTranslationStateCallback)}.
+ */
+public interface UiTranslationStateCallback {
+
+    /**
+     * @removed use {@link #onStarted(ULocale, ULocale)} instead.
+     */
+    @Deprecated
+    default void onStarted(@NonNull String sourceLocale, @NonNull String targetLocale) {
+        // no-op
+    }
+
+    /**
+     * The system is requesting translation of the UI from {@code sourceLocale} to {@code
+     * targetLocale}.
+     * <p>
+     * This is also called if either the requested {@code sourceLocale} or {@code targetLocale} has
+     * changed.
+     */
+    default void onStarted(@NonNull ULocale sourceLocale, @NonNull ULocale targetLocale) {
+        onStarted(sourceLocale.getLanguage(), targetLocale.getLanguage());
+    }
+
+    /**
+     * The system is requesting that the application temporarily show the UI contents in their
+     * original language.
+     */
+    void onPaused();
+
+    /**
+     * The system is requesting that the application restore from the temporarily paused state and
+     * show the content in translated language.
+     */
+    // TODO: Remove the default implementation when clients have implemented this.
+    default void onResumed(@NonNull ULocale sourceLocale, @NonNull ULocale targetLocale) {
+    }
+
+    /**
+     * The UI Translation session has ended.
+     */
+    void onFinished();
+}
diff --git a/android/view/translation/ViewTranslationCallback.java b/android/view/translation/ViewTranslationCallback.java
new file mode 100644
index 0000000..6efd621
--- /dev/null
+++ b/android/view/translation/ViewTranslationCallback.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.translation;
+
+import android.annotation.NonNull;
+import android.annotation.UiThread;
+import android.view.View;
+
+/**
+ * Callback for handling the translated information show or hide in the {@link View}.
+ */
+@UiThread
+public interface ViewTranslationCallback {
+    /**
+     * Called when the translated text is ready to show or if the user has requested to reshow the
+     * translated content after hiding it.
+     * <p>
+     * The translated content can be obtained from {@link View#getViewTranslationResponse}. This
+     * method will not be called before {@link View#onViewTranslationResponse} or
+     * {@link View#onVirtualViewTranslationResponses}.
+     *
+     * See {@link View#onViewTranslationResponse} for how to get the translated information.
+     *
+     * @return {@code true} if the View handles showing the translation.
+     */
+    boolean onShowTranslation(@NonNull View view);
+    /**
+     * Called when the user wants to show the original text instead of the translated text. This
+     * method will not be called before {@link View#onViewTranslationResponse} or
+     * {@link View#onViewTranslationResponse}.
+     *
+     * @return {@code true} if the View handles hiding the translation.
+     */
+    boolean onHideTranslation(@NonNull View view);
+    /**
+     * Called when the user finish the Ui translation and no longer to show the translated text.
+     *
+     * @return {@code true} if the View handles clearing the translation.
+     */
+    boolean onClearTranslation(@NonNull View view);
+
+    /**
+     * Enables padding on the view's original content.
+     * <p>
+     * This is useful when we do not modify the content directly, rather use a mechanism like
+     * {@link android.text.method.TransformationMethod}. If the app misbehaves when the displayed
+     * translation and the underlying content have different sizes, the platform intelligence can
+     * request that the original content be padded to make the sizes match.
+     *
+     * @hide
+     */
+    default void enableContentPadding() {}
+
+    /**
+     * Sets the duration for animations while transitioning the view between the original and
+     * translated contents.
+     *
+     * @hide
+     */
+    default void setAnimationDurationMillis(int durationMillis) {}
+}
diff --git a/android/view/translation/ViewTranslationRequest.java b/android/view/translation/ViewTranslationRequest.java
new file mode 100644
index 0000000..a41749a
--- /dev/null
+++ b/android/view/translation/ViewTranslationRequest.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.translation;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArrayMap;
+import android.view.autofill.AutofillId;
+
+import com.android.internal.util.DataClass;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Wrapper class representing a translation request associated with a {@link android.view.View} to
+ * be used by {@link android.service.translation.TranslationService}.
+ */
+@DataClass(genBuilder = false, genToString = true, genEqualsHashCode = true, genGetters = false,
+        genHiddenConstructor = true, genHiddenConstDefs = true)
+public final class ViewTranslationRequest implements Parcelable {
+
+    /**
+     * Constant id for the default view text to be translated. This is used by
+     * {@link Builder#setValue(String, TranslationRequestValue)}.
+     */
+    public static final String ID_TEXT = "android:text";
+
+    /**
+     * Constant id for the default view content description to be translated. This is used by
+     * {@link Builder#setValue(String, TranslationRequestValue)}.
+     *
+     * @hide
+     */
+    public static final String ID_CONTENT_DESCRIPTION = "android:content_description";
+
+    /**
+     * The {@link AutofillId} of the view associated with this request.
+     */
+    @NonNull
+    private final AutofillId mAutofillId;
+
+    @NonNull
+    @DataClass.PluralOf("translationRequestValue")
+    private final Map<String, TranslationRequestValue> mTranslationRequestValues;
+
+    /**
+     * Gets the corresponding {@link TranslationRequestValue} of the provided key.
+     * @param key String id of the translation request value to be translated.
+     * @return the {@link TranslationRequestValue}.
+     * @throws IllegalArgumentException if the key does not exist.
+     */
+    @NonNull
+    public TranslationRequestValue getValue(@NonNull String key) {
+        Objects.requireNonNull(key, "key should not be null");
+        if (!mTranslationRequestValues.containsKey(key)) {
+            throw new IllegalArgumentException("Request does not contain value for key=" + key);
+        }
+        return mTranslationRequestValues.get(key);
+    }
+
+    /**
+     * Returns all keys in this request as a {@link Set} of Strings. The keys are used by
+     * {@link #getValue(String)} to get the {@link TranslationRequestValue}s.
+     */
+    @NonNull
+    public Set<String> getKeys() {
+        return mTranslationRequestValues.keySet();
+    }
+
+
+    /**
+     * Returns the associated {@link AutofillId} of this request.
+     */
+    @NonNull
+    public AutofillId getAutofillId() {
+        return mAutofillId;
+    }
+
+    private static Map<String, TranslationRequestValue> defaultTranslationRequestValues() {
+        return Collections.emptyMap();
+    }
+
+    /**
+     * A builder for building ViewTranslationRequest.
+     */
+    public static final class Builder {
+
+        private @NonNull AutofillId mAutofillId;
+        private @NonNull Map<String, TranslationRequestValue> mTranslationRequestValues;
+
+        private long mBuilderFieldsSet = 0L;
+
+        /**
+         * Creates a new Builder.
+         *
+         * @param autofillId The {@link AutofillId} of the view associated with this request.
+         */
+        public Builder(@NonNull AutofillId autofillId) {
+            mAutofillId = autofillId;
+            com.android.internal.util.AnnotationValidations.validate(
+                    NonNull.class, null, mAutofillId);
+        }
+
+        /**
+         * Creates a new Builder.
+         *
+         * @param autofillId the {@link AutofillId} of the non-virtual view hosting the virtual view
+         * hierarchy associated with this request.
+        * @param virtualChildId the id of the virtual view in the host view. This id is the same
+         * virtual id provided through content capture.
+         */
+        public Builder(@NonNull AutofillId autofillId, long virtualChildId) {
+            mAutofillId = new AutofillId(autofillId, virtualChildId, AutofillId.NO_SESSION);
+            com.android.internal.util.AnnotationValidations.validate(
+                    NonNull.class, null, mAutofillId);
+        }
+
+        /**
+         * Sets the corresponding {@link TranslationRequestValue} for the provided key.
+         *
+         * @param key The key for this translation request value.
+         * @param value the translation request value holding the content to be translated.
+         * @return this builder.
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public Builder setValue(@NonNull String key, @NonNull TranslationRequestValue value) {
+            if (mTranslationRequestValues == null) {
+                setTranslationRequestValues(new ArrayMap<>());
+            }
+            mTranslationRequestValues.put(key, value);
+            return this;
+        }
+
+        /**
+         * Builds the instance. This builder should not be touched after calling this!
+         */
+        @NonNull
+        public ViewTranslationRequest build() {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x4; // Mark builder used
+
+            if ((mBuilderFieldsSet & 0x2) == 0) {
+                mTranslationRequestValues = defaultTranslationRequestValues();
+            }
+            ViewTranslationRequest o = new ViewTranslationRequest(
+                    mAutofillId,
+                    mTranslationRequestValues);
+            return o;
+        }
+
+        Builder setTranslationRequestValues(@NonNull Map<String, TranslationRequestValue> value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x2;
+            mTranslationRequestValues = value;
+            return this;
+        }
+
+        private void checkNotUsed() {
+            if ((mBuilderFieldsSet & 0x4) != 0) {
+                throw new IllegalStateException(
+                        "This Builder should not be reused. Use a new Builder instance instead");
+            }
+        }
+    }
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/translation/ViewTranslationRequest.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /** @hide */
+    @android.annotation.StringDef(prefix = "ID_", value = {
+        ID_TEXT,
+        ID_CONTENT_DESCRIPTION
+    })
+    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface Id {}
+
+    /**
+     * Creates a new ViewTranslationRequest.
+     *
+     * @param autofillId
+     *   The {@link AutofillId} of the view associated with this request.
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public ViewTranslationRequest(
+            @NonNull AutofillId autofillId,
+            @NonNull Map<String,TranslationRequestValue> translationRequestValues) {
+        this.mAutofillId = autofillId;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mAutofillId);
+        this.mTranslationRequestValues = translationRequestValues;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mTranslationRequestValues);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "ViewTranslationRequest { " +
+                "autofillId = " + mAutofillId + ", " +
+                "translationRequestValues = " + mTranslationRequestValues +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(ViewTranslationRequest other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        ViewTranslationRequest that = (ViewTranslationRequest) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && Objects.equals(mAutofillId, that.mAutofillId)
+                && Objects.equals(mTranslationRequestValues, that.mTranslationRequestValues);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + Objects.hashCode(mAutofillId);
+        _hash = 31 * _hash + Objects.hashCode(mTranslationRequestValues);
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeTypedObject(mAutofillId, flags);
+        dest.writeMap(mTranslationRequestValues);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ ViewTranslationRequest(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        AutofillId autofillId = (AutofillId) in.readTypedObject(AutofillId.CREATOR);
+        Map<String,TranslationRequestValue> translationRequestValues = new java.util.LinkedHashMap<>();
+        in.readMap(translationRequestValues, TranslationRequestValue.class.getClassLoader());
+
+        this.mAutofillId = autofillId;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mAutofillId);
+        this.mTranslationRequestValues = translationRequestValues;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mTranslationRequestValues);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<ViewTranslationRequest> CREATOR
+            = new Parcelable.Creator<ViewTranslationRequest>() {
+        @Override
+        public ViewTranslationRequest[] newArray(int size) {
+            return new ViewTranslationRequest[size];
+        }
+
+        @Override
+        public ViewTranslationRequest createFromParcel(@NonNull Parcel in) {
+            return new ViewTranslationRequest(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1621230365943L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/view/translation/ViewTranslationRequest.java",
+            inputSignatures = "public static final  java.lang.String ID_TEXT\npublic static final  java.lang.String ID_CONTENT_DESCRIPTION\nprivate final @android.annotation.NonNull android.view.autofill.AutofillId mAutofillId\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"translationRequestValue\") java.util.Map<java.lang.String,android.view.translation.TranslationRequestValue> mTranslationRequestValues\npublic @android.annotation.NonNull android.view.translation.TranslationRequestValue getValue(java.lang.String)\npublic @android.annotation.NonNull java.util.Set<java.lang.String> getKeys()\npublic @android.annotation.NonNull android.view.autofill.AutofillId getAutofillId()\nprivate static  java.util.Map<java.lang.String,android.view.translation.TranslationRequestValue> defaultTranslationRequestValues()\nclass ViewTranslationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate @android.annotation.NonNull android.view.autofill.AutofillId mAutofillId\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,android.view.translation.TranslationRequestValue> mTranslationRequestValues\nprivate  long mBuilderFieldsSet\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.view.translation.ViewTranslationRequest.Builder setValue(java.lang.String,android.view.translation.TranslationRequestValue)\npublic @android.annotation.NonNull android.view.translation.ViewTranslationRequest build()\n  android.view.translation.ViewTranslationRequest.Builder setTranslationRequestValues(java.util.Map<java.lang.String,android.view.translation.TranslationRequestValue>)\nprivate  void checkNotUsed()\nclass Builder extends java.lang.Object implements []\[email protected](genBuilder=false, genToString=true, genEqualsHashCode=true, genGetters=false, genHiddenConstructor=true, genHiddenConstDefs=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/android/view/translation/ViewTranslationResponse.java b/android/view/translation/ViewTranslationResponse.java
new file mode 100644
index 0000000..d993114
--- /dev/null
+++ b/android/view/translation/ViewTranslationResponse.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.translation;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArrayMap;
+import android.view.autofill.AutofillId;
+
+import com.android.internal.util.DataClass;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Wrapper class representing a translation response associated with a {@link android.view.View} to
+ * be used by {@link android.service.translation.TranslationService}.
+ */
+@DataClass(genBuilder = true, genToString = true, genEqualsHashCode = true, genGetters = false)
+public final class ViewTranslationResponse implements Parcelable {
+
+    /**
+     * The {@link AutofillId} of the view associated with this response.
+     */
+    @NonNull
+    private final AutofillId mAutofillId;
+
+    @NonNull
+    @DataClass.PluralOf("translationResponseValue")
+    private final Map<String, TranslationResponseValue> mTranslationResponseValues;
+
+    /**
+     * Gets the {@link TranslationResponseValue} of the corresponding key.
+     * @param key String id of the translated translation response value.
+     * @return the {@link TranslationResponseValue}.
+     * @throws IllegalArgumentException if the key does not exist.
+     */
+    @NonNull
+    public TranslationResponseValue getValue(@NonNull String key) {
+        Objects.requireNonNull(key);
+        if (!mTranslationResponseValues.containsKey(key)) {
+            throw new IllegalArgumentException("Request does not contain value for key=" + key);
+        }
+        return mTranslationResponseValues.get(key);
+    }
+
+    /**
+     * Returns all keys in this response as a {@link Set} of Strings. The keys are used by
+     * {@link #getValue(String)} to get the {@link TranslationResponseValue}s.
+     */
+    @NonNull
+    public Set<String> getKeys() {
+        return mTranslationResponseValues.keySet();
+    }
+
+
+    /**
+     * Returns the associated {@link AutofillId} of this response.
+     */
+    @NonNull
+    public AutofillId getAutofillId() {
+        return mAutofillId;
+    }
+
+    private static Map<String, TranslationResponseValue> defaultTranslationResponseValues() {
+        return Collections.emptyMap();
+    }
+
+    @DataClass.Suppress({"addTranslationResponseValue", "setAutofillId"})
+    abstract static class BaseBuilder {
+
+        abstract Builder setTranslationResponseValues(Map<String, TranslationResponseValue> value);
+
+        /**
+         * Sets the corresponding {@link TranslationResponseValue} for the provided key.
+         *
+         * @param key The key for this translation response value.
+         * @param value the translation response value holding the translated content.
+         * @return this builder.
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public Builder setValue(String key,
+                TranslationResponseValue value) {
+            final Builder builder = (Builder) this;
+            if (builder.mTranslationResponseValues == null) {
+                setTranslationResponseValues(new ArrayMap<>());
+            }
+            builder.mTranslationResponseValues.put(key, value);
+            return builder;
+        }
+    }
+
+
+
+    // Code below generated by codegen v1.0.22.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/translation/ViewTranslationResponse.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    @DataClass.Generated.Member
+    /* package-private */ ViewTranslationResponse(
+            @NonNull AutofillId autofillId,
+            @NonNull Map<String,TranslationResponseValue> translationResponseValues) {
+        this.mAutofillId = autofillId;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mAutofillId);
+        this.mTranslationResponseValues = translationResponseValues;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mTranslationResponseValues);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "ViewTranslationResponse { " +
+                "autofillId = " + mAutofillId + ", " +
+                "translationResponseValues = " + mTranslationResponseValues +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(ViewTranslationResponse other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        ViewTranslationResponse that = (ViewTranslationResponse) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && Objects.equals(mAutofillId, that.mAutofillId)
+                && Objects.equals(mTranslationResponseValues, that.mTranslationResponseValues);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + Objects.hashCode(mAutofillId);
+        _hash = 31 * _hash + Objects.hashCode(mTranslationResponseValues);
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeTypedObject(mAutofillId, flags);
+        dest.writeMap(mTranslationResponseValues);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ ViewTranslationResponse(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        AutofillId autofillId = (AutofillId) in.readTypedObject(AutofillId.CREATOR);
+        Map<String,TranslationResponseValue> translationResponseValues = new java.util.LinkedHashMap<>();
+        in.readMap(translationResponseValues, TranslationResponseValue.class.getClassLoader());
+
+        this.mAutofillId = autofillId;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mAutofillId);
+        this.mTranslationResponseValues = translationResponseValues;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mTranslationResponseValues);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<ViewTranslationResponse> CREATOR
+            = new Parcelable.Creator<ViewTranslationResponse>() {
+        @Override
+        public ViewTranslationResponse[] newArray(int size) {
+            return new ViewTranslationResponse[size];
+        }
+
+        @Override
+        public ViewTranslationResponse createFromParcel(@NonNull Parcel in) {
+            return new ViewTranslationResponse(in);
+        }
+    };
+
+    /**
+     * A builder for {@link ViewTranslationResponse}
+     */
+    @SuppressWarnings("WeakerAccess")
+    @DataClass.Generated.Member
+    public static final class Builder extends BaseBuilder {
+
+        private @NonNull AutofillId mAutofillId;
+        private @NonNull Map<String,TranslationResponseValue> mTranslationResponseValues;
+
+        private long mBuilderFieldsSet = 0L;
+
+        /**
+         * Creates a new Builder.
+         *
+         * @param autofillId
+         *   The {@link AutofillId} of the view associated with this response.
+         */
+        public Builder(
+                @NonNull AutofillId autofillId) {
+            mAutofillId = autofillId;
+            com.android.internal.util.AnnotationValidations.validate(
+                    NonNull.class, null, mAutofillId);
+        }
+
+        @DataClass.Generated.Member
+        @Override
+        @NonNull Builder setTranslationResponseValues(@NonNull Map<String,TranslationResponseValue> value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x2;
+            mTranslationResponseValues = value;
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        public @NonNull ViewTranslationResponse build() {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x4; // Mark builder used
+
+            if ((mBuilderFieldsSet & 0x2) == 0) {
+                mTranslationResponseValues = defaultTranslationResponseValues();
+            }
+            ViewTranslationResponse o = new ViewTranslationResponse(
+                    mAutofillId,
+                    mTranslationResponseValues);
+            return o;
+        }
+
+        private void checkNotUsed() {
+            if ((mBuilderFieldsSet & 0x4) != 0) {
+                throw new IllegalStateException(
+                        "This Builder should not be reused. Use a new Builder instance instead");
+            }
+        }
+    }
+
+    @DataClass.Generated(
+            time = 1614992272865L,
+            codegenVersion = "1.0.22",
+            sourceFile = "frameworks/base/core/java/android/view/translation/ViewTranslationResponse.java",
+            inputSignatures = "private final @android.annotation.NonNull android.view.autofill.AutofillId mAutofillId\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"translationResponseValue\") java.util.Map<java.lang.String,android.view.translation.TranslationResponseValue> mTranslationResponseValues\npublic @android.annotation.NonNull android.view.translation.TranslationResponseValue getValue(java.lang.String)\npublic @android.annotation.NonNull java.util.Set<java.lang.String> getKeys()\npublic @android.annotation.NonNull android.view.autofill.AutofillId getAutofillId()\nprivate static  java.util.Map<java.lang.String,android.view.translation.TranslationResponseValue> defaultTranslationResponseValues()\nclass ViewTranslationResponse extends java.lang.Object implements [android.os.Parcelable]\nabstract  android.view.translation.ViewTranslationResponse.Builder setTranslationResponseValues(java.util.Map<java.lang.String,android.view.translation.TranslationResponseValue>)\npublic @android.annotation.SuppressLint android.view.translation.ViewTranslationResponse.Builder setValue(java.lang.String,android.view.translation.TranslationResponseValue)\nclass BaseBuilder extends java.lang.Object implements []\[email protected](genBuilder=true, genToString=true, genEqualsHashCode=true, genGetters=false)\nabstract  android.view.translation.ViewTranslationResponse.Builder setTranslationResponseValues(java.util.Map<java.lang.String,android.view.translation.TranslationResponseValue>)\npublic @android.annotation.SuppressLint android.view.translation.ViewTranslationResponse.Builder setValue(java.lang.String,android.view.translation.TranslationResponseValue)\nclass BaseBuilder extends java.lang.Object implements []")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}